preface

There are two most important changes in Java8. The first one is Lambda expressions. If you don’t know anything about Lambda expressions, you can go here to see the details of Java8 features Lambda expressions

The other is the Stream API, under java.util.stream

What is the

A lot of places talk about Stream as the key abstraction for dealing with collections, which is way too abstract. This Stream is not an IO Stream as we used to know it, but rather a data channel that manipulates sequences of elements generated by data sources (collections, arrays, and so on). Collections focus on data, streams focus on computation.

What’s the use of

It allows you to perform very complex operations on collections such as finding, filtering, and mapping data. Operations on collection data using the Stream API are similar to database queries executed using SQL, and can be performed in parallel using the Stream API. In short, the Stream API provides an efficient and easy-to-use way to process data.

So let’s go straight to an example

@Test public void test() { List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6); For (Integer num: list) {if (num > 5) system.out.println (num); } System.out.println("-------------"); List.stream ()//.filter((e) -> e > 5)//.foreach (system.out ::println); }Copy the code

The output

8
10
9
7
6
-------------
8
10
9
7
6
Copy the code

In this example, I take the numbers 1-10 as elements of the set, and print the numbers greater than 5 through traditional iteration and Stream, respectively.

We don’t need to know why the Stream method does this, as we’ll see later, but we’ll simply demonstrate the Stream set of operations.

The other thing is that we can use chained programming when we’re doing operations in Stream mode. Chained programming means that multiple operations are connected without declaring variables in between. Like the StringBuffer we’re most familiar with.

StringBuffer buffer = new StringBuffer();
buffer.append("aaa").append("bbb").append("ccc");Copy the code

The intermediate operations on Stream all return a Stream, so you can use the Stream using chained programming, which we’ll talk about later.

Also, in Eclipse, you can add a // after a line of code to prevent chained programming from being formatted as a single line of code. Otherwise, chained programming like the one above would be formatted as follows, which is not easy to read.

list.stream().filter((e) -> e > 5).forEach(System.out::println);Copy the code

How to use

There are three steps to using the Stream operation

  • Create Stream: Get a Stream from a data source (such as a collection, array)
  • Intermediate operations: One or more intermediate operations that process data from a data source
  • Terminating action: Performs an intermediate chain of action and produces a result

Create a Stream

The Stream is created by the collection

The Collection interface in Java8 has been extended to provide two methods for retrieving streams:

  • Default Stream Stream () : Returns a sequential Stream
  • Default Stream parallelStream() : returns a parallelStream

Both of these methods are the default interface methods. If you are not familiar with Java8 features, you can see the default interface methods here

You can get the stream directly by calling the stream() method as follows

List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); ParallelStream <String> parallelStream = list.parallelstream (); // Get a parallel streamCopy the code

Create streams from arrays

The Java8 static stream() method of Arrays fetches streams from Arrays:

  • Static Stream Stream (T[] array): Returns a Stream

There are also overloaded forms that can handle arrays corresponding to primitive types:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

Example:

Integer[] nums = new Integer[8];
Stream<Integer> stream = Arrays.stream(nums);Copy the code

Create a flow from a value

You can create a Stream by displaying values using the static method stream.of (). It can accept any number of parameters.

  • Public static Stream of (T… Values) : Returns a stream

Example:

Stream < Integer > Stream = Stream of (1, 2, 3, 4, 5);Copy the code

The flow is created by the function

You can use the static methods stream.iterate () and stream.generate () to create an infinite Stream.

  • public static Stream iterate(final T seed, final UnaryOperator f)
  • public static Stream generate(Supplier s) :

The so-called infinite flow is, by definition, infinite.

As an example of iterate(), the first seed argument is a start value, and the second argument is an instance of the UnaryOperator type, which is a functional interface that inherits from Function, So we can just think of it as Function. For functional interfaces, see Lambda expressions for Java8 features here

The following is a specific example of the iterate() method

@Test
public void test2() {
    Stream.iterate(1, (x) -> x + 1).forEach(System.out::println);
}Copy the code

The first argument I specify is 1, which means 1 as the starting value, and the second argument is a Lambda expression that creates an UnaryOperator or Function instance, whose only abstract method is to take an argument and return the value of that argument +1. ForEach () is a termination operation, which we’ll talk about later.

