6

我们有默认方法,也称为防御方法和“虚拟扩展方法”。

虽然我很欣赏默认方法的巨大价值(在某些方面甚至比它们的 C# 对应物更强大),但我想知道在不访问其源代码的情况下允许扩展现有接口的决定是什么。

在 SO Brian Goetz 的一个答案中,他提到默认方法的设计非常方便以及接口演变。因此,如果我们编写一个接口,我们可以在其中填充通常必须放在单独的类中的各种实用方法。那么为什么不加倍努力并允许它用于不受我们控制的接口呢?

4

2 回答 2

9

这是由一种哲学信念驱动的:API 设计者应该控制他们的 API。虽然从外部将方法注入 API 肯定很方便,但它破坏了 API 设计者对其 API 的控制。(这有时被称为“猴子补丁”。)

关于术语:C#所谓的“扩展方法”只是扩展方法的一种形式,并不是扩展方法的定义;Java 的默认方法也是扩展方法。主要区别在于: C# 扩展方法是静态的,并且是在使用站点注入的;Java 是虚拟的和声明站点。其次,C# 扩展方法被注入到类型中,而 Java 的默认方法是的成员。(这允许您在 C#中注入sum()方法而不影响其他实例化。) List<int>List

如果您已经习惯了 C# 方法,那么很自然地会认为这是“正确”、“正常”或“真实”的方法,但实际上,这只是众多可能方法中的一种。正如其他海报所指出的那样,与 Java 的默认方法相比,C# 扩展方法有一些非常严重的缺点(例如,反射发现能力差、通过文档发现能力差、不可覆盖、需要临时冲突管理规则)。因此,相比之下,Java 玻璃杯在这里远远超过半满。

于 2015-04-07T14:42:00.627 回答
8

虽然 Brian Goetz 提到默认方法也将用于方便,但这并不意味着它是默认方法的第一个目标。

提醒一下:默认方法的存在是为了支持接口演化,这是在引入 lambda 时非常需要的东西(因为引入 lambda 使用方法通常意味着应该添加新方法)。

您正在写您的帖子,就好像它们存在于 C# 中的扩展方法没有缺点一样,但是:

  • 扩展方法有一个巨大的可发现性问题:

    • 它们没有出现在课程文档中;
    • 他们不会通过反思班级而暴露出来;
    • 当您在代码中看到扩展方法的使用时,如果没有 IDE,您将无法知道扩展方法的来源;
  • 扩展方法,就其本质而言,不允许一些标准的消歧机制:

    • 当您使用扩展方法为类方法定义重载,而该重载或类似的扩展方法由类的作者定义时会发生什么?案例 1:您的代码通过简单的重新编译切换到使用新方法;案例 2:您的代码不再编译;
    • 因此,我不建议将扩展方法定义在您无法控制的命名空间中,因为它会使此类问题变得棘手。但是,这样做意味着您无法控制的类型的扩展方法变得更加难以发现;
  • 扩展方法不灵活:

    • 一旦库定义了扩展方法,该库的客户端就无法更改或调整该扩展方法的实现;
    • 如果客户为他无法控制的类型定义了扩展方法,那么在库定义相同扩展方法的那一天,他就会遇到兼容性问题。这种可能性取决于扩展方法的作用以及该库的演变策略。

在这三个缺点中,Java 8 中存在的默认方法都没有。

您应该注意到,在 C# 中包含扩展方法的主要原因是启用 LINQ。由于 Java 对其流进行了另一种设计,我认为 Java 语言设计者不会理所当然地添加它们。

于 2015-04-06T07:13:15.013 回答