1. The first Stream – API

What is the Java Stream API?

The Java Stream functional programming interface was first introduced in Java 8 and, along with Lambda, has become a landmark feature of Java development, greatly facilitating the efficiency with which openers work with collection class data. The majority of developers are using the Java 8 VERSION of the JDK, with Java Stream and Lambda contributing a lot to this.

A Java Stream is a pipe through which data flows, operates on the data in the pipe, and flows into the next pipe. Those of you who have studied Linux plumbing should easily understand this. Before Java Stram, operations on collection classes were mostly done through the for loop. As you can see from the rest of the article, Java Stream is much cleaner, easier to use, and faster than the for loop.

The functions of pipes include: Filter, Map, sort, etc. After the collection data is processed through the Java Stream pipe, it is converted to another set of collections or data output.

Stream API replaces for loop

Let’s start with an example:

List<String> nameStrs = Arrays.asList("Monkey"."Lion"."Giraffe"."Lemur");

List<String> list = nameStrs.stream()
        .filter(s -> s.startsWith("L"))
        .map(String::toUpperCase)
        .sorted()
        .collect(toList());
System.out.println(list);
Copy the code
  • First, we use the Stream() function to convert a List into a pipe Stream
  • The filter function is called to filter the array elements using lambda expressions. Elements starting with L return true and the rest of the List elements are filtered out
  • The Map function is then called to process each element in the pipe flow, converting all letters to uppercase
  • The sort function is then called to sort the data in the pipeline flow
  • Finally, the collect function toList is called to convert the pipe stream to a List return

The final output is: [LEMUR, LION]. So if you think about this, how many lines of code would it take to iterate through an array if you were to do it in a for loop? Let’s continue learning about Java Stream!

Convert an array to a pipe flow

Convert the array to a pipe Stream using the stream.of () method.

String[] array = {"Monkey"."Lion"."Giraffe"."Lemur"};
Stream<String> nameStrs2 = Stream.of(array);

Stream<String> nameStrs3 = Stream.of("Monkey"."Lion"."Giraffe"."Lemur");
Copy the code

4. Transform collection class objects into pipe flows

Turn a collection class object into a pipe flow by calling the stream() method of the collection class.

List<String> list = Arrays.asList("Monkey"."Lion"."Giraffe"."Lemur");
Stream<String> streamFromList = list.stream();

Set<String> set = new HashSet<>(list);
Stream<String> streamFromSet = set.stream();
Copy the code

5. Convert text files to pipe streams

The files.lines method is used to convert text Files into pipe streams. The paths.get () method in the figure below is used to fetch Files, which is the Java NIO API!

In other words, you can easily load a text file using Java Stream and process the file’s contents line by line.

Stream<String> lines = Files.lines(Paths.get("file.txt"));
Copy the code

2.Stream filter and predicate logic

1. Basic code preparation

Creates an entity class with five attributes. The following code uses Lombok’s annotations Data, AllArgsConstructor so that we don’t have to write get, set methods, and full-parameter constructors. Lombok helps us generate this stereotype code at compile time.

@Data @AllArgsConstructor public class Employee { private Integer id; private Integer age; // age private String gender; // Gender private String firstName; private String lastName; }Copy the code

Write a test class that is also very simple. Create ten New Employee objects

Public class StreamFilterPredicate {public static void main(String[] args){Employee e1 = new Employee(1,23,"M"."Rick"."Beethovan");
        Employee e2 = new Employee(2,13,"F"."Martina"."Hengis");
        Employee e3 = new Employee(3,43,"M"."Ricky"."Martin");
        Employee e4 = new Employee(4,26,"M"."Jon"."Lowman");
        Employee e5 = new Employee(5,19,"F"."Cristine"."Maria");
        Employee e6 = new Employee(6,15,"M"."David"."Feezor");
        Employee e7 = new Employee(7,68,"F"."Melissa"."Roy");
        Employee e8 = new Employee(8,79,"M"."Alex"."Gussin");
        Employee e9 = new Employee(9,15,"F"."Neetu"."Singh");
        Employee e10 = new Employee(10,45,"M"."Naveen"."Jain");


        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

        List<Employee> filtered = employees.stream()
                .filter(e -> e.getAge() > 70 && e.getGender().equals("M")) .collect(Collectors.toList()); System.out.println(filtered); }}Copy the code

Note that the above filter is passed in a lambda expression (as described in the previous section) for employees older than 70 and male. The output is as follows:

[Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)]
Copy the code

What is predicate logic?

To get to the point, we already know from previous chapters that lambda expressions represent the implementation of an anonymous interface function. What does stream.filter () express specifically? See the following figure: You can see that it represents a Predicate interface, which in English means Predicate.

What is a predicate? (Baidu Encyclopedia)

What is predicate logic?

WHERE AND AND define what the subject employee is, so the logic represented by the WHERE AND AND statements is predicate logic

SELECT *
FROM employee
WHERE age > 70
AND gender = 'M'
Copy the code

Reuse of predicate logic

