不允许使用站点注入扩展方法到 java 8 的设计考虑是什么?

What was the design consideration of not allowing use-site injection of extension methods to java 8?

我们有默认方法,也称为防御方法和 'virtual extension methods'。

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

Brian Goetz 提到默认方法的设计主要是为了方便和接口进化。因此,如果我们编写一个接口,我们可以将通常必须放置在单独 class 中的各种实用方法塞入其中。那么,为什么不多走一步,允许它用于不受我们控制的接口呢?

虽然 Brian Goetz 提到为了方便起见也会使用默认方法,但这并不意味着这是默认方法的首要目标。

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

您正在编写 post,就好像 C# 中存在的扩展方法没有缺点一样,但是:

  • 扩展方法存在巨大的可发现性问题:

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

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

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

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

您应该注意到,在 C# 中包含扩展方法的一个重要原因是启用 LINQ。由于 Java 为其流采用了另一种设计,我没有看到 Java 语言设计者将它们添加为理所当然的事情。

这是由一种哲学信念驱动的:API 设计师应该控制他们的 APIs。虽然从外部将方法注入 APIs 确实很方便,但它破坏了 API 设计人员对其 API 的控制。 (这有时称为 "monkey-patching"。)

关于术语:C#所说的"extension methods"只是扩展方法的一种形式,并不是扩展方法的定义; Java的默认方法也是扩展方法。主要区别是: C# 扩展方法是 static 并在 use-site 注入; Java 是 虚拟 声明站点 。其次,C# 扩展方法被注入 types,而 Java 的默认方法是 的成员。 (这允许您在 C# 中将 sum() 方法注入 List<int> 而不会影响其他 List 实例化。)

如果您已经习惯了 C# 方法,很自然地会假设这是 "right" 或 "normal" 或 "real" 方法,但实际上,它是只是许多可能的方法之一。正如其他张贴者所指出的,与 Java 的默认方法相比,C# 扩展方法有一些非常严重的缺点(例如,反射可发现性差、通过文档的可发现性差、不可覆盖、需要临时冲突管理规则) .所以相比之下,Java 玻璃杯已经超过半满了。