Streams from Java


Processing Order

Stream objects can be processed sequentially or in parallel. In sequential * mode, elements are processed in the order of the source of “Stream”. If the Stream is ordered (such as the SortedMap implementation or List), the process guarantees the ordering of the matching sources. In other cases, however, care should be taken not to rely on order (see: Is Java’s HashMap ‘ ‘keySet() iterating order consistent?). .

Example:


  List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                      .filter(e -> (e % 2) == 1).count(); 

  System.out.println(howManyOddNumbers); // Output: 2
Copy the code

Live on Ideone

The Parallel pattern allows multiple threads to be used on multiple cores, but does not guarantee the order in which elements are processed.

If you call multiple methods on a sequential Stream, you don’t have to call each method. For example, if a Stream is filtered and the number of elements is reduced to one, no subsequent calls to methods such as sort occur. This improves the performance of sequential streams – an optimization that would not be possible with a parallel Stream. Example:


  // parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                              .filter(e -> (e % 2) == 1).count();

  System.out.println(howManyOddNumbersParallel); // Output: 2
Copy the code

Live on Ideone

Differences from Containers (or Collections)

Although some operations can be performed on Containers and Streams, they are ultimately used for different purposes and support different operations. Containers focus more on how elements are stored and how they are accessed efficiently. A Stream, on the other hand, does not provide direct access to or manipulation of its elements; It is more dedicated to a group of objects as a collective entity and performs operations on that entity as a whole. Stream and Collection are separate high-level abstractions for these different purposes.

Consuming Streams

A Stream will only be traversed if there are terminal operations, such as count(), collect(), or forEach(). Otherwise, nothing is done to the Stream.

In the example below, no terminal operation is added to the Stream, so the filter() operation is not called, and no output is produced because peek() is not a terminal operation.


  IntStream.range(1, 10).filter(a -> a % 2 == 0).peek(System.out::println);
Copy the code

Live on Ideone

This is a Stream sequence with a valid terminal operation, thus producing an output.

You can also use forEach instead of peek:


  IntStream.range(1, 10).filter(a -> a % 2 == 0).forEach(System.out::println); 
Copy the code

Live on Ideone

Output:

2 4 June 8

After an endpoint operation, the Stream is consumed by execution and cannot be reused.

In general, the operation of a Stream looks like this:




NOTE: Parameter checks are always performed even if there is no terminal operation:


  try {
      IntStream.range(1, 10).filter(null);
  } catch (NullPointerException e) {
      System.out.println("We got a NullPointerException as null was passed as an argument to filter()");
  }
Copy the code

Live on Ideone

Output:

We got a NullPointerException as null was passed as an argument to filter()

Creating a Frequency Map

The groupingBy (Classifier, Downstream) Collector allows you to classify a collection of Stream elements into a Map by classifying each element in the group and performing subsequent operations on elements classified in the same group.

A typical example of this principle is the use of a Map to compute streams. In this case, the classifier is a simple identity function that returns the element AS-is. Use counting() to count the number of elements that are equal.


  Stream.of("apple", "orange", "banana", "apple")
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .entrySet()
        .forEach(System.out::println);
Copy the code

The subsequent operation is itself a collector (Collectors. Counting ()), which operates on elements of type String and generates a result of type Long. The result of the collect method call is a Map.

This would produce the following output:

banana=1

orange=1

apple=2

Infinite Streams

You can generate a Stream without ending. Calling terminal methods on an infinite Stream causes the Stream to enter an infinite loop. A Stream limit method can be used to limit the number of Stream terms that Java processes.

This example generates a Stream of all natural numbers, starting with the number 1. Each successive term of Stream is one higher than the previous one. By calling the Stream limit method, only the first five entries of the Stream are considered and printed.


// Generate infinite stream - 1, 2, 3, 4, 5, 6, 7, ...
IntStream naturalNumbers = IntStream.iterate(1, x -> x + 1);

// Print out only the first 5 terms
naturalNumbers.limit(5).forEach(System.out::println);
Copy the code

Output:

One, two, three, four, five

Collect Elements of a Stream into a Collection Collect Elements of a Stream into a Collection


Collect with toList() and toSet()

The Stream. Collect operation makes it easy to collect Stream elements into the container:


System.out.println(Arrays
    .asList("apple", "banana", "pear", "kiwi", "orange")
    .stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]
Copy the code

Other collections instances, such as Set, can be obtained by using other built-in Collectors methods. For example, Collectors. ToSet () collects streams into a Set.


Explicit control over the implementation ofList or Set

According to Collectors# toList () and Collectors#toSet() documentation, there is no guarantee of the type, variability, serialization, or thread-safety of the List or Set returned.

To explicitly control the implementation to be returned, use Collectors# toCollection (Supplier), which returns a new empty collection of the specified type.


// syntax with method reference System.out.println(strings .stream() .filter(s -> s ! = null && s.length() <= 3) .collect(Collectors.toCollection(ArrayList::new)) ); // syntax with lambda System.out.println(strings .stream() .filter(s -> s ! = null && s.length() <= 3) .collect(Collectors.toCollection(() -> new LinkedHashSet<>())) );Copy the code

Parallel Stream

Note: Before deciding which Stream to use, look at the ParallelStream vs Sequential Stream comparison.

You can use these methods when you want to execute Stream operations concurrently.


List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();
Copy the code

Or:


Stream<String> aParallelStream = data.parallelStream();
Copy the code

To perform an operation defined for a parallel stream, call the terminal operator:


aParallelStream.forEach(System.out::println);
Copy the code

(A possible) output from the parallel Stream:

Three

Four

One

Two

Five

The order may change because all elements are processed in parallel (this may make it faster). ParallelStream is used when ordering is irrelevant.


Performance impact

In the case of networks, parallel streams can reduce the overall performance of the application because all parallel streams use a common fork-join thread pool for the network.

On the other hand, in many other cases, parallel streams can significantly improve performance based on the number of cores available in the currently running CPU.


  • One leaf knows the autumn
  • Author: Zhiqiu
  • [Reprint please reserve the original source, author.]