What's new in Java 8 - tutorials

What's new in Java 8 - tutorials

What's new in Java 8

All basic data in the document.

Object data structure:

public class Employee {
	private Long id;
	private String name;
	private Integer age;
	private Double salary;
  // constructor/getter/setter/toString
}

Aggregate data:

public class DataUtils {
	private static List<Employee> emps = new ArrayList<>();

	static {
		emps.add(new Employee(1L, "Morton", 22, 8000.0));
		emps.add(new Employee(2L, "Dahlia", 20, 13000.0));
		emps.add(new Employee(3L, "Babb", 23, 7000.0));
		emps.add(new Employee(4L, "Rice", 21, 15000.0));
		emps.add(new Employee(5L, "Handy", 26, 13000.0));
		emps.add(new Employee(6L, "Rudy", 30, 14000.0));
		emps.add(new Employee(7L, "Grady", 27, 12000.0));
		emps.add(new Employee(8L, "Brenton", 32, 6000.0));
		emps.add(new Employee(9L, "Vinson", 33, 7000.0));
		emps.add(new Employee(10L, "Kemp", 23, 14000.0));
		emps.add(new Employee(11L, "Sebastian", 22, 12000.0));
		emps.add(new Employee(12L, "Evangeline", 24, 18000.0));
		emps.add(new Employee(13L, "Lisette", 29, 8000.0));
		emps.add(new Employee(14L, "Wilkes", 25, 7000.0));
		emps.add(new Employee(15L, "Leach", 33, 6000.0));
		emps.add(new Employee(16L, "Geiger", 32, 12000.0));
		emps.add(new Employee(17L, "Holden", 34, 13000.0));
		emps.add(new Employee(18L, "Thorpe", 26, 15000.0));
		emps.add(new Employee(19L, "Adrienne", 23, 16000.0));
		emps.add(new Employee(20L, "Calderon", 21, 14000.0));
	}
	public static List<Employee> getEmployees() {
		return emps;
	}
}

1. introduction

1.1 general evaluation

  • Find out employees older than 30 from the above data
	/**
	 * Identify employees older than 30
	 */
	@Test
	public void test01() {
		List<Employee> emps = DataUtils.getEmployees();

		List<Employee> result = new ArrayList<>();
		for (Employee emp: emps) {
			if (emp.getAge() > 30) {
				result.add(emp);
			}
		}

		System.out.println(result);
	}
  • From the above data, find out the employees whose wages are more than 10000
	/**
	 * Find out employees with more than 10000 employees
	 */
	@Test
	public void test02() {
		List<Employee> emps = DataUtils.getEmployees();

		List<Employee> result = new ArrayList<>();
		for (Employee emp: emps) {
			if (emp.getSalary() > 10000) {
				result.add(emp);
			}
		}
		System.out.println(result);
	}
  • Identify employees older than 30 and paid more than 10000
	/**
	 * Identify employees over 30 and over 10000
	 */
	@Test
	public void test03() {
		List<Employee> emps = DataUtils.getEmployees();
		List<Employee> result = new ArrayList<>();
		for (Employee emp : emps) {
			if (emp.getSalary() > 10000 && emp.getAge() > 30) {
				result.add(emp);
			}
		}
		System.out.println(result);
	}

From the above code, we can see that most of the three code fragments are the same, and the only change is the judgment method of if. Those same codes are repetitive and redundant. Is there a better way to optimize them?

1.2 use interface

According to the above three requirements, we can abstract an interface, in which an abstract method is provided to implement the judgment method of the class.

public interface EmployeePredicate {
	boolean test(Employee employee);
}

According to the above three requirements, the repetitive code is extracted into a common method:

public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
  List<Employee> result = new ArrayList<>();
  for (Employee emp: emps) {
    if (employeePredicate.test(emp)) {
      result.add(emp);
    }
  }
  return result;
}

Then, for the above three requirements, we only need to use the same method. For different judgments, we only need to implement employeepredict according to the requirements.

  • Find out employees older than 30 from the above data

Interface implementation, implementation judgment mode:

public class EmployeeAgePredicate implements EmployeePredicate {
	@Override
	public boolean test(Employee employee) {
		return employee.getAge() > 30;
	}
}

Use interface:

@Test
public void test04() {
  List<Employee> emps = DataUtils.getEmployees();
  // Call public methods and implement the import interface
  List<Employee> result = queryEmployees(emps, new EmployeeAgePredicate());
  System.out.println(result);
}
  • From the above data, find out the employees whose wages are more than 10000

Interface implementation, implementation judgment mode:

public class EmployeeSalaryPredicate implements EmployeePredicate {
	@Override
	public boolean test(Employee employee) {
		return employee.getSalary() > 10000;
	}
}

Use:

@Test
public void test05() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, new EmployeeSalaryPredicate());
  System.out.println(result);
}
  • Identify employees older than 30 and paid more than 10000

Interface implementation, implementation judgment mode:

public class EmployeeAgeAndSalaryPredicate implements EmployeePredicate {
	@Override
	public boolean test(Employee employee) {
		return employee.getAge() > 30 && employee.getSalary() > 10000;
	}
}

Use:

@Test
public void test06() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, new EmployeeAgeAndSalaryPredicate());
  System.out.println(result);
}

According to the above modification, the redundant repetitive code is eliminated. However, a new problem arises, that is, each time a requirement is added, the corresponding interface implementation class will be added. In this way, there are many implementation classes for too many requirements, which is not desirable. So, how to eliminate this situation?

1.3 using anonymous classes

According to the analysis in 1.2, anonymous classes are provided in Java instead of implementation classes. With anonymous classes, we need to preserve the interface and formula methods.

Interface:

public interface EmployeePredicate {
	boolean test(Employee employee);
}

Public method:

public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
  List<Employee> result = new ArrayList<>();
  for (Employee emp: emps) {
    if (employeePredicate.test(emp)) {
      result.add(emp);
    }
  }
  return result;
}

Use anonymous classes to modify requirements.

  • Find out employees older than 30 from the above data
@Test
public void test07() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
    @Override
    public boolean test(Employee employee) {
      return employee.getAge() > 30;
    }
  });
  System.out.println(result);
}
  • From the above data, find out the employees whose wages are more than 10000
@Test
public void test08() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
    @Override
    public boolean test(Employee employee) {
      return employee.getSalary() > 10000;
    }
  });
  System.out.println(result);
}
  • Identify employees older than 30 and paid more than 10000
@Test
public void test09() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
    @Override
    public boolean test(Employee employee) {
      return employee.getAge() > 30 && employee.getSalary() > 10000;
    }
  });
  System.out.println(result);
}

By using anonymous classes to complete all the above requirements, although it solves the problem of too many implementations, it returns to the problem of repeatability. The emergence of Lambda expression in Java 8 just solves these problems and makes the code look very simple and easy to understand.

1.4 using Lambda expressions

Similarly, using Lambda expressions, we keep the interface and the public methods.

Interface:

public interface EmployeePredicate {
	boolean test(Employee employee);
}

Public method:

public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
  List<Employee> result = new ArrayList<>();
  for (Employee emp: emps) {
    if (employeePredicate.test(emp)) {
      result.add(emp);
    }
  }
  return result;
}

Use Lambda expressions:

  • Find out employees older than 30 from the above data
@Test
public void test10() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, employee -> employee.getAge() > 30);
  System.out.println(result);
}
  • From the above data, find out the employees whose wages are more than 10000
@Test
public void test11() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, employee -> employee.getSalary() > 10000);
  System.out.println(result);
}
  • Identify employees older than 30 and paid more than 10000
@Test
public void test11() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, employee -> employee.getAge() > 30 && employee.getSalary() > 10000);
  System.out.println(result);
}

Through the above modification, it can be found that using Lambda expression makes the code look obviously concise, and the intention to express is also clear.

2. Lambda expression

In the previous chapter, we felt the charm of Lambda expressions, and in this section, we will learn more about Lambda.

Lambda expression, since it is called expression, what does it want to express? In fact, lambda represents a function, which has only parameter list and function body, so lambda expression is also called anonymous function.

2.1 Lambda expression composition

Lambda consists of three parts: parameter list, arrow and function body.

  • parameter list

    1. Like ordinary functions, Lambda also has parameter lists, which are expressed in the same way, for example: (ParamType1 param1, ParamType2 param2,...).
    2. Parameter list parameter types can be omitted, such as (param1, param2,...).
    3. If there is only one parameter list and the parameter type is omitted, you can omit (), such as param1.
    4. Parameter list has no parameters, () cannot be omitted.
  • Arrow

    The parameter list of a Lambda expression is followed by an arrow, such as - >.

  • Function body

    1. Like ordinary functions, Lambda also has function bodies, such as {System.out.println("hello world!")}.
    2. If the function body has only one statement, you can omit {}, such as System.out.println("hello world!")}.
    3. If the function body has only one statement and the function has a return value, omit the return keyword.

Give an example:

  1. Complete parameter list
Comparator<Integer> comparator = (Integer a, Integer b) -> {
  return a - b;
};
  1. Parameter list omits parameter type
Comparator<Integer> comparator = (a, b) ->  {
  return a - b;
};
  1. Parameter list omitted ()
Predicate<Integer> predicate = a -> {
  return a > 3;
};
  1. Parameter list has no parameters
Runnable r = () -> {
			System.out.println("Lambda Expression");
		};
  1. There is only one statement in the function body, and {} can be omitted
