当单独的方法只有一个有效时,为什么这个 Java 泛型方法调用是不明确的?

Tim*_*ole 30 java generics overload-resolution

我试图理解为什么编译器无法解析bar方法调用。我希望bar(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 {}
Run Code Online (Sandbox Code Playgroud)

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

为什么bar调用不明确,特别是当foobar(T)调用本身不是有效的解决方案时?

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

Mic*_*ael 26

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

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

一般的做法是:

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

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

歧义性在步骤 2.6 中确定,但在步骤 3 中仍然有进一步的适当性检查。您的foo方法一定在步骤 3 处失败。您的bar方法永远不会走到那么远,因为编译器仍然认为这两种方法都是有效的可能性。人类可以确定不适当性可以解决歧义,但这并不是编译器执行操作的命令。我只能推测原因——性能可能是一个因素。

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

  • 我不了解 Java,但对于 C#,重载解析是众所周知的 NP 完全的,可以用来解决,例如 3-SAT *在编译时*。虽然规则不同,但要解决的问题基本上是相同的,因此如果 Java 的重载解析同样复杂(无论是在一般英语意义上还是在特定的计算机科学“计算复杂性”中),我一点也不会感到惊讶。 )。 (6认同)

NoD*_*und 18

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

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 {}
Run Code Online (Sandbox Code Playgroud)

如果您使用:

    bar(new Xyz());  // ok
    bar("a");   // ok
    bar(new Zyx());   // ok
    bar((Supplier<Xyz>) Xyz::new); // ok
    bar(Xyz::new); // ambiguous
Run Code Online (Sandbox Code Playgroud)

您收到此错误(使用 Java 17 尝试过),该错误与 lambda 无关,而是与类型有关T无法推断类型变量 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
Run Code Online (Sandbox Code Playgroud)

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

Example.<Xyz>bar(Xyz::new);
Run Code Online (Sandbox Code Playgroud)

我试图研究由迈克尔答案驱动的 JLS,应该更好地回答你的问题的部分是 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()); 
Run Code Online (Sandbox Code Playgroud)

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

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