有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java方法引用对于线程是不明确的。睡觉

我遇到了一个奇怪的问题,对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::sleepnative。它改变了什么吗?我不认为超载在这里起作用。为什么会这样

编辑:使用javac版本1.8.0_111


共 (3) 个答案

  1. # 1 楼答案

    问题是,Thread.sleepfoo都超载了。所以有一个循环依赖

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

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

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

    JLS §15.13.1

    For some method reference expressions, there is only one possible compile-time declaration with only one possible invocation type (§15.12.2.6), regardless of the targeted function type. Such method reference expressions are said to be exact. A method reference expression that is not exact is said to be inexact.

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

    当您查看JLS, §15.12.2.5., Choosing the Most Specific Method时,您将看到方法引用的签名仅用于精确的方法引用,因为在选择正确的foo时,还没有决定正确的sleep方法

    If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:

    • R₂ is void.
    • R₁ <: R₂.
    • R₁ is a primitive type, R₂ is a reference type, and the compile-time declaration for the method reference has a return type which is a primitive type.
    • R₁ is a reference type, R₂ is a primitive type, and the compile-time declaration for the method reference has a return type which is a reference type.

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

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

  2. # 2 楼答案

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

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

    所以这个问题实际上是由方法sleep过载引起的

    JLS指出,初始方法选择代码只查看函数接口的类型参数数量——只有在第二阶段,它才会查看函数接口内方法的签名

    JLS 15.13:

    It is not possible to specify a particular signature to be matched, for example, Arrays::sort(int[]). Instead, the functional interface provides argument types that are used as input to the overload resolution algorithm (§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)
    
  3. # 3 楼答案

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

    当你有这样的事情时,事情就更有趣了:

    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);