30000 words liver explosion Java 8 new features, I don't believe you can read it! (recommended Collection)

Hello, I'm glacier~~

To be honest, this article took me a month. All the new features of Java 8 are here. I suggest collecting them first and reading them later.

What are the new features of Java 8?

Simply put, the new features of Java 8 are as follows:

  • Lambda expression
  • Functional interface
  • Method reference and constructor reference
  • Stream API
  • Interface default and static methods
  • New time date API
  • Other new features

Among them, the most widely cited new features are Lambda expressions and stream APIs.

What are the advantages of Java 8?

In short, the advantages of Java 8 are as follows.

  • Faster
  • Less code (new syntax Lambda expressions added)
  • Powerful Stream API
  • Easy parallel
  • Minimize null pointer exceptions Optional

Lambda expression

What is a Lambda expression?

Lambda expression is an anonymous function. We can understand lambda expression as follows: lambda is a piece of code that can be passed (the code can be passed like data). Using lambda expressions can write more concise and flexible code. Moreover, the use of lambda expressions can improve the language expression ability of Java.

Anonymous Inner Class

Before introducing how to use Lambda expressions, let's take a look at anonymous inner classes. For example, we use anonymous inner classes to compare the size of two Integer type data.

Comparator<Integer> com = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
};

In the above code, we use the anonymous inner class to compare the size of two Integer type data.

Next, we can pass the instance of the above anonymous inner class as a parameter to other methods, as shown below.

 TreeSet<Integer> treeSet = new TreeSet<>(com);

The complete code is shown below.

@Test
public void test1(){
    Comparator<Integer> com = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    TreeSet<Integer> treeSet = new TreeSet<>(com);
}

Let's analyze the above code. In the whole anonymous inner class, what is really useful is the following line of code.

 return Integer.compare(o1, o2);

Other code is essentially "redundant". But in order to write the above line of code, we have to write more code in anonymous inner classes.

Lambda expression

If we use a Lambda expression to complete the comparison of two Integer type data, how should we implement it?

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

See, using Lambda expressions, we can compare two Integer type data with only one line of code.

We can also pass the Lambda expression to the construction method of TreeSet, as shown below.

 TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));

The intuitive feeling is that one line of code using Lambda expression can handle the function of multi line code of anonymous inner class.

Seeing this, many readers will ask: I use anonymous inner classes to compare the data sizes of two integer types. It's not complicated! Why should I learn a new grammar?

In fact, what I want to say is: we just briefly listed an example above. Next, let's write a slightly more complex example to compare which method is more concise using anonymous inner classes and Lambda expressions.

Compare conventional methods with Lambda expressions

For example, there is a need to obtain the information of employees older than 30 in the current company.

First, we need to create an Employee entity class to store Employee information.

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

In Employee, we simply store the Employee's name, age and salary.

Next, we create a List collection that stores multiple employees, as shown below.

protected List<Employee> employees = Arrays.asList(
        new Employee("Zhang San", 18, 9999.99),
        new Employee("Li Si", 38, 5555.55),
        new Employee("Wang Wu", 60, 6666.66),
        new Employee("Zhao Liu", 16, 7777.77),
        new Employee("pseudo-ginseng", 18, 3333.33)
);

1. General traversal set

We first use the conventional method of traversing the set to find the information of employees older than or equal to 30.

public List<Employee> filterEmployeesByAge(List<Employee> list){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(e.getAge() >= 30){
            employees.add(e);
        }
    }
    return employees;
}

Next, let's test the above method.

@Test
public void test3(){
    List<Employee> employeeList = filterEmployeesByAge(this.employees);
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

Run the test3 method, and the output information is as follows.

Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)

In general, finding employee information older than or equal to 30 is a little more complicated by using the conventional method of traversing the set.

For example, the demand has changed: obtain the information of employees whose salary is greater than or equal to 5000 in the current company.

At this point, we have to create a method of filtering by salary again.

public List<Employee> filterEmployeesBySalary(List<Employee> list){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(e.getSalary() >= 5000){
            employees.add(e);
        }
    }
    return employees;
}

After comparing the filterEmployeesByAge() method with the filterEmployeesBySalary method, we find that most of the method bodies are the same, but the judgment of conditions in the for loop is different.

If we have another requirement to find the information of employees younger than or equal to 20 in the current company, we will create a filtering method again. It seems that using conventional methods is really inconvenient!

Here, let me ask you a question: what is the best optimization method for this conventional method? I believe many small partners will say: extract public methods. Yes, extracting common methods is an optimization method, but it is not the best way. What's the best way? That is to use design patterns! Design pattern is the design principle and design pattern summarized by countless predecessors' continuous practice. You can check< Summary of design patterns - here are all the 23 design patterns you need to master! >One article to learn the topic of design patterns.

2. Optimize code using design patterns

How to use design patterns to optimize the above methods? Let's continue to look down. Students who are not familiar with design patterns can first< Summary of design patterns - here are all the 23 design patterns you need to master! >To learn.

First, we define a generic interface mypredict to filter the passed data. If it meets the rules, it returns true and if it does not, it returns false.

public interface MyPredicate<T> {

    /**
     * Filter the passed T-type data
     * true is returned if the rules are met, false is returned if the rules are not met
     */
    boolean filter(T t);
}

Next, we create the implementation class FilterEmployeeByAge of the mypredict interface to filter employee information older than or equal to 30.

public class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean filter(Employee employee) {
        return employee.getAge() >= 30;
    }
}

We define a method to filter employee information. At this time, the parameters passed include not only the employee information set, but also an interface instance defined by us. When traversing the employee set, the employee information that meets the filtering conditions will be returned.

//Optimization mode I
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate){
    List<Employee> employees = new ArrayList<>();
    for(Employee e : list){
        if(myPredicate.filter(e)){
            employees.add(e);
        }
    }
    return employees;
}

Next, we write a test method to test the optimized code.

@Test
public void test4(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeByAge());
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

Run the test4() method, and the output result information is as follows.

Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)

Writing here, do you have a feeling of sudden openness?

Yes, this is the charm of design patterns. For small partners who are not familiar with design patterns, we must refer to them< Summary of design patterns - here are all the 23 design patterns you need to master! >To learn.

We continue to obtain the information of employees whose salary is greater than or equal to 5000 in the current company. At this time, we only need to create a FilterEmployeeBySalary class to implement the mypredict interface, as shown below.

public class FilterEmployeeBySalary implements MyPredicate<Employee>{
    @Override
    public boolean filter(Employee employee) {
        return employee.getSalary() >= 5000;
    }
}

Next, you can write the test method directly and continue to call the filteremployee (list < employee > list, mypredict < employee > mypredict) method in the test method.

@Test
public void test5(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeBySalary());
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

Run the test5 method, and the output result information is as follows.

Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)
Employee(name=Zhao Liu, age=16, salary=7777.77)

It can be seen that after using the design pattern to optimize the code, no matter how the needs of filtering staff information change, we only need to create the implementation class of MyPredicate interface to implement the specific filtering logic, and then call the filterEmployee (List<Employee> list, MyPredicate<Employee> myPredicate) method to pass the employee set and filter rules into the test method.

Here, let me ask you a question: which design pattern is used to optimize the code above? If it were you, would you think of using design patterns to optimize your code? Let's think about the design pattern we use first? I will give the answer at the end of the article!

Using design patterns to optimize code is also bad: each time we define a filtering strategy, we have to create a separate filtering class!!

3. Anonymous inner class

Can the use of anonymous inner classes optimize the code we write? Next, we use anonymous inner classes to filter employee information. Let's first look at filtering employee information older than or equal to 30.

@Test
public void test6(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
        @Override
        public boolean filter(Employee employee) {
            return employee.getAge() >= 30;
        }
    });
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

Run the test6 method and output the following result information.

Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)

Then filter the employee information whose salary is greater than or equal to 5000, as shown below.

@Test
public void test7(){
    List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
        @Override
        public boolean filter(Employee employee) {
            return employee.getSalary() >= 5000;
        }
    });
    for (Employee e : employeeList){
        System.out.println(e);
    }
}

Run the test7 method and output the following result information.

Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)
Employee(name=Zhao Liu, age=16, salary=7777.77)

The anonymous inner class looks simpler than the conventional method of traversing the collection, and the filter rules are written into the anonymous inner class by creating one class at a time when using the design pattern to optimize the code, which further simplifies the code.

However, the readability of using anonymous inner class code is not high, and there are more redundant code!!

Is there a simpler way?

4. Play: Lambda expression

When using Lambda expressions, we still need to call the filteremployee (list < employee > list, mypredict < employee > mypredict) method written before.

Note: obtain the information of employees older than or equal to 30.

@Test
public void test8(){
    filterEmployee(this.employees, (e) -> e.getAge() >= 30).forEach(System.out::println);
}

See, you only need one line of code to filter and output employee information using Lambda expression. Isn't it 6.

Run the test8 method and output the following result information.

Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)

Let's look at using Lambda expression to obtain employee information with salary greater than or equal to 5000, as shown below.

@Test
public void test9(){
    filterEmployee(this.employees, (e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

Yes, using Lambda expression, another line of code is done!!

Run the test9 method and output the following result information.

Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)
Employee(name=Zhao Liu, age=16, salary=7777.77)

In addition, when using Lambda expression, we only need to give the set to be filtered, so we can filter the elements of the specified rule from the set and output the result information.

5. Play: Stream API

Using Lambda expression and Stream API, as long as the corresponding set is given, we can complete various filtering of the set and output the result information.

For example, as long as there is a set of employees, we use Lambda expression to obtain the information of employees whose salary is greater than or equal to 5000.

@Test
public void test10(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

Yes, given only one set, using Lambda expression and Stream API, one line of code can filter out the desired elements and output them.

Run the test10 method and output the following result information.

Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Wang Wu, age=60, salary=6666.66)
Employee(name=Zhao Liu, age=16, salary=7777.77)

What if we only want information about the first two employees? In fact, it is also very simple, as shown below.

@Test
public void test11(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).limit(2).forEach(System.out::println);
}

As you can see, we have added limit(2) to the code to restrict access to only two employee information. Run the test11 method and output the following result information.

Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)

You can also obtain the specified field information using Lambda expression and Stream API, such as the name of an employee whose salary is greater than or equal to 5000.

@Test
public void test12(){
    employees.stream().filter((e) -> e.getSalary() >= 5000).map(Employee::getName).forEach(System.out::println);
}

You can see that the map is used to filter out the names of employees whose salary is greater than or equal to 5000. Run the test12 method and output the following result information.

Zhang San
 Li Si
 Wang Wu
 Zhao Liu

Is it simple?

Finally, the design pattern used in this paper: strategy pattern is given.

Anonymous class to Lambda expression

Let's first look at how to convert from anonymous classes to Lambda expressions?

Here, we can use two examples to illustrate how to convert from an anonymous inner class to a Lambda expression.

  • Anonymous inner class to Lambda expression

Use anonymous inner classes as shown below.

Runnable r = new Runnable(){
    @Override
    public void run(){
        System.out.println("Hello Lambda");
    }
}

Convert to Lambda expression as follows.

Runnable r = () -> System.out.println("Hello Lambda");
  • Anonymous inner classes are passed as parameters to Lambda expressions as parameters

Use anonymous inner classes as parameters, as shown below.

TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>(){
    @Override
    public int compare(Integer o1, Integer o2){
        return Integer.compare(o1, o2);
    }
});

Use a Lambda expression as an argument, as shown below.

TreeSet<Integer> ts = new TreeSet<>(
    (o1, o2) -> Integer.compare(o1, o2);
);

Intuitively, Lambda expressions are much more concise than conventional syntax.

Syntax of Lambda expressions

Lambda expression introduces the '- >' operator into the Java language. The '- >' operator is called the operator or arrow operator of lambda expression, which divides lambda expression into two parts:

  • The left section specifies all the parameters required by the Lambda expression.

Lambda expression is essentially the implementation of the interface. The parameter list of lambda expression essentially corresponds to the parameter list of methods in the interface.

  • The right part specifies the Lambda body, that is, the function to be performed by the Lambda expression.

Lambda body is essentially the function implemented by the interface method.

We can summarize the syntax of Lambda expressions as follows.

1. Syntax format 1: no parameter, no return value, only one statement in Lambda body