Runnable r = () -> System.out.println("Lambda Expression");
  1. There is only one statement in the function body, and the function has a return value. Omit the return keyword
Predicate<Integer> predicate = a -> a > 3;

Through the above example, it is not difficult to see that Lambda expression can be received by an object, so Lambda expression can also be regarded as an object, equivalent to an anonymous class, and the object type receiving Lambda expression is also collectively referred to as a functional interface.

2.2 functional interface

By studying the types of objects that receive lambda expressions in 2.1, we can find that these interfaces have one common feature, that is, there is only one abstract method in these interfaces. So the definition of functional interface is: the interface of only one abstract method is functional interface. Lambda expressions can only work on functional interfaces.

@Functional interface annotation, general functional interfaces, are annotated by @ functional interface annotation. This annotation is used to express that the interface is a functional interface, and also to verify whether the current interface meets the definition of a functional interface. It should be noted that not only the interfaces annotated with @ functional interface are functional interfaces, but also the interfaces satisfying the definition of functional interfaces.

As we defined in Chapter 1, the employeepredict interface has only one abstract method (no @ FunctionalInterface annotation). It is a functional interface, so we can use Lambda expression to improve the code finally.

2.3 how to use Lambda expressions

In Section 2.1, we used several Lambda expressions and received them with a variable, so how do they work?

Take the following as an example:

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

We use the Runnable r variable to receive a Lambda expression. Then R is a Runnable interface type object (polymorphism). Because there is only one abstract method run() in the Runnable interface, the method that the object can call is the run() method. As we said before, Lambda expression expresses a function. In this case, it expresses the run() method. So we only need to call the run() method to make the Lambda work properly.

@Test
public void test01() {
  Runnable r = () -> System.out.println("Lambda Expression");
  r.run();
}

Similarly, for

Comparator<Integer> comparator = (a, b) -> a - b;

We just need to call the abstract method in the Comparator interface:

@Test
public void test() {
  Comparator<Integer> comparator = (a, b) -> a - b;
  int result = comparator.compare(2, 3);
  System.out.println(result);
}

As for the above predicate < integer > predicate = a - > a > 3;, how to use it will not be discussed here.

2.4 type inference

As we said above, lambda expressions can omit parameter types in parameter lists. If parameter types are omitted, how does lambda know what types of parameters are? Lambda expression is used in type inference!

Comparator<Integer> comparator = (a, b) -> a - b;

In the above example, the Comparator interface is used, and the following is the definition of the Comparator interface. In combination with the above example, the generic type of Comparator is T, which is Integer, so the parameter type of compare is T, which is also Integer.

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2); 
}

This is the type inference used in Lambda.

2.5 referencing external variables

Here we need to clarify a concept external variable, which specifies variables that are not attribute to the current function or class. For example, Lambda expression introduces a variable that is not defined in Lambda expression, and anonymous class references a variable that is not defined in the class. All these variables are attribute external variables.

Using anonymous classes, the external variables referenced before Java 1.8 must be of final type. After Java 1.8, the external variables may not use the final keyword, but the referenced external variables are not allowed to be changed.

Lambda is equivalent to an anonymous class, so there are the same restrictions on using external variables for lambda expressions.

@Test
public void test03() {
  String msg = "message";

  // msg = "hello"; cannot be modified
  Runnable r = () -> {
    //	msg = "hello"; cannot be modified
    System.out.println(msg);
  };
  // msg = "hello"; cannot be modified
  r.run();
}

Therefore, there are references to external variables in Lambda, which are not allowed to be changed.

2.6 default functional interface

Some common functional interfaces are also provided by default in the JDK. These interfaces are in the java.util.function package. Here are some basic functional interfaces:

Functional interface Function Descriptor parameter Return type Example (Application in collection)
Predicate<T> bolean test(T t); T boolean Stream<T> filter(Predicate<? super T> predicate);
Comsumer<T> void accept(T t); T void void forEach(Consumer<? super T> action);
Function<T, R> R apply(T t); T R <R> Stream<R> map(Function<? super T, ? extends R> mapper);
Supplier<T> T get() nothing T

These four functional interfaces are the most commonly used.

About packing and unpacking

For example, t in predicate < T > is a generic type, and generic types cannot accept basic types, but using wrapper types will increase memory and computation overhead. Therefore, corresponding functional interfaces are also provided in Java 8 for basic types, mainly including:

Functional interface Function Descriptor
IntPredicate boolean test(int value);
IntConsumer void accept(int value);
IntFunction R apply(int value);
IntSupplier int getAsInt();

The functional interface with int type as an example is the same as above, and other types are the same. Note that the basic types of functional interfaces are only for the commonly used long, int, and double. There is no functional interface for other basic types.

More functional interfaces:

Summary of common functional interfaces:

Functional interface Function Descriptor extend
Predicate<T> T -> boolean IntPredicate,LongPredicate,DoublePredicate
Consumer<T> T -> void IntConsumer,LongConsumer,DoubleConsumer
Function<T, R> T -> R IntFunction<R>,IntToDoubleFunction,IntToLongFunction
LongFunction<R>,LongToDoubleFunction,LongToIntFunction
DoubleFunction<R>,DoubleToIntFunction,DoubleToLongFunction
ToIntFunction<T>,ToDoubleFunction<T>,ToLongFunction<T>
Supplier<T> () -> T BooleanSupplier,IntSupplier,LongSupplier
BinaryOperator<T> (T, T) -> T IntBinaryOperator,LongBinaryOperator,DoubleBinaryOperator
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void ObjIntConsumer<T>,ObjLongConsumer<T>,ObjDoubleConsumer<T>
BiFunction<T, U, R> (T, U) -> R ToIntBiFunction<T, U>,ToLongBiFunction<T, U>,ToDoubleBiFunction<T, U>

3. Default method and static method

3.1 what is the default method

Before Java 8, all methods in an interface must be abstract methods. The class implementing the interface must implement all abstract methods in the interface, unless the class is an abstract class.

Java 8 uses a default keyword to define the default method in the interface. The method has a method body. If the class implementing the interface does not "implement" (override) the method, the implementation class will use the default method. If the implementation class "implements" (overrides) the method, the method in the implementation class will override the default method in the interface.

3.2 why use the default method

For example, the stream method in Java 8. In Java 8, the stream() method is added to the Collection to get the stream, which is an abstract method. If you do not add the default method, but add the stream() abstract method, the custom class that implements Collection should also implement the abstract method. If the custom class already exists before Java 8, then after the Java version is upgraded to Java 8, all the custom Collection implementation classes must implement the abstract method. If a Collection class in a third-party library is referenced in a project, version incompatibility occurs.

An example of using the default keyword in a Collection:

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

3.3 priority of default method

  1. The default method in the interface. If the subclass (interface inheritance, implementation) is overridden, the subclass has a higher priority.

  2. Implement multiple interfaces. There are the same default methods in the interface. You need to indicate which default method defined in the interface is used

public interface A {
	default void show() {
		System.out.println("A ..");
	}
}

public interface B {
	default void show() {
		System.out.println("B ..");
	}
}


public class C implements A, B{
	@Override
	public void show() {
    // Use Super to specify the specific method to be used
		A.super.show();
	}
}

  1. Subclass does not use the default method requirements in the interface to override and abstract the method
// There are abstract methods in the class. The class must be abstract
public abstract class C implements A, B{
	@Override
	public abstract void show();
}

3.4 static method

In Java 8, interfaces can also add static methods.

public interface InterfaceStaticMethod {
	static void staticMethod() {
		System.out.println("This is the static method in the interface");
	}
}

3.5 application of default method and static method in functional interface

  • Preidcate<T>

The interface definition of predicate < T > 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);
    }
}

Through the definition of the interface, we can see that the interface can be used for judgment. The return value of the abstract method test is boolean, and the interface also provides three default methods, namely and, negate, or and a static method isEqual. Let's discuss these methods separately.

test: for judgment

@Test
public void test01() {
  // Receive Lambda expression
  Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
  // Using Lambda
  boolean test = employeePredicate.test(new Employee(1L, "Zhang San", 30, 10000.0));
  System.out.println(test);
}

And: for combination judgment, equivalent to and

@Test
public void test02() {
  // Receive Lambda expression
  Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
  // Using and to combine two predictors
  Predicate<Employee> andPredicate = employeePredicate.and(employee -> employee.getSalary() > 10000);
  // Use
  Assert.assertFalse(andPredicate.test(new Employee(1L, "Zhang San", 30, 10000.0)));
}

Negate: negate the judgment. If negate is not used, it will be false, and vice versa

@Test
public void test03() {
  // Receive Lambda expression
  Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
  // negate inverse
  Assert.assertTrue(employeePredicate.negate().test(new Employee(1L, "Zhang San", 30, 10000.0)));
}

Or: for combination judgment, equivalent to or

@Test
public void test04() {
  // Receive Lambda expression
  Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
  Predicate<Employee> employeePredicate1 = employee -> employee.getSalary() == 10000;
  Predicate<Employee> predicate = employeePredicate.or(employeePredicate1);
  Assert.assertTrue(predicate.test(new Employee(1L, "Zhang San", 30, 10000.0)));
}

isEqual: for object comparison

