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