Index to the Java 8 New Features series.

  1. Can’t use Optional elegance to handle null Pointers?
  2. Jdk14 will be out, Jdk8 time processing posture do you know?
  3. Can’t read your colleague’s code? Lambda expressions, function interfaces

If you think the article is good, you can follow my public number: unread code (Weidudaima). A adhere to the original, to ensure the quality of the public number.

preface

We all know that Lambda and Stream are two of the highlights of Java 8. In the previous article, we have already introduced Lambda. This time, we will introduce Java 8’s Stream operation. It is completely different from the Input/Output Stream of the Java. IO package, nor is it a Stream for real-time processing of big data. The Stream operation is a Java 8 enhancement of collection operations, focusing on efficient, convenient, and elegant aggregation operations for collections. With the help of Lambda expressions, programming efficiency and readability can be significantly improved. In addition, Stream provides parallel computing mode, which can be concise to write parallel code, and can give full play to the multi-core processing advantages of today’s computers.

You should know something about Lambda before using streams. If not, refer to the previous article: Can’t Read your colleague’s code? Lambda expressions, function interfaces.

1. Stream introduction

Unlike other collections frameworks, Stream is not a data structure and does not hold data, but it does the computation and acts more like a high-level iterator. In previous iterators, we had to iterate and then do the business. Now we just specify what to do, and the Stream implicitly iterates and does what we want. In addition, streams and iterators can only be processed in one direction, like the water flowing into the Yangtze River.

Because Stream provides lazy computing and parallel processing capabilities, data will be automatically decomposed into multiple segments and processed in parallel, and the results will be summarized in the end. So the Stream operation can make the program run more efficiently.

2. Stream concept

The use of Stream always follows a certain step, which can be abstracted from the following process.

Source (source) -> data processing/transformation (intermedia) -> Result processing (terminal)

2.1. The data source

Source is also the source of data. Stream data sources can be obtained in a variety of ways, and several common methods are listed below.

  • Collection.stream(); Get the stream from the collection.
  • Collection.parallelStream(); Get the parallel flow from the collection.
  • Arrays.stream(T array) or Stream.of(); Get a stream from an array.
  • BufferedReader.lines(); Gets a stream from an input stream.
  • IntStream.of() ; Get streams from static methods.
  • Stream.generate(); Self generated stream

2.2 data processing

The data processing/transformation (Intermedia) step can have multiple operations, also known as intermedia. In this step, no matter how to operate it returns is a new stream object, the original data will not change, and this step is inert processing, that is to say, only call methods will not start processing, only when the real began to collect the results, the intermediate operations will only take effect, and if there is no complete traverse, The desired result has been obtained (for example, the first value), the traversal stops, and the result is returned. Lazy computation can significantly improve the operation efficiency.

Data processing demonstration.

@Test
public void streamDemo(a){
    List<String> nameList = Arrays.asList("Darcy"."Chris"."Linda"."Sid"."Kim"."Jack"."Poul"."Peter");
    // 1. Select a name whose length is 4
    // 2
    // 3. Iterate over the output
    nameList.stream()
            .filter(name -> name.length() == 4)
            .map(name -> "This is "+name)
            .forEach(name -> System.out.println(name));
}
// Output the result
// This is Jack
// This is Poul
Copy the code

Of course, data processing/transformation operations are not just filter and Map as shown above. In addition, map (mapToInt, flatMap, etc.), Filter, distinct, sorted, peek, limit, skip, parallel, sequential, unordered, etc.

2.3. Collect results

Terminal is the last step in stream processing, after which the stream is completely exhausted and can no longer operate. It is only at this point that intermediate processes such as data processing/transformation of the stream begin to compute, known as lazy computation above. The result processing must also be the last step in the stream operation.

Common result processing operations include forEach, forEachOrdered, toArray, Reduce, collect, min, Max, count, anyMatch, allMatch, noneMatch, findFirst, FindAny, iterator, etc.

