Before to see a lot of articles introduce Java8 Stream, but the first contact is really difficult to understand (my understanding is low), had no choice but to “rote learning”, but yesterday I called king glory (I won that game, cow the magic game MVP), a sudden inspiration, feel not understand thoroughly before suddenly understand thoroughly. So I decided to take a simple way to remember what I thought was a Java8 Stream.

Lambda expressions

grammar

Lambda expressions are the cornerstone of the Stream API, so to learn how to use the Stream API, you must first understand lambda expressions. Here’s a quick review of lambda.

We see code like this all the time

Arrays.sort(new Integer[]{1.8.7.4}, new Comparator<Integer>() {
   @Override
   public int compare(Integer first, Integer second) {
       returnfirst.compareTo(second); }});Copy the code

This is using an anonymous class, and we’ll always use an anonymous class because we only run it once and we don’t want it to last forever. Although lambda expressions are for what is called functional programming, and everyone in the community to come out, but in my opinion for the sake of square (stealing) (lazy).

The code above says trouble, but what about converting to something like this?

Arrays.sort(new Integer[]{1.8.7.4},
	(first,second) -> first.compareTo(second));
Copy the code

It’s so refreshing to look at, and it cuts out unnecessary details. For an interface that contains only one abstract method, you can create objects for that interface through a lambda interface, which is called a functional interface.

Lambda expressions introduce a new operator: ->, which divides lambda expressions into two parts

(n) -> n*n
Copy the code

The left side specifies the parameters required by the expression, or can be null if no parameters are required. On the right is the lambda code block, which specifies the action of a lambda expression.

Note that if only one method returns without a declaration, it returns by default. If there are branches that return, you need to declare them.

(n) -> {
	if( n <= 10) 
		return n*n;
	return n * 10;
}
Copy the code

Method references and constructor references

Method references

In some cases, actions that are first passed to other code already have a way to implement them. For example, in the GUI, the event object should be printed when the button is clicked

button.setOnAction(event -> System.out.println(event));
Copy the code

I want to be lazy at this point, I don’t want to write the event parameter, because there is only one parameter, can’t the JVM help me? Here is the modified code


button.setOnAction(System.out::println);
Copy the code

The expression System.out::println is a method reference equivalent to the lambda expression x -> system.out.println (x). The **::** operator separates the name of a method from the name of an object or class. Here are three main uses:

  1. Object :: instance method
  2. Class :: static methods
  3. Class :: instance methods

In the first two cases, method references are equivalent to lambda expressions that provide method parameters. For example Math::pow ==== (x,y) -> math.pow (x,y).

In the third case, the first parameter is called the object executing the method. Such as the String: : compareToIgnoreCase = = = = (x, y) – > x.com pareToIgnoreCase (y).

Also this::equals ==== x -> this.equals(x),super::equals ==== super.equals(x).

Constructor reference

List<String> strList = Arrays.asList("1"."2"."3");
Stream<Integer> stream =  strList.stream().map(Integer::new);
Copy the code

The Integer::new of the above code is the constructor reference, except that the constructor reference has the method name new. If there are multiple constructors, the compiler will infer from the context and find the appropriate one.

StreamAPI

The word Stream means a Stream of water.

In my opinion, stream looks like the picture above. The initial data of stream is small water droplets, which are processed by various “interceptors”. Some droplets are discarded, some become larger, some are colored, and some become triangles. Eventually they become colored circles. And then we put it in the result set. Most of the time, we write code that iterates through a collection and then evaluates or transforms the elements that meet the criteria to add them to the new collection. This process is similar to the above diagram. Let’s start with a piece of code

Map<String,Map<String,Integer>> resultMap = new HashMap<>();
Map<String,Integer> maleMap = new HashMap<>();
Map<String,Integer> femaleMap = new HashMap<>();

resultMap.put("male", maleMap);
resultMap.put("female",femaleMap);

for(int i = 0; i < list.size(); i++) {
    Person person = list.get(i);
    String gender = person.getGender();
    String level = person.getLevel();
    switch (gender) {
        case "male":
            Integer maleCount;
            if("gold".equals(level)) {
                maleCount = maleMap.get("gold");
                maleMap.put("gold".null! = maleCount ? maleCount +1 : 1);
            } else if("soliver".equals(level)){
                maleCount = maleMap.get("soliver");
                maleMap.put("soliver".null! = maleCount ? maleCount +1 : 1);
            }
            break;

        case "female":
            Integer femaleCount;
            if("gold".equals(level)) {
                femaleCount = femaleMap.get("gold");
                femaleMap.put("gold".null! = femaleCount ? femaleCount +1 : 1);
            } else if("soliver".equals(level)){
                femaleCount = femaleMap.get("soliver");
                femaleMap.put("soliver".null! = femaleCount ? femaleCount +1 : 1);
            }
            break; }}Copy the code

The above code is used to count the number of engineer ranks by gender. Before Java StreamAPI came out, this kind of business code should be everywhere in the system. It took me about two minutes to type the above code by hand

