1. What is a stream?

Stream uses an intuitive way similar to querying data from the database with SQL statements to provide a high-level abstraction of Java collection operations and expressions

Stream is a queue of elements from a data source and supports aggregation operations

  • Elements are objects of a specific type that form a queue. Stream in Java does not store elements, but calculates on demand
  • The source of the data source stream. Real collections, Arrays, I/Ochannels, Generators, etc.
  • Aggregation operations are similar to SQL statements, such as filter, map, reduce, find, match, sorted, etc.

Unlike previous Collection operations, Stream operations also have two basic features:

  • Pipelining: Intermediate operations will return the stream object itself. In this way, multiple operations can be concatenated into a pipeline, just like the fluent style. Doing so allows for optimizations such as laziness and short-circuiting
  • Internal iteration: In the past, collection traversal was done through iterator or For-Each to explicitly iterate outside the collection, which is called external iteration. Stream provides a way to iterate internally, implemented through the visitor pattern.

2. the characteristics of Stream

  • No storage: Stream is not a data structure, it is just a view of some kind of data source, the data source can be an array, Java container or I/O channel, etc.

  • Born for functional programming: Any modification to the Stream will not modify the data source behind it. For example, performing a filter operation on the Stream will not delete the filtered elements, but will generate a new Stream that does not contain the filtered elements.

  • Lazy execution: the operation on the Stream will not be executed immediately, but will only be executed when the user really needs the result.

  • Consumability: Stream can only be “consumed” once, once it is traversed, it will be invalid, just like the iterator of the container, if you want to traverse it again, you must regenerate it.

  • It can be seen in the figure: Get some colored balls as a data source, first filter out the red ones, and reconstruct them into random triangles. Then filter out the small triangles, and finally calculate the perimeter of the remaining graphics.

For stream processing, there are three key operations: They are the stream creation operation, intermediate operation and terminal operation.

3. Create operation

Create a stream from an existing collection (common)

In Java 8, in addition to adding a lot of Stream-related classes, the collection class itself has been enhanced, and the stream method has been added to convert a collection class into a stream.

1
2
List<String> strings = Arrays.asList("stream", "stream1", "stream2", "stream3", "stream4");
Stream<String> stream = strings.stream();

Above, create a stream through an existing List. In addition, there is a parallelStream method, which can create a parallel stream for the collection (multi-threaded mode, thread safety issues need to be considered)

Create a stream through Stream

You can use the methods provided by the Stream class to directly return a stream consisting of specified elements.

  • of
1
Stream<String> stream = Stream.of("stream", "stream1", "stream2", "stream3", "stream4");

As in the above code, create and return a Stream directly through the of method.

  • generator (rarely used)

4. Stream intermediate operation

Stream has many key operations. Multiple intermediate operations can be connected to form a pipeline. Each intermediate operation is like a worker on the pipeline. Each worker can process the pipeline, and the result after processing is still a stream.

List of common intermediate operations

  1. filter

    The filter method is used to filter out elements by setting the conditions (used to filter into a new stream).

    1
    2
    3
    4
    public static void main(String[] args) {
    List<String> strings = Arrays.asList("stream1", "", "stream2");
    strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
    }
  2. concat

    The concat method connects two Streams together to form a Stream. If the two input Streams are sorted, the new Stream is also sorted; if any of the input Streams is parallel, the new Stream is also parallel; if the new Stream is closed, the original two input Streams will be executed close processing

    1
    2
        .forEach(integer -> System.out.print(integer + "  "));
    // 1 2 3 4 5
  3. map

    The map method is used to map each element to the corresponding result, as in the following example: use map to output the square number corresponding to the element

    1
    2
    3
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().map( i -> i*i).forEach(System.out::println);
    //9,4,4,9,49,9,25

    It means to pass in a type and return a type, that is, your original String stream can be converted into a Student stream or other streams.

  4. flatMap

    The flatMap method is similar to the map method. It converts each element in the original Stream through a function. The difference is that the object converted by this function is a Stream, and it will not create a new Stream, but the original Stream. The elements of are replaced with the converted Stream. If the Stream produced by the conversion function is null, it shall be replaced by an empty Stream. flatMap has three variant methods for primitive types: flatMapToInt, flatMapToLong and flatMapToDouble

    1
    2
    3
    4
    Stream.of(1, 2, 3)
    .flatMap(integer -> Stream.of(integer * 10))
    .forEach(System.out::println);
    // 10,20,30
  5. peek

    The peek method produces a new Stream containing all the elements of the original Stream, and at the same time provides a consumption function (consumer instance). When each element of the new Stream is consumed, the given consumption function will be executed, and the consumption function will be executed first. If not Very clear, you can see the consumer function to know

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Stream.of(1, 2, 3, 4, 5)
    .peek(integer -> System.out.println("accept:" + integer))
    .forEach(System.out::println);
    // accept:1
    // 1
    // accept:2
    // 2
    // accept:3
    // 3
    // accept:4
    // 4
    // accept:5
    // 5
  6. limit/skip

    limit returns the first n elements of the Stream; skip skips the first n elements.

    1
    2
    3
    4
    5
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().limit(4).forEach(System.out::println);
    //3,2,2,3
    numbers.stream().skip(4).forEach(System.out::println);
    //7,3,5
  7. distinct

    distinct is mainly used to deduplicate

    1
    2
    3
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    numbers.stream().distinct().forEach(System.out::println);
    //3,2,7,5

5. Stream final operation

The result of the intermediate operation of Stream is still a Stream, so how to convert a Stream into the type we need? For example, calculate the number of elements in the stream, convert the stream into a collection, and so on. This is where the final operation is required

Final operations consume streams, producing a final result. That is, after the final operation, the stream cannot be used again, nor can any of the intermediate operations be used, otherwise an exception will be thrown

common final operations

  1. forEach

    Stream provides the method forEach to iterate each data in the stream

    1
    2
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
  2. count

    count is used to count the number of elements in the stream

    1
    2
    3
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

    System.out.println(numbers.stream().count());
  3. collect

    collect is a reduction operation that can accept various methods as parameters to accumulate the elements in the stream into a summary result

    1
    2
    3
    List<String> strings = Arrays.asList("Stream", "Stream1", "Stream2");å

    List<Integer> collect = strings.stream().filter(string -> string.length() <= 6).map(String::length).sorted().limit(2).distinct().collect(Collectors.toList());