A simple result processing example is shown below.

/** * convert to uppercase and collect the result, traversing the output */
@Test
public void toUpperCaseDemo(a) {
    List<String> nameList = Arrays.asList("Darcy"."Chris"."Linda"."Sid"."Kim"."Jack"."Poul"."Peter");
    List<String> upperCaseNameList = nameList.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
    upperCaseNameList.forEach(name -> System.out.println(name + ","));
}
// Output the result
// DARCY,CHRIS,LINDA,SID,KIM,JACK,POUL,PETER,
Copy the code

2.4. Short short-circuiting

There is a Stream operation called short-circuiting, which is when the Stream is infinitely large but the number of streams to be returned is finite, and the result is expected to be computed in a finite amount of time. For example findFirst  Operation.

3. Stream usage

Streams are always operated on by means of Lambda expressions. There are many ways streams can be operated on. The following are 11 commonly used operations.

3.1. Stream acquisition

Several ways to get a Stream are described in the Stream data source above. Here are some examples of how to get a Stream.

@Test
public void createStream(a) throws FileNotFoundException {
    List<String> nameList = Arrays.asList("Darcy"."Chris"."Linda"."Sid"."Kim"."Jack"."Poul"."Peter");
    String[] nameArr = {"Darcy"."Chris"."Linda"."Sid"."Kim"."Jack"."Poul"."Peter"};
    // Set to get Stream
    Stream<String> nameListStream = nameList.stream();
    // Set to get the parallel Stream
    Stream<String> nameListStream2 = nameList.parallelStream();
    // Get the Stream from the array
    Stream<String> nameArrStream = Stream.of(nameArr);
    // Get the Stream from the array
    Stream<String> nameArrStream1 = Arrays.stream(nameArr);
    // File Stream gets Stream
    BufferedReader bufferedReader = new BufferedReader(new FileReader("README.md"));
    Stream<String> linesStream = bufferedReader.lines();
    // Get stream operations from static methods
    IntStream rangeStream = IntStream.range(1.10);
    rangeStream.limit(10).forEach(num -> System.out.print(num+","));
    System.out.println();
    IntStream intStream = IntStream.of(1.2.3.3.4);
    intStream.forEach(num -> System.out.print(num+","));
}
Copy the code

3.2. The forEach

ForEach is an important method in Strean streams for traversing streams, and it supports passing in a standard Lambda expression. However, its traversal cannot be terminated with a return/break. It is also a terminal operation, after which the data in the Stream is consumed.

For example, output objects.

List<Integer> numberList = Arrays.asList(1.2.3.4.5.6.7.8.9);
numberList.stream().forEach(number -> System.out.println(number+","));
// Output the result
/ / 1,2,3,4,5,6,7,8,9,
Copy the code

3.3. The map/flatMap

Use map to map objects one-to-one to another object or form.

/** * Multiply the number value by 2 */
@Test
public void mapTest(a) {
    List<Integer> numberList = Arrays.asList(1.2.3.4.5.6.7.8.9);
    // Map to a double number
    List<Integer> collect = numberList.stream()
            .map(number -> number * 2)
            .collect(Collectors.toList());
    collect.forEach(number -> System.out.print(number + ","));
    System.out.println();

    numberList.stream()
            .map(number -> "Digital" + number + ",")
            .forEach(number -> System.out.println(number));
}
// Output the result
/ / 2,4,6,8,10,12,14,16,18,
// Number 1, number 2, number 3, number 4, number 5, number 6, Number 7, Number 8, Number 9,
Copy the code

The above map maps the data one to one, but sometimes the relationship may not be as simple as one to one, it may be one to many. In this case, use flatMap. The following is a demonstration of flattening an object using a flatMap.

/** * Flatmap Flatters objects */
@Test
public void flatMapTest(a) {
    Stream<List<Integer>> inputStream = Stream.of(
            Arrays.asList(1),
            Arrays.asList(2.3),
            Arrays.asList(4.5.6)); List<Integer> collect = inputStream .flatMap((childList) -> childList.stream()) .collect(Collectors.toList()); collect.forEach(number -> System.out.print(number +","));
}
// Output the result
6, / /
Copy the code

