The Java 8 Stream API Tutorial – Java 8流API教程

最后修改: 2016年 6月 15日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this comprehensive tutorial, we’ll go through the practical uses of Java 8 Streams from creation to parallel execution.

在这个综合教程中,我们将了解Java 8 Streams从创建到并行执行的实际用途。

To understand this material, readers need to have a basic knowledge of Java 8 (lambda expressions, Optional, method references) and of the Stream API. In order to be more familiar with these topics, please take a look at our previous articles: New Features in Java 8 and Introduction to Java 8 Streams.

为了理解这份材料,读者需要对Java 8(lambda表达式、Optional、方法引用)和Stream API有基本了解。为了更熟悉这些主题,请看看我们以前的文章。Java 8 的新功能Java 8 Streams 简介

2. Stream Creation

2.流的创造

There are many ways to create a stream instance of different sources. Once created, the instance will not modify its source, therefore allowing the creation of multiple instances from a single source.

有许多方法来创建不同来源的流实例。一旦创建,该实例将不会修改其源,因此允许从一个源创建多个实例。

2.1. Empty Stream

2.1.空流

We should use the empty() method in case of the creation of an empty stream:

在创建一个空流的情况下,我们应该使用empty()方法。

Stream<String> streamEmpty = Stream.empty();

We often use the empty() method upon creation to avoid returning null for streams with no element:

我们经常在创建时使用empty() 方法,以避免对没有元素的流返回null

public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}

2.2. Stream of Collection

2.2.收集之流

We can also create a stream of any type of Collection (Collection, List, Set):

我们也可以创建一个任何类型的CollectionCollection, List, Set)的流。

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

2.3. Stream of Array

2.3.阵列之流

An array can also be the source of a stream:

一个数组也可以是一个流的来源。

Stream<String> streamOfArray = Stream.of("a", "b", "c");

We can also create a stream out of an existing array or of part of an array:

我们也可以从一个现有的数组或数组的一部分中创建一个流。

String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);

2.4. Stream.builder()

2.4. Stream.builder()

When builder is used, the desired type should be additionally specified in the right part of the statement, otherwise the build() method will create an instance of the Stream<Object>:

当使用builder时,应在语句的右边部分额外指定所需的类型,否则build()方法将创建一个Stream<Object>:的实例。

Stream<String> streamBuilder =
  Stream.<String>builder().add("a").add("b").add("c").build();

2.5. Stream.generate()

2.5. Stream.generate()

The generate() method accepts a Supplier<T> for element generation. As the resulting stream is infinite, the developer should specify the desired size, or the generate() method will work until it reaches the memory limit:

generate()方法接受一个Supplier<T> 用于元素生成。由于生成的流是无限的,开发者应该指定所需的大小,否则generate()方法将一直工作到达到内存极限。

Stream<String> streamGenerated =
  Stream.generate(() -> "element").limit(10);

The code above creates a sequence of ten strings with the value “element.”

上面的代码创建了一个具有“元素”值的十个字符串序列。

2.6. Stream.iterate()

2.6. Stream.iterate()

Another way of creating an infinite stream is by using the iterate() method:

另一种创建无限流的方法是使用iterate()方法。

Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);

The first element of the resulting stream is the first parameter of the iterate() method. When creating every following element, the specified function is applied to the previous element. In the example above the second element will be 42.

结果流的第一个元素是iterate()方法的第一个参数。在创建每一个后面的元素时,指定的函数会应用到前面的元素。在上面的例子中,第二个元素将是42。

2.7. Stream of Primitives

2.7.基元流[/strong]

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.

Java 8提供了从三种原始类型中创建流的可能性。int、longdouble.由于Stream<T>是一个泛型接口,而且没有办法用泛型来使用基元作为类型参数,所以创建了三个新的特殊接口。IntStream, LongStream, DoubleStream.

Using the new interfaces alleviates unnecessary auto-boxing, which allows for increased productivity:

使用新的界面可以减轻不必要的自动装箱,从而提高生产力。

IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);

The range(int startInclusive, int endExclusive) method creates an ordered stream from the first parameter to the second parameter. It increments the value of subsequent elements with the step equal to 1. The result doesn’t include the last parameter, it is just an upper bound of the sequence.

range(int startInclusive, int endExclusive)方法创建一个从第一个参数到第二个参数的有序流。它以等于1的步长增加后续元素的值。结果不包括最后一个参数,它只是一个序列的上界。