Runnable r = () -> System.out.println("Hello Lambda");

Specific examples are as follows.

@Test
public void test1(){
    Runnable r = () -> System.out.println("Hello Lambda");
    new Thread(r).start();
}

2. Syntax format 2: Lambda expression requires a parameter and has no return value

Consumer<String> func = (s) -> System.out.println(s);

Specific examples are as follows.

@Test
public void test2(){
    Consumer<String> consumer = (x) -> System.out.println(x);
    consumer.accept("Hello Lambda");
}

3. Syntax format 3: when Lambda only needs one parameter, the parentheses of the parameter can be omitted

Consumer<String> func = s -> System.out.println(s);

Specific examples are as follows.

@Test
public void test3(){
    Consumer<String> consumer = x -> System.out.println(x);
    consumer.accept("Hello Lambda");
}

4. Syntax format 4: Lambda requires two parameters and has a return value

BinaryOperator<Integer> bo = (a, b) -> {
    System.out.println("Functional interface");
    return a + b;
};

Specific examples are as follows.

@Test
public void test4(){
    Comparator<Integer> comparator = (x, y) -> {
        System.out.println("Functional interface");
        return Integer.compare(x, y);
    };
}

5. Syntax format 5: when there is only one statement in Lambda body, return and braces can be omitted

BinaryOperator<Integer> bo = (a, b) -> a + b;

Specific examples are as follows.

@Test
public void test5(){
    Comparator<Integer> comparator = (x, y) ->  Integer.compare(x, y);
}

6. Syntax format 6: the data type of the parameter list of Lambda expression can be omitted, because the JVM compiler can infer the data type through the context, which is "type inference"

BinaryOperator<Integer> bo = (Integer a, Integer b) -> {
    return a + b;
};

Equivalent to

BinaryOperator<Integer> bo = (a, b) -> {
    return a + b;
};

The parameter types in the above Lambda expressions are inferred by the compiler. There is no need to specify the type in Lambda expression, and the program can still be compiled. This is because javac infers the type of parameter in the background according to the context of the program. The type of Lambda expression depends on the context and is inferred by the compiler. This is called "type inference".

Functional interface

Lambda expressions need the support of functional interfaces, so it is necessary to say what is a functional interface.

An interface that contains only one abstract method is called a functional interface.

You can create objects of this interface through Lambda expressions. (if the Lambda expression throws a checked exception, the exception needs to be declared on the abstract method of the target interface).

You can use @ FunctionalInterface annotation on any functional interface to check whether it is a functional interface. At the same time, javadoc will also contain a declaration that the interface is a functional interface.

We can customize the functional interface and use Lambda expressions to implement the corresponding functions.

For example, use functional interfaces and Lambda expressions to process strings.

First, we define a functional interface MyFunc, as shown below.

@FunctionalInterface
public interface MyFunc <T> {
    public T getValue(T t);
}

Next, we define a method to manipulate strings, where the parameters are MyFunc interface instance and the string to be converted.

public String handlerString(MyFunc<String> myFunc, String str){
    return myFunc.getValue(str);
}

Next, we test the custom functional interface. At this time, the parameters of the functional interface we pass are Lambda expressions, and the string is converted to uppercase.

@Test
public void test6(){
    String str = handlerString((s) -> s.toUpperCase(), "binghe");
    System.out.println(str);
}

Run the test6 method, and the result information is as follows.

BINGHE

We can also intercept a part of the string, as shown below.

@Test
public void test7(){
    String str = handlerString((s) -> s.substring(0,4), "binghe");
    System.out.println(str);
}

Run the test7 method, and the result information is as follows.

bing

It can be seen that we can arbitrarily operate the string through the handlerstring (myfunc < string > myfunc, string STR) method and Lambda expression.

Note: pass Lambda expression as parameter: in order to pass Lambda expression as parameter, the parameter type of receiving Lambda expression must be the type of functional interface compatible with the Lambda expression.

Typical cases of Lambda expressions

Case 1

demand

Call collections Sort () method, compare two employees by custom sorting (compare the age first, and compare by name if the age is the same), and pass Lambda expression as a parameter.

realization

Here, we first create an employee class. In order to meet the requirements, we define three fields: name, age and salary in the Employee class, as shown below.

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

Next, we define a member variable employees in the TestLambda class. The employees variable is a List collection that stores a List of employees, as shown below.

protected List<Employee> employees = Arrays.asList(
    new Employee("Zhang San", 18, 9999.99),
    new Employee("Li Si", 38, 5555.55),
    new Employee("Wang Wu", 60, 6666.66),
    new Employee("Zhao Liu", 8, 7777.77),
    new Employee("pseudo-ginseng", 58, 3333.33)
);

After the preliminary preparations are completed, we can implement the specific business logic.

@Test
public void test1(){
    Collections.sort(employees, (e1, e2) -> {
        if(e1.getAge() == e2.getAge()){
            return e1.getName().compareTo(e2.getName());
        }
        return Integer.compare(e1.getAge(), e2.getAge());
    });
    employees.stream().forEach(System.out::println);
}

The above code is relatively simple, so I won't repeat the specific logic. Run the test1 method, and the result information is as follows.

Employee(name=Zhao Liu, age=8, salary=7777.77)
Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=pseudo-ginseng, age=58, salary=3333.33)
Employee(name=Wang Wu, age=60, salary=6666.66)

If you want to flashback the output, how to deal with it? You just need to return integer compare(e1.getAge(), e2. getAge()); Change to - return integer compare(e1.getAge(), e2. getAge()); OK, as shown below.

@Test
public void test1(){
    Collections.sort(employees, (e1, e2) -> {
        if(e1.getAge() == e2.getAge()){
            return e1.getName().compareTo(e2.getName());
        }
        return -Integer.compare(e1.getAge(), e2.getAge());
    });
    employees.stream().forEach(System.out::println);
}

Run the test1 method again, and the result information is as follows.

Employee(name=Wang Wu, age=60, salary=6666.66)
Employee(name=pseudo-ginseng, age=58, salary=3333.33)
Employee(name=Li Si, age=38, salary=5555.55)
Employee(name=Zhang San, age=18, salary=9999.99)
Employee(name=Zhao Liu, age=8, salary=7777.77)

The results meet our needs.

Case 2

demand

1. Declare a functional interface in which the abstract method public String getValue(String str) is declared;

2. Declare the class TestLambda. The method written in the class uses the interface as a parameter to convert a string to uppercase and as the return value of the method.

3. Intercept the substring at the 2nd and 4th index positions of a string.

realization

First, create a functional interface MyFunction, and annotate @ FunctionalInterface on the MyFunction interface to identify that the interface is a functional interface. As shown below.

@FunctionalInterface
public interface MyFunction {
    public String getValue(String str);
}

Declare the stringHandler method in the TestLambda class. The parameters are the string to be processed and the instance of the functional interface. The logic in the method is to call the method of the functional interface to process the string, as shown below.

public String stringHandler(String str, MyFunction myFunction){
    return myFunction.getValue(str);
}

Next, we implement the logic to convert a string to uppercase, as shown below.

@Test
public void test2(){
    String value = stringHandler("binghe", (s) -> s.toUpperCase());
    System.out.println(value);
}

Run the test2 method and get the following result information.

BINGHE

Let's implement the operation of string interception, as shown below.

@Test
public void test3(){
    String value = stringHandler("binghe", (s) -> s.substring(1, 3));
    System.out.println(value);
}

Note: in the requirement, the substring is intercepted according to the second and fourth index positions, and the subscript of the string starts from 0. Therefore, substring (1,3) is used instead of substring (2,4), which is also a mistake easily made by many small partners.

In addition, using the above Lambda expression form, you can realize arbitrary processing of strings and return the processed new strings.

Run the test3 method, and the results are as follows.

in

Case 3

demand

1. Declare a functional interface with two generics. The generic type is < T, R >, where t is the type of parameter and R is the type of return value.

2. The abstract method of declaring the object in the interface.

3. Declare the method in TestLambda class. Use the interface as a parameter to calculate the sum of two long parameters.

4. Then press the product of two long parameters.

realization

First, we define the functional interface MyFunc according to the requirements, as shown below.

@FunctionalInterface
public interface MyFunc<T, R> {

    R getValue(T t1, T t2);
}

Next, we create a method in the TestLambda class to process two long data, as shown below.

public void operate(Long num1, Long num2, MyFunc<Long, Long> myFunc){
    System.out.println(myFunc.getValue(num1, num2));
}

We can use the following method to complete the sum of two long parameters.

@Test
public void test4(){
    operate(100L, 200L, (x, y) -> x + y);
}

Run the test4 method, and the results are as follows.

300

It is also very simple to realize the product of two long data.

@Test
public void test5(){
    operate(100L, 200L, (x, y) -> x * y);
}

Run the test5 method, and the results are as follows.

20000

Seeing this, I believe that many small partners have a deeper understanding of Lambda expressions. As long as you practice more, you can better master the essence of Lambda expression.

Functional interface overview

Here, I use the form of table to briefly explain the functional interface provided in Java 8.

Overview of four core functional interfaces

First, let's look at the four core functional interfaces, as shown below.

Functional interfaceParameter typeReturn typeUsage scenario
Consumer < T > consumer interfaceTvoidTo apply operations to objects of type T, the method defined by the interface is void accept (T)
Supplier < T > supply interfacenothingTReturn the object of type T, and the method defined by the interface: T get()
Function < T, R > functional interfaceTRApply an operation to an object of type T and return a result of type R. Method of interface definition: R apply (T)
Predicate < T > assertion interfaceTbooleanDetermines whether the object of type T satisfies the constraint conditions, and returns data of boolean type. Interface definition method: Boolean test (T)

Overview of other function interfaces

In addition to the four core functional interfaces, Java 8 also provides some other functional interfaces.

Functional interfaceParameter typeReturn typeUsage scenario
BiFunction(T, U, R)T, URApply operations to parameters of type T and u and return results of type R. Interface definition method: R apply (T, T,U, U)
Unaryoperator < T > (Function sub interface)TTPerforms a unary operation on an object of type T and returns the result of type T. The containing method is t apply (T)
Binaryoperator < T > (bifunction sub interface)T, TTPerforms a binary operation on an object of type T and returns the result of type T. The inclusion method is T apply(T t1, T t2)
BiConsumer<T, U>T, UvoidApply operations to parameters of type T, U. The containing method is void accept (T, t, U)
ToIntFunction<T>TintFunction to calculate int value
ToLongFunction<T>TlongFunction to calculate the value of long
ToDoubleFunction<T>TdoubleFunction to calculate the value of double
IntFunction<R>intRFunction with int argument
LongFunction<R>longRFunction with parameter of type long
DoubleFunction<R>doubleRFunction with parameter of type double

Four core functional interfaces

Consumer interface

1. Interface description

The Consumer interface is a Consumer interface and has no return value. The definition of Consumer in Java 8 is as follows.

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
    
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2. Use examples

public void handlerConsumer(Integer number, Consumer<Integer> consumer){
    consumer.accept(number);
}

@Test
public void test1(){
    this.handlerConsumer(10000, (i) -> System.out.println(i));
}

Supplier interface

1. Interface description

The Supplier interface is a supply interface with a return value. The definition of the Supplier interface in Java 8 is as follows.

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

2. Use examples

public List<Integer> getNumberList(int num, Supplier<Integer> supplier){
    List<Integer> list = new ArrayList<>();
    for(int i = 0; i < num; i++){
        list.add(supplier.get())
    }
    return list;
}

@Test
public void test2(){
    List<Integer> numberList = this.getNumberList(10, () -> new Random().nextInt(100));
    numberList.stream().forEach(System.out::println);
}

Function interface

1. Interface description

Function interface is a function interface with return value. The definition of function interface in Java 8 is as follows.

@FunctionalInterface
public interface Function<T, R> {
    