3.4. The filter

The following example shows how to pick out even numbers.

/** * filter Data filtering * To filter even numbers */
@Test
public void filterTest(a) {
    List<Integer> numberList = Arrays.asList(1.2.3.4.5.6.7.8.9);
    List<Integer> collect = numberList.stream()
            .filter(number -> number % 2= =0)
            .collect(Collectors.toList());
    collect.forEach(number -> System.out.print(number + ","));
}
Copy the code

The results are as follows.

2,4,6,8,
Copy the code

3.5. findFirst

FindFirst can find the first element in a Stream, and it returns an Optional type. If you don’t know what the Optional class is, you can find the first element in a Stream. .

/** * find the first data * returns a Optional object */
@Test
public void findFirstTest(a){
    List<Integer> numberList = Arrays.asList(1.2.3.4.5.6.7.8.9);
    Optional<Integer> firstNumber = numberList.stream()
            .findFirst();
    System.out.println(firstNumber.orElse(-1));
}
// Output the result
/ / 1
Copy the code

When the findFirst method finds the data it needs, it returns that it no longer traverses the data. Therefore, the findFirst method can operate on a Stream with infinite data. In other words, it is a short-circuiting operation.

3.6. Collect/toArray

Stream streams can easily be converted to other structures, and the following are some common examples.

 /** * Stream to another data structure */
@Test
public void collectTest(a) {
    List<Integer> numberList = Arrays.asList(1.1.2.2.3.3.4.4.5);
    // to array
    Integer[] toArray = numberList.stream()
            .toArray(Integer[]::new);
    // to List
    List<Integer> integerList = numberList.stream()
            .collect(Collectors.toList());
    // to set
    Set<Integer> integerSet = numberList.stream()
            .collect(Collectors.toSet());
    System.out.println(integerSet);
    // to string
    String toString = numberList.stream()
            .map(number -> String.valueOf(number))
            .collect(Collectors.joining()).toString();
    System.out.println(toString);
    // to string split by ,
    String toStringbJoin = numberList.stream()
            .map(number -> String.valueOf(number))
            .collect(Collectors.joining(",")).toString();
    System.out.println(toStringbJoin);
}
// Output the result
// [1, 2, 3, 4, 5]
/ / 112233445
/ / 1,1,2,2,3,3,4,4,5
Copy the code

3.7. Limit/skip

Get or drop the first n elements

/** * Get/throw the first n elements */
@Test
public void limitOrSkipTest(a) {
    // Generate your own stream of random numbers
    List<Integer> ageList = Arrays.asList(11.22.13.14.25.26);
    ageList.stream()
            .limit(3)
            .forEach(age -> System.out.print(age+","));
    System.out.println();
    
    ageList.stream()
            .skip(3)
            .forEach(age -> System.out.print(age+","));
}
// Output the result
11,22,13 / /,
/ / 14,25,26,
Copy the code

3.8. The Statistics

Mathematical statistics function, to find a group of array of the maximum value, minimum value, number, data and, average.

/** * Mathematical calculation test */
@Test
public void mathTest(a) {
    List<Integer> list = Arrays.asList(1.2.3.4.5.6);
    IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
    System.out.println(Minimum value: + stats.getMin());
    System.out.println("Maximum:" + stats.getMax());
    System.out.println("Number:" + stats.getCount());
    System.out.println("And:" + stats.getSum());
    System.out.println("Average:" + stats.getAverage());
}
// Output the result
// Minimum value: 1
// Maximum: 6
// Number: 6
/ / and: 21
// Average: 3.5
Copy the code

3.9. groupingBy

The Group aggregation function is the same as the Group by function of the database.

