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

Java 使用lambdas和泛型时,对方法的引用不明确,java,lambda,java-8,Java,Lambda,Java 8,我在下面的代码中得到了一个错误,我相信它不应该存在。。。使用JDK8U40编译此代码 public class Ambiguous { public static void main(String[] args) { consumerIntFunctionTest(data -> { Arrays.sort(data); }, int[]::new); consumerIntFunctionTest(Arra

我在下面的代码中得到了一个错误,我相信它不应该存在。。。使用JDK8U40编译此代码

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

    }
}
我认为应该没有错误,因为所有
数组::sort
引用都属于
void
类型,并且没有一个返回值。正如您所观察到的,当我显式地展开
消费者
lambda时,它确实起作用

这真的是javac中的一个bug,还是JLS声明在这种情况下lambda不能自动扩展?如果是后者,我仍然会认为这很奇怪,因为
consumerIntFunctionTest
与第一个参数
Function
不匹配

consumerIntFunctionTest(data -> {
        Arrays.sort(data);
    }, int[]::new);
lambda表达式有一个与
void
兼容的块,可以通过表达式的结构识别该块,而无需解析实际类型

相反,在示例中

consumerIntFunctionTest(Arrays::sort, int[]::new);
必须解析方法引用,以确定其是否符合
void
函数(
Consumer
)或值返回函数(
函数
)。这同样适用于简化的lambda表达式

consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new);
根据解析的目标方法,可以是兼容的或值兼容的

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

但请注意,中描述的第一步包含:

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

  • 如果满足以下所有条件,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)静态并支持arity n,或者ii)非静态并支持arity n-1

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

潜在适用性的定义超出了基本的算术检查,还考虑了功能接口目标类型的存在和“形状”。在某些涉及类型参数推断的情况下,在重载解析之后,作为方法调用参数出现的lambda表达式才能正确类型化

因此,在第一个示例中,其中一个方法是按lambda的形状排序的,而在方法引用或由唯一调用表达式组成的lambda表达式的情况下,这两个可能适用的方法都会经受第一个选择过程,并产生“不明确”的结果类型推断之前的错误可以帮助找到目标方法,以确定它是
void
还是返回值的方法

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



您还可以进一步阅读解释,这种组合类型推断和方法重载解析的限制是一个经过深思熟虑(但并不容易)的决定。

对于方法引用,您可以有完全不同的参数类型,更不用说返回类型,如果您有另一个方法,其中的arity(参数数量)是火柴

例如:

静态类Foo{
Foo(消费者运行的虚拟消费者){}
Foo(双函数longandlong){}
}
静态分类栏{
静态void方法(Runnable Runnable){}
静态void方法(整数num,字符串str){}
}
Bar.someMethod()
无法满足
LongandLongtolog
,但下面的代码在歧义方面会发出相同的编译错误:

newfoo(Bar::someMethod);
霍尔格的回答很好地解释了JLS中的逻辑和相关条款

二进制兼容性如何? 考虑
Foo
构造函数的
longandlong
版本是否不存在,但后来在库更新中添加,或者
Bar.someMethod()
的双参数版本是否不存在,但后来添加:突然之前编译的代码可能因此中断

这是方法重载的一个不幸的副作用,类似的问题甚至在lambda或方法引用出现之前就已经影响了普通方法调用

幸运的是,二进制兼容性得以保留。相关条款载于:

添加存在重载的新方法或构造函数
consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new);