注意:这个问题源于一个死链接,这是一个之前的SO问题,但这里…
请看这段代码(注意:我知道这段代码不会“工作”,应该使用Integer::compare——我只是从链接的问题中提取了它):
final ArrayList <Integer> list
= IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());
System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());
根据.min()和.max()的javadoc,两者的参数都应该是比较器。但是这里的方法引用是Integer类的静态方法。
那么,为什么要编译呢?
这是因为Integer::min解析为Comparator<Integer>接口的实现。
Integer::min的方法引用解析为Integer。min(int a, int b),解析为IntBinaryOperator,并且假定自动装箱发生在某个地方,使其BinaryOperator<Integer>。
Stream<Integer>的min() resp max()方法要求实现Comparator<Integer>接口。
现在它分解为一个方法Integer compareTo(Integer o1, Integer o2)。类型为BinaryOperator<Integer>。
因此,神奇的是,这两个方法都是BinaryOperator<Integer>。
Comparator是一个功能性接口,Integer::max遵循该接口(在考虑了自动装箱/拆箱之后)。它接受两个int值并返回一个int值——就像您期望Comparator<Integer>为的那样(同样,斜视以忽略Integer/int的差异)。
然而,我不期望它做正确的事情,给定Integer。max不符合Comparator.compare的语义。实际上,它在一般情况下是行不通的。例如,做一个小小的改变:
for (int i = 1; i <= 20; i++)
list.add(-i);
…现在最大值是-20最小值是-1。
相反,两个调用都应该使用Integer::compare:
System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
让我来解释一下这里发生了什么,因为它并不明显!
首先,stream .max()接受Comparator的一个实例,这样流中的项就可以相互比较以找到最小值或最大值,以某种您不需要太担心的最佳顺序进行比较。
问题是,为什么Integer::max被接受?毕竟它不是一个比较国!
答案在于新的lambda功能在Java 8中的工作方式。它依赖于一个被非正式地称为“单一抽象方法”接口或“SAM”接口的概念。其思想是,任何具有一个抽象方法的接口都可以由任何lambda(或方法引用)自动实现,其方法签名与接口上的一个方法匹配。因此检查Comparator接口(简单版本):
public Comparator<T> {
T compare(T o1, T o2);
}
如果一个方法正在寻找一个Comparator<Integer>,那么它本质上是在寻找这个签名:
int xxx(Integer o1, Integer o2);
我使用“xxx”是因为方法名不用于匹配目的。
因此,两者都是Integer。min(int a, int b)和Integer。max(int a, int b)的值足够接近,以至于自动装箱将允许它在方法上下文中以Comparator<Integer>的形式出现。
除了David M. Lloyd提供的信息,我们还可以补充说,允许这种情况发生的机制叫做目标类型。
其思想是,编译器为lambda表达式或方法引用分配的类型不仅取决于表达式本身,还取决于它被使用的位置。
表达式的目标是将其结果赋给的变量或将其结果传递给的参数。
Lambda表达式和方法引用被分配一个与其目标类型匹配的类型(如果可以找到这样的类型)。
有关更多信息,请参阅Java教程中的类型推断部分。