在Java中使用Lambda表达式查找最大值

17

这是我的代码

    List<Integer> ints = Stream.of(1,2,4,3,5).collect(Collectors.toList());
    Integer maxInt = ints.stream()
                              .max(Comparator.comparing(i -> i))
                              .get();

    System.out.println("Maximum number in the set is " + maxInt);

输出:

Maximum number in the set is 5

我无法区分我代码中以下部分中的两个 i

Comparator.comparing(i -> i)

有人能不能好心解释一下两个 i 的区别呢?

3个回答

24

Comparator.comparing(…) 方法旨在创建一个 Comparator,该比较器使用基于对象属性的顺序进行比较。当将 lambda 表达式 i -> i(这是 (int i) -> { return i; } 的简写)作为属性提供者函数时,生成的 Comparator 将直接比较值本身。如果要比较的对象具有像 Integer 一样的自然排序,则此方法有效。

因此,

Stream.of(1,2,4,3,5).max(Comparator.comparing(i -> i))
.ifPresent(maxInt->System.out.println("Maximum number in the set is " + maxInt));

与...相同

Stream.of(1,2,4,3,5).max(Comparator.naturalOrder())
.ifPresent(maxInt->System.out.println("Maximum number in the set is " + maxInt));

虽然后者更有效率,因为它被实现为所有具有自然排序(并实现 Comparable)类型的单例。

为什么 max 需要一个 Comparator 呢?这是因为你正在使用通用类 Stream,其中可能包含任意对象。

这允许例如像这样使用:streamOfPoints.max(Comparator.comparing(p->p.x)) 找到 x 值最大的点,而 Point 本身没有自然排序。或者像这样做:streamOfPersons.sorted(Comparator.comparing(Person::getAge))

当使用专门的 IntStream 时,可以直接使用自然顺序,这可能更有效率:

IntStream.of(1,2,4,3,5).max()
.ifPresent(maxInt->System.out.println("Maximum number in the set is " + maxInt));
为了说明“自然顺序”和基于属性的顺序之间的差异:
Stream.of("a","bb","aaa","z","b").max(Comparator.naturalOrder())
.ifPresent(max->System.out.println("Maximum string in the set is " + max));

这将打印

集合中的最大字符串为z

因为String的自然排序是按字典顺序排列,其中z大于b,b大于a。

另一方面,

Stream.of("a","bb","aaa","z","b").max(Comparator.comparing(s->s.length()))
.ifPresent(max->System.out.println("Maximum string in the set is " + max));

将会打印

集合中最长的字符串是aaa

因为aaa是流中所有String中长度最长的。这是使用Comparator.comparing的预期用例,当使用方法引用时可以使其更易读,即Comparator.comparing(String::length),几乎可以自我解释......


你说的“自然顺序”是什么意思?能举个例子解释一下吗?你说的“将值与自身进行比较”是什么意思?能举个例子解释一下吗? - Kick Buttowski
所以在这种情况下,i -> i 有点多余了吗? - Kick Buttowski
是的,正如所说,它与Comparator.naturalOrder()相同。因此对于IntStream,您甚至可以省略Comparator并实现相同的效果。 - Holger
我把我的答案发布成了一个问题,你能否看一下并与我分享你的想法? https://dev59.com/mmAf5IYBdhLWcg3wqUON - Kick Buttowski

5

这个函数(注意:->是用于闭包的,不要与=>混淆,后者是用于比较的)

i -> i

这意味着您需要按原样比较整个对象。例如,如果我有一个 i,则需要比较 i

一个不太琐碎的例子可能是:

max(Comparator.comparing(i -> -i))

这将为您提供最小值或

max(Comparator.comparing(i -> Math.abs(100-i))

这个函数会返回一个距离100最远的值。

max(Comparator.comparing(i -> i.toString()))

这将为您提供最大的字符串比较,例如"9" > "10"作为字符串。


我该如何在控制台中打印出两个i? - Kick Buttowski
2
@KickButtowski,你是指我在几个评论中链接的有关Lambdas的Oracle教程吗?;) 请说出这句话:“->不是比较符号”。我已经说过两次了。 - Peter Lawrey
@KickButtowski,这个东西几乎太简单了,以至于很难看出它存在的意义。设计上什么都不做的东西很难找到其目的。 - Peter Lawrey
3
请记住,i -> i(int i) -> { return i; } 的简写。通过查看更长的形式应该可以清楚地了解发生了什么... - Holger
1
@Kick Buttowski:i 不会发生任何变化。指定的函数仅用于确定 Comparator排序属性。如果指定的函数只返回值本身,则意味着使用值的自然顺序。这已经过时了,因为 Comparator.naturalOrder() 会执行相同的操作。但是,如果您返回 -i,它将反转顺序。如果您使用 i -> Integer.bitCount(i),它将按位计数对 int 数字进行排序。 - Holger
显示剩余12条评论

0

Comparator.comparing 期望一个函数,该函数将源对象映射到实际进行比较的值 - 在您的情况下,由于您不想预处理要比较的值,因此 i 简单地映射到它本身。


你不明白我的回答的哪一部分? - Smutje
Comparator.comparing期望一个函数,该函数将源对象映射到实际进行比较的值。 - Kick Buttowski
你能否添加一个图表来展示两个 I i->i 的关系? - Kick Buttowski
两个 i 是相同的 - Comparator.comparing 期望对每个输入元素应用一个函数,而函数 i -> i 描述了你取每个输入并将其作为输出返回,因此要比较的值 没有 被转换。 - Smutje
所以我得到了这个,但是要拿什么来比较呢?难道不是i和i进行比较吗?为什么我们需要将i与i进行比较,当它们是同一件事情时?你能理解我的困惑吗? - Kick Buttowski
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接