【问题标题】:Method reference is ambiguous for Thread.sleepThread.sleep 的方法引用不明确
【发布时间】:2018-04-26 21:10:47
【问题描述】:

我遇到了一个奇怪的问题,即对 Thread::sleep 的方法引用不明确,但具有相同签名的方法却不明确。

package test;    

public class Test
{
    public static void main(String[] args)
    {
        foo(Test::sleep, 1000L); //fine
        foo((FooVoid<Long>)Thread::sleep, 1000L); //fine
        foo(Thread::sleep, 1000L); //error
    }

    public static void sleep(long millis) throws InterruptedException
    {
        Thread.sleep(millis);
    }

    public static <P, R> void foo(Foo<P, R> function, P param) {}

    public static <P> void foo(FooVoid<P> function, P param) {}

    @FunctionalInterface
    public interface Foo<P, R> {
        R call(P param1) throws Exception;
    }

    @FunctionalInterface
    public interface FooVoid<P> {
        void call(P param1) throws Exception;
    }
}

我得到了这 2 个错误:

Error:(9, 17) java: reference to foo is ambiguous
  both method <P,R>foo(test.Test.Foo<P,R>,P) in test.Test and method <P>foo(test.Test.FooVoid<P>,P) in test.Test match

Error:(9, 20) java: incompatible types: cannot infer type-variable(s) P,R
    (argument mismatch; bad return type in method reference
      void cannot be converted to R)

我看到的唯一区别是Thread::sleepnative。它有什么改变吗?我不认为过载Thread::sleep(long, int) 在这里发挥作用。为什么会这样?

编辑:使用 javac 版本 1.8.0_111

【问题讨论】:

  • 有趣的是,Mars.2 Release (4.5.2) 中的Eclipse java编译器对你的代码没有任何问题。
  • @ErwinBolwidt 当我在 IntelliJ 中切换到 Eclipse 编译器时,我收到一条不同的错误消息,含义相同。不知道编译器的版本是什么。
  • 我尝试通过评论它编译的这个方法。 // public static

    void foo(Foo

    function, P param) { // }

  • @ErwinBolwidt Eclipse Mars (4.5) had a bug 在这个区域。由于 Neon (4.6) 也 ECJ 报告了歧义。 Winter 在切换到 ECJ 时必须使用 >= 4.6 的版本。

标签: java java-8 functional-interface


【解决方案1】:

问题在于Thread.sleepfoo 都超载了。所以存在循环依赖。

  • 为了找出使用哪个sleep方法,我们需要知道目标类型,即调用哪个foo方法
  • 为了找出调用哪个foo方法,我们需要知道参数的功能签名,即我们选择了哪个sleep方法

虽然人类读者很清楚,对于这种情况,只有 2×2 组合中的一种是有效的,但编译器必须遵循适用于任意组合的正式规则,因此,语言设计者必须做出调整。

为了方法引用的有用性,对明确的引用进行了特殊处理,例如您的Test::sleep

JLS §15.13.1

对于某些方法引用表达式,无论目标函数类型如何,都只有一种可能的编译时声明和一种可能的调用类型(第 15.12.2.6 节)。这样的方法引用表达式被称为exact。不精确的方法引用表达式被称为不精确

请注意,这种区别类似于 隐式类型 lambda 表达式 (arg -&gt; expression) 和 显式 lambda 表达式 ((Type arg) -&gt; expression) 之间的区别。

当您查看JLS, §15.12.2.5., Choosing the Most Specific Method 时,您会发现方法引用的签名仅用于确切 方法引用,就像选择正确的foo 时,决定为对sleep的方法还没做呢。

如果e 是一个精确的方法引用表达式 (§15.13.1),那么 i) 对于所有 i (1 ≤ i ≤ k),@987654337 @iVi 相同,且 ii) 以下其中一项为真:

  • R₂void
  • R₁ &lt;: R₂
  • R₁ 是原始类型,R₂ 是引用类型,方法引用的编译时声明具有原始类型的返回类型。
  • R₁ 是引用类型,R₂ 是原始类型,方法引用的编译时声明有一个返回类型,即引用类型。