@Test
public void test05() {
  // Receive Lambda expression
  Employee employee = new Employee(1L, "Zhang San", 30, 10000.0);
  // isEqual receives an object, which is the target object being compared
  Predicate<Employee> predicate = Predicate.isEqual(employee);
  // test receives an object, which is the object to compare with the target object
  Assert.assertTrue(predicate.test(employee));
}
  • Consumer<T>

The definition of the consumer < T > interface is as follows. The abstract method accept of the interface receives an object and does not return a value. The interface also provides a default method andThen

@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); };
    }
}

accept: receives an object, consumes the object, and does not return a value

@Test
public void test06() {
  Consumer<String> consumer = x -> System.out.println(x);
  consumer.accept("hello world !");
}

andThen: receive a Consumer to "consume" again

@Test
public void test07() {
  Consumer<String> consumer = x -> System.out.println(x + "^_^");
  Consumer<String> andThen = consumer.andThen(x -> System.out.println(x.toUpperCase()));
  andThen.accept("hello world!");
}
// So the print result will be:
/**
*	hello world!^_^
* HELLO WORLD!
**/
  • Function<T, R>

Function < T, R > interface is defined 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;
    }
}

The abstract method apply (t t t) of the interface takes a parameter of type T and returns a value of type R. in addition, the interface provides two default methods of compose and andThen and an identity static method.

apply: receives one object and returns another

@Test
public void test08() {
  Function<Integer, Long> integerToLongFunction = i -> i.longValue();
  Long apply = integerToLongFunction.apply(10);
}

Compose: receive a Function and pass the value returned by the Function as a parameter to the Function that currently calls compose.

@Test
public void test09() {
  Function<Integer, Integer> function1 = i -> i * 2;
  Function<Integer, Integer> function2 = function1.compose(x -> x - 1);
  Integer apply = function2.apply(10); // The result is 18 = (10 - 1) * 2
}

andThen: receive a Function and pass the value returned by the Function calling the andThen method as a parameter to the received Function

@Test
	public void test09() {
		Function<Integer, Integer> function1 = i -> i * 2;
		Function<Integer, Integer> function2 = function1.andThen(x -> x - 1);
		Integer result = function2.apply(10); // The result is 19 = 10 * 2 - 1
	}

identity: used to create a variable of type function < T, t >

@Test
public void test11() {
  // To create a Function, both the receive and return values are integers
  Function<Integer, Integer> identity = Function.identity();
  Integer apply = identity.apply(10); // Receive 10, will return 10
}
  • Comparator<T>

There are many methods in the Comparator interface. Let's focus on the following methods:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (
          1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }
    
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }

    public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(true, comparator);
    }
  
    public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(false, comparator);
    }


    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

}

compare: the default abstract method in a functional interface. The return value is int. the return value can be negative, 0, and positive, which represent that the first parameter is less than, equal to, and greater than the second parameter

@Test
public void test12() {
  Comparator<Integer> integerComparator = (x, y) -> x - y;
  int compare = integerComparator.compare(2, 3); // -1
}

reversed: reverse the comparison result

@Test
public void test13() {
  Comparator<Integer> integerComparator = (x, y) -> x - y;
  Comparator<Integer> reversed = integerComparator.reversed();
  int compare = reversed.compare(2, 3);  // 1  
}

The ncompare: this method receives a comparator, which is used to compare the two values when they are equal by using the comparator calling the ncompare.

@Test
public void test14() {
  Comparator<Integer> integerComparator = (x, y) -> x - y;
  Comparator<Integer> comparator = integerComparator.thenComparing((x, y) -> x + 4 - y);
  // 2 is equal to 2, so the rule to compare again is x + 4 - y
  int compare = comparator.compare(2, 2);
}

The ncompare also has several overloaded methods, you can see how to learn the source code.

reverseOrder: reverse comparison order

@Test
public void test15() {
  Comparator<Integer> reverseOrder = Comparator.reverseOrder();
  int compare = reverseOrder.compare(1, 2);
  System.out.println(compare);
}

naturalOrder: normal comparison order, opposite to reverseOrder

@Test
public void test16() {
  Comparator<Character> comparator = Comparator.naturalOrder();
  int compare = comparator.compare('s', 'a');
  System.out.println(compare);
}

nullsFirst: null value is smaller than any value

@Test
public void test17() {
  /**
		 * null Value is less than any non null value,
		 * If the parameter of nullsFirst is not null and the value of comparison is not null, the Comparator passed in is used for comparison
		 * If the parameter of nullsFirst is null, if neither of the compared values is null, the two values are considered equal
		 */
  // When the parameter is null
  Comparator<Integer> nullsFirst = Comparator.nullsFirst(null);
  int compare = nullsFirst.compare(1, 2); // Equal to 0
  compare = nullsFirst.compare(null, 2); // null less than 2, is - 1
  compare = nullsFirst.compare(3, null); // 3 greater than null, 1
  // When the parameter is not null
  Comparator<Integer> nullsFirstNotNull = Comparator.nullsFirst((x, y) -> x * 2 - y);
  compare = nullsFirstNotNull.compare(6, 10); // 2 if 6 * 2 is greater than 10
  compare = nullsFirstNotNull.compare(null, 6); // null less than 6, is - 1
  compare = nullsFirstNotNull.compare(6, null); // 6 greater than null, 1
}

nullsLast: a null value is larger than any value, as opposed to nullsFirst

@Test
public void test18() {
  /**
		 * null Value is greater than any non null value,
		 * If the parameter of nullsFirst is not null and the value of comparison is not null, the Comparator passed in is used for comparison
		 * If the parameter of nullsFirst is null, if neither of the compared values is null, the two values are considered equal
		 */
  // When the parameter is null
  Comparator<Integer> nullsFirst = Comparator.nullsLast(null);
  int compare = nullsFirst.compare(1, 2); // Equal to 0
  compare = nullsFirst.compare(null, 2); // null greater than 2, 1
  compare = nullsFirst.compare(3, null); // 3 less than null, - 1
  // When the parameter is not null
  Comparator<Integer> nullsFirstNotNull = Comparator.nullsLast((x, y) -> x * 2 - y);
  compare = nullsFirstNotNull.compare(6, 10); // 2 if 6 * 2 is greater than 10
  compare = nullsFirstNotNull.compare(null, 6); // null greater than 6, 1
  compare = nullsFirstNotNull.compare(6, null); // 6 less than null, - 1
}

comparing: receive the Function parameter, the object to be compared will return a new object through the Function and then compare

@Test
public void test19() {
  Comparator<Integer> comparator = Comparator.comparing(x -> {
    if (x > 0) {
      x -= 2;
    } else {
      x /= 2;
    }
    return x;
  });
  int compare = comparator.compare(1, 0); 
}

comparing also has multiple overloaded methods.

4. Method reference

Method references can be used to further simplify Lambda expressions, so method references are used in Lambda expressions. The:: symbol is used in method references.

Entity classes used in this chapter:

public class MethodReferenceBean {

	private String flag;

	// Parameterless constructor
	public MethodReferenceBean() {
	}

	// Parametrical constructor
	public MethodReferenceBean(String flag) {
		this.flag = flag;
	}

	// Example method
	public void print(String word) {
		System.out.println(word);
	}

	// Static method
	public static void print(String s1, String s2) {
		System.out.println("[" + s1 + "]" + s2);
	}

}

4.1 constructor references

By instantiating an object a, we usually operate on new A() as follows. Using method references can be simplified to A::new.

@Test
public void test20() {
  // Common writing
  Supplier<MethodReferenceBean> referenceBeanSupplier = () -> new MethodReferenceBean();
  // Constructor references, note that there are no () and - > references
  Supplier<MethodReferenceBean> methodReferenceBeanSupplier = MethodReferenceBean::new;
}

If there is a parameter constructor, the parameters in Lambda are passed in as constructor parameters:

@Test
public void test21() {
  // Common writing
  Function<String, MethodReferenceBean> referenceBeanFunction = x -> new MethodReferenceBean(x);
  // Constructor reference
  Function<String, MethodReferenceBean> referenceBeanFunction1 = MethodReferenceBean::new;
  MethodReferenceBean apply = referenceBeanFunction1.apply("constructor ...");
}

4.2 static method reference

Usually, when we call a static method of a class, it is the class name:: static method name. Using a static method reference, we can have a class name like this:: static method name. As with constructor references, if the referenced static method has parameters, the parameters of the Lambda expression are passed in as static method parameters.

@Test
public void test22() {
  // Common writing
  BiConsumer<String, String> consumer = (x, y) -> MethodReferenceBean.print(x, y);
  // Static method quotation
  BiConsumer<String, String> consumer1 = MethodReferenceBean::print;
  consumer1.accept("hello", "world");
}

4.3 example method reference

Through, when we call an instance method, we call it through the instance object. Instance method. The instance object:: instance method can be called in the method reference in this way. Similarly, if the instance method has parameters, the parameters of the Lambda expression are passed in as instance method parameters.

@Test
public void test23() {
  // instantiation
  MethodReferenceBean methodReferenceBean = new MethodReferenceBean();
  // Common writing
  Consumer<String> consumer = x -> methodReferenceBean.print(x);
  // Method reference
  Consumer<String> consumer1 = methodReferenceBean::print;
  consumer.accept("hello world!");
}

5. Optional

5.1 what is Optional

Optional is a container class provided in Java 8 to solve NPE problems. Optional is defined as follows:

public final class Optional<T> {

    private static final Optional<?> EMPTY = new Optional<>();

    private final T value;