Typically, lambda expressions in filter functions are predicate logic that is used once. If our predicate logic needs to be used in multiple places, multiple scenarios, and multiple codes, it is usually abstracted and defined separately to the subject entity it qualifies. For example, define the following predicate logic in the Employee entity class.

   public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
   public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
Copy the code

3.1. And Syntax (Union)

List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.and(Employee.genderM))
        .collect(Collectors.toList());
Copy the code

The output is as follows:

[Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)]
Copy the code

3.2. Or Syntax (Intersection)

List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.or(Employee.genderM))
        .collect(Collectors.toList());
Copy the code

The output looks like this: it’s actually all men and all men over age 70 (since 79 is also a man, so all men)

[Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan), Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin), Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman), Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor), Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin), Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)]
Copy the code

3.3. Negate syntax

List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.or(Employee.genderM).negate())
        .collect(Collectors.toList());
Copy the code

The output is as follows: invert the result of the code in the previous section, which is actually all women

[Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis), Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Mar
Copy the code

3.Stream Map operation of pipeline flows

Review the basic usage of Stream pipeline Stream map

Simplest requirement: convert every string in the collection to uppercase!

List<String> alpha = Arrays.asList("Monkey"."Lion"."Giraffe"."Lemur"); List<String> alphaUpper = new ArrayList<>();for(String s : alpha) { alphaUpper.add(s.toUpperCase()); } System.out.println(alphaUpper); //[MONKEY, LION, GIRAFFE, List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors. ToList ()); // The method reference used above, //List<String> collect = alpha.stream().map(s -> s.toupercase ()).collect(Collectors. ToList ()); //List<String> collect = alpha.stream().map(s -> s.toupercase ()). System.out.println(collect); //[MONKEY, LION, GIRAFFE, LEMUR]Copy the code

So the map function is used to transform each data element in the pipeline flow.

Handling collection elements that are not strings

The map() function can not only process data, but also convert data types. As follows:

List<Integer> lengths = alpha.stream()
        .map(String::length)
        .collect(Collectors.toList());

System.out.println(lengths); //[6, 4, 7, 5]
Stream.of("Monkey"."Lion"."Giraffe"."Lemur")
        .mapToInt(String::length)
        .forEach(System.out::println);
Copy the code

The output is as follows:

June 4 July 5Copy the code

In addition to mapToInt. There are also maoToLong, mapToDouble and so on

Three, a little more complicated: deal with object data format transformations

Again, use the Employee class from the previous section to create 10 objects. Requirements are as follows:

  • Increase the age of each Employee by one year
  • Replace “M” in gender with “male” and “F” with Female.
Public static void main(String[] args){Employee e1 = new Employee(1,23,"M"."Rick"."Beethovan");
    Employee e2 = new Employee(2,13,"F"."Martina"."Hengis");
    Employee e3 = new Employee(3,43,"M"."Ricky"."Martin");
    Employee e4 = new Employee(4,26,"M"."Jon"."Lowman");
    Employee e5 = new Employee(5,19,"F"."Cristine"."Maria");
    Employee e6 = new Employee(6,15,"M"."David"."Feezor");
    Employee e7 = new Employee(7,68,"F"."Melissa"."Roy");
    Employee e8 = new Employee(8,79,"M"."Alex"."Gussin");
    Employee e9 = new Employee(9,15,"F"."Neetu"."Singh");
    Employee e10 = new Employee(10,45,"M"."Naveen"."Jain");


    List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

    /*List<Employee> maped = employees.stream()
            .map(e -> {
                e.setAge(e.getAge() + 1);
                e.setGender(e.getGender().equals("M")?"male":"female");
                returne; }).collect(Collectors.toList()); */ List<Employee> maped = employees.stream() .peek(e -> { e.setAge(e.getAge() + 1); e.setGender(e.getGender().equals("M")?"male":"female");
            }).collect(Collectors.toList());

    System.out.println(maped);

}
Copy the code

Since the map argument e is the return value, the peek function can be used. The peek function is a special map function that can be used when the function has no return value or when the parameter is the return value.

Four, flatMap

Map can transform data in a pipe flow, but what if there are pipes in a pipe? Namely: how to deal with two-dimensional arrays and two-dimensional collection classes. Implement a simple requirement: print out each letter of the element as a set of two strings “Hello” and “world”. How do we write it if we don’t have Stream? Write a two-layer for loop. The first layer iterates over the string and splits the string into a char array, and the second for loop iterates over the char array.

List<String> words = Arrays.asList("hello"."word");
words.stream()
        .map(w -> Arrays.stream(w.split("")))    //[[h,e,l,l,o],[w,o,r,l,d]]
        .forEach(System.out::println);
Copy the code

Output the print result:

java.util.stream.ReferencePipeline$Head@3551a94
java.util.stream.ReferencePipeline$Head@531be3c5
Copy the code
  • You can’t do this with the map method, you can’t do this with the Map method. A map can only operate on one-dimensional arrays, arrays within arrays, pipes within pipes, and it can’t handle every element.

  • FlatMap can be understood as expanding all the data in several sub-pipes into the parent pipe for processing.

