Detailed usage of stream, a new feature of Java 8

1, Overview

Stream is the key abstract concept of dealing with collections in Java 8. It can specify the operations you want to perform on collections, and can perform very complex operations such as finding, filtering and mapping data. Using the Stream API to operate on the collection data is similar to the database query executed with SQL. You can also use the Stream API to perform operations in parallel. In short, the Stream API provides an efficient and easy-to-use way to process data.

characteristic:

  1. It is not a data structure and will not save data.
  2. The original data source will not be modified. It will save the operated data to another object. (Reservation: after all, peek method can modify the elements in the stream)
  3. During the intermediate processing of inert evaluation flow, only the operation is recorded and will not be executed immediately. The actual calculation will not be carried out until the termination operation is executed.

2, Classification

  1. Stateless: refers to that the processing of elements is not affected by previous elements;
  2. Stateful: this means that the operation cannot continue until all elements are obtained.
  3. Non short circuit operation: it means that all elements must be processed to obtain the final result;
  4. Short circuit operation: it means that the final result can be obtained when meeting some qualified elements, such as A | B. as long as A is true, there is no need to judge the result of B.

3, Specific usage

  1. Common creation methods of flow

1.1 use the stream() and parallelStream() methods under Collection

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //Get a sequential stream
Stream<String> parallelStream = list.parallelStream(); //Get a parallel stream

1.2 use the stream() method in Arrays to convert the array into a stream

Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);

1.3 use static methods in Stream: of(), iterate(), generate()

Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
 
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
 
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);

1.4 using BufferedReader The lines () method converts each line of content into a stream

BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);

1.5 using pattern Splitasstream() method to separate strings into streams

Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
  1. Intermediate operation of stream

2.1 screening and slicing

  1. Filter: filter some elements in the stream
  2. limit(n): get n elements
  3. skip(n): skip n elements and realize paging with limit(n)
  4. distinct: remove duplicate elements by hashCode() and equals() of elements in the stream
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
        .distinct() //6 7 9 8 10 12 14
        .skip(2) //9 8 10 12 14
        .limit(2); //9 8
newStream.forEach(System.out::println);

2.2 mapping

  1. map: takes a function as an argument, which is applied to each element and mapped to a new element.
  2. flatMap: take a function as a parameter, change each value in the stream into another stream, and then connect all streams into one stream.
List<String> list = Arrays.asList("a,b,c", "1,2,3");
 
//Convert each element into a new element without commas
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc  123
 
Stream<String> s3 = list.stream().flatMap(s -> {
    //Convert each element into a stream
    String[] split = s.split(",");
    Stream<String> s2 = Arrays.stream(split);
    return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3

2.3 sorting

  1. sorted(): natural sorting. Elements in the stream need to implement the Comparable interface
  2. sorted(Comparator com): custom sorting, custom Comparator sorter
List<String> list = Arrays.asList("aa", "ff", "dd");
//The String class itself has implemented the comparable interface
list.stream().sorted().forEach(System.out::println);// aa dd ff
 
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
Student s3 = new Student("aa", 30);
Student s4 = new Student("dd", 40);
List<Student> studentList = Arrays.asList(s1, s2, s3, s4);
//User defined sorting: ascending by name first, and ascending by age if the names are the same
studentList.stream().sorted(
        (o1, o2) -> {
            if (o1.getName().equals(o2.getName())) {
                return o1.getAge() - o2.getAge();
            } else {
                return o1.getName().compareTo(o2.getName());
            }
        }
).forEach(System.out::println);

2.4 consumption
peek: like a map, you can get every element in the stream. However, map receives a Function expression with a return value; peek receives a Consumer expression without a return value.

Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
List<Student> studentList = Arrays.asList(s1, s2);
 
studentList.stream()
        .peek(o -> o.setAge(100))
        .forEach(System.out::println);   
//result:
Student{name='aa', age=100}
Student{name='bb', age=100}       
  1. Termination of stream

3.1 matching and aggregation operations

  1. allMatch: receives a Predicate function. It returns true only when each element in the stream conforms to the assertion. Otherwise, it returns false
  2. noneMatch: receives a Predicate function. It returns true only when every element in the stream does not conform to the assertion. Otherwise, it returns false
  3. anyMatch: receives a Predicate function. As long as one element in the stream satisfies the assertion, it returns true; otherwise, it returns false
  4. findFirst: returns the first element in the stream
  5. findAny: returns any element in the stream
  6. count: returns the total number of elements in the stream
  7. max: returns the maximum value of the element in the stream
  8. min: returns the minimum value of the element in the stream
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
 
boolean allMatch = list.stream().allMatch(e -> e > 10); //false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
boolean anyMatch = list.stream().anyMatch(e -> e > 4);  //true
 
Integer findFirst = list.stream().findFirst().get(); //1
Integer findAny = list.stream().findAny().get(); //1
 
long count = list.stream().count(); //5
Integer max = list.stream().max(Integer::compareTo).get(); //5
Integer min = list.stream().min(Integer::compareTo).get(); //1

3.2 protocol operation

  1. Optional reduce(BinaryOperatoraccumulator):
    During the first execution, the first parameter of the accumulator function is the first element in the flow, and the second parameter is the second element of the element in the flow; In the second execution, the first parameter is the result of the first function execution, and the second parameter is the third element in the flow; And so on.
  2. T reduce(T identity, BinaryOperator accumulator): the process is the same as above, except that during the first execution, the first parameter of the accumulator function is identity, and the second parameter is the first element in the flow.
  3. U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner):
    In a serial stream, this method is the same as the second method, that is, the third parameter combiner does not work. In parallel stream, we know that the stream is forkjoin ed by multiple threads for execution. At this time, the execution process of each thread is the same as that of the second method reduce(identity,accumulator), while the third parameter combiner function takes the execution result of each thread as a new stream, and then uses the first method reduce(accumulator) process for specification.
//After testing, when the number of elements is less than 24, the number of threads in parallel is equal to the number of elements. When it is greater than or equal to 24, the number of threads in parallel is 16
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);
 
Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
System.out.println(v);   // 300
 
Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(v1);  //310
 