When you run this code, you’ll see that the console keeps printing numbers without stopping (which is infinite), and that the numbers are always incrementing equally, with a difference of one.

Look again at the example of the generate() method, which takes an argument from a Supplier instance

@Test
public void test2() {
    Stream.generate(() -> Math.random()).forEach(System.out::println);
}Copy the code

In the generate() method argument, I create a Supplier instance with a Lambda expression that returns a random number.

Running this code, you’ll see that the console is always printing random numbers greater than or equal to 0 and less than 1.

In the middle of operation

After creating the flow, we can do the intermediate operations. Multiple intermediate operations can be joined together to form a pipeline that does not perform any processing unless termination is triggered on the pipeline! Processing all at once on termination is called “lazy evaluation,” which I’ll demonstrate later.

Intermediate operations fall into four categories

  • screening
  • cutting
  • mapping
  • The sorting

screening

There are two methods for filtering

Filter (Predicate P), which receives Predicate instances and filters them based on the test method of the instance, for example

@Test
public void test() {
    List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6);
    list.stream()//
            .filter((e) -> e > 5)//
            .forEach(System.out::println);
}Copy the code

The output

8, 10, 9, 7, 6Copy the code

Create an instance of Predicate with a Lambda expression, where (e) -> e > 5 is the implementation of the test method, which takes an Integer type e argument and returns true if greater than 5, false otherwise.

Distinct (), which you should recognize as duplicating, removes duplicate elements based on hashCode() and equals() of the elements generated by the stream. Such as

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 4, 4, 5, 6);
    list.stream()//
            .distinct()//
            .forEach(System.out::println);
}Copy the code

The output

One, two, three, four, five, sixCopy the code

cutting

There are also two ways to cut

Limit (long maxSize), truncates the stream so that it does not exceed a given number of elements, much like an SQL statement. Such as

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .limit(3)//
            .forEach(System.out::println);
}Copy the code

The output

1
2
3
Copy the code

Skip (long n), return a stream with the first n elements removed. If there are less than n elements in the stream, an empty stream is returned. Such as

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .skip(3)//
            .forEach(System.out::println);
}Copy the code

The output

4
5
6
Copy the code

As you can see, this method complements the limit method, limit goes to the end, skip goes to the head.

mapping

Map (Function f), which takes an instance of Function. The abstract method apply for Function takes an argument and returns a value, which is the result of the mapping. Such as

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.stream()//
            .map(x -> x*x)
            .forEach(System.out::println);
}Copy the code

The output

1 April 9Copy the code

You can see that the new value mapped to each element in the flow is the square of that element.

MapToDouble (ToDoubleFunction f), this function is similar to map, except that the result of the mapping must be of type Double. Such as

@Test public void test() { List<Integer> list = Arrays.asList(1, 2, 3); List.stream ()//.mapToDouble(x -> x+0.1).foreach (system.out ::println); }Copy the code

The output

1.1
2.1
3.1
Copy the code

You can see that elements of type Integer are mapped to types Double

There are also mapToInt(ToIntFunction f) and mapToLong(ToLongFunction F) methods, which will not be demonstrated here.

FlatMap (Function f), which maps each element in the stream to a stream. Such as

import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.Test; public class TestStreamAPI { @Test public void test() { List<String> list = Arrays.asList("abc", "efg", "xyz"); list.stream()// .flatMap(TestStreamAPI::string2Stream)// .forEach(System.out::println); } public static stream <Character> string2Stream(String) str) { List<Character> list = new ArrayList<>(); char[] charArray = str.toCharArray(); for (char c : charArray) { list.add(c); } return list.stream(); }}Copy the code

The output

a
b
c
e
f
g
x
y
z
Copy the code

Just as the mapToDouble method maps each element in a Stream to a value of type Double, it now maps to a Stream.

The sorting

Sorted (), which sorts the elements in the stream in their natural order. Such as

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted()//
            .forEach(System.out::println);
}
Copy the code

The output

a
c
d
Copy the code

Sorted (Comparator comp), which sorts elements in a stream in Comparator order. Such as

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted((x,y) -> -x.compareTo(y))//
            .forEach(System.out::println);
}Copy the code

The output

d
c
a
Copy the code

Termination of operations

The termination operation generates results from the intermediate operation pipeline of the flow. The result can be any value that is not a stream, such as List, Integer, or even void.

There are three types of termination operations

  • Find and match
  • reduction
  • collect

Find and match

There are so many ways to find and match that I will only demonstrate a few typical ones.

  • AllMatch (Predicate P) checks whether all elements match
  • AnyMatch (Predicate P) checks whether at least one element matches
  • NoneMatch (Predicate P) checks if all elements are not matched
  • FindFirst () returns the first element
  • FindAny () returns any element in the current stream
  • Count () returns the total number of elements in the stream
  • Max (Comparator c) returns the maximum value in the stream
  • Min (Comparator c) returns the minimum value in the stream
  • ForEach (Consumer C) Internal iteration (using the Collection interface requires the user to do iteration, called external iteration. In contrast, the Stream API uses internal iteration.)

Example allMatch Predicate (p)

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); boolean allMatch = list.stream()// .allMatch(x -> x > 2); // Whether all elements are greater than 2 system.out.println (allMatch); }Copy the code

