注意:这个问题源于一个死链接,这是一个以前的 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,两者的参数都应该是 Comparator
。然而,这里的方法引用指向 Integer
类的静态方法。
那么,为什么要编译呢?
Integer::compare
而不是 Integer::max
和 Integer::min
。
Integer
的方法不是Comparator
的方法。
让我解释一下这里发生了什么,因为它并不明显!
首先,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>
。
Comparator
是一个功能接口,并且 Integer::max
符合该接口(在考虑自动装箱/拆箱之后)。它需要两个 int
值并返回一个 int
- 正如您期望的 Comparator<Integer>
一样(再次,眯着眼睛忽略整数/整数差异)。
但是,鉴于 Integer.max
不符合 Comparator.compare
的语义,我不希望它做正确的事。事实上,它通常并不真正起作用。例如,做一个小改动:
for (int i = 1; i <= 20; i++)
list.add(-i);
... 现在 max
值为 -20,min
值为 -1。
相反,两个调用都应使用 Integer::compare
:
System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
Comparator<Integer>
将有 int compare(Integer, Integer)
...Java 允许 int max(int, int)
的方法引用转换为它并不令人难以置信...
Integer::max
的语义?从它的角度来看,您传入了一个满足其规范的函数,这就是它真正可以继续进行的全部内容。
Comparator.compare
的类型签名。它应该返回 {LessThan, GreaterThan, Equal}
的 enum
,而不是 int
。这样,功能接口实际上不会匹配,并且您会收到编译错误。 IOW:Comparator.compare
的类型签名没有充分捕捉到比较两个对象的含义的语义,因此与比较对象完全无关的其他接口意外地具有相同的类型签名。
这是因为 Integer::min
解析为 Comparator<Integer>
接口的实现。
Integer::min
的方法引用解析为 Integer.min(int a, int b)
,解析为 IntBinaryOperator
,并且可能自动装箱发生在某处,使其成为 BinaryOperator<Integer>
。
并且 Stream<Integer>
的 min()
和 max()
方法要求实现 Comparator<Integer>
接口。
现在这将解析为单个方法 Integer compareTo(Integer o1, Integer o2)
。属于 BinaryOperator<Integer>
类型。
因此奇迹发生了,因为这两种方法都是 BinaryOperator<Integer>
。
Integer::min
实现 Comparable
并不完全正确。它不是可以实现任何东西的类型。但它被评估为实现 Comparable
的对象。
Comparator<Integer>
是一个单一抽象方法(又名“函数式”)接口,并且 Integer::min
履行其契约,因此 lambda 可以解释为 this。我不知道您如何看待 BinaryOperator 在这里发挥作用(或 IntBinaryOperator,或者)——它与 Comparator 之间没有子类型关系。
除了 David M. Lloyd 提供的信息之外,还可以补充一点,允许这样做的机制称为目标类型。
这个想法是编译器分配给 lambda 表达式或方法引用的类型不仅取决于表达式本身,还取决于它的使用位置。
表达式的目标是分配其结果的变量或传递其结果的参数。
Lambda 表达式和方法引用被分配一个与其目标类型匹配的类型,如果可以找到这样的类型。
有关更多信息,请参阅 Java 教程中的 Type Inference section。
我的数组获取最大值和最小值时出错,所以我的解决方案是:
int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
list.stream().mapToInt(i -> i).max().get()
。.getAsInt()
而不是get()
,因为您正在处理OptionalInt
。max()
函数提供自定义比较器时!Comparator
文档我们可以看到它使用注释@FunctionalInterface
进行了修饰。这个装饰器是允许将Integer::max
和Integer::min
转换为Comparator
的魔法。@FunctionalInterface
主要仅用于文档目的,因为编译器可以很高兴地使用具有单个抽象方法的任何接口来执行此操作。