Map<String,Map<String,Integer>> result = list.stream().collect(
    Collectors.toMap(
	    person -> person.getGender(),
	    person -> Collections.singletonMap(person.getLevel(), 1),
	    (existValue,newValue) -> {
	        HashMap<String,Integer> newMap = new HashMap<>(existValue);
	        newValue.forEach((key,value) ->{
	            if(newMap.containsKey(key)) {
	                newMap.put(key, newMap.get(key) + 1);
	            } else{ newMap.put(key, value); }});returnnewMap; }));Copy the code

Or I could change it to something like this

Map<String,Map<String,Integer>> result =  stream.collect(
    Collectors.groupingBy(
        Person::getGender, 
        Collectors.toMap(
            person->person.getLevel(), 
            person -> 1,
            (existValue,newValue) -> existValue + newValue
        )
    )
);

Copy the code

Not only are there fewer code blocks, but the logic is even clearer. I can stream it all the time.

A Stream can be either finite or infinite, but of course we use finite streams most often (for loops are finite streams). As in the above diagram, we can do all sorts of common things with elements in a Stream. Such as summing, filtering, grouping, Max, min, etc., so start using Stream now

The characteristics of the Stream

  1. The Stream itself does not store elements; elements may be stored in the underlying collection or produced.
  2. The Stream operators do not change the source object; instead, they return a Stream that holds the new object
  3. The Stream operator is deferred and may not execute until the result is needed.

Stream API

Functional interface The parameter types The return type Abstract method name describe Other methods
Runnable There is no void run Performs an operation without arguments and return values There is no
Supplier<T> There is no T get Provide a value of type T
Counsumer<T> T void accept Handles a value of type T chain
BiConsumer<T,U> T,U void accept Handles values of type T and type U chain
Function<T,R> T R apply A function of type T compose,andThen,identity
BiFunction<T,U,R> T,U R apply A function with arguments of type T and U andThen
UnaryOperator<T> T T apply Unary operation on type T compose,andThen,identity
BinaryOperator<T> T,T T apply Binary operation on type T andThen
Predicate<T> T boolean test A function that evaluates Boolean values And,or,negate,isEqual
BiPredicate<T,U> T,U boolean test A function that takes two arguments and evaluates a Boolean and,or,negate

Difference between map() and flatMap()

When you use the map method, you apply a function to each element and collect the returned value into a new Stream.

Stream<String[]> -> flatMap -> Stream<String> Stream<Set<String>> -> flatMap -> Stream<String> Stream<List<String>> -> FlatMap -> Stream<String> Stream<List<Object>> -> flatMap -> Stream<Object> {{1,2}, {3,4}, {5,6}} -> flatMap -> {1,2,3,4,5,6}Copy the code

Intermediate and end operations

All operations on Stream fall into two categories: intermediate operations and end operations. Intermediate operations are just flags (calling these methods does not really start the Stream’s traversal). The actual calculation is triggered only when the operation ends. If the API returns Stream, the intermediate operation is performed. If the API returns Stream, the intermediate operation is performed.

How to debug

  1. Use code snippets such asIntStream) of (1, 2, 3, 4, 5)) fiter (I - > {return I % 2 = = 0; })Just type the breakpoint on the code segment.
  2. Reference methods can also be debugged, with breakpoints in isDouble for exampleIntStream) of (1, 2, 3, 4, 5)) fiter (MyMath: : isDouble)

Apis that are hard to understand

  1. How did reduce() get done before?

int sum = 0;
for(int value in values) {
	sum = sum + value;
}

Copy the code

Now switch to stream

values.stream().reduce(Integer::sum);
Copy the code

The reduce() method is a binary function: start with the first two elements of the flow and apply it to other elements in the flow.

How to write good Stream code

The Stream API is designed for convenience. Streams can be used to group, aggregate, Max, min, sort, and sum data that is not easy to process at the SQL level. So don’t overcomplicate it, just write it. There will come a time when you’re good enough to write clean code. Or start now by converting a lot of the for loops in your project to stream mode.

Code sample

I wanted to write a large chunk of code to style the conversion to the Stream API, but thought it was unnecessary. I found some code from the Hutool tool class on Github to complete the conversion example. (You can improve the capabilities of the Stream API in this way)

  1. Count the number of occurrences of each element (think jdK7)
[a,b,c,c,c] -> a:1,b:1,c:3

Arrays.asList("a"."b"."c"."c"."c").stream().collect(Collectors.groupingBy(str->str, Collectors.counting()));
Copy the code
  1. Convert collections to strings with specific delimiters, prefixed and suffixed (think jdK7)
List<String> myList = Arrays.asList("a"."b"."c"."c"."c");
myList.stream().collect(Collectors.joining(","."{"."}"));

Copy the code
  1. Check that the list is not completely empty (please imagine how to implement jdK7)
myList.stream().anyMatch(s -> ! s.isEmpty());Copy the code