The output

true
Copy the code

FindFirst () example

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); Optional<Integer> first = list.stream()// .findFirst(); Integer val = first.get(); System.out.println(val); // output 10}Copy the code

The Optional class is a container class for Java8

Max (Comparator c) example

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); Optional<Integer> first = list.stream()// .min(Integer::compareTo); Integer val = first.get(); System.out.println(val); // output 3}Copy the code

ForEach (Consumer C) example has been used a lot, but I won’t show it here.

reduction

There are two methods of reduction

  • Reduce (T iden, BinaryOperator b) can combine elements ina stream repeatedly to obtain a value. Return T
  • Reduce (BinaryOperator b) can combine elements ina stream repeatedly to obtain a value. Returns the Optional

Reduce (T iden, BinaryOperator b) example

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); Integer result = list.stream()// .reduce(2, Integer::sum); System.out.println(result); } // 1 + 2+ 5+7+3Copy the code

Reduce (BinaryOperator b) examples

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); Optional<Integer> optional = list.stream()// .reduce(Integer::sum); Integer result = optional.get(); System.out.println(result); // output 25 = 10+5+7+3Copy the code

collect

Collect (Collector C) Converts streams to other forms. Receive an implementation of the Collector interface, a method for summarizing elements in the stream.

Here are specific examples

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); List<Integer> resultList = list.stream()//.collect(Collectors. ToList ()); Set<Integer> resultSet = list.stream()//.collect(Collectors. ToSet ()); System.out.println(resultList); // Output [10, 5, 7, 3] system.out.println (resultSet); // Output [3, 5, 7, 10]}Copy the code

The above code collects elements from the stream into a List and a Set, respectively, as well as into their types, such as Map, Optional, and even Integer. There are other things you can do during the collection process, such as the following example, but for more you can refer to the API

@Test public void test() { List<Integer> list = Arrays.asList(10, 5, 7, 3); Long count = list.stream()//.collect(Collectors. Counting ()); System.out.println(count); Integer sum = list.stream()//.collect(Collectors. SummingInt (x -> x)); System.out.println(sum); // output 25}Copy the code

Stream deferred execution

In the “intermediate operations” section above, I talked about lazy evaluation, where the Stream operation is delayed. This means they wait until they need results.

Here’s an example.

So let’s get an entity class Person, and notice that in the getAge() method I’ve printed a sentence

public class Person { private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() {system.out.println ("getAge() execute "); return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; }}Copy the code

Then there is the test method

@Test public void test() { List<Person> list = Arrays.asList(// new Person("Jason", 18), // new Person("Hank", 46), // new Person("Alice", 23)); List.stream ()//.filter(x -> x.getage () > 20); }Copy the code

Run the test method and you will find that nothing is output because it does not terminate the operation, so the intermediate operation is not executed. Let’s add one more termination operation

@Test public void test() { List<Person> list = Arrays.asList(// new Person("Jason", 18), // new Person("Hank", 46), // new Person("Alice", 23)); List.stream ()//.filter(x -> x.getage () > 20)//.foreach (system.out ::println); }Copy the code

The output

GetAge () executes Person [name=Hank, age=46] getAge() executes Person [name=Alice, age=23]Copy the code

You can see that the getAge() method executes and that two Person objects with ages greater than 20 are found and printed