Understanding and using Stream in Java

Keywords: Java JavaSE

Understanding and using Stream in Java

Or some things encountered at work, because the foundation was not very good or the things used at work were relatively limited. So now I'm slowly making up for some of my previous knowledge blind spots. Maybe it's a relatively simple thing in everyone's opinion. In that sentence, blogs are mainly records. This blog is to supplement the knowledge of Stream.

Operate List and collection classes in normal code

When we write code, we usually encounter the need to operate the collection class, such as finding out the qualified elements in the collection, or we need to do some operations on some elements in the collection. My previous code is as follows:

public static void ordinary(List<Integer> arr) {
    //Find special, for loop traversal
    List<Integer> result = new ArrayList<>();
    long tmp = System.currentTimeMillis();
    for (Integer integer : arr) {
        if (integer > 5) {
            result.add(integer);
        }
    }
    System.err.println(System.currentTimeMillis() - tmp);
    System.err.println(result);

    result.clear();
    //Lambda expressions in java are essentially anonymous inner classes. Local variables cannot be accessed in anonymous inner classes, only constants can be accessed.
    tmp = System.currentTimeMillis();
    arr.forEach(integer -> {
        if (integer > 5) result.add(integer);
    });
    System.err.println(System.currentTimeMillis() - tmp);
    System.err.println(result);
}

Even if we use foreach and lambda expressions, we can see that the amount of code is still a little, and it's always awkward to write like this. So Java provides Stream to facilitate our writing.

Operation of Java Stream

Use Stream to write code with the same functions as above. The specific code is as follows:

public static void streamTrain(List<Integer> arr) {
    long tmp = System.currentTimeMillis();
    List<Integer> result = arr.stream().filter(integer -> integer > 5).collect(Collectors.toList());
    System.err.println(System.currentTimeMillis() - tmp);
    System.err.println(result);
}

You can see that the code is significantly reduced, and the advantages of Stream are not just these.

Brief introduction to Stream

The Java 8 API adds a new abstraction called stream, which allows you to process data in a declarative way. Stream uses an intuitive way similar to querying data from a database with SQL statements to provide a high-level abstraction for the operation and expression of Java collections. Stream API can greatly improve the productivity of Java programmers and enable programmers to write efficient, clean and concise code. This style regards the set of elements to be processed as a stream, which is transmitted in the pipeline and can be processed on the nodes of the pipeline, such as filtering, sorting, aggregation, etc. The element flow is processed by intermediate operation in the pipeline, and finally the result of the previous processing is obtained by final operation.

The above introduction comes from the rookie tutorial. My understanding is to regard the data in the collection as water flow, and use the methods provided in Stream to process a series of water flow, such as aggregation, filtering, diversion, etc. This concept is similar to that in Storm. Finally, we get the data we want. Simplify our code while ensuring code readability.

Introduction to common API s

