Thread.sleep的方法引用不明确

Win*_*ter 16 java java-8 functional-interface

我遇到一个奇怪的问题,方法引用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到了这两个错误:

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

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

编辑:使用javac版本1.8.0_111

Erw*_*idt 12

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

public static void sleep(long millis) {
}

public static void sleep(long millis, int nanos) {
}
Run Code Online (Sandbox Code Playgroud)

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

JLS指示初始方法选择代码仅查看功能接口的类型参数的数量 - 仅在第二阶段中查看功能接口内的方法的签名.

JLS 15.13:

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

(本节倒数第二段)

因此,在可能匹配功能接口的情况下Thread::sleep,过载可能与功能接口匹配.这就是为什么你得到"引用foo是模糊的"错误.void sleep(long)FooVoid<P>void sleep(long, int)Foo<P, R>

当它试图进一步去看看如何搭配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)
Run Code Online (Sandbox Code Playgroud)

  • 我没有看到引用的句子如何匹配你的语句"初始方法选择代码只查看类型参数的数量".它没有说"类型参数的数量".§15.13清楚地说"*当一个类型的多个成员方法具有相同名称时,或者当一个类具有多个构造函数时,根据表达式所针对的功能接口类型选择适当的方法或构造函数,如指定的那样在§15.13.1.*" (4认同)

Hol*_*ger 9

问题是,这两个,Thread.sleepfoo,超载.所以存在循环依赖.

  • 为了找出sleep使用哪种方法,我们需要知道目标类型,即foo调用哪种方法
  • 为了找出foo要调用的方法,我们需要知道参数的功能签名,即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),那么ⅰ)(1≤ ≤k)时,U是一样的V,和ii)以下之一是真实的:

  • R?void.
  • R? <: R?.
  • R?是原始类型,R?是引用类型,方法引用的编译时声明具有返回类型,它是基本类型.
  • R?是一个引用类型,R?是一个基本类型,方法引用的编译时声明具有一个返回类型,它是一个引用类型.

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

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


Eug*_*ene 6

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

当您遇到类似这样的事情时,事情会变得更有趣:

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

}

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

}
Run Code Online (Sandbox Code Playgroud)

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

cool(i -> "Test"); // this will fail compilation 
Run Code Online (Sandbox Code Playgroud)

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

foo((Long t) -> Thread.sleep(t), 1000L);
Run Code Online (Sandbox Code Playgroud)

  • 基本上,您已确定了问题。 (3认同)