words.stream()
        .flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
        .forEach(System.out::println);
Copy the code

Output the print result:

h
e
l
l
o
w
o
r
d
Copy the code

4.Stream state and parallel operation

Review the Stream pipeline flow operation

  • Source operations: Arrays, collection classes, and line text files can be converted into pipeline streams for data processing
  • Intermediate operations: Processing data in a Stream, such as filtering, data conversion, and so on
  • Terminal operation: Converts a Stream pipe Stream to another data type. We haven’t talked about that yet, but we’ll talk about that later in the chapter.

Take a look at the following brain map to get a clearer idea:

Intermediate operation: stateful and stateless

In fact, in programmer programming, often come into contact with “stateless”, “stateless”, most people are more confused. And the word “state” seems to mean different things in different contexts. To understand the meaning of the word “state” in programming, here are a few key points:

  • State usually means public data, stateful means there is “public data.”
  • Because there is common data, state often requires additional storage.
  • State is usually operated by multiple people, multiple users, multiple threads and multiple times, which involves state management and change operations.

Is it more confusing? Let me give you an example

  • A Web development session is a state in which multiple requests from visitors are associated with the same session, which needs to be stored in memory or redis. Multiple requests use the same common session, which is the state data.
  • Vue’s Vuex store is a state that is first common to multiple components, second can be modified by different components, and finally it needs to be stored independently of the component. So store is a state.

Back to our Stream pipe Stream

  • The filter is associated with the map operation and does not need to be associated with the elements before or after the pipe flow, so there is no additional need to record the relationships between elements. Enter an element and get a result.
  • Sorted is a sort operation, while distinct is a deduplication operation. Operations like this are related to other elements, and I can’t do the whole thing myself. Just like class roll call is stateless, you can answer when you are called. If the class is in order of size, then it’s not your business. You have to compare your height with the people around you and remember that the comparison you remember is a “state”. So this operation is a stateful operation.

3. Limit and Skip pipeline data interception

List<String> limitN = Stream.of("Monkey"."Lion"."Giraffe"."Lemur")
        .limit(2)
        .collect(Collectors.toList());
List<String> skipN = Stream.of("Monkey"."Lion"."Giraffe"."Lemur")
        .skip(2)
        .collect(Collectors.toList());
Copy the code
  • The limt method passes in an integer n to intercept the first n elements in the pipe. The piped data is: [Monkey, Lion].
  • The skip method, as opposed to the limit method, is used to skip the first n elements and intercept elements from n to the end. The data after pipeline processing is: [Giraffe, Lemur]

Delete Distinct elements

You can also use the distinct method to unduplicate elements in a pipe. The distinct method is always used to compare elements with each other. The distinct method calls the equals method of objects to compare objects. If you have your own rules, override the equals method.

List<String> uniqueAnimals = Stream.of("Monkey"."Lion"."Giraffe"."Lemur"."Lion")
        .distinct()
        .collect(Collectors.toList());
Copy the code

[“Monkey”, “Lion”, “Giraffe”, “Lemur”]

5, Sorted

By default, sorted is sorted in the natural order of letters. The sequence is: [Giraffe, Lemur, Lion, Monkey], G before L, L before M. When the first letter cannot distinguish the order, it compares the second letter.

List<String> alphabeticOrder = Stream.of("Monkey"."Lion"."Giraffe"."Lemur")
        .sorted()
        .collect(Collectors.toList());
Copy the code

And we’re going to talk about sorting in detail, so we’re just going to do an understanding for now.

Serial, parallel and sequence

In general, stateful and stateless operations do not concern us. Unless? : You use parallel operation.

Using the example of queuing classes by height: if there is one person in the class who is responsible for sorting, the result will be correct. What if there were two or three people waiting in line? It could end up in a mess. A person can only guarantee the order of the people he sorts, he cannot guarantee the order of others.

  • The advantage of serial is that order is guaranteed, but processing is usually slower
  • The advantage of parallelism is that processing of elements is faster (in general), but ordering is not guaranteed. This can lead to stateful operations that don’t get you what you want.

Stream.of("Monkey"."Lion"."Giraffe"."Lemur"."Lion")
        .parallel()
        .forEach(System.out::println);
Copy the code
  • The parallel() function represents parallel rather than serial processing of elements in a pipe. However, this can result in the subsequent elements in the pipe flow being processed first and the preceding elements being processed later, i.e. the order of the elements.

If you have a small amount of data, you can’t observe it, but if you have a large amount of data, you can observe that the order of the data is not guaranteed.

Monkey
Lion
Lemur
Giraffe
Lion
Copy the code

