为什么当只有一个方法在分离时有效时,这个Java泛型方法调用是模棱两可的?

2022-09-01 10:47:43

我试图理解为什么编译器无法解析方法调用。我希望始终选择,因为 由于 上的上限,因此永远无法匹配。barbar(Xyz::new)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)

为什么调用是模棱两可的,特别是当 和 调用本身不是有效的分辨率时?barfoobar(T)

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


答案 1

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

Java 解析方法调用的方式很复杂。它由JLS定义,我纯粹是为了确定如何解析方法而将其设置为7500字。粘贴到文本编辑器中,它有15页。

一般的方法是:

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

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

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

您的代码在泛型,重载和方法引用的交集处运行,这三者都是在不同时间引入的;对我来说,编译器会挣扎并不奇怪。


答案 2

你的问题主要是类型推断的问题,而不是模棱两可的方法的问题:

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,而是关于类型:无法推断类型变量TT

  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);

我试图研究JLS,由Michael的答案驱动,应该更好地回答你的问题的部分是18.5.1。调用适用性推理

我在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失败了。

我认为在lambdas的情况下,编译器不会从“Xyz”推断出类型T。


推荐