Java 8 流的 .min() 和 .max():为啥会编译?

Posted

技术标签:

【中文标题】Java 8 流的 .min() 和 .max():为啥会编译?【英文标题】:Java 8 stream's .min() and .max(): why does this compile?Java 8 流的 .min() 和 .max():为什么会编译? 【发布时间】:2014-04-28 23:43:59 【问题描述】:

注意:这个问题源自之前的 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::maxInteger::min @ChristofferHammarström 我知道;请注意我在代码摘录之前所说的“我知道,这很荒谬” 我不是想纠正你,我是在告诉一般人。你说得好像你认为荒谬的部分是Integer 的方法不是Comparator 的方法。 【参考方案1】:

让我解释一下这里发生了什么,因为它并不明显!

首先,Stream.max() 接受 Comparator 的实例,以便可以将流中的项目相互比较以找到最小值或最大值,以某种您无需过多担心的最佳顺序。

那么问题当然是,为什么Integer::max 会被接受?毕竟它不是比较器!

答案在于新的 lambda 功能在 Java 8 中的工作方式。它依赖于一个非正式地称为“单一抽象方法”接口或“SAM”接口的概念。这个想法是任何具有一个抽象方法的接口都可以由任何 lambda - 或方法引用 - 其方法签名与接口上的一个方法匹配。所以检查Comparator接口(简单版):

public Comparator<T> 
    T compare(T o1, T o2);

如果一个方法正在寻找Comparator&lt;Integer&gt;,那么它本质上就是在寻找这个签名:

int xxx(Integer o1, Integer o2);

我使用“xxx”因为方法名称不用于匹配目的

因此,Integer.min(int a, int b)Integer.max(int a, int b) 都足够接近,自动装箱将允许它在方法上下文中显示为 Comparator&lt;Integer&gt;

【讨论】:

或者:list.stream().mapToInt(i -&gt; i).max().get(). @assylias 您想使用.getAsInt() 而不是get(),因为您正在处理OptionalInt ...当我们只想为max() 函数提供自定义比较器时! 值得注意的是,这个“SAM 接口”实际上被称为“功能接口”,查看Comparator 文档我们可以看到它被注释@FunctionalInterface 修饰。这个装饰器是允许Integer::maxInteger::min 转换为Comparator 的魔法。 @ChrisKerekes 装饰器 @FunctionalInterface 主要仅用于文档目的,因为编译器可以通过一个抽象方法对任何接口进行愉快的处理。【参考方案2】:

我的数组获取最大值和最小值时出错,所以我的解决方案是:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();

【讨论】:

【参考方案3】:

这是因为 Integer::min 解析为 Comparator&lt;Integer&gt; 接口的实现。

Integer::min 的方法引用解析为Integer.min(int a, int b),解析为IntBinaryOperator,并且可能在某处发生了自动装箱,使其成为BinaryOperator&lt;Integer&gt;

Stream&lt;Integer&gt;min()max()方法要求实现Comparator&lt;Integer&gt;接口。 现在这解析为单一方法Integer compareTo(Integer o1, Integer o2)。类型为BinaryOperator&lt;Integer&gt;

因此奇迹发生了,因为这两种方法都是BinaryOperator&lt;Integer&gt;

【讨论】:

Integer::min 实现Comparable 并不完全正确。它不是可以实现任何东西的类型。但它被评估为一个实现Comparable的对象。 @Lii 谢谢,我刚刚修好了。 Comparator&lt;Integer&gt; 是一个单一抽象方法(又名“函数式”)接口,Integer::min 履行了它的契约,因此 lambda 可以解释为这样。我不知道你如何看待 BinaryOperator 在这里发挥作用(或 IntBinaryOperator,或者)——它和 Comparator 之间没有子类型关系。【参考方案4】:

除了 David M. Lloyd 提供的信息之外,还可以补充一点,允许这样做的机制称为目标类型

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

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

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

有关更多信息,请参阅 Java 教程中的 Type Inference section。

【讨论】:

【参考方案5】:

Comparator 是一个功能接口Integer::max 符合该接口(在考虑自动装箱/拆箱后)。它需要两个 int 值并返回一个 int - 就像您期望的 Comparator&lt;Integer&gt; 一样(再次,眯着眼睛忽略整数/整数差异)。

但是,鉴于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());

【讨论】:

功能接口我知道;我知道为什么它会给出错误的结果。我只是想知道编译器到底怎么没有对我大喊大叫 @fge:那是你不清楚的拆箱吗? (我还没有仔细研究过那部分。)Comparator&lt;Integer&gt; 将有 int compare(Integer, Integer)... 这并不令人难以置信,Java 允许 int max(int, int) 的方法引用转换为... @fge: 编译器应该知道Integer::max 的语义吗?从它的角度来看,你传入了一个满足其规范的函数,这就是它真正可以继续下去的全部。 @fge:特别是,如果您了解正在发生的部分,但对其中的一个特定方面感兴趣,则值得在问题中明确说明以避免人们在浪费时间解释你已经知道的部分。 我认为根本问题是Comparator.compare 的类型签名。它应该返回enumLessThan, GreaterThan, Equal,而不是int。这样,功能接口实际上不会匹配,并且您会收到编译错误。 IOW:Comparator.compare 的类型签名没有充分捕捉到比较两个对象意味着什么的语义,因此与比较对象完全无关的其他接口意外地具有相同的类型签名。

以上是关于Java 8 流的 .min() 和 .max():为啥会编译?的主要内容,如果未能解决你的问题,请参考以下文章

math对象的主要方法

使用java编写两个方法min和max在链表中查找最大值和最小值,但输入列表是整数数组

java 计算大小

java 数据类型整理

来自输入文本文件的 Min Max 和 Avg

2019.10.8