In general, Parallel () can make good use of the CPU’s multi-core processor to achieve better execution efficiency and performance. But there are special cases where parallel is not suitable: for an in-depth look at this article: blog.oio.de/2016/01/22/… Several points in this article illustrate the applicable scenarios for parallel operations:

  • Data sources are easy to split: From a performance perspective, Parallel () works better with ArrayList than LinkedList. Because an ArrayList is array-based in its data structure, it can be easily split into multiple arrays based on their indexes.

  • Suitable for stateless operations: the calculation of each element must not depend on or affect the calculation of any other element.
  • No change in the underlying data source: Parallel () processing is not suitable for scenarios reading from text files. Parallel () is a fixed set from the start, so it can be split evenly and processed synchronously.

5. Sort the collection as you would use SQL

Before we begin, let me ask a quick question: We now have an Employee class.

@Data @AllArgsConstructor public class Employee { private Integer id; private Integer age; // age private String gender; // Gender private String firstName; private String lastName; }Copy the code

Do you know how to sort a List of Employee objects in reverse order by gender and then by age? If you don’t know the solution within four lines of code (one line could have done it, but I formatted it to be four lines), I think you need to go step by step.

String List sort

Cities is an array of strings. Notice that the first letter of London is lowercase.

List<String> cities = Arrays.asList(
        "Milan"."london"."San Francisco"."Tokyo"."New Delhi"
);
System.out.println(cities);
//[Milan, london, San Francisco, Tokyo, New Delhi]

cities.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(cities);
//[london, Milan, New Delhi, San Francisco, Tokyo]

cities.sort(Comparator.naturalOrder());
System.out.println(cities);
//[Milan, New Delhi, San Francisco, Tokyo, london]
Copy the code
  • When using the sort method, the result is [London, Milan, New Delhi, San Francisco, Tokyo], sorted by string.case_inSENSItive_ORDER (case insensitive)
  • If ordered in naturalOrder using the comparator.naturalorder () letter, the result is: [Milan, New Delhi, San Francisco, Tokyo, London]

We can also use the collator Comparator in a Stream pipe Stream.

cities.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);

//Milan
//New Delhi
//San Francisco
//Tokyo
//london
Copy the code

In Java 7 we used collections.sort () to take an array argument and sort the array. After Java 8, you can call the sort() method of the collection class directly to sort. The argument to the sort() method is an implementation class for the Comparator’s Comparator interface, which we’ll cover in the next section.

Integer type List sort

List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9); System.out.println(numbers); //[6, 2, 1, 4, 9] numbers.sort(Comparator.naturalOrder()); // Natural sort system.out.println (numbers); //[1, 2, 4, 6, 9] numbers.sort(Comparator.reverseOrder()); // Sort system.out.println (numbers); //[9, 6, 4, 2, 1]Copy the code

Third, according to the object field pairListThe sorting

This one is a little bit more interesting, just to give you an example.

Employee e1 = new Employee(1,23,"M"."Rick"."Beethovan");
Employee e2 = new Employee(2,13,"F"."Martina"."Hengis");
Employee e3 = new Employee(3,43,"M"."Ricky"."Martin");
Employee e4 = new Employee(4,26,"M"."Jon"."Lowman");
Employee e5 = new Employee(5,19,"F"."Cristine"."Maria");
Employee e6 = new Employee(6,15,"M"."David"."Feezor");
Employee e7 = new Employee(7,68,"F"."Melissa"."Roy");
Employee e8 = new Employee(8,79,"M"."Alex"."Gussin");
Employee e9 = new Employee(9,15,"F"."Neetu"."Singh");
Employee e10 = new Employee(10,45,"M"."Naveen"."Jain");


List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

employees.sort(Comparator.comparing(Employee::getAge));
employees.forEach(System.out::println);
Copy the code
  • First, we created 10 Employee objects and converted them to a List
  • Then the key code: uses the Employee::getAge function as the sorting field of the object, that is, the age of the Employee as the sorting field
  • We then call the forEach method on the List to print the sorting result of the List as follows (of course we overwrote the toString method for Employee, otherwise it wouldn’t make sense to print the result) :
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Copy the code
  • If we want the List to be sorted in reverse order by age, use the reversed() method. Such as:
employees.sort(Comparator.comparing(Employee::getAge).reversed());
Copy the code

4. The Comparator chain pairListThe sorting

The following code is sorted in reverse order first by gender and then by age.

employees.sort( Comparator.comparing(Employee::getGender) .thenComparing(Employee::getAge) .reversed() ); employees.forEach(System.out::println); // The reserved order is reversed. // The reserved order is reversed. // The reserved order is reversed.Copy the code

The observant will notice that we only used a reversed() method, which is not quite the same as SQL. This problem is not easy to describe in language, I suggest you go to see the video!

The sorting result is as follows:

Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)
Copy the code

6. Functional interface Comparator

What is a functional interface?

A functional interface is an interface that has only one abstract method in it. The Comparator interface we used in the previous section is a typical functional interface that has only one abstract method compare.