    R apply(T t);
    
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

2. Use examples

public String handlerString(String str, Function<String, String> func){
    return func.apply(str);
}

@Test
public void test3(){
    String str = this.handlerString("binghe", (s) -> s.toUpperCase());
    System.out.println(str);
}

Predict interface

1. Interface description

The Predicate interface is an assertion interface, and the return value type is boolean. The definition of the Predicate interface in Java 8 is as follows.

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

2. Use examples

public List<String> filterString(List<String> list, Predicate<String> predicate){
    List<String> strList = new ArrayList<>();
    for(String str : list){
        if(predicate.test(str)){
            strList.add(str);
        }
    }
    return strList;
}

@Test
public void test4(){
    List<String> list = Arrays.asList("Hello", "Lambda", "binghe", "lyz", "World");
    List<String> strList = this.filterString(list, (s) -> s.length() >= 5);
    strList.stream().forEach(System.out::println);
}

Note: as long as we learn the usage of the four core functional interfaces in Java 8, we will know how to use other functional interfaces!

HashMap in Java 7 and Java 8

  • JDK7 HashMap structure is array + linked list (in case of element collision, new elements will be added to the beginning of the linked list)
  • JDK8 HashMap structure is array + linked list + red black tree (in case of element collision, new elements will be added to the end of the linked list. When the total capacity of HashMap is greater than or equal to 64 and the size of a linked list is greater than or equal to 8, the linked list will be transformed into a red black tree (Note: red black tree is a kind of binary tree))

JDK8 HashMap reordering

If an element of the red black tree in the HashMap is deleted and the elements are reordered, the HashCode code of the elements to be reordered does not need to be calculated, but only the current element needs to be placed at the position (total length of the HashMap + position of the current element in the HashMap).

Screening and slicing

  • filter -- receive Lambda and exclude some elements from the stream.
  • limit -- truncate the stream so that its elements do not exceed the given number.
  • skip(n) -- skip elements and return a stream that throws away the first n elements. If there are less than n elements in the stream, an empty stream is returned. Complementary to limit(n)
  • distinct -- filter to remove duplicate elements through hashCode() and equals() of the elements generated by the stream

Intermediate operation

  • map -- receive Lambda, convert elements into other forms or extract information. Receive a function as an argument, which is applied to each element and mapped to a new element.
  • flatMap -- take a function as a parameter, replace each value in the stream with another stream, and then connect all streams into one stream
  • sorted() -- natural sorting
  • sorted(Comparator com) -- customized sorting

Terminate operation

  • allMatch -- check to see if all elements match
  • anyMatch -- check to see if at least one element matches
  • noneMatch -- check if there are no matching elements
  • findFirst -- returns the first element
  • findAny -- returns any element in the current stream
  • count -- returns the total number of elements in the stream
  • max -- returns the maximum value in the stream
  • min -- minimum value in return flow

reduction

  • Reduce (t identity, binary operator) / reduce (binary operator) - you can combine the elements in the stream repeatedly to get a value.
  • collect -- convert the Stream to another form. Receive the implementation of a Collector interface, which is used to summarize the elements in the Stream

Note: the stream cannot be used again after termination

Optional container class

Used to avoid null pointer exceptions as much as possible

  • Optional. Of (T): create an optional instance
  • Optional.empty(): create an empty optional instance
  • Optional. Ofnullable (T): if t is not null, create an optional instance; otherwise, create an empty instance
  • isPresent(): judge whether the value is included
  • Orelse (T): if the calling object contains a value, return the value; otherwise, return t
  • orElseGet(Supplier s): if the calling object contains a value, return the value; otherwise, return the value obtained by S
  • map(Function f): if there is a value, process it and return the processed Optional. Otherwise, return Optional empty()
  • flatMap(Function mapper): similar to map, the return value must be Optional

Method reference and constructor reference

Method reference

When the operation to be passed to the Lambda body already has an implemented method, you can use the method reference! It should be noted here that the parameter list of the implementation abstract method must be consistent with the parameter list of the method reference method!

So what is a method reference? The method reference is the operator "::" that separates the method name from the name of the object or class.

There are three use cases:

  • Object:: instance method
  • Class:: static method
  • Class:: instance method

Here, we can list several examples.

For example:

(x) -> System.out.println(x);

Equivalent to:

System.out::println

For example:

BinaryOperator<Double> bo = (x, y) -> Math.pow(x, y);

Equivalent to

BinaryOperator<Double> bo = Math::pow;

For example:

compare((x, y) -> x.equals(y), "binghe", "binghe")

Equivalent to

compare(String::equals, "binghe", "binghe")

Note: when the first parameter of the method to be referenced is the calling object, and the second parameter is the second parameter (or no parameter) of the method to be referenced: ClassName::methodName.

Constructor reference

The format is as follows:

ClassName::new

Combined with functional interface, it is automatically compatible with methods in functional interface. The constructor reference can be assigned to the defined method, and the constructor parameter list should be consistent with the parameter list of the abstract method in the interface!

For example:

Function<Integer, MyClass> fun = (n) -> new MyClass(n);

Equivalent to

Function<Integer, MyClass> fun = MyClass::new;

Array reference

The format is shown below.

type[]::new

For example:

Function<Integer, Integer[]> fun = (n) -> new Integer[n];

Equivalent to

Function<Integer, Integer[]> fun = Integer[]::new;

Stream in Java 8

What is Stream?

There are two of the most important changes in Java 8. The first is a Lambda expression; The other is the Stream API(java.util.stream. *).

Stream is a key abstract concept for processing collections in Java 8. It can specify the operations you want to perform on collections, and can perform very complex operations such as finding, filtering and mapping data. Using the Stream API to operate on the collection data is similar to a database query executed using SQL. You can also use the Stream API to perform operations in parallel. In short, the Stream API provides an efficient and easy-to-use way to process data

A stream is a data channel used to manipulate a sequence of elements generated by a data source (set, array, etc.). "Set is about data, flow is about calculation!"

be careful:
① Stream itself does not store elements.
② Stream does not change the source object. Instead, they return a new stream that holds the result.
③ Stream operations are deferred. This means that they wait until the results are needed.

Three steps of Stream operation

  • Create Stream

A data source (such as collection and array) to obtain a stream.

  • Intermediate operation

An intermediate operation chain that processes the data of the data source.

  • Terminate operation (terminal operation)

A termination operation that executes an intermediate chain of operations and produces results.

How to create a Stream?

The Collection interface in Java 8 is extended to provide two methods to obtain streams:

1. Get Stream

  • Default stream < E > stream(): returns a sequential stream
  • Default stream < E > parallelstream(): returns a parallel stream

2. Create a Stream from an array

The static method stream() of Arrays in Java 8 can obtain the array stream:

  • Static < T > stream < T > stream (t [] array): returns a stream

Overloaded form, which can handle arrays of corresponding basic types:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

3. Create flow from value

You can use the static method stream Of() creates a stream by displaying values. It can receive any number of parameters.

  • Public static < T > stream < T > of (t... Values): returns a stream

4. Create flow by function

You can create an infinite stream by creating a stream from a function.

You can use the static method stream Iterate() and stream Generate() to create an infinite stream.

  • iteration

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

