使用lambdas和泛型时,对方法的引用是不明确的

ski*_*iwi 23 java lambda java-8

我在下面的代码中收到错误,我认为不应该存在...使用JDK 8u40编译此代码.

public class Ambiguous {
    public static void main(String[] args) {
        consumerIntFunctionTest(data -> {
            Arrays.sort(data);
        }, int[]::new);

        consumerIntFunctionTest(Arrays::sort, int[]::new);
    }

    private static <T> void consumerIntFunctionTest(final Consumer<T> consumer, final IntFunction<T> generator) {

    }

    private static <T> void consumerIntFunctionTest(final Function<T, ?> consumer, final IntFunction<T> generator) {

    }
}
Run Code Online (Sandbox Code Playgroud)

错误如下:

错误:(17,9)java:对consumerIntFunctionTest的引用与net.tuis.ubench.Ambiguous中的方法consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction)和方法consumerIntFunctionTest(java.util.)都不明确. net.tuis.ubench.Ambiguous匹配中的function.Function,java.util.function.IntFunction)

错误发生在以下行:

consumerIntFunctionTest(Arrays::sort, int[]::new);
Run Code Online (Sandbox Code Playgroud)

我相信应该没有错误,因为所有Arrays::sort引用都是类型的void,并且它们都没有返回值.正如您所看到的,当我明确扩展lambda 时它确实有效Consumer<T>.

这真的是javac中的错误,还是JLS声明lambda在这种情况下无法自动扩展?如果是后者,我仍然认为这很奇怪,consumerIntFunctionTest因为第一个参数Function<T, ?>不应该匹配.

Hol*_*ger 9

在你的第一个例子中

consumerIntFunctionTest(data -> {
        Arrays.sort(data);
    }, int[]::new);
Run Code Online (Sandbox Code Playgroud)

lambda表达式具有一个void兼容的块,可以通过表达式的结构来识别,而无需解析实际的类型.

相比之下,在这个例子中

consumerIntFunctionTest(Arrays::sort, int[]::new);
Run Code Online (Sandbox Code Playgroud)

必须解析方法引用,以确定它是否符合void函数(Consumer)或值返回函数(Function).这同样适用于简化的lambda表达式

consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new);
Run Code Online (Sandbox Code Playgroud)

void根据已解决的目标方法,它可以是兼容的或价值兼容的.

问题是解析方法需要有关所需签名的知识,这应该通过目标类型确定,但是在知道通用方法的类型参数之前,目标类型是未知的.虽然理论上两者都可以立即确定,但是在规范中简化了(仍然非常复杂)过程,该方法首先执行重载决策,最后应用类型推断(参见JLS§15.12.2).因此,类型推断可以提供的信息不能用于解决重载决策.

但请注意15.12.2.1中描述的第一步.识别可能适用的方法包含:

根据以下规则,表达式可能与目标类型兼容:

  • 如果满足以下所有条件,则lambda表达式(第15.27节)可能与函数接口类型(第9.8节)兼容:

    • 目标类型的函数类型的arity与lambda表达式的arity相同.

    • 如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2).

    • 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(第15.27.2节).

  • 方法引用表达式(第15.13节)可能与函数接口类型兼容,如果类型的函数类型arity为n,则存在至少一个可能适用于方法引用表达式的方法,其中arity为n(§15.13.1),并且以下之一是真的:

    • 方法引用表达式具有ReferenceType :: [TypeArguments]标识符的形式,并且至少一个可能适用的方法是i)static并支持arity n,或ii)not static并支持arity n-1.

    • 方法引用表达式具有一些其他形式,并且至少一个可能适用的方法不是静态的.

...

潜在适用性的定义超出了基本的arity检查,也考虑了功能接口目标类型的存在和"形状".在某些涉及类型参数推断的情况下,在重载解析之前,无法正确键入作为方法调用参数出现的lambda表达式.

因此,在第一个示例中,其中一个方法按照lambda的形状进行排序,而在方法引用或lambda表达式由单个调用表达式组成的情况下,两个可能适用的方法都会忍受第一个选择过程并产生"模糊"错误在类型推断之前可以启动以帮助找到目标方法以确定它是否是void返回方法.

请注意,与使用x->{ foo(); }显式void兼容的lambda表达式一样,您可以使用x->( foo() )使lambda表达式显式值兼容.


你疯了进一步阅读这个答案,解释说这种组合类型推断和方法重载决策的限制是一个刻意(但不容易)的决定.

  • 正如在链接的答案中详细说明的那样(参见`比较`示例),在重载解析期间不考虑lambda表达式/方法引用的返回类型.请注意,最近的编译器会在声明站点上向您发出有关重载方法的潜在歧义的警告,而无需通过实际模糊的调用来查找.您知道"目标类型未知"适用于编译器(遵循正式过程)而不是我们的人类读者,并且不需要泛型,而只需要严格的解析步骤. (2认同)
  • @mkurz 这是 *表达式* 的正常包装,因为 `foo()` 可以是表达式或语句,而 `(foo())` 只能是表达式。例如,您可以写“var result = (foo());”,但不能写“(foo());”作为语句。同样,`{ foo(); }` 只能是一个语句,因为您可以将其写在需要语句的地方,但不能写 `var result = { foo(); }`。 (2认同)