Second, the characteristics of functional interface

  • The interface has one and only one abstract method, such as compare in the figure above
  • Allows static non-abstract methods to be defined
  • Allows defining default defalut non-abstract methods (the default method is also unique to java8, see below)
  • Allow public methods in java.lang.Object, such as equals in the figure above.
  • FunctionInterface annotations are not required, and if an interface meets the definition of “functional interface”, it does not matter. Adding this annotation makes it easier for the compiler to check. If you write @functionInterface instead of a functional interface, the compiler will report an error

You can even say that functional interfaces are specifically designed for lambda expressions, which are anonymous implementation classes that implement only abstract methods in the interface.

The default keyword

By the way, the default keyword, prior to java8

  • Interfaces are implementations that cannot have methods; all methods are abstract
  • To implement an interface, you must implement all the methods in the interface

This leads to a problem: when an interface has many implementation classes, modifying the interface becomes a very cumbersome task, requiring modifying all implementation classes of the interface.

This problem has plagued Java engineers for a long time, but in Java 8 it has been fixed, yes, with the default method

  • The default method can have its own default implementation, that is, a method body.
  • Interface implementation classes may not implement the default method and may use it instead.

Examples of functional interfaces in JDK

java.lang.Runnable,

java.util.Comparator,

java.util.concurrent.Callable

Interfaces under the java.util.function package, such as Consumer, Predicate, Supplier, etc

5. Custom Comparator sorting

We define a custom collator that implements the compare function (the only abstract method of the functional interface Comparator). Returns 0 for equal elements, -1 for less than the last, and 1 for greater than the last. This rule is the same as before Java 8.

The following code is implemented as a custom interface implementation class: sort in reverse order by age!

employees.sort(new Comparator<Employee>() {
    @Override
    public int compare(Employee em1, Employee em2) {
        if(em1.getAge() == em2.getAge()){
            return 0;
        }
        return em1.getAge() - em2.getAge() > 0 ? -1:1;
    }
});
employees.forEach(System.out::println);
Copy the code

The final print is as follows, sorted by custom rules for age.

Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)
Copy the code

This code is written as a lambda expression. The left side of the arrow is the parameter, the right side is the function body, and the parameter type and return value are determined automatically based on the context. As follows:

employees.sort((em1,em2) -> {
    if(em1.getAge() == em2.getAge()){
        return 0;
    }
    returnem1.getAge() - em2.getAge() > 0 ? - 1:1; }); employees.forEach(System.out::println);Copy the code

7.Stream finds and matches elements

When we operate on an array or collection class, we often encounter requirements such as:

  • Contains a matching rule element
  • Whether all elements match a certain “matching rule”
  • Whether all elements do not match one of the “matching rules”
  • Finds the first element that matches the match rule
  • Finds any element that matches the match rule

These requirements are cumbersome to write with a for loop, which requires both a for loop and a break! This section shows you how to implement “find and match” using the Stream API.

Compare how simple it is

Employees is a List of 10 employee objects, which we have used several times in the previous section; the code is not listed here.

If we do not use the Stream API implementation, find if the list of employees contains employees older than 70? The code is as follows:

boolean isExistAgeThan70 = false;
for(Employee employee:employees){
  if(employee.getAge() > 70){
    isExistAgeThan70 = true;
    break; }}Copy the code

If we use the Stream API, the following line of code uses the “predicate logic” we learned earlier.

boolean isExistAgeThan70 = employees.stream().anyMatch(Employee.ageGreaterThan70);
Copy the code

It is also possible to replace the predicate logic with a lambda expression, as follows:

boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 72);
Copy the code

So, we introduced the first matching rule function: anyMatch, which determines whether a Stream contains a “matching rule” element. The matching rule can be a lambda expression or a predicate.

Other matching rule functions are introduced

  • Are all employees older than 10? AllMatch match rule function: checks if all elements in a Stream match a certain “match rule”.
boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10);
Copy the code
  • Are there no employees under the age of 18? NoneMatch match rule function: Determines whether all elements in a Stream do not match any of the “match rules”.
boolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);
Copy the code

Element search and Optional

Find the first employee older than 40 from the list in order.

Optional<Employee> employeeOptional
        =  employees.stream().filter(e -> e.getAge() > 40).findFirst();
System.out.println(employeeOptional.get());
Copy the code

Print the result

Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Copy the code

The Optional class represents the presence or absence of a value. Introduced in java8 so that null is not returned.

  • IsPresent () will return true if Optional contains a value and false otherwise.
  • IfPresent (Consumer Block) executes the given code block when the value is present. We introduced the Consumer functional interface in Chapter 3; It lets you pass a Lambda expression that takes a parameter of type T and returns void.
  • T get() returns the value if it exists, otherwise? Raise a NoSuchElement exception.
  • T orElse(T other) returns a value if it exists, otherwise a default value.

Watch the video on how to use various functions in Optinal! B station viewing address

  • FindFirst is used to find the first element that matches the “match rule” and returns Optional
  • FindAny is used to findAny element that matches the “matching rule” and returns Optional

8.Stream collection element reduction

