Java 8流的.min()和.max():为什么要编译?


215

注意:这个问题来自于以前的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());

据javadoc的.min().max(),两者的参数应该是一个Comparator。然而,这里的方法引用是针对Integer该类的静态方法的。

那么,为什么要编译呢?


6
请注意,它无法正常运行,应该使用Integer::compare代替Integer::maxInteger::min
ChristofferHammarström

@ChristofferHammarström我知道;请注意我在代码摘录之前怎么说“我知道,这很荒谬”
fge 2015年

3
我并不是要纠正你,而是要告诉别人。听起来好像您认为荒谬的部分是的方法Integer不是的方法Comparator
ChristofferHammarström,2015年

Answers:


242

让我解释一下这里发生的事情,因为这并不明显!

首先,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>在方法上下文中显示为。


28
或可替换地:list.stream().mapToInt(i -> i).max().get()
assylias 2014年

13
@assylias 在处理时,您想使用.getAsInt()而不是。get()OptionalInt
skiwi 2014年

...当我们只想为max()函数提供自定义比较器时!
Manu343726

值得注意的是,该“ SAM接口”实际上称为“功能接口”,在Comparator文档中我们可以看到它由注释修饰@FunctionalInterface。这个装饰器是允许Integer::maxInteger::min转换为的魔术Comparator
克里斯·克瑞克斯

2
@ChrisKerekes装饰器@FunctionalInterface主要仅用于文档目的,因为编译器可以使用单个抽象方法通过任何接口高兴地完成此操作。
errantlinguist

117

Comparator是一个功能性接口,并且Integer::max与该接口兼容(考虑了自动装箱/拆箱后)。它采用两个int值并返回int-就像您期望的那样Comparator<Integer>(再次,斜视忽略了Integer / int的差异)。

不过,我不希望它做正确的事情,因为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());

1
我知道功能接口;我知道为什么会给出错误的结果。我只是想知道编译器在
多大程度上

6
@fge:是您不清楚的拆箱吗?(我没有仔细研究过那部分。)A Comparator<Integer>将会int compare(Integer, Integer)……Java允许将方法引用int max(int, int)转换为该值…… 这并不令人费解……
Jon Skeet 2014年

7
@fge:编译器是否应该了解的语义Integer::max?从它的角度来看,您传入了一个符合其规范的函数,这就是它真正可以进行的所有工作。
Mark Peters 2014年

6
@fge:特别是,如果您了解正在发生的事情的一部分,但是对它的一个特定方面感兴趣,那么在问题中要弄清楚这一点,以避免人们浪费时间解释已经知道的内容。
乔恩·斯基特

20
我认为根本的问题是的类型签名Comparator.compare。它应该返回enum{LessThan, GreaterThan, Equal},而不是一个int。这样,功能接口实际上将不匹配,您将得到编译错误。IOW:的类型签名Comparator.compare没有充分地捕捉到比较两个对象的含义的语义,因此与比较对象完全无关的其他接口意外地具有相同的类型签名。
约尔格W¯¯米塔格

19

这行得通,因为Integer::min解决了Comparator<Integer>接口的实现。

的方法,参照Integer::min解析为Integer.min(int a, int b),解决了IntBinaryOperator,大概自动装箱某处发生使其成为一个BinaryOperator<Integer>

并要求接口的min()resp max()方法得以实现。 现在,这解决了单个方法。属于哪种类型。Stream<Integer>Comparator<Integer>
Integer compareTo(Integer o1, Integer o2)BinaryOperator<Integer>

因此,魔术发生了,因为两种方法都是BinaryOperator<Integer>


Integer::min实现不是完全正确的Comparable。它不是可以实现任何内容的类型。但是它被评估为实现的对象Comparable
2014年

1
@Lii谢谢,我刚刚修复了它。
skiwi 2014年

Comparator<Integer>是一种单一抽象方法(又称“功能性”)接口,并Integer::min履行其约定,因此lambda可以解释为此接口。我不知道您如何看待BinaryOperator在这里(或IntBinaryOperator)的作用-它与Comparator之间没有子类型关系。
圣保罗Ebermann

2

除了David M. Lloyd提供的信息外,还可以添加允许这种操作的机制称为目标类型

这个想法是,编译器分配给lambda表达式或方法引用的类型不仅取决于表达式本身,还取决于它的使用位置。

表达式的目标是为其结果分配到的变量或为其结果传递到的参数。

如果可以找到Lambda表达式和方法引用,则为其分配与其目标类型匹配的类型。

有关更多信息,请参见Java教程中的类型推断部分


1

我在获取最大值和最小值时遇到了一个错误,因此我的解决方案是:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.