Thread.sleep 的方法引用不明确

2022-09-02 12:30:15

我遇到了一个奇怪的问题,其中对方法的引用是模棱两可的,但具有相同签名的方法不是。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::sleepnativeThread::sleep(long, int)

编辑:使用 javac 版本 1.8.0_111


答案 1

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

public static void sleep(long millis) {
}

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

因此,问题实际上是由方法睡眠过载引起的。

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

JLS 15.13:

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

(本节倒数第二段)

所以在 的情况下,潜在匹配功能接口,而重载可能匹配功能接口。这就是为什么你得到“引用foo是模棱两可的”错误。Thread::sleepvoid 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)

答案 2

问题是 和 都过载。所以有一个循环依赖关系。Thread.sleepfoo

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

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

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

JLS §15.13.1

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

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

当您查看 JLS§ 15.12.2.5.选择最具体的方法时,您会看到方法引用的签名仅用于精确的方法引用,因为在选择正确的方法时,尚未做出正确方法的决定。foosleep

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

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

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

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


推荐