    private Optional() {
        this.value = null;
    }

    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
  
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }
  
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
  
    public T orElse(T other) {
        return value != null ? value : other;
    }
  
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }
  
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
}

From the source code, we can see that there are two properties in Optional, namely, EMPTY and value. By default, EMPTY initializes an Optional object with a value of null. Value is the attribute that the Optional container stores data. Note that the constructor of Optional is private, so you can't create an Optional object by creating an object with new.

The Optional corresponding to the basic data types are OptionalInt, OptionalDouble and optionalrong.

5.2 create

  • empty: get an Optional object with null value
@Test
public void test01() {
  Optional<String> empty = Optional.empty();
}
  • of: create an Optional object that cannot be null. The method takes a parameter as the value value value, which cannot be null
@Test
public void test02() {
  Optional<String> string = Optional.of("Cant not be null");
}
  • Of nullable: creates a nullable Optional object, which takes a parameter as the value value, which can be null
@Test
public void test03() {
  Optional<String> nullable = Optional.ofNullable("Can be null");
}

If a null value is passed in, the resulting Optional object will be equal to Optional.empty().

5.2 obtaining values

  • Get: the value used to get the value in Optional
@Test
public void test04() {
  Optional<Integer> integer = Optional.of(100);
  // Call get method to get the value saved in value
  Integer integer1 = integer.get();
}

Note: before obtaining the value, it is necessary to determine whether the value exists. If the value does not exist, calling get to get the value will throw an exception!

5.3 judge null value

  • isPresent: determines whether the value attribute in Optional is null. If it is not null, it returns true. Otherwise, it returns false
@Test
public void test05() {
  Optional<Integer> integer = Optional.of(100);
  // Value exists returns true 
  boolean present = integer.isPresent();
}
  • ifPresent: this method receives a Comsumer type parameter, which is used to determine whether the value exists, and if so, execute the Comsumer operation
@Test
public void test06() {
  Optional<Integer> integer = Optional.of(100);
  // Print out the value if it exists
  integer.ifPresent(System.out::println);
}

5.4 operating values

  • Filter: filter values. This method receives a Predicate parameter. If the value in Optional meets the parameter conditions, it returns the Optional parameter. Otherwise, it returns the empty Optional parameter. If the original Optional value is null, the newly generated Optional value is null
@Test
public void test07() {
  Optional<Integer> integer = Optional.of(100);
  // Because 100 is not greater than 200, an empty Optional is returned
  Optional<Integer> filter = integer.filter(x -> x > 200);
}
  • map: the operation value returns the new value. This method receives a Function type object, receives the value in Optional, generates a new value, wraps it as Optional and returns it. If the original value of Optional is null, the newly generated value of Optional is null
@Test
public void test08() {
  Optional<Integer> integer = Optional.ofNullable(100);
  // Return the value 100 + 20 in 'Optional' to generate a new 'Optional' return
  Optional<Integer> integer1 = integer.map(x -> x + 20);
}
  • flatMap: receives a Function parameter, which returns an Optional object
@Test
public void test09() {
  Optional<Integer> integer = Optional.of(100);
  // Optional < long > longoptional is generated by the parameter definition of flatMap
  Optional<Long> longOptional = integer.flatMap(x -> Optional.ofNullable(x.longValue()));
}

5.4 operation null value

  • orElse: returns the parameter received by the method when the value in Optional is null
@Test
public void test10() {
  Optional<Integer> integer = Optional.ofNullable(null);
  // Because the value in Optional is null, a new value of 20 will be generated
  Integer integer1 = integer.orElse(20);
}
  • orElseGet: this method takes a Supplier parameter to return the value generated by the Supplier when the value in Optional is null
@Test
public void test11() {
  Optional<Integer> integer = Optional.ofNullable(null);
  Integer integer1 = integer.orElseGet(() -> 20);
}
  • orElseThrow: this method takes a Supplier parameter, which generates an exception to throw when the value in Optional is null
@Test
public void test12() {
  Optional<Integer> o = Optional.ofNullable(null);
  o.orElseThrow(RuntimeException::new);
}

6. Stream

In Java, collection is the most commonly used data structure. Usually, when we operate a combination, we do it through iterative traversal, which is implemented by our own code. Stream is provided in Java 8 to facilitate collection operations.

6.1 what is Stream

In short, Stream is a new API for operating collections in Java 8. Using Stream API can make our operations on collections only focus on data changes, rather than iterative processes. For example, we select employees older than 30 from the Employee set to form a new set. We need to do this:

@Test
public void test01() {
  List<Employee> employees = DataUtils.getEmployees();
  List<Employee> result = employees.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
}

There is no for loop, and we don't pay attention to the iterative process any more. In this requirement, we only need to pay attention to filter conditions older than 30, filter (x - > x.getage() > 30) and results form a new collection, collect(Collectors.toList()).

6.2 internal iteration

The iterative operation of the flow is carried out in the method implementation, and we do not need to perform the display iterative operation. So when we use flow operations to aggregate data, we only need to focus on the data itself, rather than iterative operations.

6.3 primary consumption

After obtaining the flow, the operation on the flow is one-time, that is, the flow can only be operated once. If the flow that has been operated is operated again, an exception will be thrown:

@Test
public void test02() {
  List<Employee> employees = DataUtils.getEmployees();
  // Acquisition stream
  Stream<Employee> stream = employees.stream();
  // Stream is operated once
  Set<Employee> collect = stream.collect(Collectors.toSet());
  // Operating the flow again will throw an exception java.lang.IllegalStateException: stream has already been operated upon or closed
  TreeSet<Employee> collect1 = stream.collect(Collectors.toCollection(TreeSet::new));
}

6.4 implementation mechanism

The implementation mechanism involves two concepts: lazy evaluation and early evaluation.

Lazy evaluation actually does not execute when executing code, which is not easy to understand. Let's use the following example to illustrate:

@Test
public void test03() {
  List<Employee> employees = DataUtils.getEmployees();
  Stream<Employee> employeeStream = employees.stream().filter(x -> {
    System.out.println(x.getAge());
    return x.getAge() > 30;
  });
}

When executing the above code, we will find that System.out.println(x.getAge()); is not executed, so we call filter an inert evaluation operation, and the corresponding code operation will not be executed during the inert evaluation. The final result returns the Stream stream.

Early evaluation will eventually generate corresponding results when it is executed. Perform lazy evaluation instead of returning Stream. See the following example:

@Test
public void test04() {
  List<Employee> employees = DataUtils.getEmployees();
  long employeeStream = employees.stream().filter(x -> {
    System.out.println(x.getAge());
    return x.getAge() > 30;
  }).count();
}

When executing the above code, we will find that System.out.println(x.getAge()); has been executed, so we call count as early evaluation operation. Early evaluation period will perform so lazy evaluation and get the final result.

According to early evaluation and lazy evaluation, flow operation is also divided into intermediate operation and terminal operation.

6.5 access flow

  • of

Method definition:

// Generate a stream of the corresponding type from the incoming value
public static<T> Stream<T> of(T... values)

// Stream a single object
public static<T> Stream<T> of(T t)

Use:

Stream<Integer> of1 = Stream.of(1, 2, 3, 4, 5);

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Stream<List<Integer>> of2 = Stream.of(integers);
  • iterate

The corresponding flow is generated by iteration. The signature of the method is as follows, where seed is the initial value and f is the iteration process:

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

Use:

// The value will be generated from 1, followed by 2.
Stream<Integer> i = Stream.iterate(0, x -> x + 2);

Note that this generates an infinite flow, which is used with limit.

  • Arrays.stream()

To generate a stream from an array:

int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
  • generate

Definition:

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

Use:

// Receive a Supplier generated value
Stream<Double> generate = Stream.generate(Math::random);

Note that the flow generated in this way is also infinite.

6.6 intermediate operation

The intermediate operation will define the process of processing flow. When the terminal operates, the intermediate operation will execute and get the final result. Note that the intermediate operation returns a new flow.

6.6.1 screening

  • Filter: receive a Predicate parameter to filter the elements in the flow according to the judgment criteria defined in the Predicate

Definition:

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

Usage:

@Test
public void test05() {
  // filter receives a Predicate, so this is based on whether the age of employees in the collection is greater than 30
  List<Employee> result = employees.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
}
  • distinct: filter out the repeated elements in the stream and remove them, so that the elements in the collection are not repeated (de duplicated)

Definition:

Stream<T> distinct();

Use:

@Test
public void test06() {
  // Remove duplicate elements from collection
  List<Employee> collect = employees.stream().distinct().collect(Collectors.toList());
}

6.6.2 slice

  • limit: receives a long value and returns a stream no longer than the specified value

Definition:

Stream<T> limit(long maxSize);

Use:

@Test
public void test07() {
  // Return the first three elements in the flow, if not, return all elements
  List<Employee> collect = employees.stream().limit(3).collect(Collectors.toList());
}
  • skip: receives a value of type long and returns a flow other than the specified value length element, which is opposite to limit

Definition:

Stream<T> skip(long n);

Use:

@Test
public void test08() {
  // Skip the first three elements and return the flow of all elements except these three
  List<Employee> collect = employees.stream().skip(3).collect(Collectors.toList());
}

6.6.3 mapping

  • Map: map the elements in the stream to a new value

Definition:

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

Use:

// Make a new stream of employee names in the collection  
Stream<String> stringStream = employees.stream().map(Employee::getName);
  • flatMap: receive a Function, which will generate a new Stream return