上述规则已在 §15.12.2.5 中说明。对于非泛型方法,针对泛型方法重定向到 §18.5.4(此处适用,因为您的 foo 方法是泛型的),包含 exactly the same rule,措辞略有不同。

由于在选择最具体的方法时没有考虑方法引用的签名,所以没有最具体的方法,foo的调用是模棱两可的。第二个编译器错误是继续处理源代码并可能报告更多错误的策略的结果,而不是在第一个错误处立即停止编译。 foo 的两次调用之一会导致“不兼容的类型”错误,如果该调用正在发生,但实际上由于“歧义调用”错误已被排除。

【讨论】:

  • 这必须是这里被接受和赞成的答案。太好了!
  • 我在交换foo方法的顺序后也发现了问题,感谢正确答案,:)
  • @Holger,虽然我同意您回答中的一般信息,但您不应该根据第 18.5.4 条争论吗?在那里你会发现一个类似的项目符号“如果 ei 是一个精确的方法引用”,所以结果是一样的。
  • @StephanHerrmann:你说得对,泛型方法有重定向。原则上,我必须引用整个 §15.12.2.5 §18.5.4 来证明根本没有不精确的方法引用的规则,但是,我决定将这个答案保持在合理的尺寸。
【解决方案2】:

我个人认为这是某种递归,有点像这样:我们需要解析方法才能找到目标类型,但我们需要知道目标类型才能解析方法时间>。这与 特殊的 void 兼容性规则有关,但我承认我并不完全明白。

如果你有这样的东西,事情会更有趣:

public static void cool(Predicate<String> predicate) {

}

public static void cool(Function<String, Integer> function) {

}

并尝试通过以下方式调用它:

cool(i -> "Test"); // this will fail compilation 

顺便说一句,如果你让你的 lambda 显式,这将起作用:

foo((Long t) -> Thread.sleep(t), 1000L);

【讨论】:

  • 基本上,您发现了问题。
【解决方案3】:

您可以在自己的类中重新创建问题,方法是向类 Test 添加一个带有两个参数的方法 sleep,如下所示:

public static void sleep(long millis) {
}

public static void sleep(long millis, int nanos) {
}

所以问题确实是方法sleep过载造成的。

JLS 表明初始方法选择代码只查看功能接口的类型参数的数量 - 只有在第二阶段,它才查看功能接口内方法的签名。

JLS 15.13:

无法指定要匹配的特定签名, 例如,Arrays::sort(int[])。相反,功能接口 提供用作重载输入的参数类型 分辨率算法(§15.12.2)。

(本节倒数第二段)

所以在Thread::sleep 的情况下,void sleep(long) 可能匹配功能接口FooVoid&lt;P&gt;,而重载void sleep(long, int) 可能匹配功能接口Foo&lt;P, R&gt;。这就是为什么您会收到“对 foo 的引用不明确”的错误。

当它尝试进一步查看如何将Foo&lt;P, R&gt; 与函数方法R call(P param1) 匹配到方法void sleep(long, int) 时,它发现这实际上是不可能的,并且您会得到另一个编译错误:

test/Test.java:7: error: incompatible types: cannot infer type-variable(s) P,R
        foo(Thread::sleep, 1000L); // error
           ^
    (argument mismatch; bad return type in method reference
      void cannot be converted to R)

【讨论】:

  • 有趣,如果你交换你定义的foo方法的顺序,错误信息是不同的。
  • 我看不出引用的句子如何与您的陈述“初始方法选择代码仅查看类型参数的数量”相匹配。它没有说明“类型参数的数量”。 §15.13 明确规定“当一个类型的多个成员方法具有相同名称时,或者当一个类具有多个构造函数时,根据表达式所针对的功能接口类型选择适当的方法或构造函数,如第 15.13.1 节所述。
  • 这种解释没有意义,因为void sleep(long, int)可能与功能接口Foo&lt;P, R&gt; 匹配——或者至少不超过void sleep(long)。并且还不清楚类型参数的数量与此有什么关系。
猜你喜欢
  • 1970-01-01
  • 2015-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多