  • generate

public static<T> Stream<T> generate(Supplier<T> s)

Intermediate operation of Stream

Multiple intermediate operations can be connected to form a pipeline. Unless the termination operation is triggered on the pipeline, the intermediate operation will not perform any processing! When the operation is terminated, it is processed all at once, which is called "lazy evaluation"

1. Screening and slicing

2. Mapping

3. Sorting

Termination of Stream

The terminal operation generates results from the pipeline of the stream. The result can be any value that is not a stream, such as List, Integer, or even void.

1. Find and match

2. Statute

3. Collection

The implementation of the method in the Collector interface determines how to perform collection operations (such as collecting List, Set and Map). However, the Collectors utility class provides many static methods to easily create common Collector instances. The specific methods and instances are shown in the table below

Parallel stream and serial stream

Parallel flow is to divide a content into multiple data blocks and process each data block with different threads.

Java 8 optimizes parallelism, and we can easily operate data in parallel. The Stream API can declaratively communicate with the
sequential() switches between parallel and sequential streams

Fork/Join framework

1. Brief overview

Fork/Join framework: when necessary, fork a large task into several small tasks (when it can't be disassembled), and then join and summarize the operation results of each small task

2. The difference between fork / join framework and traditional thread pool

Adopt "work stealing" mode:
When executing a new task, it can split it into smaller tasks, add the small task to the thread queue, and then steal one from the queue of a random thread and put it in its own queue.

Compared with the common thread pool implementation, the advantage of fork/join framework lies in the processing of the tasks contained in it In a general thread pool, if a thread cannot continue to run for some reason, the thread will be in a waiting state In the fork/join framework implementation, if a subproblem cannot continue to run because it waits for another subproblem to complete Then the thread dealing with this sub problem will actively look for other sub problems that have not yet run to execute This method reduces the waiting time of threads and improves performance.

Stream overview

There are two of the most important changes in Java 8. The first is a Lambda expression; The other is the Stream API(java.util.stream. *).

Stream is a key abstract concept for processing collections in Java 8. It can specify the operations you want to perform on collections, and can perform very complex operations such as finding, filtering and mapping data. Using the Stream API to operate on the collection data is similar to a database query executed using SQL. You can also use the Stream API to perform operations in parallel. In short, the Stream API provides an efficient and easy-to-use way to process data.

What is a Stream?

What is a stream?

It can be understood as follows: a stream is a data channel, which is used to manipulate the sequence of elements generated by data sources (sets, arrays, etc.).

"Set is about data, flow is about calculation!"

be careful:

① Stream itself does not store elements.

② Stream does not change the source object. Instead, they return a new stream that holds the result.

③ Stream operations are deferred. This means that they wait until the results are needed.

Stream operation steps

1. Create a Stream

A data source (such as collection and array) to obtain a stream.

2. Intermediate operation

An intermediate operation chain that processes the data of the data source.

3. Terminate operation (terminal operation)

A termination operation that executes an intermediate chain of operations and produces results.

How do I create a Stream?

Here, create the test class TestStreamAPI1, and all operations are completed in the TestStreamAPI1 class.

(1) Create a Stream through the stream() method or the parallelStream() method provided by the Collection series Collection.

In Java 8, the Collection interface is extended to provide two default methods for obtaining streams, as shown below.

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

The stream() method returns a sequential stream and the parallelStream() method returns a parallel stream.

We can use the following code to create sequential and parallel streams.

List<String> list = new ArrayList<>();
list.stream();
list.parallelStream();

(2) Get the array stream through the static method stream() in Arrays.

The static method stream() of the Arrays class in Java 8 can get the array stream, as shown below.

public static <T> Stream<T> stream(T[] array) {
    return stream(array, 0, array.length);
}

The function of the above code is to pass in a generic array and return the generic Stream stream.

In addition, the following overloaded form of the stream() method is provided in the Arrays class.

public static <T> Stream<T> stream(T[] array) {
    return stream(array, 0, array.length);
}

public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
    return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}

public static IntStream stream(int[] array) {
    return stream(array, 0, array.length);
}

public static IntStream stream(int[] array, int startInclusive, int endExclusive) {
    return StreamSupport.intStream(spliterator(array, startInclusive, endExclusive), false);
}

public static LongStream stream(long[] array) {
    return stream(array, 0, array.length);
}

public static LongStream stream(long[] array, int startInclusive, int endExclusive) {
    return StreamSupport.longStream(spliterator(array, startInclusive, endExclusive), false);
}

public static DoubleStream stream(double[] array) {
    return stream(array, 0, array.length);
}

public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {
    return StreamSupport.doubleStream(spliterator(array, startInclusive, endExclusive), false);
}

Basically, it can meet the operation of converting arrays of basic types into Stream streams.

We can use the following code example to create a Stream stream using the stream() method of the Arrays class.

Integer[] nums = new Integer[]{1,2,3,4,5,6,7,8,9};
Stream<Integer> numStream = Arrays.stream(nums);

(3) Get the array Stream through the static method of() of the Stream class.

You can use the static method stream Of() creates a stream by displaying values. It can receive any number of parameters.

Let's first look at the of() method of Stream, as shown below.

public static<T> Stream<T> of(T t) {
    return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
@SafeVarargs
@SuppressWarnings("varargs") 
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

You can see that in the Stream class, two of() methods are provided. One needs to pass in only one generic parameter, and the other needs to pass in a variable generic parameter.

We can use the following code example to create a Stream using the of method.

Stream<String> strStream = Stream.of("a", "b", "c");

(4) Create infinite flow

You can use the static method stream Iterate() and stream Generate() to create an infinite stream.

Let's take a look at the source code of the iterate() method and generate() method in the Stream class, as shown below.

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
    Objects.requireNonNull(f);
    final Iterator<T> iterator = new Iterator<T>() {
        @SuppressWarnings("unchecked")
        T t = (T) Streams.NONE;

        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public T next() {
            return t = (t == Streams.NONE) ? seed : f.apply(t);
        }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
        iterator,
        Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}

public static<T> Stream<T> generate(Supplier<T> s) {
    Objects.requireNonNull(s);
    return StreamSupport.stream(
        new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

It can be seen from the source code that the iterate() method mainly uses the "iteration" method to generate infinite streams, while the generate() method mainly uses the "generation" method to generate infinite streams. We can use the following code example to generate a Stream stream using these two methods.

  • iteration
Stream<Integer> intStream = Stream.iterate(0, (x) -> x + 2);
intStream.forEach(System.out::println);

Running the above code will always output even numbers at the terminal, and this operation will continue. What if we only need to output 10 even numbers? In fact, it is also very simple. You can use the limit method of the Stream object to limit it, as shown below.

Stream<Integer> intStream = Stream.iterate(0, (x) -> x + 2);
intStream.limit(10).forEach(System.out::println);
  • generate
Stream.generate(() -> Math.random()).forEach(System.out::println);

The above code will always output random numbers. If we only need to output 5 random numbers, we only need to use the limit() method to limit them.

Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);

(5) Create an empty stream

An empty() method is provided in the Stream class, as shown below.

public static<T> Stream<T> empty() {
    return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
}

We can use the empty() method of the Stream class to create an empty Stream, as shown below.

Stream<String> empty = Stream.empty();

Intermediate operation of Stream

Multiple intermediate operations can be connected to form a pipeline. Unless the termination operation is triggered on the pipeline, the intermediate operation will not perform any processing! When the operation is terminated, it is processed all at once, which is called "lazy evaluation". The intermediate operation of Stream will not have any result data output.

As a whole, the intermediate operations of Stream can be divided into filtering and slicing, mapping and sorting. Next, we will briefly describe these intermediate operations.

Screening and slicing

Here, I organize the operations related to filtering and slicing into the following table.

methoddescribe
filter(Predicate p)Receive a Lambda expression to exclude some elements from the stream
distinct()Filter to remove duplicate elements through hashCode() and equals() of the elements generated by the flow
limit(long maxSize)Truncate the stream so that its elements do not exceed the given number
skip(long n)Skip elements and return a stream that throws away the first n elements. If there are less than n elements in the stream, an empty stream is returned. Complementary to limit(n)

Next, let's give a few simple examples to deepen our understanding.

In order to better test the program, I first constructed an object array, as shown below.

protected List<Employee> list = Arrays.asList(
    new Employee("Zhang San", 18, 9999.99),
    new Employee("Li Si", 38, 5555.55),
    new Employee("Wang Wu", 60, 6666.66),
    new Employee("Zhao Liu", 8, 7777.77),
    new Employee("pseudo-ginseng", 58, 3333.33)
);

The definition of Employee class is as follows.

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

The definition of the Employee class is relatively simple. I won't repeat it here. In the following examples, we all operate with a collection of employee objects. OK, let's start the specific operation case.

1.filter() method

The filter() method is mainly used to receive Lambda expressions and exclude some elements from the Stream. Its source code in the Stream interface is as follows.

Stream<T> filter(Predicate<? super T> predicate);

You can see that in the filter() method, you need to pass the object of the Predicate interface. What the hell is the Predicate interface? Click in to see the source code.

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

You can see that Predicate is a functional interface. The main method defined in the interface is the test() method. The test() method will receive a generic object t and return data of boolean type.

After seeing this, I believe you understand: the filter() method filters data according to the return result of the test() method of the predict interface. If the return result of the test() method is true, it complies with the rules; If the return result of the test() method is false, the rule is not met.

Here, we can use the following example to briefly illustrate the use of the filter() method.

//Internal iteration: there is no iteration in this process, and the Stream api performs the iteration
//Intermediate operation: no operation will be performed
Stream<Person> stream = list.stream().filter((e) -> {
    System.out.println("Stream API Intermediate operation");
    return e.getAge() > 30;
});

After executing the termination statement, we iterate and print at the same time, but we do not iterate the above collection. In fact, this is an internal iteration, which is completed by the Stream API.

Let's take a look at the external iteration, that is, our artificial iteration.

//External iteration
Iterator<Person> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

2.limit() method

The main function is to cut off the flow so that its elements do not exceed the given number.

Let's first look at the definition of the limit method, as shown below.

Stream<T> limit(long maxSize);

The definition of the limit() method in the Stream interface is relatively simple. You only need to pass in a long number.

We can use the limit() method as shown below.

//Take 2 values after filtering
list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);

Here, we can cooperate with other intermediate operations and truncate the flow so that we can obtain the corresponding number of elements. Moreover, in the above calculation, as long as two qualified elements are found, the data will not be iterated further, which can improve the efficiency.

3.skip() method

Skip elements and return a stream that throws away the first n elements. If there are less than n elements in the stream, an empty stream is returned. Complementary to limit(n).

The source code definition is as follows.

Stream<T> skip(long n);

The source code definition is relatively simple. Similarly, you only need to pass in a long number. This means skipping n elements.

A simple example is shown below.

//Skip the first 2 values
list.stream().skip(2).forEach(System.out :: println);

4.distinct() method

Filter to remove duplicate elements through hashCode() and equals() of the elements generated by the stream.

The source code definition is as follows.

Stream<T> distinct();

The purpose is to de duplicate the elements in the convection.

We can use the diinct () method as follows.

list.stream().distinct().forEach(System.out :: println);

Here's one thing to note: distinct needs to rewrite the hashCode () and equals () methods in the entity before it can be used.

mapping

The methods related to mapping are shown in the following table.

methoddescribe
map(Function f)Receive a function as an argument, which is applied to each element and mapped to a new element.
mapToDouble(ToDoubleFunction f)Receive a function as an argument, which will be applied to each element to generate a new DoubleStream.
mapToInt(ToIntFunction f)Receive a function as a parameter, which will be applied to each element to generate a new IntStream.
mapToLong(ToLongFunction f)Receive a function as an argument, which will be applied to each element to generate a new LongStream
flatMap(Function f)Take a function as a parameter, replace each value in the stream with another stream, and then connect all streams into one stream

1.map() method

Receive a function as an argument, which is applied to each element and mapped to a new element.

Let's first look at the declaration of the Stream interface for the map() method in Java 8, as shown below.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

We can use the map() method as follows.

//Map every element in the stream to the map function, execute this function for each element, and then return
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf);

//Get the name of each Person in Person, and then return a collection
List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());

2.flatMap()

Take a function as a parameter, replace each value in the stream with another stream, and then connect all streams into one stream.

Let's first look at the declaration of the Stream interface for the flatMap() method in Java 8, as shown below.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

We can use the flatMap() method in the following way. For your understanding, I've posted all the code to test the flatMap() method.

/**
     * flatMap - Receive a function as a parameter, replace each value in the stream with a stream, and then connect all streams into a stream
     */
    @Test
    public void testFlatMap () {
        StreamAPI_Test s = new StreamAPI_Test();
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
        list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println);

        //If you use a map, you need to write it like this
        list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> {
            e.forEach(System.out::println);
        });
    }

    /**
     * Converts a string to a stream
     */
    public Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        return list.stream();
    }

In fact, the map method is equivalent to the add method of the collaboration. If the add is a collection, it will become a two-dimensional array, while the flatMap method is equivalent to the addAll method of the collaboration. If the parameter is a collection, it will only merge the two collections rather than become a two-dimensional array.

sort

The methods related to sorting are shown in the table below.

methoddescribe
sorted()Generate a new stream, which is sorted in natural order
sorted(Comparator comp)Generates a new stream, sorted in comparator order

As can be seen from the above table, there are two methods for sorting: one is to pass no parameters, which is called natural sorting, and the other is to pass Comparator interface parameters, which is called custom sorting.

Let's first look at the declaration of the Stream interface for the sorted() method in Java 8, as shown below.

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

The definition of the sorted() method is relatively simple, so I won't repeat it.

We can also use the sorted() method of Stream as follows.

// Natural sorting
List<Employee> persons = list.stream().sorted().collect(Collectors.toList());

//Custom sorting
List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
    if (e1.getAge() == e2.getAge()) {
        return 0;
    } else if (e1.getAge() > e2.getAge()) {
        return 1;
    } else {
        return -1;
    }
}).collect(Collectors.toList());

Termination of Stream

The terminal operation generates results from the pipeline of the stream. The result can be any value that is not a stream, such as List, Integer, Double, String, etc., or even void.

In Java 8, the termination operation of Stream can be divided into: find and match, specification and collection. Next, we will briefly explain these termination operations.

Find and match

The methods for finding and matching in Stream API are shown in the following table.

methoddescribe
allMatch(Predicate p)Check that all elements match
anyMatch(Predicate p)Check to see if at least one element matches
noneMatch(Predicate p)Check that no elements match
findFirst()Returns the first element
findAny()Returns any element in the current stream
count()Returns the total number of elements in the stream
max(Comparator c)Returns the maximum value in the stream
min(Comparator c)Returns the minimum value in the stream
forEach(Consumer c)Internal iteration (using the Collection interface requires users to do iterations, which is called external iteration. On the contrary, the Stream API uses internal iteration)

Similarly, we will give a simple example of each important method. Here, we first establish an employee class. The definition of the Employee class is as follows.

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
    private Stauts stauts;
    public enum Stauts{
        WORKING,
        SLEEPING,
        VOCATION
    }
}

Next, we define a set of employees for testing in the test class, as shown below.

protected List<Employee> employees = Arrays.asList(
    new Employee("Zhang San", 18, 9999.99, Employee.Stauts.SLEEPING),
    new Employee("Li Si", 38, 5555.55, Employee.Stauts.WORKING),
    new Employee("Wang Wu", 60, 6666.66, Employee.Stauts.WORKING),
    new Employee("Zhao Liu", 8, 7777.77, Employee.Stauts.SLEEPING),
    new Employee("pseudo-ginseng", 58, 3333.33, Employee.Stauts.VOCATION)
);

All right, ready. Next, we begin to test each termination method of the Stream.

1.allMatch()

The allMatch() method means to check whether all elements are matched. Its definition in the Stream interface is as follows.

boolean allMatch(Predicate<? super T> predicate);

We can use the allMatch() method through an example like the following.

boolean match = employees.stream().allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

Note: when using the allMatch() method, the allMatch() method returns true only when all elements match the criteria.

2.anyMatch() method

The anyMatch method means to check whether at least one element is matched. Its definition in the Stream interface is as follows.

boolean anyMatch(Predicate<? super T> predicate);

We can use the anyMatch() method with an example like the following.

boolean match = employees.stream().anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

Note: when using the anyMatch() method, the anyMatch() method will return true as long as any element meets the conditions.

3.noneMatch() method

The noneMatch() method means to check whether all elements are not matched. Its definition in the Stream interface is as follows.

boolean noneMatch(Predicate<? super T> predicate);

We can use the noneMatch() method with an example like the following.

boolean match = employees.stream().noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

Note: when using the noneMatch() method, the noneMatch() method will return true only if all elements do not meet the conditions.

4.findFirst() method

The findFirst() method returns the first element. Its definition in the Stream interface is as follows.

Optional<T> findFirst();

We can use the findFirst() method through an example like the following.

Optional<Employee> op = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(op.get());

5.findAny() method

The findAny() method returns any element in the current Stream. Its definition in the Stream interface is as follows.

Optional<T> findAny();

We can use the findAny() method through an example like the following.

Optional<Employee> op = employees.stream().filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts())).findFirst();
System.out.println(op.get());

6.count() method

The count() method represents the total number of elements in the return Stream. Its definition in the Stream interface is as follows.

long count();

We can use the count() method with examples like the following.

long count = employees.stream().count();
System.out.println(count);

7.max() method

The max() method represents the maximum value in the return Stream. Its definition in the Stream interface is as follows.

Optional<T> max(Comparator<? super T> comparator);

We can use the max() method with examples like the following.

Optional<Employee> op = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op.get());

8.min() method