The Stream API provides stream.reduce for reduction of collection elements. The reduce function takes three arguments:

  • Identity identification: an element that is the initial value of the reduction operation and the default result if the stream is empty.
  • An Accumulator Accumulator: a function that takes two arguments: the partial result of the reduction operation and the next element of the stream.
  • Combiner Combiner (optional): a function used to merge the partial results of a reduction operation when a reduction is parallelized, or when the type of an accumulator parameter does not match the type implemented by the accumulator.

    Looking at the figure above, let’s understand the accumulator first:

  • The result of phase accumulation is the first parameter of the accumulator
  • The collection traverses the element as the second argument to the accumulator

Integer Type reduction

Reduce has an initial value of 0, and the accumulator can be a lambda expression or a method reference.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); int result = numbers .stream() .reduce(0, (subtotal, element) -> subtotal + element); System.out.println(result); //21 int result = numbers .stream() .reduce(0, Integer::sum); System.out.println(result); / / 21Copy the code

String reduction

Not only can the Integer type be reduced, but any set of types can be reduced as long as the accumulator parameter types match.

List<String> letters = Arrays.asList("a"."b"."c"."d"."e");
String result = letters
        .stream()
        .reduce("", (partialString, element) -> partialString + element);
System.out.println(result);  //abcde


String result = letters
        .stream()
        .reduce("", String::concat);
System.out.println(result);  //ancde
Copy the code

Complex object reduction

Add up the ages of all the employees.

Employee e1 = new Employee(1,23,"M"."Rick"."Beethovan");
Employee e2 = new Employee(2,13,"F"."Martina"."Hengis");
Employee e3 = new Employee(3,43,"M"."Ricky"."Martin");
Employee e4 = new Employee(4,26,"M"."Jon"."Lowman");
Employee e5 = new Employee(5,19,"F"."Cristine"."Maria");
Employee e6 = new Employee(6,15,"M"."David"."Feezor");
Employee e7 = new Employee(7,68,"F"."Melissa"."Roy");
Employee e8 = new Employee(8,79,"M"."Alex"."Gussin");
Employee e9 = new Employee(9,15,"F"."Neetu"."Singh");
Employee e10 = new Employee(10,45,"M"."Naveen"."Jain"); List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum); System.out.println(total); / / 346Copy the code
  • A map is used to process the elements in the Stream from type Employee to type Integer (age).
  • The Integer type in the Stream is then reduced

The use of Combiner Combiner

In addition to using the map function to achieve the set reduction after type conversion, we can also use the Combiner Combiner to achieve, here for the first time use the Combiner Combiner. Because the element in the Stream Stream is Employee and the accumulator returns an Integer, the two types do not match. In this case, Combiner Combiner can be used to perform quadratic reduction on the results of the accumulator, which is equivalent to performing type conversion.

Integer total3 = employees.stream() .reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); // Notice that the reduce method takes three arguments system.out.println (total); / / 346Copy the code

The result is the same as the data type conversion using map.

Parallel stream data reduction (using the aggregator)

For the reduction calculation of set elements with large amount of data, it can better reflect the power of Stream parallel Stream calculation.

Integer total2 = employees .parallelStream() .map(Employee::getAge) .reduce(0,Integer::sum,Integer::sum); // Notice that the reduce method takes three arguments system.out.println (total); / / 346Copy the code

9.StreamAPI terminal operation

Java Stream pipeline data processing operations

In a previous article in this issue, I introduced you to Java Stream pipe flows as Java apis for simplifying the handling of collection-class elements. In the use of the process is divided into three stages. Before I start this article, I still feel like I need to introduce some new people to these three stages, as shown below:

  • Phase 1 (blue in the figure) : Convert a collection, array, or line text file to a Java Stream pipe flow
  • Stage 2 (dotted line in figure) : Pipeline streaming data processing operation, processing each element in the pipeline. The output element in the previous pipe acts as the input element in the next pipe.
  • Stage 3 (green) : pipeline flow result processing operations, which is the core of this article.

Before we begin, it’s still worth reviewing an example we gave you earlier:

List<String> nameStrs = Arrays.asList("Monkey"."Lion"."Giraffe"."Lemur");

List<String> list = nameStrs.stream()
        .filter(s -> s.startsWith("L"))
        .map(String::toUpperCase)
        .sorted()
        .collect(toList());
System.out.println(list);
Copy the code
  • We first convert the string List to a pipe stream using the stream() method
  • The pipeline data processing operation is then performed by filtering all strings starting with uppercase L using the fliter function, converting the strings in the pipeline toUpperCase toUpperCase, and then calling the sorted method. The use of these apis has been covered in previous articles in this issue. Lambda expressions and function references are also used.
  • Finally, the Collect function is used for result processing to convert the Java Stream pipeline Stream into a List. The output of the final list is:[LEMUR, LION]

How many lines of code would you need to do this if you weren’t using a Java Stream pipe Stream? Getting back to the point, this article introduces you to the third stage: what can be done with pipe flow processing results? Here we go!

Second, ForEach and ForEachOrdered

If we just want to print the result of processing the Stream pipe Stream, rather than cast it, we can use the forEach() or forEachOrdered() methods.

