Write in front
If functional interfaces and lambda expressions are the cornerstone of functional programming in Java, stream is the most magnificent building on the cornerstone.
Only when you are familiar with stream can you say you are familiar with Java Functional programming.
This paper mainly introduces the basic concept and basic operation of Stream, so that we can have a preliminary understanding of Stream.
The sample code for this article is available on gitee: Javafp: sample code for Java functional programminghttps://gitee.com/cnmemset/javafp
Concept of stream
First, take a typical stream example:
public static void simpleStream() { List<String> words = Arrays.asList("hello", "world", "I", "love", "you"); int letterCount = words.stream() .filter(s -> s.length() > 3) // Filter out words with length less than or equal to 3 .mapToInt(String::length) // Map each word to word length .sum(); // Calculated total length 5(hello) + 5(world) + 4(love) = 14 // The output is 14 System.out.println(letterCount); }
In the above example, we will list the strings words is used as the data source of stream, and then the The series operation (sum) method of filter map reduce belongs to reduce Operations), and map and reduce will be described in detail later Operation. If you have big data programming experience, it will be easier to understand the meaning of map and reduce.
The definition of stream is rather obscure, which can be roughly understood as a sequence of data elements supporting serial or parallel operations. It has the following characteristics:
- First, stream is not a data structure, it does not store data. Stream is a data view on a data source. The data source can be an array, a Collection class, or even an I/O channel. It passes through a calculation pipeline of computational operations) to perform filter map reduce operations on the data of the data source.
- Second, stream inherently supports functional programming. An important feature of functional programming is that it does not modify the value of variables (no "side effects"). Any operation on stream will not modify the data in the data source. For example, if you filter a stream whose data source is Collection, only a new stream object will be generated, and the elements in the underlying data source will not be deleted.
- Third, many operations of stream are lazy looking. Lazy evaluation means that the operation is only a description of the stream and will not be executed immediately. This kind of lazy operation is called intermediate operation in stream operations).
- Fourth, the data presented by stream can be infinite. For example, Stream.generate can generate an infinite stream. We can pass limit(n) method to convert an infinite flow into a finite flow, or The findFirst() method terminates an infinite stream.
- Finally, elements in the stream can only be consumed once. And iterators Iterator Similarly, when you need to repeatedly access an element, you need to regenerate a new stream.
Stream operations can be divided into two categories: intermediate operations Operations and terminal operations). A stream pipeline consists of a data source + 0 or more intermediate operations + 1 termination operation.
Intermediate operation:
intermediate operation Operations) refer to operations that convert one stream to another, such as filter and map operations. intermediate operations are inert. Their function is only to describe a new stream and will not be executed immediately.
Terminate operation:
Terminate operation Operations) refer to those operations that will produce a new value or side effect, such as count and forEach Operation. Only when a termination operation is encountered, the previously defined intermediate operation will actually be executed. It should be noted that when a stream performs a termination operation, its status will become "consumed" and can no longer be used.
In order to prove that "intermediate operations are inert", we designed an experimental example code:
public static void intermediateOperations() { List<String> words = Arrays.asList("hello", "world", "I", "love", "you"); System.out.println("start: " + System.currentTimeMillis()); Stream<String> interStream = words.stream() .filter(s -> { try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing } return s.length() > 3; }); IntStream intStream = interStream.mapToInt(s -> { try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing } return s.length(); }); // Because both filter and map operations are intermediate operations and will not be executed, // Therefore, they are not affected by Thread.sleep and take a short time System.out.println("after filter && map: " + System.currentTimeMillis()); int letterCount = intStream.sum(); // sum is a termination operation and will execute the intermediate operations defined previously, // Thread.sleep is actually executed, taking 5(filter) + 3(mapToInt) = 8 seconds System.out.println("after sum: " + System.currentTimeMillis()); // The output is 14 System.out.println(letterCount); }
The output of the above code is similar:
start: 1633438922526 after filter && map: 1633438922588 after sum: 1633438930620 14
It can be seen that the above code verifies that "intermediate operations are inert": there is only a few tens of milliseconds between printing "start" and printing "after filter & & map", while printing "after" "Sum" proved that only when it was encountered after 8 seconds sum After operation, filter and map The function defined in is actually executed.
Generate a stream object
In Java 8, four stream interfaces are introduced: stream, IntStream, LongStream and DoubleStream, corresponding to Object type and basic types int, long and double respectively. As shown in the figure below:
In Java, stream related operations are basically implemented through the above four interfaces, and specific stream implementation classes are not involved. To get a stream, you usually do not create it manually, but call the corresponding tool method.
Common tools and methods include:
- Collection method: Collection.stream() or Collection.parallelStream()
- Array method: Arrays.stream(Object [])
- Factory method: Stream.of(Object[]), IntStream.range(int, int), or Stream.iterate(Object, UnaryOperator) wait
- Read file method: BufferedReader.lines()
- class java.nio.file.Files also provides Stream related API s, such as Files.list, Files.walk, etc
Basic operation of Stream
Taking the interface stream as an example, let's first introduce some basic operations of stream.
forEach()
The forEach method in Stream is similar to the forEach method in Collection, which performs the specified operation on each element.
The forEach method signature is:
void forEach(Consumer<? super T> action)
The forEach method is a termination operation, which means that all intermediate operations before it will be executed, and then executed immediately action .
filter()
The method signature of the filter method is:
Stream<T> filter(Predicate<? super T> predicate)
The filter method is an intermediate operation. Its function is based on parameters Predict filter element and return a Stream that only contains elements that meet the predict condition.
Example code:
public static void filterStream() { List<String> words = Arrays.asList("hello", "world", "I", "love", "you"); words.stream() .filter(s -> s.length() > 3) // Filter out words with length less than or equal to 3 .forEach(s -> System.out.println(s)); }
The above code output is:
hello world love
limit()
The limit method signature is:
Stream<T> limit(long maxSize);
The limit method is a short-circuiting intermediate operation. It is used to cut off the current Stream and leave only the most maxSize Elements form a new Stream. Short circuiting means converting a Stream of infinite elements into a Stream of finite elements.
For example, Random.ints can generate an approximately infinite stream of random integers. We can limit the number of random integers generated by the limit method. Example code:
public static void limitStream() { Random random = new Random(); // Print 5 random integers in [1, 100) in the left closed right open interval random.ints(1, 100) .limit(5) .forEach(System.out::println); }
The output of the above code is similar:
90 31 31 52 63
distinct()
The method signature of distinct is:
Stream<T> distinct();
distinct is an intermediate operation that returns a Stream after removing duplicate elements.
The author once encountered an interesting scene: to generate 10 non repeated random numbers. This requirement can be realized in combination with the Random.ints method (Random.ints can generate an approximately infinite random integer stream). The example code is as follows:
public static void distinctStream() { Random random = new Random(); // In the left closed right open interval [1, 100), 10 non repeated numbers are randomly generated random.ints(1, 100) .distinct() .limit(10) .forEach(System.out::println); /* // An interesting question is that if the limit method is placed in front of distinct, // Is there any difference between the result and the above code? // Welcome to group discussion. random.ints(1, 100) .limit(10) .distinct() .forEach(System.out::println); */ }
sorted()
There are two signatures for sorted methods:
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
The former is sorted in natural order, and the latter is sorted according to the specified comparator.
The sorted method is an intermediate operation similar to the Collection.sort method.
The example code is as follows:
public static void sortedStream() { List<String> list = Arrays.asList("Guangdong", "Fujian", "Hunan", "Guangxi"); // Natural sorting list.stream().sorted().forEach(System.out::println); System.out.println("==============="); // To sort provinces, first sort by length. If the length is the same, sort by alphabetical order list.stream().sorted((first, second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }).forEach(System.out::println); }
The output of the above code is:
Fujian Guangdong Guangxi Hunan =============== Hunan Fujian Guangxi Guangdong
epilogue
Welcome to the functional programming world of Java!!!
This paper introduces the concept and basic operation of Stream, especially the concepts of intermediate operation and termination operation.
After carefully reading this article, you should have a preliminary understanding of Stream, but this is only an introduction to Stream programming. The more interesting, challenging and playable is the map reduce operation to be introduced later.