Integer v2 = list.stream().reduce(0,
        (x1, x2) -> {
            System.out.println("stream accumulator: x1:" + x1 + "  x2:" + x2);
            return x1 - x2;
        },
        (x1, x2) -> {
            System.out.println("stream combiner: x1:" + x1 + "  x2:" + x2);
            return x1 * x2;
        });
System.out.println(v2); // -300
 
Integer v3 = list.parallelStream().reduce(0,
        (x1, x2) -> {
            System.out.println("parallelStream accumulator: x1:" + x1 + "  x2:" + x2);
            return x1 - x2;
        },
        (x1, x2) -> {
            System.out.println("parallelStream combiner: x1:" + x1 + "  x2:" + x2);
            return x1 * x2;
        });
System.out.println(v3); //197474048

3.3 collection operation

  1. collect: receive a Collector instance and integrate the elements in the stream into another data structure.
  2. Collector < T, a, R > is an interface with the following five abstract methods:
  3. Supplier < A > supplier(): create A result container A
  4. Biconsumer < A, T > calculator(): consumer interface. The first parameter is container a and the second parameter is element T in the flow.
  5. Binaryoperator < a > combiner(): function interface. The function of this parameter is the same as that of the combiner parameter in a method (reduce), which combines the running results of each sub process in the parallel flow (container a after the operation of the accumulator function).
  6. Function < A, R > finisher(): functional interface, parameter: container a, return type: final desired result r of collect method.
  7. Set < characteristics > characteristics(): returns an immutable set set to indicate the characteristics of the Collector. There are three characteristics:
    1.CONCURRENT: indicates that this collector supports concurrency. (there are other descriptions in the official documents. I haven't explored them yet, so I won't translate too much)
    2.UNORDERED: indicates that the collection operation will not retain the original order of elements in the stream.
    3.IDENTITY_FINISH: indicates that the finish parameter is only an identifier and can be ignored.

3.3.1 Collector tool library: Collectors

Student s1 = new Student("aa", 10,1);
Student s2 = new Student("bb", 20,2);
Student s3 = new Student("cc", 10,3);
List<Student> list = Arrays.asList(s1, s2, s3);
 
//Pretend to be a list
List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]
 
//Convert to set
Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]
 
//Convert to map. Note: the key s cannot be the same, otherwise an error will be reported
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
 
//String separator connection
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)
 
//Aggregation operation
//1. Total number of students
Long count = list.stream().collect(Collectors.counting()); // 3
//2. Maximum age (the smallest minBy is the same)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
//3. Age of all
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
//4. Average age
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
// Bring all the above methods
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());
 
//grouping
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//Multiple grouping, first according to type, and then according to age
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));
 
//partition
//It is divided into two parts, one is older than 10 years old and the other is less than or equal to 10 years old
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
 
//Statute
Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40

3.3.2 Collectors.toList() parsing

//toList source code
public static <T> Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
            (left, right) -> {
                left.addAll(right);
                return left;
            }, CH_ID);
}
//For a better understanding, let's transform the lambda expression in the source code
public <T> Collector<T, ?, List<T>> toList() {
    Supplier<List<T>> supplier = () -> new ArrayList();
    BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t);
    BinaryOperator<List<T>> combiner = (list1, list2) -> {
        list1.addAll(list2);
        return list1;
    };
    Function<List<T>, List<T>> finisher = (list) -> list;
    Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));   
    return new Collector<T, List<T>, List<T>>() {
        @Override
        public Supplier supplier() {
            return supplier;
        }
        @Override
        public BiConsumer accumulator() {
            return accumulator;
        }
        @Override
        public BinaryOperator combiner() {
            return combiner;
        }
        @Override
        public Function finisher() {
            return finisher;
        }
        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    };
}

Tags: Java

Posted by gunabalans on Wed, 04 May 2022 21:43:00 +0300