Definition:

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

Use:

@Test
public void test10() {
  // Preparation data
  List<List<Employee>> emp = new ArrayList<>();
  emp.add(employees.subList(1, 3));
  emp.add(employees.subList(3, 6));
  emp.add(employees.subList(6, 9));
  emp.add(employees.subList(9, 12));
  // Each element in the flow is generated into a new flow, and then one element in the new flow is intercepted, and these flows are combined into one
  List<Employee> employee = emp.stream().flatMap(x -> x.stream().limit(1)).collect(Collectors.toList());
}

This method may be a little difficult for new contacts to understand.

6.6.4 sorting

  • sorted

    The sorted method provided by the flow can be used to sort the elements in the collection. Note that if the elements in the collection are custom classes, the class implements the compatible interface.

    Definition of the complete Employee class:

    // Implement compatible interface
    public class Employee implements Comparable{
    	private Long id;
    	private String name;
    	private Integer age;
    	private Double salary;
    
    	public Employee() {
    	}
    
    	public Employee(Long id, String name, Integer age, Double salary) {
    		this.id = id;
    		this.name = name;
    		this.age = age;
    		this.salary = salary;
    	}
    
    	// getter/setter/toString
    
      // Implement CompareTo method
    	@Override
    	public int compareTo(Object o) {
    		if (o instanceof Employee) {
    			Employee e = (Employee) o;
    			return this.age - e.getAge();
    		} else {
    			throw new RuntimeException("Incorrect type");
    		}
    	}
    }
    

    If the compareTo method in the Comparable interface is implemented, the sorted method in the flow will be used to sort according to this rule:

    List<Employee> collect = emps.stream()
      														// Sort by compareTo method in Comparable
      														.sorted()
      														.collect(Collectors.toList());
    
  • sorted(Comparator<? super T> comparator)

    The user-defined class can also not implement the Comparable interface. You can use the sorted (Comparator <? Super T > Comparator) method of the stream to pass in a Comparator's Lambda expression to sort, for example, by the Age attribute of Employee:

    // Write method 1: Lambda expression of Comparator interface
    List<Employee> collect = emps.stream()
    				.sorted((x, y) -> x.getAge() - y.getAge())
    				.collect(Collectors.toList());
    // Write method 2: use the default method in the Comparator interface
    List<Employee> collect = emps.stream()
    				.sorted(Comparator.comparing(employee -> employee.getAge()))
    				.collect(Collectors.toList());
    // Method 3: use method reference
    List<Employee> collect = emps.stream()
    				.sorted(Comparator.comparingInt(Employee::getAge))
    				.collect(Collectors.toList());
    

6.7 terminal operation

The terminal operation will generate the final result.

6.7.1 matching

  • anyMatch: judge whether any of the elements meet the given conditions

Definition:

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

Use:

@Test
public void test11() {
  // Returns true if there is only one element x.getage() > 30 in the stream
  boolean b = employees.stream().anyMatch(x -> x.getAge() > 30);
}
  • allMatch: judge whether all elements meet the given conditions

Definition:

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

Use:

@Test
public void test12() {
  // All elements in the stream satisfy x.getage() > 30 to return true
  boolean b = employees.stream().allMatch(x -> x.getAge() > 30);
}
  • noneMatch: determine whether all elements do not meet the given conditions

Definition:

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

Use:

@Test
public void test13() {
  // All elements in the stream do not satisfy x.getage() > 30 to return true
  boolean b = employees.stream().noneMatch(x -> x.getAge() > 30);
}

6.7.2 lookup

  • findFirst: finds the first element and returns

Definition:

Optional<T> findAny();

Use:

@Test
public void test14() {
  // Return the first element in employees. If employee is empty, the returned Optional element is empty
  Optional<Employee> first = employees.stream().findFirst();
}
  • findAny: find returns any element

Definition:

Optional<T> findAny();

Use:

@Test
public void test15() {
  // Return any element in employees. If employees is empty, the returned Optional is empty
  Optional<Employee> any = employees.stream().findAny();
}

6.7.3 reduction

  • Reduce: reduce the elements in the stream to a value

Definition:

// Calculate according to the way given by accumulator
Optional<T> reduce(BinaryOperator<T> accumulator);
// identity as the initial value, and then participate in the operation in the way that the accumulator writes to you
T reduce(T identity, BinaryOperator<T> accumulator);

Use:

@Test
public void test16() {
  // Reduce (binary operator < T > accumulator); map the age of employees, and perform the sum operation in reduce
  Optional<Integer> reduce = employees.stream().map(Employee::getAge).reduce(Integer::sum);
  // Same as the above example, only a base of 10 is added
  Integer reduce1 = employees.stream().map(Employee::getAge).reduce(10, Integer::sum);
}
  • max: maximum

Definition:

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

Use:

@Test
public void test17() {
  Optional<Employee> max = employees.stream().max(Comparator.comparingDouble(Employee::getSalary));
}
  • min: minimum

Definition:

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

Use:

@Test
public void test18() {
  Optional<Employee> min = employees.stream().min((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
}
  • count: number

Definition:

long count();

Use:

@Test
public void test19() {
  // Count the number of elements in the collection
  long count = employees.stream().count();
}

6.7.4 traversal

  • foreach

Definition:

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

Use:

@Test
public void test26() {
  employees.stream().forEach(System.out::println);
}

6.8 collect

6.8.1 generating sets

As mentioned earlier. collect(Collectors.toList()), we will generate a result of the flow collection after a series of operations on the flow. Let's learn more about the use of collect.

  • For example, generate List collection Collectors.toList
List<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.toList());
  • Generate Set Collectors.toSet
Set<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.toSet());

Whether generating a List or a Set is determined by the compiler, we can also specify which type of Set to generate.

  • Generate collection collectors.tocollection of the specified type (xxx)
TreeSet<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.toCollection(TreeSet::new));
ArrayList<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.toCollection(ArrayList::new));
  • Generate Map collection Collectors.toMap()
Map<String, Employee> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.toMap(Employee::getName, e -> e));

The toMap method can generate Map results. The first parameter is the Key of Map, and the second parameter is the Value of Map.

6.8.2 generating values

Finally, streams can generate not only sets, but also corresponding values. For example, the number of elements, the maximum value, the minimum value, and so on.

  • Number of calculation elements Collectors.counting()
Long count = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.counting());

This method counts the number of elements in the stream

  • Calculate the average Collectors.averagingDouble()
Double average = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.averagingDouble(Employee::getAge));

This method is based on the given attribute statistics. As mentioned above, the averagingDouble and similar averagingInt and averagingLong are used.

  • Calculate total Collectors.summingDouble()
Double sum = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.summingDouble(Employee::getSalary));

This method is based on the given attribute statistics. As used above, summingDouble also has summingInt and summingLong.

  • Calculate the maximum value of Collectors.maxBy()
Optional<Employee> max = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.maxBy(Comparator.comparingInt(Employee::getAge)));

This method is to find the maximum value of a given attribute, and a comparison method needs to be passed in. Note that an Optional object is generated here, and what Optional receives is the type of element in the stream.

  • Calculate minimum Collectors.minBy()
Optional<Employee> max = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.minBy(Comparator.comparingInt(Employee::getAge)));

This method is to find out the minimum value of a given attribute and receive a comparison method. Note that an Optional object is generated here, and what Optional receives is the type of element in the stream.

  • Generate summary statistics
DoubleSummaryStatistics summaryStatistics = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.summarizingDouble(Employee::getSalary));

This method is to generate statistics of the given element attributes in the flow, including quantity, sum, minimum, maximum and average values, such as:

{count=5, sum=44000.000000, min=6000.000000, average=8800.000000, max=13000.000000}

Similarly, summarizingDouble is used here, and the corresponding summarizingInt and summarizingLong are also used here.

6.8.3 block

  • Collectors.partitioningBy()

The collect method can also block the data in the stream according to a certain judgment rule. For example, block employees by whether they are older than 30:

Map<Boolean, List<Employee>> collect = emps.stream().collect(Collectors.partitioningBy(employee -> employee.getAge() > 30));

The result returns a Map. The Key of the Map is the judgment condition true or false, and the Value is the data separated according to the judgment condition.

6.8.4 grouping

  • Collectors.groupingBy()

Grouping is different from partitioning by true and false. Grouping can be divided by any value. For example, to group employees by age, divide employees of the same age into the same group:

Map<Integer, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getAge));

The result returns a Map. The Key of the Map saves the Value to divide the group. The Value saves the result.

The above method is similar to:

Map<Integer, List<Employee>> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.groupingBy(Employee::getAge, Collectors.toList()));

That is, groupingBy method has multiple overloaded methods. The method of one parameter will take the incoming parameter as the Key, and the corresponding set will be saved in the Map as the Value.

Given collector

There are two parameter methods. The first parameter is the Key of Map, and the second parameter receives a collector, as follows:

Map<Integer, Long> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.groupingBy(Employee::getAge, Collectors.counting()));

Multilevel grouping

@Test
public void test29() {
  Map<String, Map<String, List<Employee>>> collect = employees.stream().collect(Collectors.groupingBy(e -> e.getAge() > 30 ? "Old age" : "normal",
                                                                                                      Collectors.groupingBy(e -> e.getSalary() > 10000 ? "High salary" : "General salary")));
  System.out.println(collect);
}

6.8.5 generating strings

  • Collectors.joining()