The rangeClosed(int startInclusive, int endInclusive) method does the same thing with only one difference, the second element is included. We can use these two methods to generate any of the three types of streams of primitives.

rangeClosed(int startInclusive, int endInclusive)方法做了同样的事情,只有一个区别,即第二个元素被包含在内。我们可以使用这两个方法来生成三种类型的基元流中的任何一种。

Since Java 8, the Random class provides a wide range of methods for generating streams of primitives. For example, the following code creates a DoubleStream, which has three elements:

自 Java 8 起,Random 类提供了一系列用于生成基元流的方法。例如,下面的代码创建了一个DoubleStream,它有三个元素。

Random random = new Random();
DoubleStream doubleStream = random.doubles(3);

2.8. Stream of String

2.8.字符串的流

We can also use String as a source for creating a stream with the help of the chars() method of the String class. Since there is no interface for CharStream in JDK, we use the IntStream to represent a stream of chars instead.

我们也可以使用String作为源,在String类的chars()方法的帮助下创建一个流。由于JDK中没有CharStream的接口,我们使用IntStream来表示一个字符流。

IntStream streamOfChars = "abc".chars();

The following example breaks a String into sub-strings according to specified RegEx:

下面的例子根据指定的RegEx将一个字符串分成子字符串。

Stream<String> streamOfString =
  Pattern.compile(", ").splitAsStream("a, b, c");

2.9. Stream of File

2.9.文件流[/strong]

Furthermore, Java NIO class Files allows us to generate a Stream<String> of a text file through the lines() method. Every line of the text becomes an element of the stream:

此外,Java NIO类Files 允许我们通过lines()方法生成一个文本文件的Stream<String>。文本的每一行都成为流的一个元素。

Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = 
  Files.lines(path, Charset.forName("UTF-8"));

The Charset can be specified as an argument of the lines() method.

Charset可以作为lines()方法的一个参数来指定。

3. Referencing a Stream

3.引用一个流

We can instantiate a stream, and have an accessible reference to it, as long as only intermediate operations are called. Executing a terminal operation makes a stream inaccessible.

我们可以实例化一个流,并且对它有一个可访问的引用,只要只调用中间操作。执行一个终端操作会使流变得不可访问.

To demonstrate this, we will forget for a while that the best practice is to chain the sequence of operation. Besides its unnecessary verbosity, technically the following code is valid:

为了证明这一点,我们将暂时忘记最好的做法是连锁操作的顺序。除了不必要的繁琐之外,从技术上讲,下面的代码是有效的。

Stream<String> stream = 
  Stream.of("a", "b", "c").filter(element -> element.contains("b"));
Optional<String> anyElement = stream.findAny();

However, an attempt to reuse the same reference after calling the terminal operation will trigger the IllegalStateException:

然而,在调用终端操作后试图重复使用同一引用将触发IllegalStateException:

Optional<String> firstElement = stream.findFirst();

As the IllegalStateException is a RuntimeException, a compiler will not signalize about a problem. So it is very important to remember that Java 8 streams can’t be reused.

由于IllegalStateException是一个RuntimeException,编译器将不会发出问题的信号。因此,记住Java 8 流不能被重复使用是非常重要的。

This kind of behavior is logical. We designed streams to apply a finite sequence of operations to the source of elements in a functional style, not to store elements.

这种行为是符合逻辑的。我们设计流是为了将有限的操作序列以函数式的方式应用于元素的来源,而不是为了存储元素。

So to make the previous code work properly, some changes should be made:

因此,为了使以前的代码正常工作,应该做一些改变。

List<String> elements =
  Stream.of("a", "b", "c").filter(element -> element.contains("b"))
    .collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Optional<String> firstElement = elements.stream().findFirst();

4. Stream Pipeline

4.水流管道

To perform a sequence of operations over the elements of the data source and aggregate their results, we need three parts: the source, intermediate operation(s) and a terminal operation.

为了对数据源的元素进行一连串的操作并汇总其结果,我们需要三个部分:中间操作终端操作。

Intermediate operations return a new modified stream. For example, to create a new stream of the existing one without few elements, the skip() method should be used:

中间操作返回一个新的修改过的流。例如,要在现有的流中创建一个没有几个元素的新流,应该使用skip()方法。

Stream<String> onceModifiedStream =
  Stream.of("abcd", "bbcd", "cbcd").skip(1);

If we need more than one modification, we can chain intermediate operations. Let’s assume that we also need to substitute every element of the current Stream<String> with a sub-string of the first few chars. We can do this by chaining the skip() and map() methods:

