为什么这个 Java 泛型方法调用在分开时只有一个方法有效时不明确?

Why is this Java generic method call ambiguous when only one method is valid when separate?

我想了解为什么编译器无法解析 bar 方法调用。我希望 bar(Xyz::new) 总是 select bar(Supplier) 因为 bar(T extends Xyz) 由于 Xyz.

的上限而永远无法匹配
public <T extends Xyz> void foo(T s) {}
public <T extends Xyz> void bar(T s) {}
public <T extends Xyz> void bar(Supplier<T> s) {}

public void example() {
    foo(Xyz::new); // not valid (does not extend Xyz)

    bar((Supplier<Xyz>) Xyz::new); // valid (explicitly a Supplier)
    bar(Xyz::new); // ambiguous - but only one method is valid?
}

public static class Xyz {}

如果 bar(T) 不适用,即使单独使用(如 foo(T) 所示),那么唯一的选择肯定是 bar(Supplier) 使它成为一个明确的重载。

为什么 bar 调用不明确,尤其是当 foobar(T) 调用本身不是有效分辨率时?

以上代码的可运行示例:https://www.jdoodle.com/ia/kqP

你是对的,更聪明的编译器应该能够明确地解决这个问题。

Java 解析方法调用的方式复杂。就是defined by the JLS,我写了7500字纯粹是为了确定一个方法怎么解析。粘贴到文本编辑器中,它是 15 页。

一般做法是:

  1. 编译时步骤 1:确定要搜索的类型(此处没有问题)
  2. 编译时步骤 2:确定方法签名
    1. 确定潜在适用的方法
    2. 第 1 阶段:识别严格调用适用的匹配 Arity 方法
    3. 第 2 阶段:识别适用于松散调用的匹配 Arity 方法
    4. 第 3 阶段:确定变量 Arity 调用适用的方法
    5. 选择最具体的方法
    6. 方法调用类型
  3. 编译时第 3 步:选择的方法是否合适?

我不了解所有细节及其与您的具体案例的关系。如果您想深入研究它,那么我已经链接了完整的规范。希望这个解释足以满足您的目的:

歧义性 在第 2.6 步确定,但在第 3 步还有进一步的 适当性 检查。您的 foo方法必须在第 3 步失败。您的 bar 方法永远不会走那么远,因为编译器仍然认为这两种方法都是有效的可能性。人可以确定不适当性解决了歧义,但这并不是编译器做事的顺序。我只能推测原因 - 性能可能是一个因素。

您的代码在泛型、重载和方法引用的交叉点运行,这三者都是在不同的时间引入的;编译器会遇到困难对我来说并不奇怪。

你的问题主要是类型推断的问题,而不是方法不明确的问题:

public <T extends Xyz> void bar(T s) {} // bar(Xyz)
public void bar(String s) {}
public <T extends Zyx> void bar(T s) {} // bar(Zyx)
public <T extends Xyz> void bar(Supplier<T> s) {}
public static class Xyz {}
public static class Zyx {}

如果您使用:

    bar(new Xyz());  // ok
    bar("a");   // ok
    bar(new Zyx());   // ok
    bar((Supplier<Xyz>) Xyz::new); // ok
    bar(Xyz::new); // ambiguous

你得到这个错误(用 Java 17 试过)这不是关于 lambda,而是关于类型 T: cannot infer type-variable(s) T

  both method <T#1>bar(T#1) in Example and method <T#2>bar(Supplier<T#2>) in Example match
  where T#1,T#2 are type-variables:
    T#1 extends Zyx declared in method <T#1>bar(T#1)
    T#2 extends Xyz declared in method <T#2>bar(Supplier<T#2>)
Example.java:18: error: incompatible types: cannot infer type-variable(s) T

Java不够聪明,无法找到具体类型T是这种情况,你必须帮助它:

Example.<Xyz>bar(Xyz::new);

我试图查看由 Michael 回答驱动的 JLS,而应该更好地回答您问题的部分是 18.5.1. Invocation Applicability Inference

我经常遇到与 Java 7 和 Collections:

相同类型的错误
public static <T extends Zyx> void bar(java.util.List<T> s) {} // bar(Zyx)
public static <T extends Zyx> void bar(T s) {} // bar(List)
bar(new Zyx()); 
bar(java.util.Collections.emptyList()); 

更糟糕的是 Eclipse 没有问题,而 javac 却失败了。

我想在 lambda 的情况下,编译器不会从“Xyz”推断类型 T。