Stream.of("Monkey"."Lion"."Giraffe"."Lemur"."Lion")
        .parallel()
        .forEach(System.out::println);
Stream.of("Monkey"."Lion"."Giraffe"."Lemur"."Lion")
        .parallel()
        .forEachOrdered(System.out::println);
Copy the code
  • The parallel() function means that elements in a pipe are processed in parallel rather than sequentially, which is faster. However, this can result in the subsequent elements in the pipe flow being processed first and the preceding elements being processed later, i.e. the order of the elements
  • Given its name, forEachOrdered methods can ensure that elements are output in the same order as they enter the pipeline stream, although the order in which they are processed may not be guaranteed. This looks like this (the forEach method does not guarantee this order) :
Monkey
Lion
Giraffe
Lemur
Lion
Copy the code

Collect elements

The most common uses of Java Stream are to convert a collection class into a pipe Stream, to process pipe Stream data, and to convert pipe Stream results into a collection class. The Collect () method gives us the ability to convert the pipe stream processing results into a collection class.

3.1. Collection as Set

You can use the Collectors. ToSet () method to collect the result of processing the Stream, and collect all elements into the Set collection.

Set<String> collectToSet = Stream.of(
   "Monkey"."Lion"."Giraffe"."Lemur"."Lion") .collect(Collectors.toSet()); // The final elements in the collectToSet are :[Monkey, Lion, Giraffe, Lemur]. Note that the Set is not heavy.Copy the code

3.2. Collect the List

Similarly, elements can be collected into lists using the toList() collector.

List<String> collectToList = Stream.of(
   "Monkey"."Lion"."Giraffe"."Lemur"."Lion").collect(Collectors.toList()); // The final elements in collectToList are: [Monkey, Lion, Giraffe, Lemur, Lion]Copy the code

3.3. General collection methods

The collection methods of elements introduced above are all dedicated. For example, you can use Collectors. ToSet () to collect Collectors of the Set type. Use Collectors. ToList () to collect it as a List. Is there a general way to collect data elements into any Collection interface subtype? So, here’s a generic way to collect elements. You can collect data elements into any Collection type: a way to provide a constructor to the desired Collection type.

LinkedList<String> collectToCollection = Stream.of(
   "Monkey"."Lion"."Giraffe"."Lemur"."Lion").collect(Collectors.toCollection(LinkedList::new)); // The final elements in the collectToCollection are: [Monkey, Lion, Giraffe, Lemur, Lion]Copy the code

Note: The code uses LinkedList::new, which actually calls the LinkedList constructor to gather elements into the LinkedList. Of course, you can use things like LinkedHashSet::new and PriorityQueue::new to collect data elements into other collection types for more general use.

3.4. Collected an Array

Collect the Stream’s processing results with the toArray(String[]::new) method, collecting all elements into a String array.

String[] toArray = Stream.of(
   "Monkey"."Lion"."Giraffe"."Lemur"."Lion") .toArray(String[]::new); // The elements in the final toArray string array are: [Monkey, Lion, Giraffe, Lemur, Lion]Copy the code

3.5. A Map is collected

The Collectors. ToMap () method is used to collect data elements into the Map, but there is a problem with whether the elements in the pipe are keys or values. We use a function.identity () method, which simply returns a “t -> t” (the input is the lambda expression of the output). The pipe flow handler function DISTINCT () is also used to ensure uniqueness of Map key values.