Generate a string of elements in the stream according to some rules. For example, generate a string output for the names of employees older than 30. Each name is separated by "," ", and wrapped with" [","] ".

String collect = emps.stream().filter(e -> e.getAge() > 30).map(Employee::getName).collect(Collectors.joining(",", "[", "]"));

Note: there are three overloaded methods of joining, namely, no parameter, one parameter and three parameters, in which the generated string of elements without parameters is directly connected; the generated string of elements with one parameter is connected through the characters received by the parameter; the three parameters are used as the connection method, prefix and suffix of the generated string.

6.8.6 mapping

  • Collectors.mapping()
List<Integer> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.mapping(Employee::getAge, Collectors.toList()));

This method can map one element to another, as above, map all age attributes of Employee to a List set.

6.8.7 reduction

  • Collectors.reducing()

Definition:

// No initial value
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {
        class OptionalBox implements Consumer<T> {
            T value = null;
            boolean present = false;

            @Override
            public void accept(T t) {
                if (present) {
                    value = op.apply(value, t);
                }
                else {
                    value = t;
                    present = true;
                }
            }
        }
// Given initial value
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
        return new CollectorImpl<>(
                boxSupplier(identity),
                (a, t) -> { a[0] = op.apply(a[0], t); },
                (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
                a -> a[0],
                CH_NOID);
    }
  
// Given initial values and conversion functions
public static <T, U> Collector<T, ?, U> reducing(U identity,
                                Function<? super T, ? extends U> mapper,
                                BinaryOperator<U> op) {
        return new CollectorImpl<>(
                boxSupplier(identity),
                (a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); },
                (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
                a -> a[0], CH_NOID);
    }

Use:

@Test
public void test28() {
  // Return the most paid employee
  Optional<Employee> collect = employees.stream().collect(Collectors.reducing((e1, e2) -> e1.getSalary() >= e2.getSalary() ? e1 : e2));
  
  // Initial value
  Employee e = new Employee(33L, "Tom", 28, 20000.0);
  // Given the initial value, the initial value will participate in the subsequent operation and return the employee with the most salary
  Employee collect1 = employees.stream().collect(Collectors.reducing(e, (x, y) -> x.getSalary() > y.getSalary() ? x : y));
  // Given the initial value, convert it to salary to calculate and add the value
  Double collect2 = employees.stream().collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum));
}

6.9 numerical flow

The basic type has a fixed byte length in memory. The packing type is an object, and its byte length is greater than the basic type. If the basic types all adopt the packing type

Taking part in the operation will cause more system memory overhead.

The conversion of basic type to packing type (boxing) and packing type to basic type (unpacking) will also cause additional calculation overhead.

The corresponding Java 8 provides corresponding streams for the commonly used double, int and long, which are doublestrream, IntStream and LongStream respectively.

Take IntStream as an example to learn how to use Stream operations corresponding to basic types.

2.5.1 generating flow

  1. of(int value) or of (int... Values):
IntStream of1 = IntStream.of(3);
IntStream of2 = IntStream.of(3, 2, 4, 8);

The method is to build the incoming parameters into a corresponding flow.

  1. range(int startInclusive, int endExclusive):
// Value 1-10 cannot be taken to 10
IntStream range = IntStream.range(1, 10);

Generates the value of the specified range (startInclusive to endExclusive), the number generated does not contain the second parameter.

  1. rangeClosed(int startInclusive, int endExclusive):
// Values 1-10 can be taken as 10
IntStream rangeClosed = IntStream.rangeClosed(1, 10);

Generates the value of the specified range (startInclusive to endExclusive), and the number generated contains the second parameter.

  1. mapToInt
@Test
public void test() {
  IntStream intStream = employees.stream().mapToInt(Employee::getAge);
}

Convert flow of wrapper type to flow of basic type.

2.5.2 generate statistics

  1. summaryStatistics():
// Get statistics
IntSummaryStatistics statistics = of.summaryStatistics();
// Get the average value of the elements in the flow from the statistics
statistics.getAverage();
// Get the number of elements in the stream from the statistics
statistics.getCount();
// Get the maximum value of the element in the flow from the statistics
statistics.getMax();
// Get the minimum value of the element in the flow from the statistics
statistics.getMin();
// Get the sum of the elements in the flow from the statistics
statistics.getSum();
// Add an element and the statistics will be recalculated
statistics.accept(20);
// Combine another statistic and the statistic will be recalculated
statistics.combine(statistics);

2.5.3 packing type and basic type Stream operation

List<Integer> integerStream = Arrays.asList(1, 2, 3, 4, 5);

integerStream.stream()
  .mapToInt(x -> x)
  .min();

The mapToXxx method can convert the flow of the original wrapper class to the corresponding basic type flow. For example, if the element in a Stream is of Integer type, the corresponding method is IntStream maptoint (tointfunction <? Super T > mapper); and the generated method is IntStream.

Similarly, Long type corresponds to LongStream maptolong (tolongfunction <? Super T > mapper);, which generates LongStream.

2.5.4 basic type Stream operation

Note that the flow can only be terminated once.

  1. count()
IntStream intStream = IntStream.range(1, 10);
intStream.count();

Gets the number of elements in the stream.

  1. sum()
IntStream intStream = IntStream.range(1, 10);
intStream.sum();

Calculates the sum of the elements in the flow.

  1. average()
IntStream intStream = IntStream.range(1, 10);
intStream.average();

Calculates the average number of elements in the flow.

  1. min()
IntStream intStream = IntStream.range(1, 10);
intStream.min();

Calculates the minimum value of an element in the flow.

  1. max
@Test
public void test22() {
  IntStream intStream = IntStream.range(1, 10);
  intStream.max();
}

Calculates the maximum value in the flow.

  1. boxed()
IntStream intStream = IntStream.range(1, 10);
Stream<Integer> boxed = intStream.boxed();

Stream converted to the corresponding packaging type.

6.10 parallel flow

Previously, the streams we mentioned are serial streams, and all stream operations are performed in sequence. Now we want to talk about the parallel flow, which is to divide the serial flow into several parts. Each part is executed in parallel at the same time, and the results are summarized after execution.

If we already have a serial stream, we only need to call the parallel() method to get the parallel stream from this serial stream:

List<Employee> employees = emps.stream().parallel().filter(x -> x.getAge() > 30).collect(Collectors.toList());

If you get parallel streams directly from a collection, you can call the parallelStream() method:

List<Employee> employees = emps.parallelStream().filter(employee -> employee.getAge() > 30).collect(Collectors.toList());

When to use parallel flow and when to use serial flow are determined by code execution speed. It should be noted that parallel flow is not necessarily faster than serial flow, which depends on code execution environment and complexity.

From the above example, we can also see that the operation of parallel flow and serial flow is the same, but the way to get the flow is different.

Parallel flow operation shares variables, which will affect the calculation results.

@Test
public void test30() {
  A a = new A();
  LongStream.range(1, 1000000000).parallel().forEach(x -> a.add(x));
  // Every time, the results are different
  System.out.println(a.i);
}

class A {
  public long i = 0;
  public void add(long j) {
    i += j;
  }
}

7. Time and date

7.1 TemporalField and TemporalUnit

There are some important interfaces in the new time and date API in Java 8, which are TemporalField and TemporalUnit. They are used in many places. The common implementation classes of these two interfaces are ChronoField and chrononunit.

7.1.1 ChronoField

ChronoField is an enumeration class, in which there are many enumeration values. For example, ChronoField.day ﹣ of ﹣ month indicates the day of the month.

@Test
public void test42() {
  ChronoField dayOfMonth = ChronoField.DAY_OF_MONTH;
  // Get range value
  ValueRange range = dayOfMonth.range();
  // Get the base unit day of month so the value is Days
  TemporalUnit baseUnit = dayOfMonth.getBaseUnit();
  // Get the range unit day of month so the value is Months
  TemporalUnit rangeUnit = dayOfMonth.getRangeUnit();
  // For time
  boolean timeBased = dayOfMonth.isTimeBased();
  // Can be used for date
  boolean dateBased = dayOfMonth.isDateBased();
}

7.2.2 ChronoUnit

Chrononunit is an enumeration class with many enumeration values, such as chrononunit.days for days.

@Test
public void test43() {
  ChronoUnit days = ChronoUnit.DAYS;
  // Based on date or not
  boolean dateBased = days.isDateBased();
  // Time based or not
  boolean timeBased = days.isTimeBased();
  // Get duration
  Duration duration = days.getDuration();
}

7.2 date, time and datetime

Java 8 provides a new time and date API, in which java.time.LocalDate is used for operation date, java.time.LocalTime is used for operation time, and java.time.LocalDateTime is used for operation date and time.

Although date, time or datetime are different classes, their creation and operation methods are similar. In these three classes, several methods use TemporalField and TemporalUnit, and also provide the isSupported method to determine whether TemporalField and TemporalUnit are supported.

boolean supported = now.isSupported(ChronoField.NANO_OF_DAY);
boolean supported1 = now.isSupported(ChronoUnit.DAYS);

7.3 create

All three provide the same creation method, in which the of method receives the specified value for creation, the parse method specifies the format type for value creation, and the now method creates the current result.

7.3.1 about date