The min() method represents the minimum value in the return Stream. Its definition in the Stream interface is as follows.

Optional<T> min(Comparator<? super T> comparator);

We can use the min() method with examples like the following.

Optional<Double> op = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(op.get());

9.forEach() method

The forEach() method represents an internal iteration (using the Collection interface requires the user to do an iteration, which is called an external iteration. On the contrary, the Stream API uses an internal iteration). Its definition inside the Stream interface is as follows.

void forEach(Consumer<? super T> action);

We can use the forEach() method with examples like the following.

employees.stream().forEach(System.out::println);

Statute

The specification methods in the Stream API are shown in the following table.

methoddescribe
reduce(T iden, BinaryOperator b)You can combine the elements in the flow repeatedly to get a value. Return T
reduce(BinaryOperator b)You can combine the elements in the flow repeatedly to get a value. Return to optional < T >

The definition of the reduce() method in the Stream interface is as follows.

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

We can use the reduce method through an example like the following.

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("----------------------------------------");
Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(op.get());

We can also search for the number of occurrences of "Zhang" in the employees list.

 Optional<Integer> sum = employees.stream()
   .map(Employee::getName)
   .flatMap(TestStreamAPI1::filterCharacter)
   .map((ch) -> {
    if(ch.equals('six'))
     return 1;
    else
     return 0;
   }).reduce(Integer::sum);
  System.out.println(sum.get());

Note: the above example uses hard coding to accumulate a specific value. You can optimize the code in your actual work.

collect

methoddescribe
collect(Collector c)Convert the Stream to another form. Receive the implementation of a Collector interface, which is used to summarize the elements in the Stream

The definition of the collect() method in the Stream interface is as follows.

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

We can use the collect method with examples like the following.

Optional<Double> max = employees.stream()
   .map(Employee::getSalary)
   .collect(Collectors.maxBy(Double::compare));
  System.out.println(max.get());
  Optional<Employee> op = employees.stream()
   .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
  System.out.println(op.get());
  Double sum = employees.stream().collect(Collectors.summingDouble(Employee::getSalary));
  System.out.println(sum);
  Double avg = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
  System.out.println(avg);
  Long count = employees.stream().collect(Collectors.counting());
  System.out.println(count);
  System.out.println("--------------------------------------------");
  DoubleSummaryStatistics dss = employees.stream()
   .collect(Collectors.summarizingDouble(Employee::getSalary));
  System.out.println(dss.getMax());

How do I collect Stream streams?

The implementation of the method in the Collector interface determines how to perform collection operations (such as collecting List, Set and Map). Collectors utility class provides many static methods, which can easily create common Collector instances. The specific methods and examples are as follows:

methodReturn typeeffect
toListList<T>Collect the elements in the stream into a List
toSetSet<T>Collect the elements in the stream into a Set
toCollectionCollection<T>Collect the elements in the stream into the created collection
countingLongCount the number of elements in the flow
summingIntIntegerSumming integer attributes of elements in a stream
averagingIntDoubleCalculates the average value of the Integer attribute of the element in the flow
summarizingIntIntSummaryStatisticsCollect statistics for the Integer attribute in the stream. E.g. average value
joiningStringEach string in the connection stream
maxByOptional<T>Select the maximum value according to the comparator
minByOptional<T>Select the minimum value according to the comparator
reducingType of reductionStarting from an initial value as an accumulator, BinaryOperator is combined with the elements in the stream one by one to reduce it to a single value
collectingAndThenThe type returned by the conversion functionWrap another collector and convert its result to a function
groupingByMap<K, List<T>>According to an attribute value, the attribute is K and the result is V
partitioningByMap<Boolean, List<T>>Partition according to true or false

The use examples corresponding to each method are shown in the following table.

methodUse example
toListList<Employee> employees= list.stream().collect(Collectors.toList());
toSetSet<Employee> employees= list.stream().collect(Collectors.toSet());
toCollectionCollection<Employee> employees=list.stream().collect(Collectors.toCollection(ArrayList::new));
countinglong count = list.stream().collect(Collectors.counting());
summingIntint total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntdouble avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary))
summarizingIntIntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
CollectorsString str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByOptional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducingint total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThenint how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<Emp.Status, List<Emp>> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningByMap<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
public void test4(){
    Optional<Double> max = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.maxBy(Double::compare));
    System.out.println(max.get());

    Optional<Employee> op = emps.stream()
        .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

    System.out.println(op.get());

    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));

    System.out.println(sum);

    Double avg = emps.stream()
        .collect(Collecors.averagingDouble(Employee::getSalary));
    System.out.println(avg);
    Long count = emps.stream()
        .collect(Collectors.counting());

    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
 

What is parallel flow?

In short, parallel flow is to divide a content into multiple data blocks and process each data block with different threads.

Java 8 optimizes parallelism, and we can easily operate data in parallel. The Stream API can declaratively switch between parallel and sequential streams through parallel() and sequential().

Fork/Join framework

Fork/Join framework: when necessary, fork a large task into several small tasks (when it cannot be disassembled), and then join and summarize the operation results of each small task.

What is the difference between Fork/Join framework and traditional thread pool?

Adopt "work stealing" mode:

When executing a new task, it can split it into smaller tasks for execution, add the small task to the thread queue, and then steal one from the queue of a random thread and put it in its own queue.

Compared with the general implementation of thread pool, the advantage of fork/join framework is reflected in the processing mode of the tasks contained in it. In a general thread pool, if the task being executed by a thread cannot continue to run for some reason, the thread will be in a waiting state. In the implementation of fork/join framework, if a subtask cannot continue to run because it waits for the completion of another subtask. Then the thread dealing with this sub problem will actively look for other sub tasks that have not yet run to execute. This method reduces the waiting time of threads and improves the performance of programs.

Fork/Join framework instance

After understanding the principle of the ForJoin framework, we will manually write an example program that uses the Fork/Join framework to realize the cumulative sum, so as to help readers better understand the Fork/Join framework. Well, no nonsense. Let's go to the code. Let's have a good experience of the power of the Fork/Join framework through the following code.

package io.binghe.concurrency.example.aqs;
 
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
    public static final int threshold = 2;
    private int start;
    private int end;
    public ForkJoinTaskExample(int start, int end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        int sum = 0;
        //If the task is small enough, calculate the task
        boolean canCompute = (end - start) <= threshold;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // If the task is greater than the threshold, it is split into two subtasks for calculation
            int middle = (start + end) / 2;
            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
 
            // Execute subtasks
            leftTask.fork();
            rightTask.fork();
 
            // Wait for task execution to end and merge its results
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
 
            // Merge subtasks
            sum = leftResult + rightResult;
        }
        return sum;
    }
    public static void main(String[] args) {
        ForkJoinPool forkjoinPool = new ForkJoinPool();
 
        //Generate a calculation task to calculate 1 + 2 + 3 + 4
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
 
        //Perform a task
        Future<Integer> result = forkjoinPool.submit(task);
 
        try {
            log.info("result:{}", result.get());
        } catch (Exception e) {
            log.error("exception", e);
        }
    }
}

Parallel flow instances in Java 8

Java 8 optimizes the parallel flow a lot, and greatly simplifies the workload of programmers in development. We can use the parallel flow in Java 8 to process our data only by using the following code.

LongStream.rangeClosed(0, 10000000L).parallel().reduce(0, Long::sum);

How to gracefully switch between parallel and serial streams in Java 8?

The Stream API can declaratively switch between parallel and serial streams through parallel() and sequential().

Optional class

What is the Optional class?

The optional < T > class (java.util.Optional) is a container class that represents the existence or nonexistence of a value. Originally, null was used to indicate the nonexistence of a value. Now optional can better express this concept. And you can avoid null pointer exceptions.

Common methods of Optional class:

  • Optional. Of (T): create an optional instance.
  • Optional.empty(): create an empty optional instance.
  • Optional. Ofnullable (T): if t is not null, create an optional instance; otherwise, create an empty instance.
  • isPresent(): judge whether to include a value.
  • Orelse (T): if the calling object contains a value, return the value; otherwise, return t.
  • orElseGet(Supplier s): if the calling object contains a value, return the value; otherwise, return the value obtained by S.
  • map(Function f): if there is a value, process it and return the processed Optional. Otherwise, return Optional empty().
  • flatMap(Function mapper): similar to map, the return value must be Optional.

Optional class example

1. Create Optional class

(1) Create an empty Optional object using the empty() method:

Optional<String> empty = Optional.empty();

(2) Create an Optional object using the of() method:

String name = "binghe";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[binghe]", opt.toString());

The value passed to of() cannot be null, otherwise a null pointer exception will be thrown. For example, the following program will throw a null pointer exception.

String name = null;
Optional<String> opt = Optional.of(name);

If we need to pass some null values, we can use the following example.

String name = null;
Optional<String> opt = Optional.ofNullable(name);

Using ofNullable() method, when a null value is passed in, an exception will not be thrown, but an empty Optional object will be returned, just as we use Optional The empty () method is the same.

2.isPresent

We can use this isPresent() method to check whether there is a value in an Optional object. Only if the value is non empty will it return true.

Optional<String> opt = Optional.of("binghe");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

Before Java 8, we generally used the following methods to check null values.

if(name != null){
    System.out.println(name.length);
}

In Java 8, we can check null values in the following ways.

Optional<String> opt = Optional.of("binghe");
opt.ifPresent(name -> System.out.println(name.length()));

3.orElse and orElseGet

(1)orElse

The orElse() method is used to return the default value in the Optional object. It is passed in a "default parameter". If there is a value in the object, it is returned. Otherwise, the passed in "default parameter" is returned.

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("binghe");
assertEquals("binghe", name);

(2)orElseGet

Similar to the orElse() method, but this function does not receive a "default parameter", but a function interface.

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "binghe");
assertEquals("binghe", name);

(3) What is the difference between the two?

To understand the difference between the two, let's first create a method that has no parameters and returns a fixed value.

public String getDefaultName() {
    System.out.println("Getting Default Name");
    return "binghe";
}

Next, take two tests to see what the difference between the two methods is.

String text;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
assertEquals("binghe", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultName());
assertEquals("binghe", defaultText);

In this example, our Optional object contains a null value. Let's see the program execution result:

Using orElseGet:
Getting default name...
Using orElse:
Getting default name...

value does not exist in both Optional objects, so the execution result is the same.

So what happens when there is data in the Optional object? Let's verify it.

String name = "binghe001";

System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
assertEquals("binghe001", defaultName);

System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("binghe001", defaultName);

The operation results are as follows.

Using orElseGet:
Using orElse:
Getting default name...

As you can see, when the orElseGet() method is used, the getDefaultName() method is not executed because the Optional contains a value, while when orElse is used, it is executed as usual. Therefore, you can see that when the value exists, orElse creates one more object than orElseGet. If there is network interaction when creating objects, the overhead of system resources will be relatively large, which needs our attention.

4.orElseThrow

When the orElseThrow() method encounters a non-existent value, it does not return a default value, but throws an exception.

String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new);

5.get

The get() method indicates that the value is obtained from the Optional object.

Optional<String> opt = Optional.of("binghe");
String name = opt.get();
assertEquals("binghe", name);

You can also return wrapped values using the get() method. However, the value must exist. When the value does not exist, a NoSuchElementException exception is thrown.

Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();

6.filter

Receive a functional interface. When it conforms to the interface, an Optional object is returned; otherwise, an empty Optional object is returned.

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);
boolean isBinghe = nameOptional.filter(n -> "binghe".equals(name)).isPresent();
assertTrue(isBinghe);
boolean isBinghe001 = nameOptional.filter(n -> "binghe001".equals(name)).isPresent();
assertFalse(isBinghe001);

Using the filter() method will filter out elements we don't need.

Next, let's look at an example. For example, there is a Person class, as shown below.

public class Person{
    private int age;
    public Person(int age){
        this.age = age;
    }
    //Omit the get set method
}

For example, we need to filter out people aged between 25 and 35. Before Java 8, we need to create the following method to detect whether everyone's age range is between 25 and 35.

public boolean filterPerson(Peron person){
    boolean isInRange = false;
    if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
        isInRange =  true;
    }
    return isInRange;
}

It seems very troublesome. We can test it in the following ways.

