This article has been published on Github /JavaMap. It contains a series of articles on advanced technology knowledge for Java programmers.
Java8 was released by Oracle in 2014 and is the most revolutionary release since Java5.
Java8 absorbs the essence of other languages and brings functional programming, lambda expressions, streams, and a host of new features that will allow you to code efficiently and elegantically.
1. What is Stream?
Stream is a new interface to Java8 that allows data collections to be processed declaratively. Stream is not a collection type that does not hold data. It can be thought of as a high-level Iterator that iterates through a collection of data.
The Stream operations can be stacked step by step like the Builder to form a pipeline. Pipelining generally consists of data source + zero or multiple intermediate operations + one terminal operation. Intermediate operations can change the flow to another flow, such as using a filter to filter elements and a map to extract values.
Stream is closely related to lambda expressions, and this article assumes that you already know the basics of lambda.
2. Characteristics of Stream
- You can traverse (consume) only once. A Stream instance can only be traversed once, and the end operation ends after one traversal. A second traversal requires the instance to be regenerated, similar to an Iterator.
- Secure the data source. Changes to any element in the Stream will not cause the data source to be modified, such as filtering out an element in the Stream, and traversing the data source again will still fetch that element.
- Lazy. The filter and map operations are concatenated to form a series of intermediate operations that would never be performed without a terminal operation (such as Collect).
3. Method for creating Stream instances
(1) Create Stream instance with specified value
// of is the static method of Stream
Stream<String> strStream = Stream.of("hello"."java8"."stream");
// Or use primitive type streams
IntStream intStream = IntStream.of(1.2.3);
Copy the code
(2) Create Stream instance with collection (common)
// Initialize an immutable list object using the Guava library
ImmutableList<Integer> integers = ImmutableList.of(1.2.3);
The List interface inherits the Collection interface. Java8 adds the Stream method to the Collection interface
Stream<Integer> stream = integers.stream();
Copy the code
(3) Create Stream instance with array
// Initialize an array
Integer[] array = {1.2.3};
// Use the stream static method of Arrays
Stream<Integer> stream = Arrays.stream(array);
Copy the code
(4) Create a Stream instance using a generator
// Randomly generate 100 integers
Random random = new Random();
// Add limit otherwise infinite flow
Stream<Integer> stream = Stream.generate(random::nextInt).limit(100);
Copy the code
(5) Create Stream instances using iterators
// Generate 100 odd numbers, add limit otherwise infinite stream
Stream<Integer> stream = Stream.iterate(1, n -> n + 2).limit(100);
stream.forEach(System.out::println);
Copy the code
(6) Use IO interface to create Stream instance
The list method returns Stream
Stream<Path> pathStream = Files.list(Paths.get("/"));
Copy the code
4. Stream Common operations
The Stream interface defines many operations, which can be roughly divided into two categories: intermediate operations and terminal operations.
(1) Intermediate operation
Intermediate operations return another stream, and multiple intermediate operations can be joined together to form a query.
Intermediate operations are lazy and do nothing if there is no terminal operation on the stream.
The following are common intermediate operations:
The map operation
A map maps each element of an input stream to another element to form an output stream.
// Initialize an immutable string
List<String> words = ImmutableList.of("hello"."java8"."stream");
// Count the length of each word in the list
List<Integer> list = words.stream()
.map(String::length)
.collect(Collectors.toList());
// output: 5 5 6
list.forEach(System.out::println);
Copy the code
FlatMap operation
List<String[]> list1 = words.stream()
.map(word -> word.split("-"))
.collect(Collectors.toList());
// output: [Ljava.lang.String;@59f95c5d,
// [Ljava.lang.String;@5ccd43c2
list1.forEach(System.out::println);
Copy the code
Na? You expect a List and return a List<String[]> because the split method returns a String[].
At this point you can think of converting an array to a stream, hence the second version
Stream<Stream<String>> arrStream = words.stream()
.map(word -> word.split("-"))
.map(Arrays::stream);
// output: java.util.stream.ReferencePipeline$Head@2c13da15,
// java.util.stream.ReferencePipeline$Head@77556fd
arrStream.forEach(System.out::println);
Copy the code
Again, this problem can be solved by flatstreaming with a flatMap, which takes each element of the stream and turns it into another output stream
Stream<String> strStream = words.stream()
.map(word -> word.split("-"))
.flatMap(Arrays::stream)
.distinct();
// output: hello java8 stream
strStream.forEach(System.out::println);
Copy the code
The filter operation
Filter receives Predicate objects, which are filtered by conditions, and the elements that meet the conditions generate another stream.
// Filter out words greater than 5 and print them out
List<String> words = ImmutableList.of("hello"."java8"."hello"."stream");
words.stream()
.filter(word -> word.length() > 5)
.collect(Collectors.toList())
.forEach(System.out::println);
// output: stream
Copy the code
(2) Terminal operation
Terminal operations convert the stream to concrete return values, such as List, Integer, and so on. Common terminal operations include foreach, min, Max, and count.
Foreach is very common. Here’s an example of Max.
// Find the maximum value
List<Integer> integers = Arrays.asList(6.20.19);
integers.stream()
.max(Integer::compareTo)
.ifPresent(System.out::println);
// output: 20
Copy the code
5. Real: Refactoring old code with Stream
Suppose you have a requirement: filter out students older than 20 with a score greater than 95.
Using the for loop:
private List<Student> getStudents(a) {
Student s1 = new Student("xiaoli".18.95);
Student s2 = new Student("xiaoming".21.100);
Student s3 = new Student("xiaohua".19.98);
List<Student> studentList = Lists.newArrayList();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
return studentList;
}
public void refactorBefore(a) {
List<Student> studentList = getStudents();
// Use temporary list
List<Student> resultList = Lists.newArrayList();
for (Student s : studentList) {
if (s.getAge() > 20 && s.getScore() > 95) { resultList.add(s); }}// output: Student{name=xiaoming, age=21, score=100}
resultList.forEach(System.out::println);
}
Copy the code
Using a for loop initializes a temporary list to store the final result, which is not elegant or concise.
After refactoring using lambda and stream:
public void refactorAfter(a) {
List<Student> studentLists = getStudents();
// output: Student{name=xiaoming, age=21, score=100}
studentLists.stream().filter(this::filterStudents).forEach(System.out::println);
}
private boolean filterStudents(Student student) {
// Select students older than 20 with a score greater than 95
return student.getAge() > 20 && student.getScore() > 95;
}
Copy the code
It’s convenient to use filters and method references to make your code clear without declaring a temporary list.
6. Common pitfalls with Stream
Myth # 1: Repeatedly consuming Stream objects
Once a stream object is consumed, it cannot be consumed again.
List<String> strings = Arrays.asList("hello"."java8"."stream");
Stream<String> stream = strings.stream();
stream.forEach(System.out::println); // ok
stream.forEach(System.out::println); // IllegalStateException
Copy the code
Error after execution of the above code:
java.lang.IllegalStateException: stream has already been operated upon or closed
(2) Mistake two: Modify the data source
Failed to add a new string during a stream operation:
List<String> strings = Arrays.asList("hello"."java8"."stream");
// expect: HELLO JAVA8 STREAM WORLD, but throw UnsupportedOperationException
strings.stream()
.map(s -> {
strings.add("world");
return s.toUpperCase();
}).forEach(System.out::println);
Copy the code
Note: Never modify the data source while operating on the flow.
conclusion
Java8 streaming programming can make your code elegant to a certain extent, but avoid common pitfalls such as don’t re-consume objects and don’t modify data sources.
— END —
Daily request praise: hello technical person, praise first then see form a habit, your praise is my power on the way forward, the next stage is more wonderful.
The blogger graduated from Huazhong University of Science and Technology with a master’s degree. He is a programmer who pursues technology and has passion for life. A few years in a number of first-line Internet companies, with years of actual combat experience.
Search the official wechat account “Architect who loves to laugh”, I have technology and story, waiting for you.
The articles are constantly updated, you can see my archived series of articles on Github /JavaMap, have interview experience and technical expertise, welcome Star.