Thread.sleep的方法引用不明确

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

 package test; public class Test { public static void main(String[] args) { foo(Test::sleep, 1000L); //fine foo((FooVoid)Thread::sleep, 1000L); //fine foo(Thread::sleep, 1000L); //error } public static void sleep(long millis) throws InterruptedException { Thread.sleep(millis); } public static 

void foo(Foo

function, P param) {} public static

void foo(FooVoid

function, P param) {} @FunctionalInterface public interface Foo

{ R call(P param1) throws Exception; } @FunctionalInterface public interface FooVoid

{ void call(P param1) throws Exception; } }

我得到了这两个错误:

 Error:(9, 17) java: reference to foo is ambiguous both method 

foo(test.Test.Foo

,P) in test.Test and method

foo(test.Test.FooVoid

,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

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

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

所以问题实际上是由于方法睡眠过载这一事实引起的。

JLS指示初始方法选择代码仅查看function接口的类型参数的数量 – 仅在第二阶段中它查看function接口内的方法的签名。

JLS 15.13:

无法指定要匹配的特定签名,例如,Arrays :: sort(int [])。 相反,function接口提供的参数类型用作重载决策算法(第15.12.2节)的输入。

(本节倒数第二段)

因此,在Thread::sleep的情况下, void sleep(long)可能与function接口FooVoid

匹配,而overload void sleep(long, int)可能与function接口Foo匹配。 这就是为什么你得到“引用foo是模糊的”错误。

当它试图进一步看看如何将Foo与函数方法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) 

问题是Thread.sleepfoo都被重载了。 所以存在循环依赖。

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

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

为了方法引用的有用性,对于明确的引用有一种特殊的处理方式,比如你的Test::sleep

JLS§15.13.1

对于某些方法引用表达式,只有一种可能的编译时声明,只有一种可能的调用类型(第15.12.2.6节),而不管目标函数类型如何。 这样的方法参考表达式被认为是精确的 。 据说不精确的方法引用表达式是不精确的

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

当您查看JLS,§15.12.2.5。,选择最具体的方法时 ,您将看到方法引用的签名仅用于精确的方法引用,就像选择正确的foo ,决定正确的sleep方法尚未制定。

如果e是精确的方法参考表达式(§15.13.1),则i)对于所有i (1≤i≤k), U iV i相同,并且ii)以下之一为真:

  • R₂ void
  • R₁ <: R₂
  • R₁是基本类型, R₂是引用类型,方法引用的编译时声明具有返回类型,它是基本类型。
  • R₁是引用类型, R₂是基本类型,方法引用的编译时声明具有返回类型,它是引用类型。

上述规则已在§15.12.2.5中说明。 对于非generics方法,重定向到§18.5.4以获取generics方法(这里适用于您的foo方法是通用的),包含完全相同的规则 ,但措辞略有不同。

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

我个人认为这是某种递归,不知何故这样: 我们需要解析方法以找到目标类型,但我们需要知道目标类型才能解决方法 。 这与特殊的无效兼容性规则有关 ,但我承认我并没有完全理解它。

当你有这样的事情时,事情甚至更有乐趣:

 public static void cool(Predicate predicate) { } public static void cool(Function function) { } 

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

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

顺便说一句,如果你使lambda 明确 ,这将工作:

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