@Test
public void test01() {
  // Creation date, specify year, month, day
  LocalDate date1 = LocalDate.of(2020, 1, 10);
  LocalDate date2 = LocalDate.of(2020, Month.JANUARY, 30);
  // Create date, specify format date
  LocalDate parse = LocalDate.parse("2020-10-20"); // default format
  LocalDate parse1 = LocalDate.parse("2020/10/20", DateTimeFormatter.ofPattern("yyyy/MM/dd")); // Specified format
  // Get current date
  LocalDate now = LocalDate.now(); // System default time zone
  LocalDate.now(ZoneId.of("Asia/Shanghai")); // Designated time zone
  LocalDate.now(Clock.systemDefaultZone()); // System default time zone
  // Specify year and day of year
  LocalDate date = LocalDate.ofYearDay(2020, 1);
}

7.3.2 about time

@Test
public void test02() {
  // Creation time, specifying hours, minutes, seconds and nanoseconds for creation, of method has multiple overloads
  LocalTime of = LocalTime.of(0, 1, 2, 3);
  // Create time, specify format time
  LocalTime p = LocalTime.parse("20:20:20"); // default format
  LocalTime p1 = LocalTime.parse("10:20", DateTimeFormatter.ofPattern("HH:mm"));// Specified format
  // Get current time
  LocalTime now = LocalTime.now(); // System default time zone
  LocalTime.now(ZoneId.of("Asia/Shanghai")); // Designated time zone
  LocalTime.now(Clock.systemDefaultZone()); // System default time zone
  // Time stamp acquisition in a day 
  LocalTime localTime = LocalTime.ofSecondOfDay(1 * 24 * 60 * 60 - 1); // Second of the day
  LocalTime localTime1 = LocalTime.ofNanoOfDay(1L * 60 * 60 * 1000_000_000); // Millisecond of the day
}

7.3.3 about date and time

@Test
public void test03() {
  LocalDate nowDate = LocalDate.now();
  LocalTime nowTime = LocalTime.now();
  // of method has multiple constructors
  LocalDateTime of = LocalDateTime.of(nowDate, nowTime); // LocalDate LocalTime
  LocalDateTime.of(2010, 10, 20, 17, 50); // Mm / DD / yyyy, H / m, 
 	// Create by format
  LocalDateTime parse = LocalDateTime.parse("2020-01-10T18:01:50.722"); // default format
  LocalDateTime parse1 = LocalDateTime.parse("2020-01-20 20:20:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));	// Custom format
  // Get current
  LocalDateTime now = LocalDateTime.now(); // System default time zone
  LocalDateTime.now(ZoneId.of("Asia/Shanghai")); // Designated time zone
  LocalDateTime.now(Clock.systemDefaultZone()); // System default time zone
  // Timestamps seconds, milliseconds (not timestamps), time zone
  LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(1578972303, 621000000, ZoneOffset.ofHours(8));
}

7.4 obtaining values

7.4.1 about date

  • Calendar system, chronology system
@Test
public void test01() {
  LocalDate localDate = LocalDate.now();
  // Get calendar system default ISO
  IsoChronology chronology = localDate.getChronology();
  // Chronology system default CE (chronology)
  Era era = localDate.getEra();
}
  • Data value
@Test
public void test02() {
  LocalDate localDate = LocalDate.now();
  // Year of acquisition
  int year = localDate.getYear();
  // Get Month object
  Month month = localDate.getMonth();
  // Get int value of monthly score
  int monthValue = localDate.getMonthValue();
  // Gets the day of the date on the day of the year
  int dayOfYear = localDate.getDayOfYear();
  // Gets the day in the date on the day of the month
  int dayOfMonth = localDate.getDayOfMonth();
  // Gets the day in the day of the week
  DayOfWeek dayOfWeek = localDate.getDayOfWeek();
  // Specified unit acquisition
  int i = localDate.get(ChronoField.DAY_OF_MONTH);
  // Get the specified unit to get the long value
  long aLong = localDate.getLong(ChronoField.DAY_OF_WEEK);
}
  • time stamp
Test
public void test03 () {
		LocalDate now = LocalDate.now();
		// Time stamp unit day
		long l = now.toEpochDay();
)
  • Range
@Test
public void test44() {
  LocalDate now = LocalDate.now();
  // The range of days in the month of the date is as follows: 1-31
  ValueRange range = now.range(ChronoField.DAY_OF_MONTH);
  // Days in the year 366
  int i = now.lengthOfYear();
  // Days in the month 31
  int i1 = now.lengthOfMonth();
}

7.4.2 about time

  • Data value
@Test
public void test45() {
  LocalTime now = LocalTime.now();
  // Acquisition hours
  int hour = now.getHour();
  // Get minutes
  int minute = now.getMinute();
  // Get seconds
  int second = now.getSecond();
  // Getting nanoseconds
  int nano = now.getNano();
  // Specified unit acquisition
  int i = now.get(ChronoField.MINUTE_OF_HOUR);
  // Get by specified unit, get long value
  long aLong = now.getLong(ChronoField.SECOND_OF_MINUTE);
}
  • time stamp
@Test
public void test46() {
  LocalTime now = LocalTime.now();
  // Day time stamp nanosecond
  long l = now.toNanoOfDay();
  // Time stamp seconds of the day
  int i = now.toSecondOfDay();
}
  • Range
@Test
public void test47() {
  LocalTime now = LocalTime.now();
  // Get the range of hours in the day 0 - 23
  ValueRange range = now.range(ChronoField.HOUR_OF_DAY);
}

7.4.3 about date and time

Date time integrates all methods of date and time, that is, it can be used to obtain time-related data or date related data, for example:

@Test
public void test48() {
  LocalDateTime now = LocalDateTime.now();
  // What day of the year is the date related day
  int dayOfYear = now.getDayOfYear();
  // Get hour value
  int hour = now.getHour();
}

7.5 format

7.5.1 DateTimeFormatter

To build a DateTimeFormatter object:

@Test
public void test49() {
  // Specified format
  DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  // Specify format, region
  DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.CHINA);
  // It is used for LocalDate format FormatStyle enumeration class. It defines SHORT in various formats as 20-1-14, or 2020-01-14
  DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
  // Used for LocalTime formatting FormatStyle enumeration class, defining multiple formats SHORT as 3:06 p.m. or 15:06 p.m
  DateTimeFormatter formatter4 = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
  // It is used for LocalDateTime to format FormatStyle enumeration class. It defines multiple formats, SHORT, as 20-1-14, 3:08 p.m., 2020-01-14, 15:06 p.m
  DateTimeFormatter formatter5 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
}

7.5.2 format date or time

@Test
public void test() {
  DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

  DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

  // Date format
  String format = LocalDate.now().format(dateFormatter);
  // Time format
  String format1 = LocalTime.now().format(timeFormatter);
  // Date time format
  String format2 = LocalDateTime.now().format(formatter);

  // Format build date or time
  LocalDate parse = LocalDate.parse("2020/01/20", dateFormatter);
  LocalTime parse1 = LocalTime.parse("20:20:10", timeFormatter);
  LocalDateTime parse2 = LocalDateTime.parse("2020/01/20 20:10:02", formatter);

}

7.6 amendment

The withXxx method is provided in the date or time API to modify the value of the corresponding type.

7.6.1 about date

@Test
public void test51() {
  LocalDate now = LocalDate.now();
  // Change Month to February
  LocalDate date = now.withMonth(2);
  // Modify Year
  LocalDate date1 = now.withYear(2018);
  // Change day to 20 in the middle of the month
  LocalDate date2 = now.withDayOfMonth(20);
  // Modify days to 200 days in the middle of the year
  LocalDate date3 = now.withDayOfYear(200);
  // Modify to March, specify company to modify
  LocalDate with = now.with(ChronoField.MONTH_OF_YEAR, 3);
}

7.6.2 about time

@Test
public void test52() {
  LocalTime now = LocalTime.now();
  // Nanosecond to 1
  LocalTime localTime = now.withNano(1);
  // Seconds to 30
  LocalTime localTime1 = now.withSecond(30);
  // Minutes to 20
  LocalTime localTime2 = now.withMinute(20);
  // Hour changed to 12
  LocalTime localTime3 = now.withHour(12);
  // Hour is changed to 20, and the specified unit is modified
  LocalTime with = now.with(ChronoField.HOUR_OF_DAY, 20);
}

7.6.3 about date and time

The withXxx method in LocalDateTim is a combination of LocaDate and LocalTime.

7.7 calculation

7.7.1 about date

  • plus
@Test
public void test53() {
  LocalDate now = LocalDate.now();
  // Plus 20 days
  LocalDate date = now.plusDays(20);
  // Plus two weeks
  LocalDate date1 = now.plusWeeks(2);
  // Add two months.
  LocalDate date2 = now.plusMonths(2);
  // Plus 1 years
  LocalDate date3 = now.plusYears(1);
  // Add 10 days, custom unit
  LocalDate plus = now.plus(10, ChronoUnit.DAYS);
}
  • reduce
@Test
public void test54() {
  LocalDate now = LocalDate.now();
  // Reduce 1 days
  LocalDate date = now.minusDays(1);
  // Less 2 weeks
  LocalDate date1 = now.minusWeeks(2);
  // Reduce 3 months
  LocalDate date2 = now.minusMonths(3);
  // Reduction of 1 years
  LocalDate date3 = now.minusYears(1);
  // Minus 2 days, custom unit
  LocalDate minus = now.minus(2, ChronoUnit.DAYS);
}

7.7.2 about date

  • plus
