了解Stream.of()和IntStream.range()之间的区别

2023/05/31

1. 概述

Stream API是Java 8中的主要新特性之一。

在本教程中,我们将讨论一个有趣的话题:Stream.of()和IntStream.range()之间的区别。

2. 问题简介

我们可以使用Stream.of()方法初始化一个Stream对象,例如Stream.of(1, 2, 3, 4, 5)。或者,如果我们想要初始化一个整数流,IntStream是一种更直接的类型,例如IntStream.range(1, 6)。但是,这两种方法创建的整数流的行为可能不同。

像往常一样,我们将通过一个例子来理解这个问题。首先,让我们以不同的方式创建两个流:

Stream<Integer> normalStream = Stream.of(1, 2, 3, 4, 5);
IntStream intStreamByRange = IntStream.range(1, 6);

接下来,我们将对上面的两个流执行相同的例程:

STREAM.peek(add to a result list)
    .sorted()
    .findFirst();

因此,我们在每个流上调用三个方法:

  • 首先-调用peek()方法将处理过的元素收集到结果列表中
  • 然后-对元素进行排序
  • 最后-从流中取出第一个元素

由于两个流包含相同的整数元素,我们认为在执行后,两个结果列表也应该包含相同的整数。那么接下来,让我们编写一个测试来检查它是否产生了我们期望的结果:

List<Integer> normalStreamPeekResult = new ArrayList<>();
List<Integer> intStreamPeekResult = new ArrayList<>();

// First, the regular Stream
normalStream.peek(normalStreamPeekResult::add)
    .sorted()
    .findFirst();
assertEquals(Arrays.asList(1, 2, 3, 4, 5), normalStreamPeekResult);

// Then, the IntStream
intStreamByRange.peek(intStreamPeekResult::add)
    .sorted()
    .findFirst();
assertEquals(Arrays.asList(1), intStreamPeekResult);

执行后发现,normalStream.peek()填充的结果列表包含所有整数元素。然而,由intStreamByRange.peek()填充的列表只有1个元素。

接下来,让我们弄清楚为什么它会这样。

3. 流是惰性的

在我们解释为什么两个流在前面的测试中产生不同的结果列表之前,让我们先了解一下Java Stream在设计上是惰性的。

“惰性”意味着流仅在被告知产生结果时才执行所需的操作。换句话说,在执行终端操作之前,不会执行对流的中间操作。这种惰性行为可能是一个优势,因为它允许更高效的处理并防止不必要的计算。

为了快速理解这种惰性行为,让我们暂时摆脱之前测试中的sort()方法调用并重新运行它:

List<Integer> normalStreamPeekResult = new ArrayList<>();
List<Integer> intStreamPeekResult = new ArrayList<>();

// First, the regular Stream
normalStream.peek(normalStreamPeekResult::add)
    .findFirst();
assertEquals(Arrays.asList(1), normalStreamPeekResult);

// Then, the IntStream
intStreamByRange.peek(intStreamPeekResult::add)
    .findFirst();
assertEquals(Arrays.asList(1), intStreamPeekResult);

这次,两个流都只填充了相应结果列表中的第一个元素。这是因为findFirst()方法是终端操作,只需要一个元素-第一个

现在我们了解了Stream是惰性的,接下来,让我们弄清楚为什么当sorted()方法加入调用时两个结果列表不同。

4. 调用sorted()可能会将流变成“急切”

首先,让我们看一下由Stream.of()初始化的流。终端操作findFirst()只需要流中的第一个整数,它是sorted()操作之后的第一个

我们知道必须遍历所有整数才能对它们进行排序。因此,调用sorted()已将流变为“急切”。因此,每个元素都会调用peek()方法

另一方面,IntStream.range()返回一个按顺序排序的IntStream。也就是说,IntStream对象的输入已经排序了。此外,当它对已经排序的输入进行排序时,Java会应用优化以使sorted()操作成为无操作。因此,结果列表中仍然只有一个元素。

接下来,让我们看另一个基于TreeSet的Stream示例:

List<String> peekResult = new ArrayList<>();

TreeSet<String> treeSet = new TreeSet<>(Arrays.asList("CCC", "BBB", "AAA", "DDD", "KKK"));

treeSet.stream()
    .peek(peekResult::add)
    .sorted()
    .findFirst();

assertEquals(Arrays.asList("AAA"), peekResult);

我们知道TreeSet是一个有序的集合。因此,我们看到peekResult列表只包含一个字符串,尽管我们调用了sorted()。

5. 总结

在本文中,我们以Stream.of()和IntStream.range()为例来理解调用sorted()可能会使Stream从“惰性”变为“急切”。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章