我遇到了一个奇怪的问题,即对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;
}
}
我得到了这两个错误:
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::sleep
是native
。它有什么改变吗?我不认为重载Thread::sleep(long, int)
在这里发挥作用。为什么会这样?
编辑:使用javac版本1.8.0_111
答案 0 :(得分:12)
您可以通过向类Test添加一个方法sleep
来重新创建问题,如下所示:
public static void sleep(long millis) {
}
public static void sleep(long millis, int nanos) {
}
所以这个问题实际上是由方法sleep过载引起的。
JLS指示初始方法选择代码仅查看功能接口的类型参数的数量 - 仅在第二阶段中查看功能接口内的方法的签名。
无法指定要匹配的特定签名, 例如,Arrays :: sort(int [])。相反,功能界面 提供用作重载输入的参数类型 分辨率算法(§15.12.2)。
(本节倒数第二段)
因此,在Thread::sleep
的情况下,void sleep(long)
可能与功能接口FooVoid<P>
匹配,而重载void sleep(long, int)
可能与功能接口Foo<P, R>
匹配。这就是为什么你得到“foo引用含糊不清”的错误。
当它试图进一步看看如何将Foo<P, R>
与功能方法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)
答案 1 :(得分:8)
问题是Thread.sleep
和foo
都被重载了。所以存在循环依赖。
sleep
方法,我们需要知道目标类型,即要调用的foo
方法foo
方法,我们需要知道参数的功能签名,即我们选择的sleep
方法虽然人类读者很清楚,对于这种情况,只有2×2组合中的一个是有效的,编译器必须遵循适用于任意组合的正式规则,因此,语言设计者必须进行切割。
为了方法参考的有用性,对于明确的引用有一种特殊的处理方式,例如Test::sleep
:
对于某些方法引用表达式,只有一种可能的编译时声明,只有一种可能的调用类型(第15.12.2.6节),而不管目标函数类型如何。这样的方法引用表达式被称为 exact 。不完全的方法引用表达式被认为是不精确。
请注意,这种区别类似于隐式类型 lambda表达式(arg -> expression
)和显式类型 lambda表达式((Type arg) -> expression
)之间的区别
当您查看JLS, §15.12.2.5., Choosing the Most Specific Method时,您会看到方法引用的签名仅用于完全方法引用,就像选择正确的foo
时一样,正确的sleep
方法的决定尚未确定。
如果
e
是精确的方法参考表达式(§15.13.1),则i)对于所有 i (1≤ i ≤k),U
i 与V
i 相同,且ii)以下之一为真:
R₂
是void
。R₁ <: R₂
。R₁
是基本类型,R₂
是引用类型,方法引用的编译时声明具有返回类型,它是基本类型。R₁
是引用类型,R₂
是基本类型,方法引用的编译时声明具有返回类型,它是引用类型。
上述规则已在§15.12.2.5中说明。对于非泛型方法,重定向到§18.5.4以获取泛型方法(此处适用于foo
方法是通用的),包含exactly the same rule,但措辞略有不同。
由于在选择最具体的方法时不考虑方法引用的签名,因此没有最具体的方法,foo
的调用是不明确的。第二个编译器错误是策略继续处理源代码并可能报告更多错误的结果,而不是在第一个错误时停止编译。 foo
的两次调用之一导致了“不兼容类型”错误,如果调用正在发生,但实际上由于“模糊调用”错误而被排除。
答案 2 :(得分:5)
我个人认为这是某种递归,不管怎样这样:我们需要解析方法才能找到目标类型,但我们需要知道目标类型才能解决方法。这与特殊的无效兼容性规则有关,但我承认我没有完全理解它。
当你有这样的事情时,事情甚至更有乐趣:
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);