After introducing the basic concepts, let's talk about the common API s. These are found by looking at the source code. It is suggested that if you don't know how to call, you can see the source code and know how to write it.

  • Stream<T> filter(Predicate<? super T> predicate);
    

    Returns a stream consisting of elements of this stream that match the given predicate. I understand that it is a filter, which filters the data in the stream through your parameters. The example code is as follows:

    arr.stream().filter(integer -> integer > 5)
    
  • <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    

    Returns a stream consisting of the results of the elements that apply the given function to this stream. I understand that it is to perform some operations on the elements in the stream, and then return the element after the operation (the returned element can be a new element). The example code is as follows:

    arr.stream().map(integer -> integer*integer+"")
    
  • IntStream mapToInt(ToIntFunction<? super T> mapper);
    

    Returns an IntStream containing the results of applying the given function to the elements of this stream. It is to convert the result of your operation into int. for example, if you have a user set and you want the int set of all user ages in the past, you can use this method. I won't write the code anymore.

  • <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    

    Returns a stream consisting of the result of replacing each element of the stream with the contents of the mapping stream generated by applying the provided mapping function to each element. Each mapping stream is closed after its contents are put into the stream. (if the mapped stream is null, use an empty stream instead.) this is not easy to understand, that is, regenerate each element in your stream into a stream, operate these streams, and close the sub stream after using the sub stream. For example, if you traverse a folder of a directory, the subflow is equivalent to a subfolder, and then traverse these subfolders, you can use this method. Another example is to slice an object into multiple objects. Examples are as follows:

    public static void flatMapTrain() {
        List<List<Integer>> arr = new ArrayList<>();
        List<Integer> result = arr.stream().flatMap(integers -> integers.stream()).filter(integer -> integer > 5).collect(Collectors.toList());
        System.err.println(result);
    }
    

    The function of this code is to merge all values greater than 5 in arr into a new set. It can not only be used in this way, but also be more complex in development. For example, there is a user class set in which multiple address sets are nested. You can use this method to determine whether all users in this set belong to a certain address.

  • Stream<T> distinct();
    

    Returns a stream composed of different elements of the stream according to Object.equals(Object)). Note that the equals method is called, and remember to override the equals method of the object when calling this method. If there's nothing to write, don't write it.

  • Stream<T> sorted();
    

    Returns a stream consisting of the elements of the stream, sorted in natural order. If the element of this stream is not Comparable, a java.lang.ClassCastException may be thrown when performing terminal operations. Two points to note: the result of sorting is natural sorting, that is, 1, 2, 3. If the element does not implement Comparable, an exception may be thrown. So there is the following method.

  • Stream<T> sorted(Comparator<? super T> comparator);
    

    Returns the stream composed of the elements of the stream, sorted according to the provided Comparator. You can sort the elements in the flow according to your requirements. The example code is as follows:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    List<Integer> result = numbers.stream().sorted((o1, o2) -> o2 - o1).collect(Collectors.toList());
    System.err.println(result);
    

    The example code here is to reverse the set.

  • Stream<T> peek(Consumer<? super T> action);
    

    Returns a stream consisting of the elements of the stream, and performs the provided operation on each element because the element is consumed from the result stream. I understand that it is a snooping method, which is mainly used for debugging, such as printing elements in the current stream to facilitate inspection. The sample code is no longer written.

  • Stream<T> limit(long maxSize);
    

    Returns a stream consisting of elements of the stream whose length is truncated to no more than maxSize. Note: if the pass in is negative, an exception will be thrown. The example code is as follows:

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    List<Integer> result = numbers.stream().sorted((o1, o2) -> o2 - o1).limit(3).collect(Collectors.toList());
    System.err.println(result);
    
  • Stream<T> skip(long n);
    

    After discarding the first n elements of the stream, the stream composed of the remaining elements of the stream is returned. If the stream contains fewer than n elements, an empty stream is returned. Similar to the above, if the passed in parameter is negative, an IllegalArgumentException exception will be thrown.

  • void forEach(Consumer<? super T> action);
    

    Perform an operation on each element of this flow. This is a terminal operation. The above methods are all intermediate operations. This method is the final operation, that is, after calling this method, all streams are used up. Generally, it is only used at the end.

  • Object[] toArray();
    

    Returns an array containing the stream elements. Pay attention to the returned format. You need to force the type.

  • <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
    

    Perform variable reduction on the elements of this stream. Variable reduction is where the reduction value is a variable result container, such as ArrayList, and elements are merged by updating the state of the result rather than replacing the result. I often use the last step of the above example code to convert the stream to a new collection.

  • long count();
    

    Returns the count of elements in this stream. This is a special case of reduction, which is a terminal operation. Just sum and see how many elements are in the stream.

  • Optional<T> min(Comparator<? super T> comparator);
    

    Returns the smallest element of this stream based on the provided Comparator. This is a special case of reduction. My understanding is to take the smallest element and close the flow. This knowledge point of Optional will be explained in the next blog. I have learned it recently. Now I can simply understand it as an elegant judgment whether it is null or not.

  • Optional<T> max(Comparator<? super T> comparator);
    

    Returns the largest element of this stream based on the Comparator provided. This is a special case of reduction. Contrary to the above.

remarks

The current blog is only a preliminary use of API. Later, you may understand some underlying principles, such as how to implement the underlying Stream? Why is the for loop fast when the data is small? Is Stream fast when the data is large? These questions are slowly supplemented in the follow-up.

Everyone's situation at work is different. It may be easier to use Stream in some cases and for loop in some cases. Or specific to their own work, blindly pursuing a certain writing method is not good. Learn more about some convenient writing methods, and there will be more ideas for writing code in the future.

That's it. It's over.

Posted by edcellgavin on Sat, 02 Oct 2021 10:48:57 -0700