Map<String, Integer> toMap = Stream.of(
    "Monkey"."Lion"."Giraffe"."Lemur"."Lion").distinct().collect(Collectors. ToMap (function.identity (), // Element input is output, As key s -> (int) s.ars ().distinct().count()// The different number of letters in the input element, as value)); {Monkey=6, Lion=4, Lemur=5, Giraffe=6}Copy the code

3.6. Collect groupingBy by groups

GroupingBy is used to collect elements in groups. The following code shows how to collect different data elements into different lists based on the first letter and encapsulate them into maps.

Map<Character, List<String>> groupingByList =  Stream.of(
    "Monkey"."Lion"."Giraffe"."Lemur"."Lion"Collect (Collectors. GroupingBy (s -> s.charat (0), // Group elements according to the first letter of the element, and counting() // add this line of code to achieve group statistics); {G=[Giraffe], L=[Lion, Lemur, Lion], M=[Monkey]} {G=1, L=3, M=1}Copy the code

Here’s a description of the procedure: groupingBy takes the first parameter as a grouping condition, and the second parameter is a child collector.

Other common methods

boolean containsTwo = IntStream.of(1, 2, 3).anyMatch(i -> i == 2); // Check whether the pipe contains 2.true

long nrOfAnimals = Stream.of(
    "Monkey"."Lion"."Giraffe"."Lemur").count(); NrOfAnimals: 4 int sum = intstream.of (1, 2, 3).sum(); Sum: 6 OptionalDouble Average = intstream.of (1, 2, 3).average(); OptionalDouble[2.0] int Max = intstream.of (1, 2, 3).max().orelse (0); Max: 3 intStream.of (1, 2, 3).SummaryStatistics (); Statistics: IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, Max =3}Copy the code

10. How does Java8 sort maps

In this article, you’ll learn how to sort maps using Java. A few days ago, a friend met this question in an interview. It seems to be a very simple question, but it is also very easy to make people confused if you do not carefully study it. So I decided to write an article like this. In Java, there are several ways to sort a Map, but we’ll focus on Java 8 Stream, which is a very elegant way to do this.

What is a Java 8 Stream

With Java 8 Streams, we can sort maps by keystroke and value. Here’s how it works:

  1. Convert a collection class object such as a Map or List to a Stream object
  2. The use of Streamssorted()Method to sort them
  3. Finally return it toLinkedHashMap(You can keep the sort order)

The sorted() method takes aComparator as an argument, allowing you to sort a Map by any type of value. If you’re not familiar with the Comparator, there was an article a few days ago about sorting lists using the Comparator.

Merge (

Before we get to Map sorting, it’s worth talking about the HashMap merge() function, which is used to deal with elements of a Map when keys are duplicated. This function takes three arguments:

  • Parameter 1: the put key to the map
  • Parameter 2: The value of put to map
  • Parameter 3: What to do with the value if the key is duplicated. It can be a function or it can be written as a lambda expression.
        String k = "key";
        HashMap<String, Integer> map = new HashMap<String, Integer>() {{
            put(k, 1);
        }};
        map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
Copy the code

Looking at the code above, we first create a HashMap and put an element with a key of K :1 into it. When we call merge and put k:2 key-value pairs into the map, the k key repeats and the following lambda expression is executed. OldVal + newVal(1+2); now there is only one element in the map and that is k:3.

Lambda expressions are simple: they represent anonymous functions, with arguments to the left of the arrow and function bodies to the right of the arrow. The parameter types and return values of a function are determined by the code context.

3. Sort by Map key

The following example uses a Java 8 Stream to sort by the Map key:

// Create a Map and fill in the data Map<String, Integer> codes = new HashMap<>(); codes.put("United States", 1);
codes.put("Germany"49); codes.put("France", 33);
codes.put("China", 86);
codes.put("Pakistan", 92); Map<String, Integer> sortedMap = codes.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new ) ); Sortedmap.entryset ().foreach (system.out ::println); sortedmap.entryset ().foreach (system.out ::println);Copy the code

Take a look at the second code:

  • The Map type is first converted to the stream type using entrySet().stream().
  • It then uses the sorted method, which is sorted by Map.Entry.comparingByKey(), which is sorted by the Map key
  • Finally, use collect method to transform Stream into LinkedHashMap. Merge (lambda) ¶ Merge (lambda) ¶ Merge (lambda) ¶ Merge (lambda) ¶ merge (lambda) ¶ merge (lambda) Since there are no duplicate keys in this example, you can return either the new or old values.

The above program will print the following on the console, with the keys (country/region names) in natural alphabetical order:

China=86
France=33
Germany=49
Pakistan=92
United States=1
Copy the code

Note the use of LinkedHashMap to store the results of the sort to preserve order. By default, Collectors. ToMap () returns a HashMap. A HashMap does not guarantee the order of elements.

If you want to reverse order by key, add the code shown in red.

4. Sort by Map value

Of course, you can also use the Stream API to sort the Map by its value:

Map<String, Integer> sortedMap2 = codes.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (oldVal, newVal) -> oldVal,
                LinkedHashMap::new));

sortedMap2.entrySet().forEach(System.out::println);
Copy the code

This is the output that shows the Map sorted by value:

United States=1
France=33
Germany=49
China=86
Pakistan=92
Copy the code

5. Use TreeMap to sort

You probably know that TreeMap elements are ordered, so TreeMap sorting is a good idea. All you need to do is create a TreeMap object and put the data from HashMapput into TreeMap, very simple:

// Convert 'HashMap' to 'TreeMap' Map<String, Integer> sorted = new TreeMap<>(codes);Copy the code

Here is the output:

China=86
France=33
Germany=49
Pakistan=92
United States=1
Copy the code

As shown above, the keys (country/region names) are sorted in natural alphabetical order.

Finally: above code

String k = "key"; HashMap<String, Integer> map = new HashMap<String, Integer>() {{ put(k, 1); }}; map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal); // Create a Map and fill in the data Map<String, Integer> codes = new HashMap<>(); codes.put("United States", 1);
codes.put("Germany"49); codes.put("France", 33);
codes.put("China", 86);
codes.put("Pakistan", 92); Map<String, Integer> sortedMap = codes.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new ) ); Sortedmap.entryset ().foreach (system.out ::println); sortedmap.entryset ().foreach (system.out ::println); // sort the map by values Map<String, Integer> sorted = codes.entrySet().stream() .sorted(Map.Entry.comparingByValue()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new)); sorted.entrySet().forEach(System.out::println);Copy the code