assertTrue(filterPerson(new Peron(18)));
assertFalse(filterPerson(new Peron(29)));
assertFalse(filterPerson(new Peron(16)));
assertFalse(filterPerson(new Peron(34)));
assertFalse(filterPerson(null));

If you use Optional, what is the effect?

public boolean filterPersonByOptional(Peron person){
     return Optional.ofNullable(person)
       .map(Peron::getAge)
       .filter(p -> p >= 25)
       .filter(p -> p <= 35)
       .isPresent();
}

Using Optional looks much cleaner. Here, map() just converts one value to another, and this operation does not change the original value.

7.map

If there is a value, process it and return the processed Optional. Otherwise, return Optional empty().

List<String> names = Arrays.asList("binghe001", "binghe002", "", "binghe003", "", "binghe004");
Optional<List<String>> listOptional = Optional.of(names);

int size = listOptional
    .map(List::size)
    .orElse(0);
assertEquals(6, size);

In this example, we use a List set to encapsulate some strings, and then use Optional to encapsulate the List, and map() it to obtain the length of the List set. The result returned by map() is also encapsulated in an Optional object. Here, when the value does not exist, we will return 0 by default. As follows, we get the length of a string.

String name = "binghe";
Optional<String> nameOptional = Optional.of(name);

int len = nameOptional
    .map(String::length())
    .orElse(0);
assertEquals(6, len);

We can also use the map() method in conjunction with the filter() method, as shown below.

String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
    pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);

correctPassword = passOpt
    .map(String::trim)
    .filter(pass -> pass.equals("password"))
    .isPresent();
assertTrue(correctPassword);

The meaning of the above code is to verify the password and check whether the password is the specified value.

8.flatMap

Similar to map, the return value must be Optional.

Suppose we now have a Person class.

public class Person {
    private String name;
    private int age;
    private String password;
 
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
 
    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
 
    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }
    // Ignore get set method
}

Next, we can package the Person into Optional and test it, as shown below.

Person person = new Person("binghe", 18);
Optional<Person> personOptional = Optional.of(person);

Optional<Optional<String>> nameOptionalWrapper = personOptional.map(Person::getName);
Optional<String> nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("binghe", name1);

String name = personOptional
    .flatMap(Person::getName)
    .orElse("");
assertEquals("binghe", name);

Note: the method getName returns an Optional object. If we use map, we need to call the get() method again, but not if we use flatMap().

Default method

Default method in interface

Java 8 allows the interface to contain methods with specific implementation. This method is called "default method", and the default method is decorated with the default keyword.

For example, we can define an interface MyFunction, which contains a default method getName, as shown below.

public interface MyFunction<T>{
    T get(Long id);
    default String getName(){
        return "binghe";
    }
}

Principle of default method

In Java 8, the default method has the principle of "class first".

If a default method is defined in an interface and a method with the same name is defined in another parent class or interface, the following principles shall be followed.

1. Select the method in the parent class. If a parent class provides a concrete implementation, the default method with the same name and parameters in the interface will be ignored.

For example, there is now an interface MyFunction and a class MyClass, as shown below.

  • MyFunction interface
public interface MyFunction{
    default String getName(){
        return "MyFunction";
    }
}
  • MyClass class
public class MyClass{
    public String getName(){
        return "MyClass";
    }
}

At this point, create a SubClass class, inherit the MyClass class, and implement the MyFunction interface, as shown below.

public class SubClass extends MyClass implements MyFunction{
    
}

Next, we create a SubClassTest class to test the SubClass class, as shown below.

public class SubClassTest{
    @Test
    public void testDefaultFunction(){
        SubClass subClass = new SubClass();
        System.out.println(subClass.getName());
    }
}

Running the above program will output the string: MyClass.

2. Interface conflict. If a parent interface provides a default method and another interface provides a method with the same name and parameter list (whether the method is the default method or not), the method must be overridden to resolve the conflict.

For example, there are now two interfaces, MyFunction and MyInterface, each with a default method getName(), as shown below.

  • MyFunction interface
public interface MyFunction{
    default String getName(){
        return "function";
    }
}
  • MyInterface interface
public interface MyInterface{
    default String getName(){
        return "interface";
    }
}

The implementation class MyClass implements both the MyFunction interface and the MyInterface interface. Since the getName() default method exists in both the MyFunction interface and the MyInterface interface, MyClass must override the getName() method to resolve the conflict, as shown below.

public class MyClass{
    @Override
    public String getName(){
        return MyInterface.super.getName();
    }
}

At this point, the getName method in the MyClass class returns: interface.

If the getName() method in MyClass overrides the getName() method of the MyFunction interface, as shown below.

public class MyClass{
    @Override
    public String getName(){
        return MyFunction.super.getName();
    }
}

At this point, the getName method in the MyClass class returns: function.

Static methods in interfaces

In Java 8, static methods can be added to the interface, using the interface name Method name. For example, the static method send() is defined in the MyFunction interface.

public interface MyFunction{
    default String getName(){
        return "binghe";
    }
    static void send(){
        System.out.println("Send Message...");
    }
}

We can directly call the send static method of the MyFunction interface in the following way.

MyFunction.send();

Local time and timestamp

Main methods:

  • now: a static method that creates an object based on the current time
  • of: a static method that creates an object based on a specified date / time
  • plusDays,plusWeeks,plusMonths,plusYears: add days, weeks, months and years to the current LocalDate object
  • Minusdays, minuswakes, minusmonths, minusyears: subtract days, weeks, months, and years from the current LocalDate object
  • plus,minus: add or reduce a Duration or Period
  • withDayOfMonth,withDayOfYear,withMonth,withYear: modify the month days, year days, month and year to the specified value and return a new LocalDate object
  • getDayOfYear: get the number of days of the year (1 ~ 366)
  • getDayOfWeek: get the day of the week (return a DayOfWeek enumeration value)
  • getMonth: get the Month and return a Month enumeration value
  • getMonthValue: get the month (1 ~ 12)
  • getYear: get the year
  • until: get the Period object between two dates, or specify the number of chrononunits
  • isBefore,isAfter: compare two localdates
  • isLeapYear: judge whether it is a leap year

Use LocalDate, LocalTime, LocalDateTime

Instances of the LocalDate, LocalTime, and LocalDateTime classes are immutable objects that represent the date, time, date, and time using the ISO-8601 calendar system. They provide a simple date or time and do not contain current time information. It also does not contain time zone related information.

Note: ISO-8601 calendar system is the representation of date and time of modern citizens formulated by the international organization for standardization

methoddescribe
now()Static method to create an object based on the current time
of()Static method to create an object based on a specified date / time
plusDays, plusWeeks, plusMonths, plusYearsAdd days, weeks, months and years to the current LocalDate object
minusDays, minusWeeks, minusMonths, minusYearsSubtract days, weeks, months and years from the current LocalDate object
plus, minusAdd or remove a Duration or Period
withDayOfMonth, withDayOfYear, withMonth, withYearModify the month days, year days, month and year to the specified value and return a new LocalDate object
getDayOfMonthGet month days (1-31)
getDayOfYearDays of acquisition year (1-366)
getDayOfWeekGets the day of the week (returns a DayOfWeek enumeration value)
getMonthGet the Month and return a Month enumeration value
getMonthValueGet month (1-12)
getYearYear of acquisition
untilGet the Period object between two dates, or specify the number of chrononunits
isBefore, isAfterCompare two localdates
isLeapYearDetermine whether it is a leap year

The sample code is shown below.

// Get current system time
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);
// Operation result: 2019-10-27T13:49:09.483

// Specify date and time
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
System.out.println(localDateTime2);
// Operation result: 2019-10-27T13:45:10

LocalDateTime localDateTime3 = localDateTime1
        // Plus three years
        .plusYears(3)
        // Minus three months
        .minusMonths(3);
System.out.println(localDateTime3);
// Operation result: 2022-07-27T13:49:09.483

System.out.println(localDateTime1.getYear());       // Operation result: 2019
System.out.println(localDateTime1.getMonthValue()); // Operation result: 10
System.out.println(localDateTime1.getDayOfMonth()); // Operation result: 27
System.out.println(localDateTime1.getHour());       // Operation result: 13
System.out.println(localDateTime1.getMinute());     // Operation result: 52
System.out.println(localDateTime1.getSecond());     // Operation result: 6

LocalDateTime localDateTime4 = LocalDateTime.now();
System.out.println(localDateTime4);     // 2019-10-27T14:19:56.884
LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10);
System.out.println(localDateTime5);     // 2019-10-10T14:19:56.884

Instant timestamp

Operation for timestamp. It is based on the description experienced since the first year of Unix (traditionally set as midnight on January 1, 1970).

The sample code is shown below.

Instant instant1 = Instant.now();    // Get UTC time zone by default
System.out.println(instant1);
// Operation result: 2019-10-27T05:59:58.221Z

// Offset operation
OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
// Operation result: 2019-10-27T13:59:58.221+08:00

// Get timestamp
System.out.println(instant1.toEpochMilli());
// Operation result: 157215614500

// Take the first year of Unix as the starting point for offset operation
Instant instant2 = Instant.ofEpochSecond(60);
System.out.println(instant2);
// Operation result: 1970-01-01T00:01:00Z

Duration and Period

Duration: used to calculate two time intervals.

Period: used to calculate the interval between two dates.

Instant instant_1 = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Instant instant_2 = Instant.now();

Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// Operation result: 1000

LocalTime localTime_1 = LocalTime.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();

System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// Operation result: 1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();

Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears());      // Operation results: 1
System.out.println(period.getMonths());     // Operation results: 1
System.out.println(period.getDays());       // Operation result: 18

Manipulation of dates

TemporalAdjuster: time corrector. Sometimes we may need to obtain, for example, adjust the date to "next Sunday".

TemporalAdjusters: this class provides a large number of common implementations of TemporalAdjusters through static methods.

For example, get the next Sunday as follows:

LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));

The complete example code is shown below.

LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);     // 2019-10-27T14:19:56.884

// Get the date of this first day
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth()));            // 2019-10-01T14:22:58.574
// Get the date of the next weekend
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));       // 2019-11-03T14:22:58.574

// Custom: next business day
LocalDateTime localDateTime2 = localDateTime1.with(l -> {
    LocalDateTime localDateTime = (LocalDateTime) l;
    DayOfWeek dayOfWeek =  localDateTime.getDayOfWeek();

    if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
       return localDateTime.plusDays(3);
    } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
       return localDateTime.plusDays(2);
    } else {
       return localDateTime.plusDays(1);
    }
});
System.out.println(localDateTime2);
// Operation result: 2019-10-28T14:30:17.400

Parsing and formatting

java.time.format.DateTimeFormatter class: this class provides three formatting methods:

  • Predefined standard formats
  • Locale related formats
  • Custom format

The sample code is shown below.

DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
System.out.println(strDate1);
// Operation result: October 27, 2019

// Date -> String
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd  HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
System.out.println(strDate2);
// Operation result: October 27, 2019 14:36:11

// String -> Date
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
System.out.println(localDateTime1);
// Operation result: 2019-10-27T14:37:39

Time zone processing

Java 8 adds support for time zones. The time zones are ZonedDate, ZonedTime, and ZonedDateTime.

Each time zone corresponds to an ID in the format of "{region} / {City}", such as Asia/Shanghai.

  • ZoneId: this class contains all the time zone information
  • getAvailableZoneIds(): all time zone information can be obtained
  • of(id): obtain the ZoneId object with the specified time zone information

The sample code is shown below.