/** * groupingBy * By age */
@Test
public void groupByTest(a) {
    List<Integer> ageList = Arrays.asList(11.22.13.14.25.26);
    Map<String, List<Integer>> ageGrouyByMap = ageList.stream()            
        .collect(Collectors.groupingBy(age -> String.valueOf(age / 10)));
    ageGrouyByMap.forEach((k, v) -> {
        System.out.println("Age" + k + "Those over 0 years old are:" + v);
    });
}
// Output the result
[11, 13, 14]
// People in their 20s are: [22, 25, 26]
Copy the code

3.10. partitioningBy

/** * partitioningBy * give a group of ages, separating adults and minors */
public void partitioningByTest(a) {
    List<Integer> ageList = Arrays.asList(11.22.13.14.25.26);
    Map<Boolean, List<Integer>> ageMap = ageList.stream()
            .collect(Collectors.partitioningBy(age -> age > 18));
    System.out.println("Minor:" + ageMap.get(false));
    System.out.println("Adult:" + ageMap.get(true));
}
// Output the result
// Minors: [11, 13, 14]
// Adult: [22, 25, 26]
Copy the code

3.11. Advanced – Generate your own Stream

/** * generate your own Stream */
@Test
public void generateTest(a){
    // Generate your own stream of random numbers
    Random random = new Random();
    Stream<Integer> generateRandom = Stream.generate(random::nextInt);
    generateRandom.limit(5).forEach(System.out::println);
    // Generate your own UUID stream
    Stream<UUID> generate = Stream.generate(UUID::randomUUID);
    generate.limit(5).forEach(System.out::println);
}

// Output the result
/ / 793776932
/ / - 2051545609
/ / - 917435897
/ / 298077102
/ / - 1626306315
// 31277974-841a-4ad0-a809-80ae105228bd
// f14918aa-2f94-4774-afcf-fba08250674c
// d86ccefe-1cd2-4eb4-bb0c-74858f2a7864
// 4905724b-1df5-48f4-9948-fa9c64c7e1c9
// 3af2a07f-0855-455f-a339-6e890e533ab3
Copy the code

In the example above, the Stream Stream is infinite, but the result obtained is finite, and the Limit is used to Limit the number of retrivals, so this operation is also a short-circuiting operation.

4. Stream advantages

4.1. Simplicity and elegance

Properly used and formatted Stream manipulation code is not only concise and elegant, but also pleasing to the eye. Here’s a comparison of the encoding styles for the same operation with and without a Stream Stream.

/** * Comparison of encoding styles with and without streaming */
@Test
public void diffTest(a) {
    // Do not use stream operations
    List<String> names = Arrays.asList("Jack"."Jill"."Nate"."Kara"."Kim"."Jullie"."Paul"."Peter");
    // Select names of length 4
    List<String> subList = new ArrayList<>();
    for (String name : names) {
        if (name.length() == 4) { subList.add(name); }}// Separate the values with commas
    StringBuilder sbNames = new StringBuilder();
    for (int i = 0; i < subList.size() - 1; i++) {
        sbNames.append(subList.get(i));
        sbNames.append(",");
    }
    // Remove the last comma
    if (subList.size() > 1) {
        sbNames.append(subList.get(subList.size() - 1));
    }
    System.out.println(sbNames);
}
// Output the result
// Jack, Jill, Nate, Kara, Paul
Copy the code

If you are using a Stream operation.

// Use Stream
String nameString = names.stream()
       .filter(num -> num.length() == 4)
       .collect(Collectors.joining(","));
System.out.println(nameString);
Copy the code

4.2. Lazy computing