如果我们需要不止一次的修改,我们可以将中间的操作连锁起来。让我们假设我们还需要将当前Stream<String>中的每个元素替换成前几个字符的子字符串。我们可以通过连锁skip()map()方法来做到这一点。

Stream<String> twiceModifiedStream =
  stream.skip(1).map(element -> element.substring(0, 3));

As we can see, the map() method takes a lambda expression as a parameter. If we want to learn more about lambdas, we can take a look at our tutorial Lambda Expressions and Functional Interfaces: Tips and Best Practices.

正如我们所看到的,map()方法需要一个lambda表达式作为参数。如果我们想了解更多关于lambdas的信息,我们可以看看我们的教程lambda表达式和功能接口。提示和最佳实践

A stream by itself is worthless; the user is interested in the result of the terminal operation, which can be a value of some type or an action applied to every element of the stream. We can only use one terminal operation per stream.

一个流本身是没有价值的;用户感兴趣的是终端操作的结果,它可以是某种类型的值或应用于流中每个元素的操作。我们在每个流中只能使用一个终端操作。

The correct and most convenient way to use streams is by a stream pipeline, which is a chain of the stream source, intermediate operations, and a terminal operation:

使用流的正确和最方便的方法是通过流管道,这是一个由流源、中间操作和终端操作组成的链条:

List<String> list = Arrays.asList("abc1", "abc2", "abc3");
long size = list.stream().skip(1)
  .map(element -> element.substring(0, 3)).sorted().count();

5. Lazy Invocation

5.懒惰的调用

Intermediate operations are lazy. This means that they will be invoked only if it is necessary for the terminal operation execution.

中间操作是懒惰的。这意味着,只有在终端操作的执行需要时才会调用它们。

For example, let’s call the method wasCalled(), which increments an inner counter every time it’s called:

例如,让我们调用方法 wasCalled()每次调用都会增加一个内部计数器。

private long counter;
 
private void wasCalled() {
    counter++;
}

Now let’s call the method wasCalled() from operation filter():

现在让我们从操作wasCalled()中调用方法filter()

List<String> list = Arrays.asList(“abc1”, “abc2”, “abc3”);
counter = 0;
Stream<String> stream = list.stream().filter(element -> {
    wasCalled();
    return element.contains("2");
});

As we have a source of three elements, we can assume that the filter() method will be called three times, and the value of the counter variable will be 3. However, running this code doesn’t change counter at all, it is still zero, so the filter() method wasn’t even called once. The reason why is missing of the terminal operation.

由于我们有一个包含三个元素的源,我们可以假设filter()方法将被调用三次,而counter变量的值将是3。然而,运行这段代码并没有改变counter,它仍然是零,所以filter()方法甚至没有被调用一次。其原因是终端操作的缺失。

Let’s rewrite this code a little bit by adding a map() operation and a terminal operation, findFirst(). We will also add the ability to track the order of method calls with the help of logging:

让我们稍微重写一下这段代码,添加一个map()操作和一个终端操作,findFirst().我们还将在日志的帮助下添加跟踪方法调用顺序的能力。

Optional<String> stream = list.stream().filter(element -> {
    log.info("filter() was called");
    return element.contains("2");
}).map(element -> {
    log.info("map() was called");
    return element.toUpperCase();
}).findFirst();

The resulting log shows that we called the filter() method twice and the map() method once. This is because the pipeline executes vertically. In our example, the first element of the stream didn’t satisfy the filter’s predicate. Then we invoked the filter() method for the second element, which passed the filter. Without calling the filter() for the third element, we went down through the pipeline to the map() method.

结果日志显示,我们调用了filter()方法两次,map()方法一次。这是因为流水线是垂直执行的。在我们的例子中,流的第一个元素不满足过滤器的谓词。然后我们对第二个元素调用了filter()方法,该方法通过了过滤器。在没有为第三个元素调用filter()的情况下,我们通过管道向下到map()方法。

The findFirst() operation satisfies by just one element. So in this particular example, the lazy invocation allowed us to avoid two method calls, one for the filter() and one for the map().

findFirst()操作仅由一个元素来满足。所以在这个特殊的例子中,懒人调用让我们避免了两个方法的调用,一个是filter(),一个是map()

6. Order of Execution

6.执行的顺序

From the performance point of view, the right order is one of the most important aspects of chaining operations in the stream pipeline:

从性能的角度来看,正确的顺序是流管道中链式操作的最重要方面之一:

long size = list.stream().map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).skip(2).count();

Execution of this code will increase the value of the counter by three. This means that we called the map() method of the stream three times, but the value of the size is one. So the resulting stream has just one element, and we executed the expensive map() operations for no reason two out of the three times.

执行这段代码将使计数器的值增加3。这意味着我们调用了三次流的map()方法,但是size的值是1。所以结果流只有一个元素,而我们在三次中的两次无缘无故地执行了昂贵的map()操作。

If we change the order of the skip() and the map() methods, the counter will increase by only one. So we will call the map() method only once:

如果我们改变skip()map()方法的顺序计数器将只增加一个。所以我们将只调用map()方法一次。

long size = list.stream().skip(2).map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).count();

This brings us to the following rule: intermediate operations which reduce the size of the stream should be placed before operations which are applying to each element. So we need to keep methods such as skip(), filter(), and distinct() at the top of our stream pipeline.

这就给我们带来了以下规则。减少流大小的中间操作应该放在应用于每个元素的操作之前。因此我们需要将诸如skip()、filter()distinct()等方法放在我们流管道的顶部。

7. Stream Reduction

7.减少水流

The API has many terminal operations which aggregate a stream to a type or to a primitive: count(), max(), min(), and sum(). However, these operations work according to the predefined implementation. So what if a developer needs to customize a Stream’s reduction mechanism? There are two methods which allow us to do this, the reduce() and the collect() methods.

API有许多终端操作,它们将一个流聚集到一个类型或一个基元上。count()、max()、min()、sum()。然而,这些操作是根据预定义的实现来工作的。那么,如果开发者需要自定义流的缩减机制呢?有两种方法允许我们这样做,即reduce()collect()方法。

7.1. The reduce() Method

7.1.reduce()方法

There are three variations of this method, which differ by their signatures and returning types. They can have the following parameters:

这个方法有三种变化,它们的签名和返回类型不同。它们可以有以下参数。

identity – the initial value for an accumulator, or a default value if a stream is empty and there is nothing to accumulate

identity – 累加器的初始值,如果流是空的,没有什么可累加的,则是默认值。

accumulator – a function which specifies the logic of the aggregation of elements. As the accumulator creates a new value for every step of reducing, the quantity of new values equals the stream’s size and only the last value is useful. This is not very good for the performance.

累加器 – 一个指定元素聚合逻辑的函数。由于累加器每减少一步都会创建一个新的值,新值的数量等于流的大小,只有最后一个值是有用的。这对性能不是很好。

combiner – a function which aggregates the results of the accumulator. We only call combiner in a parallel mode to reduce the results of accumulators from different threads.

combiner – 一个将累加器的结果汇总的函数。我们只在并行模式下调用combiner,以减少来自不同线程的累加器的结果。

Now let’s look at these three methods in action:

现在让我们来看看这三种方法的作用。

OptionalInt reduced =
  IntStream.range(1, 4).reduce((a, b) -> a + b);

reduced = 6 (1 + 2 + 3)

还原的= 6 (1 + 2 + 3)

int reducedTwoParams =
  IntStream.range(1, 4).reduce(10, (a, b) -> a + b);

reducedTwoParams = 16 (10 + 1 + 2 + 3)

reducedTwoParams = 16 (10 + 1 + 2 + 3)

int reducedParams = Stream.of(1, 2, 3)
  .reduce(10, (a, b) -> a + b, (a, b) -> {
     log.info("combiner was called");
     return a + b;
  });

The result will be the same as in the previous example (16), and there will be no login, which means that combiner wasn’t called. To make a combiner work, a stream should be parallel:

结果将与前面的例子(16)一样,没有登录,这意味着组合器没有被调用。为了使组合器工作,一个流应该是并行的。

int reducedParallel = Arrays.asList(1, 2, 3).parallelStream()
    .reduce(10, (a, b) -> a + b, (a, b) -> {
       log.info("combiner was called");
       return a + b;
    });

The result here is different (36), and the combiner was called twice. Here the reduction works by the following algorithm: the accumulator ran three times by adding every element of the stream to identity. These actions are being done in parallel. As a result, they have (10 + 1 = 11; 10 + 2 = 12; 10 + 3 = 13;). Now combiner can merge these three results. It needs two iterations for that (12 + 13 = 25; 25 + 11 = 36).