// Get all time zones
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);
// [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador, Asia/Pontianak, Africa/Cairo, Pacific/Pago_Pago, Africa/Mbabane, Asia/Kuching, Pacific/Honolulu, Pacific/Rarotonga, America/Guatemala, Australia/Hobart, Europe/London, America/Belize, America/Panama, Asia/Chungking, America/Managua, America/Indiana/Petersburg, Asia/Yerevan, Europe/Brussels, GMT, Europe/Warsaw, America/Chicago, Asia/Kashgar, Chile/Continental, Pacific/Yap, CET, Etc/GMT-1, Etc/GMT-0, Europe/Jersey, America/Tegucigalpa, Etc/GMT-5, Europe/Istanbul, America/Eirunepe, Etc/GMT-4, America/Miquelon, Etc/GMT-3, Europe/Luxembourg, Etc/GMT-2, Etc/GMT-9, America/Argentina/Catamarca, Etc/GMT-8, Etc/GMT-7, Etc/GMT-6, Europe/Zaporozhye, Canada/Yukon, Canada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand, America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asia/Dubai, Europe/Isle_of_Man, America/Araguaina, Cuba, Asia/Novosibirsk, America/Argentina/Salta, Etc/GMT+3, Africa/Tunis, Etc/GMT+2, Etc/GMT+1, Pacific/Fakaofo, Africa/Tripoli, Etc/GMT+0, Israel, Africa/Banjul, Etc/GMT+7, Indian/Comoro, Etc/GMT+6, Etc/GMT+5, Etc/GMT+4, Pacific/Port_Moresby, US/Arizona, Antarctica/Syowa, Indian/Reunion, Pacific/Palau, Europe/Kaliningrad, America/Montevideo, Africa/Windhoek, Asia/Karachi, Africa/Mogadishu, Australia/Perth, Brazil/East, Etc/GMT, Asia/Chita, Pacific/Easter, Antarctica/Davis, Antarctica/McMurdo, Asia/Macao, America/Manaus, Africa/Freetown, Europe/Bucharest, Asia/Tomsk, America/Argentina/Mendoza, Asia/Macau, Europe/Malta, Mexico/BajaSur, Pacific/Tahiti, Africa/Asmera, Europe/Busingen, America/Argentina/Rio_Gallegos, Africa/Malabo, Europe/Skopje, America/Catamarca, America/Godthab, Europe/Sarajevo, Australia/ACT, GB-Eire, Africa/Lagos, America/Cordoba, Europe/Rome, Asia/Dacca, Indian/Mauritius, Pacific/Samoa, America/Regina, America/Fort_Wayne, America/Dawson_Creek, Africa/Algiers, Europe/Mariehamn, America/St_Johns, America/St_Thomas, Europe/Zurich, America/Anguilla, Asia/Dili, America/Denver, Africa/Bamako, Europe/Saratov, GB, Mexico/General, Pacific/Wallis, Europe/Gibraltar, Africa/Conakry, Africa/Lubumbashi, Asia/Istanbul, America/Havana, NZ-CHAT, Asia/Choibalsan, America/Porto_Acre, Asia/Omsk, Europe/Vaduz, US/Michigan, Asia/Dhaka, America/Barbados, Europe/Tiraspol, Atlantic/Cape_Verde, Asia/Yekaterinburg, America/Louisville, Pacific/Johnston, Pacific/Chatham, Europe/Ljubljana, America/Sao_Paulo, Asia/Jayapura, America/Curacao, Asia/Dushanbe, America/Guyana, America/Guayaquil, America/Martinique, Portugal, Europe/Berlin, Europe/Moscow, Europe/Chisinau, America/Puerto_Rico, America/Rankin_Inlet, Pacific/Ponape, Europe/Stockholm, Europe/Budapest, America/Argentina/Jujuy, Australia/Eucla, Asia/Shanghai, Universal, Europe/Zagreb, America/Port_of_Spain, Europe/Helsinki, Asia/Beirut, Asia/Tel_Aviv, Pacific/Bougainville, US/Central, Africa/Sao_Tome, Indian/Chagos, America/Cayenne, Asia/Yakutsk, Pacific/Galapagos, Australia/North, Europe/Paris, Africa/Ndjamena, Pacific/Fiji, America/Rainy_River, Indian/Maldives, Australia/Yancowinna, SystemV/AST4, Asia/Oral, America/Yellowknife, Pacific/Enderbury, America/Juneau, Australia/Victoria, America/Indiana/Vevay, Asia/Tashkent, Asia/Jakarta, Africa/Ceuta, Asia/Barnaul, America/Recife, America/Buenos_Aires, America/Noronha, America/Swift_Current, Australia/Adelaide, America/Metlakatla, Africa/Djibouti, America/Paramaribo, Europe/Simferopol, Europe/Sofia, Africa/Nouakchott, Europe/Prague, America/Indiana/Vincennes, Antarctica/Mawson, America/Kralendijk, Antarctica/Troll, Europe/Samara, Indian/Christmas, America/Antigua, Pacific/Gambier, America/Indianapolis, America/Inuvik, America/Iqaluit, Pacific/Funafuti, UTC, Antarctica/Macquarie, Canada/Pacific, America/Moncton, Africa/Gaborone, Pacific/Chuuk, Asia/Pyongyang, America/St_Vincent, Asia/Gaza, Etc/Universal, PST8PDT, Atlantic/Faeroe, Asia/Qyzylorda, Canada/Newfoundland, America/Kentucky/Louisville, America/Yakutat, Asia/Ho_Chi_Minh, Antarctica/Casey, Europe/Copenhagen, Africa/Asmara, Atlantic/Azores, Europe/Vienna, ROK, Pacific/Pitcairn, America/Mazatlan, Australia/Queensland, Pacific/Nauru, Europe/Tirane, Asia/Kolkata, SystemV/MST7, Australia/Canberra, MET, Australia/Broken_Hill, Europe/Riga, America/Dominica, Africa/Abidjan, America/Mendoza, America/Santarem, Kwajalein, America/Asuncion, Asia/Ulan_Bator, NZ, America/Boise, Australia/Currie, EST5EDT, Pacific/Guam, Pacific/Wake, Atlantic/Bermuda, America/Costa_Rica, America/Dawson, Asia/Chongqing, Eire, Europe/Amsterdam, America/Indiana/Knox, America/North_Dakota/Beulah, Africa/Accra, Atlantic/Faroe, Mexico/BajaNorte, America/Maceio, Etc/UCT, Pacific/Apia, GMT0, America/Atka, Pacific/Niue, Australia/Lord_Howe, Europe/Dublin, Pacific/Truk, MST7MDT, America/Monterrey, America/Nassau, America/Jamaica, Asia/Bishkek, America/Atikokan, Atlantic/Stanley, Australia/NSW, US/Hawaii, SystemV/CST6, Indian/Mahe, Asia/Aqtobe, America/Sitka, Asia/Vladivostok, Africa/Libreville, Africa/Maputo, Zulu, America/Kentucky/Monticello, Africa/El_Aaiun, Africa/Ouagadougou, America/Coral_Harbour, Pacific/Marquesas, Brazil/West, America/Aruba, America/North_Dakota/Center, America/Cayman, Asia/Ulaanbaatar, Asia/Baghdad, Europe/San_Marino, America/Indiana/Tell_City, America/Tijuana, Pacific/Saipan, SystemV/YST9, Africa/Douala, America/Chihuahua, America/Ojinaga, Asia/Hovd, America/Anchorage, Chile/EasterIsland, America/Halifax, Antarctica/Rothera, America/Indiana/Indianapolis, US/Mountain, Asia/Damascus, America/Argentina/San_Luis, America/Santiago, Asia/Baku, America/Argentina/Ushuaia, Atlantic/Reykjavik, Africa/Brazzaville, Africa/Porto-Novo, America/La_Paz, Antarctica/DumontDUrville, Asia/Taipei, Antarctica/South_Pole, Asia/Manila, Asia/Bangkok, Africa/Dar_es_Salaam, Poland, Atlantic/Madeira, Antarctica/Palmer, America/Thunder_Bay, Africa/Addis_Ababa, Asia/Yangon, Europe/Uzhgorod, Brazil/DeNoronha, Asia/Ashkhabad, Etc/Zulu, America/Indiana/Marengo, America/Creston, America/Punta_Arenas, America/Mexico_City, Antarctica/Vostok, Asia/Jerusalem, Europe/Andorra, US/Samoa, PRC, Asia/Vientiane, Pacific/Kiritimati, America/Matamoros, America/Blanc-Sablon, Asia/Riyadh, Iceland, Pacific/Pohnpei, Asia/Ujung_Pandang, Atlantic/South_Georgia, Europe/Lisbon, Asia/Harbin, Europe/Oslo, Asia/Novokuznetsk, CST6CDT, Atlantic/Canary, America/Knox_IN, Asia/Kuwait, SystemV/HST10, Pacific/Efate, Africa/Lome, America/Bogota, America/Menominee, America/Adak, Pacific/Norfolk, Europe/Kirov, America/Resolute, Pacific/Tarawa, Africa/Kampala, Asia/Krasnoyarsk, Greenwich, SystemV/EST5, America/Edmonton, Europe/Podgorica, Australia/South, Canada/Central, Africa/Bujumbura, America/Santo_Domingo, US/Eastern, Europe/Minsk, Pacific/Auckland, Africa/Casablanca, America/Glace_Bay, Canada/Eastern, Asia/Qatar, Europe/Kiev, Singapore, Asia/Magadan, SystemV/PST8, America/Port-au-Prince, Europe/Belfast, America/St_Barthelemy, Asia/Ashgabat, Africa/Luanda, America/Nipigon, Atlantic/Jan_Mayen, Brazil/Acre, Asia/Muscat, Asia/Bahrain, Europe/Vilnius, America/Fortaleza, Etc/GMT0, US/East-Indiana, America/Hermosillo, America/Cancun, Africa/Maseru, Pacific/Kosrae, Africa/Kinshasa, Asia/Kathmandu, Asia/Seoul, Australia/Sydney, America/Lima, Australia/LHI, America/St_Lucia, Europe/Madrid, America/Bahia_Banderas, America/Montserrat, Asia/Brunei, America/Santa_Isabel, Canada/Mountain, America/Cambridge_Bay, Asia/Colombo, Australia/West, Indian/Antananarivo, Australia/Brisbane, Indian/Mayotte, US/Indiana-Starke, Asia/Urumqi, US/Aleutian, Europe/Volgograd, America/Lower_Princes, America/Vancouver, Africa/Blantyre, America/Rio_Branco, America/Danmarkshavn, America/Detroit, America/Thule, Africa/Lusaka, Asia/Hong_Kong, Iran, America/Argentina/La_Rioja, Africa/Dakar, SystemV/CST6CDT, America/Tortola, America/Porto_Velho, Asia/Sakhalin, Etc/GMT+10, America/Scoresbysund, Asia/Kamchatka, Asia/Thimbu, Africa/Harare, Etc/GMT+12, Etc/GMT+11, Navajo, America/Nome, Europe/Tallinn, Turkey, Africa/Khartoum, Africa/Johannesburg, Africa/Bangui, Europe/Belgrade, Jamaica, Africa/Bissau, Asia/Tehran, WET, Europe/Astrakhan, Africa/Juba, America/Campo_Grande, America/Belem, Etc/Greenwich, Asia/Saigon, America/Ensenada, Pacific/Midway, America/Jujuy, Africa/Timbuktu, America/Bahia, America/Goose_Bay, America/Virgin, America/Pangnirtung, Asia/Katmandu, America/Phoenix, Africa/Niamey, America/Whitehorse, Pacific/Noumea, Asia/Tbilisi, America/Montreal, Asia/Makassar, America/Argentina/San_Juan, Hongkong, UCT, Asia/Nicosia, America/Indiana/Winamac, SystemV/MST7MDT, America/Argentina/ComodRivadavia, America/Boa_Vista, America/Grenada, Asia/Atyrau, Australia/Darwin, Asia/Khandyga, Asia/Kuala_Lumpur, Asia/Famagusta, Asia/Thimphu, Asia/Rangoon, Europe/Bratislava, Asia/Calcutta, America/Argentina/Tucuman, Asia/Kabul, Indian/Cocos, Japan, Pacific/Tongatapu, America/New_York, Etc/GMT-12, Etc/GMT-11, Etc/GMT-10, SystemV/YST9YDT, Europe/Ulyanovsk, Etc/GMT-14, Etc/GMT-13, W-SU, America/Merida, EET, America/Rosario, Canada/Saskatchewan, America/St_Kitts, Arctic/Longyearbyen, America/Fort_Nelson, America/Caracas, America/Guadeloupe, Asia/Hebron, Indian/Kerguelen, SystemV/PST8PDT, Africa/Monrovia, Asia/Ust-Nera, Egypt, Asia/Srednekolymsk, America/North_Dakota/New_Salem, Asia/Anadyr, Australia/Melbourne, Asia/Irkutsk, America/Shiprock, America/Winnipeg, Europe/Vatican, Asia/Amman, Etc/UTC, SystemV/AST4ADT, Asia/Tokyo, America/Toronto, Asia/Singapore, Australia/Lindeman, America/Los_Angeles, SystemV/EST5EDT, Pacific/Majuro, America/Argentina/Buenos_Aires, Europe/Nicosia, Pacific/Guadalcanal, Europe/Athens, US/Pacific, Europe/Monaco]