As mentioned above, Data processing/Transformation (Intermedia) Operation map (mapToInt, FlatMap, filter, distinct, sorted, peek, limit, skip, parallel, sequential, and unordered are not immediately called when methods are called. Instead, it takes effect when it is actually used, which allows the operation to be delayed until it is actually used.

Here’s an example to illustrate this.

 /** * Find the even number */
 @Test
 public void lazyTest(a) {
     // Generate your own stream of random numbers
     List<Integer> numberLIst = Arrays.asList(1.2.3.4.5.6);
     // Find an even number
     Stream<Integer> integerStream = numberLIst.stream()
             .filter(number -> {
                 int temp = number % 2;
     			 if (temp == 0 ){
                     System.out.println(number);
                 }
                 return temp == 0;
             });

     System.out.println("Dividing line");
     List<Integer> collect = integerStream.collect(Collectors.toList());
 }
Copy the code

If there is no lazy calculation, then obviously the even number will be printed first and then the dividing line. And the actual effect is this.

Dividing lines 2, 4, 6Copy the code

You can see that lazy computation delays computation until it’s really needed.

4.3. Parallel computing

ParallelStream method can be used to obtain Stream instead of Stream method to obtain parallel processing Stream, parallel processing can give full play to the advantages of multi-core, and does not increase the coding complexity.

The following code demonstrates the difference between serial and parallel computing time when you generate 10 million random numbers, multiply each number by 2 and sum it up.

  /** * Parallel computing */
 @Test
 public void main(a) {
     // Generate your own stream of random numbers, taking 10 million random numbers
     Random random = new Random();
     Stream<Integer> generateRandom = Stream.generate(random::nextInt);
     List<Integer> numberList = generateRandom.limit(10000000).collect(Collectors.toList());

     // Serial - Take 10 million random numbers, each random number by 2, and sum
     long start = System.currentTimeMillis();
     int sum = numberList.stream()
         .map(number -> number * 2)
         .mapToInt(x -> x)
         .sum();
     long end = System.currentTimeMillis();
     System.out.println("Serial time:"+(end - start)+"Ms, and are :"+sum);

     // Parallel - Take 10 million random numbers, each random number by 2, and sum
     start = System.currentTimeMillis();
     sum = numberList.parallelStream()
         .map(number -> number * 2)
         .mapToInt(x -> x)
         .sum();
     end = System.currentTimeMillis();
     System.out.println("Parallel time:"+(end - start)+"Ms, and are :"+sum);
 }
Copy the code

You get the following output.

Serial time:1005Ms, and are:481385106Parallel time:47Ms, and are:481385106
Copy the code

The effect is obvious, and the code is neat and elegant.

5. Stream suggestions

5.1 Ensure correct layout

As you can see from the above use case, the code that operates with a Stream Stream is much cleaner and more readable. But if the layout is not correct, then it will look bad, such as the following code example of the same function, a few more layers of operation, isn’t it a bit confusing?

/ / no layout
String string = names.stream().filter(num -> num.length() == 4).map(name -> name.toUpperCase()).collect(Collectors.joining(","));
/ / layout
String string = names.stream()
        .filter(num -> num.length() == 4)
        .map(name -> name.toUpperCase())
        .collect(Collectors.joining(","));
Copy the code

5.2 Ensure function purity

If you want your Stream to produce the same result every time for the same operation, then you must ensure the purity of the Lambda expression, namely the following two points.

  • No elements are changed in Lambda.
  • Lambda does not depend on any element that may change.

Both of these are important to ensure that the function is idempotent, otherwise your program’s execution results may become unpredictable, as in the following example.

@Test
public void simpleTest(a){
    List<Integer> numbers = Arrays.asList(1.2.3);
    int[] factor = new int[] { 2 };
    Stream<Integer> stream = numbers.stream()
            .map(e -> e * factor[0]);
    factor[0] = 0;
    stream.forEach(System.out::println);
}
// Output the result
/ / 0
/ / 0
/ / 0
Copy the code

The code has been uploaded

https://github.com/niumoo/jdk-feature/blob/master/src/main/java/net/codingme/feature/jdk8/Jdk8Stream.java.

After < >

Personal website: www.codingme.net if you like this article, you can follow the public number, grow together. Attention to the public number reply resources can not routine access to the most popular Java core knowledge collating & interview materials.