这里的结果是不同的(36),组合器被调用了两次。这里的还原是通过以下算法进行的:累积器运行三次,将流中的每个元素都加到identity。这些动作都是平行进行的。结果,他们有(10 + 1 = 11;10 + 2 = 12;10 + 3 = 13;)。现在组合器可以合并这三个结果。它需要进行两次迭代(12+13=25;25+11=36)。

7.2. The collect() Method

7.2.collect()方法

The reduction of a stream can also be executed by another terminal operation, the collect() method. It accepts an argument of the type Collector, which specifies the mechanism of reduction. There are already created, predefined collectors for most common operations. They can be accessed with the help of the Collectors type.

流的减少也可以由另一个终端操作执行,即collect()方法。它接受一个类型为Collector的参数,它指定了还原机制。对于大多数常见的操作,已经有了预定义的收集器。它们可以在Collectors类型的帮助下被访问。

In this section, we will use the following List as a source for all streams:

在本节中,我们将使用以下List作为所有流的来源。

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
  new Product(14, "orange"), new Product(13, "lemon"),
  new Product(23, "bread"), new Product(13, "sugar"));

Converting a stream to the Collection (Collection, List or Set):

将一个流转换为CollectionCollection, List orSet):

List<String> collectorCollection = 
  productList.stream().map(Product::getName).collect(Collectors.toList());

Reducing to String:

还原为字符串

String listToString = productList.stream().map(Product::getName)
  .collect(Collectors.joining(", ", "[", "]"));

The joiner() method can have from one to three parameters (delimiter, prefix, suffix). The most convenient thing about using joiner() is that the developer doesn’t need to check if the stream reaches its end to apply the suffix and not to apply a delimiter. Collector will take care of that.

joiner()方法可以有一到三个参数(分隔符、前缀、后缀)。使用joiner()最方便的地方是,开发者不需要检查流是否到达终点来应用后缀,也不需要应用分隔符。Collector会处理这个问题。

Processing the average value of all numeric elements of the stream:

处理流中所有数字元素的平均值:

double averagePrice = productList.stream()
  .collect(Collectors.averagingInt(Product::getPrice));

Processing the sum of all numeric elements of the stream:

处理流中所有数字元素的总和:

int summingPrice = productList.stream()
  .collect(Collectors.summingInt(Product::getPrice));

The methods averagingXX(), summingXX() and summarizingXX() can work with primitives (int, long, double) and with their wrapper classes (Integer, Long, Double). One more powerful feature of these methods is providing the mapping. As a result, the developer doesn’t need to use an additional map() operation before the collect() method.

方法averagingXX(), summingXX()summarizingXX()可以与基元(int, long, double)以及它们的封装类(Integer, Long, Double)一起工作。这些方法的一个更强大的特点是提供映射。因此,开发者不需要在collect()方法之前使用一个额外的map()操作。

Collecting statistical information about stream’s elements:

收集关于流元素的统计信息:

IntSummaryStatistics statistics = productList.stream()
  .collect(Collectors.summarizingInt(Product::getPrice));

By using the resulting instance of type IntSummaryStatistics, the developer can create a statistical report by applying the toString() method. The result will be a String common to this one “IntSummaryStatistics{count=5, sum=86, min=13, average=17,200000, max=23}.”

通过使用产生的IntSummaryStatistics类型的实例,开发者可以通过应用toString()方法创建一个统计报告。结果将是一个与此相同的String “IntSummaryStatistics{count=5, sum=86, min=13, average=17,200000, max=23}”

It is also easy to extract from this object separate values for count, sum, min, and average by applying the methods getCount(), getSum(), getMin(), getAverage(), and getMax(). All of these values can be extracted from a single pipeline.

通过应用getCount()、getSum()、getMin()、getAverage()、getMax()方法,也很容易从这个对象中提取count、sum、min、average的单独值。所有这些值都可以从一个管道中提取。

Grouping of stream’s elements according to the specified function:

根据指定的功能对流的元素进行分组:

Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
  .collect(Collectors.groupingBy(Product::getPrice));

In the example above, the stream was reduced to the Map, which groups all products by their price.

在上面的例子中,流被减少到Map,它按价格对所有产品进行分组。

Dividing stream’s elements into groups according to some predicate:

根据一些谓词将流的元素分成若干组:

Map<Boolean, List<Product>> mapPartioned = productList.stream()
  .collect(Collectors.partitioningBy(element -> element.getPrice() > 15));

Pushing the collector to perform additional transformation:

推动采集器进行额外的转换:

Set<Product> unmodifiableSet = productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
  Collections::unmodifiableSet));

In this particular case, the collector has converted a stream to a Set, and then created the unchangeable Set out of it.

在这个特殊的案例中,收集器已经将一个流转换为Set,然后从中创建了不可更改的Set

Custom collector:

自定义收集器:

If for some reason a custom collector should be created, the easiest and least verbose way of doing so is to use the method of() of the type Collector.

如果因为某些原因需要创建一个自定义的收集器,最简单和最省事的方法是使用Collector.类型的of()方法。

Collector<Product, ?, LinkedList<Product>> toLinkedList =
  Collector.of(LinkedList::new, LinkedList::add, 
    (first, second) -> { 
       first.addAll(second); 
       return first; 
    });

LinkedList<Product> linkedListOfPersons =
  productList.stream().collect(toLinkedList);

In this example, an instance of the Collector got reduced to the LinkedList<Persone>.

在这个例子中,Collector的一个实例被还原为LinkedList

8. Parallel Streams

8.平行流

Before Java 8, parallelization was complex. The emergence of the ExecutorService and the ForkJoin simplified a developer’s life a little bit, but it was still worth remembering how to create a specific executor, how to run it, and so on. Java 8 introduced a way of accomplishing parallelism in a functional style.

在Java 8之前,并行化是很复杂的。ExecutorServiceForkJoin的出现将开发人员的生活简化了一些,但是仍然值得记住如何创建一个特定的执行器,如何运行它,等等。Java 8引入了一种以函数式风格完成并行化的方法。

The API allows us to create parallel streams, which perform operations in a parallel mode. When the source of a stream is a Collection or an array, it can be achieved with the help of the parallelStream() method:

该API允许我们创建并行流,它以并行模式执行操作。当流的来源是一个Collection或一个array时,可以在parallelStream()方法的帮助下实现。

Stream<Product> streamOfCollection = productList.parallelStream();
boolean isParallel = streamOfCollection.isParallel();
boolean bigPrice = streamOfCollection
  .map(product -> product.getPrice() * 12)
  .anyMatch(price -> price > 200);

If the source of a stream is something other than a Collection or an array, the parallel() method should be used:

如果流的源头不是Collectionarray,应该使用parallel()方法。

IntStream intStreamParallel = IntStream.range(1, 150).parallel();
boolean isParallel = intStreamParallel.isParallel();

Under the hood, Stream API automatically uses the ForkJoin framework to execute operations in parallel. By default, the common thread pool will be used and there is no way (at least for now) to assign some custom thread pool to it. This can be overcome by using a custom set of parallel collectors.

在引擎盖下,Stream API自动使用ForkJoin框架来并行地执行操作。默认情况下,将使用公共线程池,没有办法(至少现在)为其分配一些自定义的线程池。这一点可以通过使用一组自定义的并行收集器来克服。

When using streams in parallel mode, avoid blocking operations. It is also best to use parallel mode when tasks need a similar amount of time to execute. If one task lasts much longer than the other, it can slow down the complete app’s workflow.

在并行模式下使用流时,要避免阻塞性操作。当任务需要类似的时间来执行时,也最好使用并行模式。如果一个任务比另一个任务持续的时间长很多,就会拖慢整个应用程序的工作流程。

The stream in parallel mode can be converted back to the sequential mode by using the sequential() method:

并行模式的流可以通过使用sequential()方法转换回顺序模式。

IntStream intStreamSequential = intStreamParallel.sequential();
boolean isParallel = intStreamSequential.isParallel();

9. Conclusion

9.结论

The Stream API is a powerful, but simple to understand set of tools for processing the sequence of elements. When used properly, it allows us to reduce a huge amount of boilerplate code, create more readable programs, and improve an app’s productivity.

Stream API是一套强大但简单易懂的工具,用于处理元素的序列。如果使用得当,它可以让我们减少大量的模板代码,创建更可读的程序,并提高应用程序的生产力。

In most of the code samples shown in this article, we left the streams unconsumed (we didn’t apply the close() method or a terminal operation). In a real app, don’t leave an instantiated stream unconsumed, as that will lead to memory leaks.

在本文所展示的大多数代码示例中,我们没有占用流(我们没有应用close()方法或终端操作)。在真正的应用程序中,不要让一个实例化的流不被占用,因为这将导致内存泄漏。

The complete code samples that accompany this article are available over on GitHub.

本文所附的完整代码样本可在GitHub上获得