@Test
public void test55() {
  LocalTime now = LocalTime.now();
  // Add two hours.
  LocalTime localTime = now.plusHours(2);
  // Add 20 seconds.
  LocalTime localTime1 = now.plusSeconds(20);
  // Add 10 points
  LocalTime localTime2 = now.plusMinutes(10);
  // Plus 2000 nanoseconds
  LocalTime localTime3 = now.plusNanos(2000);
  // Plus 20 seconds, custom units
  LocalTime plus = now.plus(20, ChronoUnit.SECONDS);
}
  • reduce
@Test
public void test56() {
  LocalTime now = LocalTime.now();
  // Reduce one hour
  LocalTime localTime = now.minusHours(1);
  // Reduce 30 minutes
  LocalTime localTime1 = now.minusMinutes(30);
  // Minus 50 seconds
  LocalTime localTime2 = now.minusSeconds(50);
  // Minus 10000 nanoseconds
  LocalTime localTime3 = now.minusNanos(10000);
  // Minus 20 seconds
  LocalTime minus = now.minus(20, ChronoUnit.SECONDS);
}

7.7.3 about date and time

LocalDateTime contains all the plusXxx and miusxx methods in LocalDate and LocalTime, such as:

@Test
public void test57() {
  LocalDateTime now = LocalDateTime.now();
  // Plus 20 days
  LocalDateTime localDateTime = now.plusDays(20);
  // Reduce 20 hours
  LocalDateTime localDateTime1 = now.minusHours(20);
}

7.8 comparison

  • About dates
@Test
public void test() {
  LocalDate date1 = LocalDate.now();
  LocalDate date2 = LocalDate.parse("2020-01-20");
  // Is date1 before date2
  boolean before = date1.isBefore(date2);
  // Is date1 after date2
  boolean after = date1.isAfter(date2);
  // Is date1 equal to date2
  boolean equal = date1.isEqual(date2);
}
  • About time
@Test
public void  test59() {
  LocalTime time1 = LocalTime.now();
  LocalTime time2 = LocalTime.parse("20:20:20");
  // Is time1 before time2 
  boolean before = time1.isBefore(time2);
  // Is time1 before time2
  boolean after = time1.isAfter(time2);
}
  • About date time
@Test
public void test60() {
  LocalDateTime dateTime1 = LocalDateTime.now();
  LocalDateTime dateTime2 = LocalDateTime.parse("2020-01-20 20:30:40");
  // Is dateTime1 equal to dateTime2
  boolean equal = dateTime1.isEqual(dateTime2);
  // Is dateTime1 before dateTime2
  boolean before = dateTime1.isBefore(dateTime2);
  // Is dateTime1 after dateTime2
  boolean after = dateTime1.isAfter(dateTime2);
}

7.9 time, date and time date conversion

@Test
public void test61() {
  LocalDate localDate = LocalDate.now();
  LocalTime localTime = LocalTime.now();
  // Generate LocalDateTime with LocalDate and LocalTime
  LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);

  // Convert LocalDateTime to LocalDate
  LocalDate date = localDateTime.toLocalDate();
  // Convert LocalDateTime to LocalTime
  LocalTime localTime1 = localDateTime.toLocalTime();

  // LocalDate plus LocalTime to get LocalDateTime hours, minutes, seconds, nanoseconds
  LocalDateTime dateTime = localDate.atTime(2, 30, 20, 1000);
  LocalDateTime dateTime1 = localDate.atTime(localTime);
  // Get the start time of the day, and convert LocalDate to LocalDateTime
  LocalDateTime dateTime2 = localDate.atStartOfDay();

  // LocalTime plus LocalDate to get LocalDateTime
  LocalDateTime dateTime3 = localTime.atDate(localDate);

}

7.9 machine date time Instant

7.9.1 creation

@Test
public void test24() {
  // current time
  Instant now = Instant.now();
  // Default format resolution generation
  Instant parse = Instant.parse("2020-01-11T02:02:53.241Z");
  // Timestamp millisecond generation
  Instant instant = Instant.ofEpochMilli(90 * 60 * 1000);
  // Time stamp MS + NS
  Instant instant1 = Instant.ofEpochSecond(60 * 60, 30L * 60 * 1000_000_000);
  // Unix original time 1970-01-01 00:00:00 
  Instant instant2 = Instant.EPOCH;
}

7.9.2 obtaining values

@Test
public void test25() {
  Instant now = Instant.now();
  // Get timestamp seconds long
  long aLong = now.getLong(ChronoField.INSTANT_SECONDS);
  System.out.println(aLong);
  // Get nanosecond in current time
  int nano = now.getNano();
  // Get timestamp seconds
  long epochSecond = now.getEpochSecond();
  // Get millisecond timestamp
  long l = now.toEpochMilli();
}

7.9.3 comparison

@Test
public void test26() {
  Instant instant1 = Instant.now();
  Instant instant2 = Instant.parse("2020-01-11T02:02:53.241Z");
  // Is instant1 after instant2
  boolean after = instant1.isAfter(instant2);

  // Is instant1 before instant2
  boolean before = instant1.isBefore(instant2);
}

7.9.4 computing

@Test
public void test27() {
  Instant instant1 = Instant.now();
  // Minus 20 minutes
  Instant minus = instant1.minus(Duration.ofMinutes(20));
  // Subtract 2000 milliseconds first parameter value second parameter unit
  Instant minus1 = instant1.minus(2000, ChronoUnit.MILLIS);
  // Subtract nanoseconds
  Instant instant = instant1.minusNanos(60 * 60 * 1000_000_000);
  // Minus seconds
  Instant instant2 = instant1.minusSeconds(60 * 60);
  // Subtract milliseconds
  Instant instant3 = instant1.minusMillis(60 * 60 * 1000);
}
@Test
public void test28() {
  Instant now = Instant.now();
  // Add 1 hours
  Instant plus = now.plus(Duration.ofHours(1));
  // Add 1 minute value unit
  Instant plus1 = now.plus(1, ChronoUnit.MINUTES);
  // Garner second
  Instant instant = now.plusNanos(60 * 60 * 1000_000_000);
  // Add second
  Instant instant1 = now.plusSeconds(60 * 60);
  // Milliseconds
  Instant instant2 = now.plusMillis(60 * 60 * 1000);
}

7.9.5 modification

@Test
public void test29() {
  Instant now = Instant.now();
  // The machine time is the time stamp second, so changing the second is equivalent to changing the time stamp to 1000, and the result is 1970-01-01 00:16:40.464Z
  Instant with = now.with(ChronoField.INSTANT_SECONDS, 1000);
}

7.10 Duration

Duration can be used to get two time periods of the same type or date, or to specify a specific type of time period. If the first time is larger than the second time, the value obtained is negative.

7.10.1 create

@Test
public void test30() {
  Instant now = Instant.now();
  Instant parse = Instant.parse("2020-01-11T02:02:53.241Z");
  Duration between = Duration.between(now, parse);
  Duration duration1 = Duration.ofDays(1);
  Duration duration = Duration.ofHours(1);
  Duration duration2 = Duration.ofMinutes(1);
  Duration duration3 = Duration.ofSeconds(1);
  Duration duration4 = Duration.ofSeconds(1, 1000_000_000);
  Duration duration5 = Duration.ofMillis(10000);
  Duration duration6 = Duration.ofNanos(10000000);
  // The maximum specified value is chrononunit.days
  Duration of = Duration.of(100, ChronoUnit.DAYS);
}

7.10.2 obtaining values

@Test
public void test31() {
  Duration duration = Duration.ofDays(1);
  // Get the absolute value,
  Duration abs = duration.abs();
  // Get seconds
  long l = duration.get(ChronoUnit.SECONDS);
  // Get milliseconds
  int nano = duration.getNano();
  // Get seconds
  long seconds = duration.getSeconds();
  // Get the supported TemporalUnit value
  List<TemporalUnit> units = duration.getUnits();
}
@Test
public void test32() {
  Duration duration = Duration.ofHours(25);
  // Convert to days, give up less than one day
  long l = duration.toDays();
  // Convert to hours
  long l1 = duration.toHours();7.
  long l4 = duration.toMinutes();
  long l2 = duration.toMillis();
  long l3 = duration.toNanos();
}

7.10.2 calculation and modification

Duration also supports calculation and modification of values, and also adds multiplication and division operations:

@Test
public void test() {
  Duration duration = Duration.ofHours(10);
  Duration duration1 = duration.dividedBy(10);
  Duration duration2 = duration.multipliedBy(10);
}

7.10.3 participation in time operation

// Minus 20 minutes
Instant minus = instant1.minus(Duration.ofMinutes(20));

7.11 expansion

7.11.1 Period

Period is the same as Duration, but Period is mainly an operation on LocalDate.

7.11.2 TemporalAdjuster

TemporalAdjusters are also involved in time and date. Many static methods are encapsulated in TemporalAdjusters to quickly generate TemporalAdjuster objects.

Such as:

@Test
public void test34() {
  LocalDate now = LocalDate.now();
  // Time jumps to the first day of the month
  LocalDate with = now.with(TemporalAdjusters.firstDayOfMonth());
}

For more usage of TemporalAdjusters, please refer to the methods in TemporalAdjusters.

78 original articles published, 55 praised, 30000 visitors+
Private letter follow

Tags: Lambda Java Attribute less

Posted on Tue, 14 Jan 2020 00:57:59 -0800 by raven2009