// Building LocalDateTime by time zone
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador"));
System.out.println(localDateTime1);
// 2019-10-27T00:46:21.268

// Display time in time zone format
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTime1);
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]

Conversion with traditional date processing

JDK annotation

Annotations in JDK5

1. Notes (@)

Annotation is equivalent to a mark. Adding an annotation to a program is equivalent to adding a mark to the program. (new features of JDK1.5).

2. Function

Tell the javac compiler or java development tool... Pass some information to it as a tag.

3. How to understand notes?

An annotation is a class.

Tags can be added to packages, classes, fields, methods, method parameters, and local variables. Multiple annotations can exist at the same time.

There is no ';' at the end of each annotation Or other special symbols.

The basic annotation information required to define annotations is shown below.

@SuppressWarnings("deprecation")  //Compiler warning obsolete (source phase)
@Deprecated                        //Obsolete (Runtime phase)
@Override                        //Rewrite (source phase)
@Retention(RetentionPolicy.RUNTIME)    
//Keep annotations until the program runs. (Runtime phase)
@Target({ElementType.METHOD,ElementType.TYPE})
//Tags can be defined not only on methods, but also on classes, interfaces, enumerations, etc.

be careful:

1) Annotation classes are required to add annotations. RetentionPolicy is an enumeration class with three members.

2) An array can be stored in the Target. Its default value is any element.

  • ElementType.METHOD: indicates that it can only be marked on the method.
  • ElementType.TYPE: indicates that tags can only be defined on classes, interfaces, enumerations, etc

    3) ElementType is also an enumeration class. Members include: ANNOTATION_TYPE (annotation), CONSTRUCTOR (construction METHOD), FIEID (member variable), LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE.

4. About notes

  • Meta annotation: Annotation of annotation (Understanding: add annotation to an annotation class)
  • Metadata: data of data
  • Meta information: information of information

5. Notes are divided into three stages

java source file -- > class file -- > bytecode in memory.

The annotation of Retention has three values: (corresponding to the three stages of annotation respectively)

  • RetentionPolicy.SOURCE
  • RetentionPolicy.CLASS
  • RetentionPolicy.RUNTIME

Note: the default stage for annotations is Class.

6. Attribute type of annotation

Primitive type (that is, eight basic data types), String type, Class type, array type, enumeration type and annotation type.

7. Add attribute for annotation

Value: is a special attribute. If only one value attribute needs to be set when setting the value or other attributes adopt the default value, then value = can be omitted and the set value can be written directly.

For example:@SuppressWarnings("deprecation")

Specify a default value for the attribute (default value):
For example: String value() default "blue"; //Defined in annotation class

Properties of array type:
For example: int[] arrayArr() default {3,4,5,5};//Defined in annotation class
SunAnnotation(arrayArr={3,9,8}) //Set array value
 be careful:If there is only one element in the array attribute, the brace can be omitted from the attribute value part.
For example: SunAnnotation(arrayArr=9)

Properties of enumeration type:
For example: EnumDemo.TrafficLamp lamp()
////The enumeration type attribute is defined in the annotation class. Here, the user-defined enumeration class enumdemo is used Java does not give the relevant code, here is just an example
default EnumDemo.TrafficLamp.RED;

Attributes of annotation type:
For example: MetaAnnotation annotationAttr()
//Define in an annotation class and specify the default value,
//This attribute is associated with the annotation class: metaannotation java, 
default @MetaAnnotation("lhm");
//Set annotation attribute value
@SunAnnotation(annotationAttr=@MetaAnnotation("flx"))

Annotations in Java 8

For annotations (also known as metadata), Java 8 has two main improvements: type annotations and repeated annotations.

1. Type notes

1) Java 8's type annotation extends the scope of annotation use.

Before java 8, annotations can only be used where they are declared. From java 8, annotations can be applied anywhere.

For example:

Create class instance

new @Interned MyObject();

Type mapping

myString = (@NonNull String) str;

In the implements statement

class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }

throw exception declaration

void monitorTemperature() throws@Critical TemperatureException { ... }

be careful:

In Java 8, annotations can be used when declaring variables or parameters when type conversion or even assigning new objects.
Java annotations can support any type.

Type annotations are only syntax, not semantics, and will not affect the compilation time, loading time, and running time of java. In other words, type annotations are not included when compiling into class files.

2) Add ElementType TYPE_ Use and ElementType TYPE_ Parameter (on Target)

Program element type ElementType. Of two new annotations TYPE_ Use and ElementType TYPE_ Parameter is used to describe new occasions of annotation.

  • ElementType.TYPE_PARAMETER indicates that the annotation can be written in the declaration statement of type variables.
  • ElementType.TYPE_USE indicates that the annotation can be written in any statement that uses types (for example, types in declaration statements, generic and cast statements).

For example, the following example.

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

3) Role of type annotation

Type annotations are used to support strong type checking in Java programs. Combined with the third-party plug-in tool Checker Framework (Note: this plug-in is so easy, which will not be introduced here), runtime errors can be detected during compilation (for example, unsupported operation exception, NumberFormatException, NullPointerException, etc. are all runtime errors) to improve code quality. This is what type annotations do.

Note: using the Checker Framework, you can find where type annotations appear and check them.

For example, the following code.

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        @NonNull Object my = new Object();
    }
}

Use javac to compile the above classes: (of course, if you download the Checker Framework plug-in, you don't need to be so troublesome)

javac -processor checkers.nullness.NullnessChecker TestDemo.java

The above compilation is passed, but if you modify the code:

@NonNull Object my = null;

However, if you don't want to use type annotations to detect errors, you don't need a processor. Normal javac testdemo Java can be compiled, but NullPointerException will be reported at runtime.

In order to automatically check such exceptions during compilation, you can check out error exceptions in advance through type annotation and Checker Framework.

Note that the annotation @ NonNull is not supported in Java versions 5, 6 and 7, but the checker framework has a downward compatible solution, which is to annotate the type annotation @ NonNull with / * * /.

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        /*@NonNull*/ Object my = null;
    }
}

In this way, the javac compiler will ignore the comment block, but the javac compiler in the checker framework can also detect the @ NonNull error.
The runtime error can be found at compile time through type annotation + checker framework.

2. Repeat notes

It is allowed to use the same annotation multiple times on the same declaration type (class, property, or method).

One limitation of using annotations in previous versions of Java 8 is that the same annotation can only be used once in the same location and cannot be used multiple times.

Java 8 introduces the repeated annotation mechanism, so that the same annotation can be used multiple times in the same place. The repeating annotation mechanism itself must be annotated with @ Repeatable.

In fact, repeated annotation is not a language change, but a compiler level change. The technical level is still the same.

For example, we can use the following example to specifically compare previous versions of Java 8 with annotations in Java 8.

1) Customize a wrapper class Hints annotation to place a set of specific Hints annotations

@interface MyHints {
    Hint[] value();
}
 
@Repeatable(MyHints.class)
@interface Hint {
    String value();
}

Use wrapper class as container to store multiple annotations (old version method)

@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}

Use multiple annotations (new method)

@Hint("hint1")
@Hint("hint2")
class Person {}

2) The complete class test is shown below.

public class RepeatingAnnotations {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filters {
        Filter[] value();
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Filters.class)
    public @interface Filter {
        String value();
    }
    @Filter("filter1")
    @Filter("filter2")
    public interface Filterable {
    }
    public static void main(String[] args) {
        for (Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) {
            System.out.println(filter.value());
        }
    }
}

Output results:

filter1
filter2

analysis:

Annotation Filter is annotated by @ repeatable (filters. Class). Filters is just a container. It holds filters. The compiler tries to hide its existence from programmers. In this way, the Filterable interface can be annotated twice by Filter.

In addition, the reflective API provides a new method getAnnotationsByType() to return the type of repeated annotation (note that filterable. Class. Getannotation (Filters. Class) will return the filter instance injected by the compiler.

3) Before java 8, there was a solution to reuse annotations, but the readability was not good.

public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
    @Annotations({@MyAnnotation(role="Admin"),@MyAnnotation(role="Manager")})  
    public void doSomeThing(){  
    }  
}

The implementation of Java 8 (another annotation is used to store repeated annotations, and the stored annotation Authorities are used to extend repeated annotations when in use), which is more readable.

@Repeatable(Annotations.class) 
public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
    @MyAnnotation(role="Admin")  
    @MyAnnotation(role="Manager")
    public void doSomeThing(){  
    }  
} 

what? Do not understand? Then another wave!!!

Java 8 enhancements to annotations

Java 8 provides two improvements to annotation processing: repeatable annotations and annotations that can be used for types. Generally speaking, it is relatively simple. Next, we will illustrate the repeated annotations and type annotations in Java 8 in the form of examples.

First, let's define an annotation class BingheAnnotation, as shown below.

package io.mykit.binghe.java8.annotition;

import java.lang.annotation.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description Define annotation
 */
@Repeatable(BingheAnnotations.class)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface BingheAnnotation {
    String value();
}

Note: there is one more @ Repeatable(BingheAnnotations.class) annotation on the BingheAnnotation annotation class than the ordinary annotation. A little partner will ask: what is this? This is the key to defining repeatable annotations in Java 8 Class, don't worry, just keep looking down.

Next, we define a BingheAnnotations annotation class, as shown below.

package io.mykit.binghe.java8.annotation;

import java.lang.annotation.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description Define annotation
 */
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface BingheAnnotations {
    BingheAnnotation[] value();
}

See here, you understand!! Yes, BingheAnnotations is also an annotation class. Compared with BingheAnnotations annotation class, it lacks a @ Repeatable(BingheAnnotations.class) annotation, that is, the definition of BingheAnnotations annotation class is almost no different from ordinary annotations. It is worth noting that in the BingheAnnotations annotation class, we define an array of BingheAnnotation annotation classes, that is, the BingheAnnotations annotation class contains multiple BingheAnnotation annotations. Therefore, specify @ Repeatable(BingheAnnotations.class) on the BingheAnnotation annotation class to indicate that the BingheAnnotation annotation can be reused on classes, fields, methods, parameters, construction methods and parameters.

Next, we create a Binghe class and define an init() method in the Binghe class. On the init method, we repeatedly use the @ BingheAnnotation annotation to specify the corresponding data, as shown below.

package io.mykit.binghe.java8.annotation;

/**
 * @author binghe
 * @version 1.0.0
 * @description Test notes
 */
@BingheAnnotation("binghe")
@BingheAnnotation("class")
public class Binghe {

    @BingheAnnotation("init")
    @BingheAnnotation("method")
    public void init(){

    }
}

At this point, we can test the repeated annotation. Create the class BingheAnnotationTest to test the repeated annotation, as shown below.

package io.mykit.binghe.java8.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author binghe
 * @version 1.0.0
 * @description Test notes
 */
public class BingheAnnotationTest {

    public static void main(String[] args) throws NoSuchMethodException {
        Class<Binghe> clazz = Binghe.class;
        BingheAnnotation[] annotations = clazz.getAnnotationsByType(BingheAnnotation.class);
        System.out.println("The repeated annotations on the class are as follows:");
        Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " "));

        System.out.println();
        System.out.println("=============================");

        Method method = clazz.getMethod("init");
        annotations = method.getAnnotationsByType(BingheAnnotation.class);
        System.out.println("The repeated notes on the method are as follows:");
        Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " "));
    }
}

Run the main() method and output the following result information.

The repeated annotations on the class are as follows:
binghe class 
=============================
The repeated notes on the method are as follows:
init method 

Well, that's all for today. I'm binghe. If you have any questions, you can leave a message below or add my wechat: sun_shine_lyz, I'll pull you into the group, exchange technology together, advance together, and be awesome together~~

Collated from Github: https://github.com/MaRuifu/Ja... Github Author: brother Ma

Keywords: Programming Lambda Programmer stream java8

Added by scm24 on Thu, 06 Jan 2022 01:37:54 +0200