What is flow? Does it have anything to do with the IO stream? Java 1.8 introduced the concept of stream, which is not an IO stream. When I first learned it, I easily thought of this stream and IO stream together. When I learned it, I always thought about it and IO stream. But learned later found……. A Java stream and an IO stream are two completely different concepts.
So what is flow?
Directly on a lot of concepts of what you may not have the patience to see, very wordy, so first on the code:
public class Dish{ private final String name; // private final Boolean vegetarian; Private final int calories; Private final Type Type; Public enum Type{MEAT,FISH,OTHER};
public Dish(String name, boolean vegetarian, int calories, Type type) { this.name = name; this.vegetarian = vegetarian; this.calories = calories; this.type = type; } public String getName() { return name; } public boolean isVegetarian() { return vegetarian; } public int getCalories() { return calories; } public Type getType() { return type; }}Copy the code
public class collect {
private static List<Dish> menu = Arrays.asList( new Dish("pork" ,false ,800 , Dish.Type.MEAT), new Dish("beef" ,false ,700 , Dish.Type.MEAT), new Dish("chicken" ,false ,400 , Dish.Type.MEAT), new Dish("french froes" ,true ,530 ,Dish.Type.OTHER), new Dish("rice" ,true ,350 , Dish.Type.OTHER), new Dish("season fruit" ,true ,120 ,Dish.Type.OTHER), new Dish("pizza" ,true ,550 , Dish.Type.OTHER), new Dish("prawns" ,false ,300 , Dish.Type.FISH), new Dish("salmon" ,false ,450 , Dish.Type.FISH) ); public static void main(String[] args){ ToListCollector<Dish> collector = new ToListCollector<Dish>(); menu.stream() .forEach(n -> {System.out.println(n.getName()); }); }Copy the code
} for convenience, I used the example directly from the book Java8 in action, so what is the result?
Pork Beef Chicken French Froes Rice season fruit Pizza Prawns Salmon prints out the name of each dish in turn. As we can see above, the object of type List calls a method called stream(), It then calls the foreach() method, which, based on the arguments passed in to foreach(), should take a functional interface as its parameter (if you’re wondering about functional interfaces and Lambda expressions, see my article on Java behavior parameterization, three of them). Looking at the Lambda expression I passed in, you should know that the functional interface should be Consumer, which takes a generic argument and returns no value. I call the parameter’s getName() method in the body of the Lambda and print it out, and the program prints out the name attribute for each element in the ArrayList.
There are two questions here: the stream() method and the foreach() method
The stream() method converts the elements in the ArrayList into a stream, while the foreach() method processes each element in the stream, which is the Lambda you pass in (method references are fine). Most Java collections frameworks come with a stream() method that converts elements of a collection into a stream. What exactly is a stream? You can think of it as another way of representing sets, but that’s not accurate. Streams are disposable, which means that you create a stream by calling the stream() method of the collection, process the stream, and then the stream disappears.
Foreach () is a method that substitutes elements from the stream into a Consumer accept() method, and then acts on the elements based on the Lambda expression or method reference you pass in.
In this way, a stream looks like a walk through the elements of a collection and does something to the elements, but it’s much simpler and more efficient. Why is it easier? Because it can be done in parallel. In order to make full use of CPU, parallel operations will be added to the processing of elements in a collection. However, when multiple threads operate on a collection, there will be thread safety problems, so the first thing we think of is locking, but the code fragments of locking are all serial processing. So there is not much improvement in locking and single thread processing efficiency for parallel operations on this set, as I wrote in the following example:
public class collect { private static List<Dish> menu = Arrays.asList( new Dish("pork" ,false ,800 , Dish.Type.MEAT), new Dish("beef" ,false ,700 , Dish.Type.MEAT), new Dish("chicken" ,false ,400 , Dish.Type.MEAT), new Dish("french froes" ,true ,530 ,Dish.Type.OTHER), new Dish("rice" ,true ,350 , Dish.Type.OTHER), new Dish("season fruit" ,true ,120 ,Dish.Type.OTHER), new Dish("pizza" ,true ,550 , Dish.Type.OTHER), new Dish("prawns" ,false ,300 , Dish.Type.FISH), new Dish("salmon" ,false ,450 , Dish.Type.FISH) ); static int index; Public static void main(String[] args){public static void main(String[] args){ i < 10 ; I++){new Thread(()->{// the first thing to do is to iterate through the collection. // For example, we want to filter the vegetable and print its name. In this case, menu is a member variable to lock. synchronized (menu.getClass()) { if(index < menu.size()) { if (menu.get(index).isVegetarian()) { System.out.println(menu.get(index).getName()); index++; } else { index++; } } } }).start(); }}}Copy the code
As you can see in the above code, the code in the thread is almost all locked, so the threads are created to run almost sequentially, not taking full advantage of multithreading. So, let’s try again with a stream:
public class collect {
private static List<Dish> menu = Arrays.asList( new Dish("pork" ,false ,800 , Dish.Type.MEAT), new Dish("beef" ,false ,700 , Dish.Type.MEAT), new Dish("chicken" ,false ,400 , Dish.Type.MEAT), new Dish("french froes" ,true ,530 ,Dish.Type.OTHER), new Dish("rice" ,true ,350 , Dish.Type.OTHER), new Dish("season fruit" ,true ,120 ,Dish.Type.OTHER), new Dish("pizza" ,true ,550 , Dish.Type.OTHER), new Dish("prawns" ,false ,300 , Dish.Type.FISH), new Dish("salmon" ,false ,450 , Dish.Type.FISH) ); static int index; public static void main(String[] args){ menu.parallelStream() .filter(Dish::isVegetarian) .forEach(n -> System.out.println(n.getName())); }}Copy the code
The menu collection first calls a method called paralleStream(). The difference between this and the stream() method is that after it is called, the stream is processed in parallel. It segments the elements of the stream. It is then distributed to child threads for processing. I will continue to talk about stream parallelism in a later article, but I will show you that the introduction of streams with Lambda expressions greatly simplifies our programming.
After calling the paralleStream() method, I called a fileter() method, which functions as a filter. It accepts a Predict function interface that takes a generic parameter and returns a Boolean value. The stream calls this method and removes all elements that return false, leaving only elements that return true for the next method to operate on.
In the previous example, I called filter() and foreach(), one to filter the elements in the stream and the other to print out the elements in the stream. These two operations are actually two kinds of operations. Filter () is the intermediate operation. That is, the stream will generate a stream after passing through this method, that is, the original elements in the stream will generate a new stream to the next method processing; The foreach() method is a terminal operation that does not return the stream after the operation, and the terminal operation marks the end of the stream.
What other intermediate and terminal operations does Java provide for us? I’ll apply them to an example and explain them all:
public class collect {
private static List<Dish> menu = Arrays.asList( new Dish("pork" ,false ,800 , Dish.Type.MEAT), new Dish("beef" ,false ,700 , Dish.Type.MEAT), new Dish("chicken" ,false ,400 , Dish.Type.MEAT), new Dish("french froes" ,true ,530 ,Dish.Type.OTHER), new Dish("rice" ,true ,350 , Dish.Type.OTHER), new Dish("season fruit" ,true ,120 ,Dish.Type.OTHER), new Dish("pizza" ,true ,550 , Dish.Type.OTHER), new Dish("prawns" ,false ,300 , Dish.Type.FISH), new Dish("salmon" ,false ,450 , Dish.Type.FISH) ); static int index; public static void main(String[] args){ List<Integer> list = menu.stream() .filter(m -> ! m.isVegetarian()) .map(Dish::getCalories) .sorted(Comparator.comparingInt(n->n)) .limit(4) .skip(1) .collect(Collectors.toList()); }Copy the code
} What does this code do?
Is to screen out all the foods that are not vegetables and map all Dish objects in the stream generated by filter() to their calories. Instead of Dish objects, Interger objects are now stored in the stream. Then, we use the sorted() method to sort the calories in the stream and generate a stream with a descending int value. Intercept the first four elements of the stream with the limit() method; Skip () to skip the previous element in the stream; The Collectors () method is passed in to save the elements of the flow to a List object.
1. Filter () method: intermediate operation
This method accepts a Predict function interface, which means you can pass in a Lambda expression or a method reference. The abstract methods in the Predict interface are
boolean test(T t); This method returns a Boolean value, and only elements that return true are added to the new stream and passed to the next method.
2. Map () method: intermediate operation
This method accepts a Function<? super T ,? Extends R> is a functional interface that maps passed elements to other types of elements. The abstract method in Function is
R apply(T t); The apply method takes a value of a generic type and returns a value of a generic type. In this case, we pass in an element of Dish type and call the getcalorie () method of Dish object to return an integer value.
3. Sorted () method: intermediate
This method accepts a Compator<? A functional interface of type super T> that is intended to sort elements in the stream based on the passed Lambda expression or method reference of type Compator, where the abstract method is
int compare(T o1, T o2); The compare method takes two generic arguments, in this case two elements in the stream, and compares them to size according to its own logic, as in the Compator.comparingInt() call I just made, which actually does this internally:
public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0:1); }Copy the code
4. Limit () method: intermediate operation
This method takes a long value, meaning truncation, that is, the first n elements of the stream (the long value passed to the limit method) are extracted to generate a new one to be passed to the next method.
5. Skip () method: intermediate operation
This method takes a long value, meaning skip, that is, skip the first n elements of the stream and extract the remaining elements to generate a new one to be passed to the next method.
6. Collect () Method: Terminal operation
This method is important and means collector, which I will not focus on in a later post, but we will only cover its basic usage here. It accepts a Collector<? Super T,A,R> functional interface, which collects all the elements of the stream into A container, which is defined by itself. The Collectors. ToList () we wrote above is a Java native method that collects all elements into a List.
The above is some basic operation of convection, we can also follow the source code to learn
Flows and sets work separately. Why is that? As you can see in the above example, we use streams to manipulate elements in collections, and because of the functional interface, these operations are very convenient, so we can say that collections are used to hold elements, and streams are used to manipulate elements.