跳至主要內容

Java8新特性

晨光-向大约 72 分钟JavaJava语言

Java8新特性

1. Lamda表达式 [函数式编程]

Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

java中,我们无法将函数作为参数传递给一个方法,也无法声明一个函数的方法.

lambda作用

lambda : lambda表达式的"类型是函数",但在java中.lambda表达式是"对象",但他们必须依附于一类特别多对象类型---"函数式接口
  • 传递行为,而不是值
    • 提升抽象层次
    • API重用性更好
    • 更加灵活

lambda概要

  • Java Lambda 表达式是一种匿名函数; 它是没有声明的方法, 即没有访问修饰符.返回值声明和名字

1.1 lambda表达式入门

1. 匿名内部类(优化) ---》 lambad

// 匿名内部类 
jButton.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
       System.out.println("Button Pressed");
       }
  });
// lambda 表达式
  jButton.addActionListener(event -> {
      System.out.println("Button Pressed");
      System.out.println("Button");
  });

2. lambda表达式基本结构

语法:

 (argument) -> {body}
 
 (atg1,arg2...) -> {body}
 (type1 arg1,type2 arg2...) -> {body}
(param1.param2,param3) -> {

}

结构

1.2 lambda使用 list集合(遍历)

   List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
        // 遍历方式一
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("---------");
        // 遍历方式二 1.5之后
        for (Integer i : list) {
            System.out.println(i);
        }
        System.out.println("---------");
        // 遍历方式三 1.8 匿名内部类
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
		// lambda 表达式
        list.forEach(l -> System.out.println(l));

lambda 以及 方法引用

 		// lambda 表达式
        // list.forEach(l -> System.out.println(l));
		// 方法引用 优化
        list.forEach(System.out::println);

1.3 函数式接口 😄

1. 关于函数式接口: 😄

@FunctionalInterface
public interface MyInterface {
    /**
     * 抽象方法
     */
    void test();
}
  • 注意: 重载的方法,可以与共存 如: toString()
1. 如果一个接口只有一个(抽象方法),那么该接口就是一个"函数式接口".
2. 如果我们在某个接口上声明了FunctionalInterface(@FunctionalInterface注解),那么编译器就会按照"函数式接口"的定义来要求该接口.
3. 如果某个接口只有一个(抽象方法),但我们并没有给该接口声明@FunctionalInterface注解,那么编译器依旧会将该接口看做"函数式接口".

2. 函数式接口使用

1. 入门
/**
 * @Author: CHGGX
 * @Date: 2020/03/29 0:08
 * @Description: <h1> 函数式接口 </h1>
 * 1. 当接口只有一个抽象方法时: 为函数式接口
 * 2. 多个抽象方法时: 不为函数式接口
 * 3. 重载的方法,可以与共存 toString()
 */
@FunctionalInterface
interface MyInterface {
    /**
     * 抽象方法: 交给子类实现
     */
    void test();

    /**
     * 方法: 继承自Object类(顶级类), 本类实现
     * @return
     */
    @Override
    String toString();
}

/**
 * @author CHGGX
 * 函数式接口测试
 */
public class FunctionalInterfaceTest {

    public void myTest(MyInterface myInterface) {
        System.out.println(1);
        myInterface.test();
        System.out.println(2);
    }

    public static void main(String[] args) {
        FunctionalInterfaceTest test = new FunctionalInterfaceTest();
        // 匿名内部类
        test.myTest(new MyInterface() {
            @Override
            public void test() {
                System.out.println("myTest");
            }
        });
        System.out.println("----------");
            // lambda表达式
            test.myTest(() -> {
                System.out.println("myTest");
            });
        System.out.println(MyInterface.class);
        System.out.println(MyInterface.class.getSuperclass());
        System.out.println(MyInterface.class.getInterfaces()[0]);
    }

}
2. 深入
package com.chggx.jdk8.inter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 1:27
 * @Description: <h1> 函数式接口测试 </h1>
 * 1. lambda表达式
 * 2. stream流
 * 3. 方法引用
 */
public class FunctionalInterfaceTest2 {

    public static void main(String[] args) {
        // 1. lambda表达式的类型根据 "上下文" 断定
        TheInterface t1 = () -> {
        };
        System.out.println(t1.getClass().getInterfaces()[0]);

        TheInterface2 t2 = () -> {
        };
        System.out.println(t2.getClass().getInterfaces()[0]);

        // 2. 线程接口  Runnable: @FunctionalInterface 函数式接口
        // 线程Thread 使用线程接口 Runnable(lambda) 由于是函数是接口,可以直接使用lambda
        new Thread(() -> System.out.println("hello word!")).start();

        // 3. 将集合中的每个元素变为"大写"
        List<String> list = Arrays.asList("hello", "word", "hello word");
        // 使用JAVA8 consumer 函数式接口
        // toUpperCase() 方法将字符串小写字符转换为大写。
        list.forEach(item -> System.out.println(item.toUpperCase()));

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

        // 3.1 将值存入一个新的集合
        // diamond  new ArrayList<String>(): String类型省略 1.6 后支持
        List<String> targetList = new ArrayList<String>();
        // 3.1.1 先遍历集合1,放入目标集合
        list.forEach(item -> targetList.add(item.toUpperCase()));
        // 3.1.2 遍历目标集合 System.out.println(target) --->  方法引用 System.out::println
        targetList.forEach(System.out::println);


        // 3.2 优化 3.1 步骤
        // stream流 的方式
        // lambda形式
        System.out.println("------lambda形式------");
        list.stream()
                // 方法将字符串小写字符转换为大写。
                .map(item -> item.toUpperCase())
                .forEach(item -> System.out.println(item));
        System.out.println("------方法引用------");
        // 方法引用形式
        list.stream()
                // 方法将字符串小写字符转换为大写。
                // item -> item.toUpperCase() ----> String::toUpperCase(方法引用)
                .map(String::toUpperCase)
                // item -> System.out.println(item) --> System.out::println
                .forEach(System.out::println);


    }

}

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 0:08
 * @Description: <h1> 函数式接口 </h1>
 * 1. 当接口只有一个抽象方法时: 为函数式接口
 * 2. 多个抽象方法时: 不为函数式接口
 */
@FunctionalInterface
interface TheInterface {
    /**
     * 抽象方法: 交给子类实现
     */
    void method();
}

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 0:08
 * @Description: <h1> 函数式接口 </h1>
 * 1. 当接口只有一个抽象方法时: 为函数式接口
 * 2. 多个抽象方法时: 不为函数式接口
 */
@FunctionalInterface
interface TheInterface2 {
    /**
     * 抽象方法: 交给子类实现
     */
    void method2();
}

1.4 接口 😄

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

1. 接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

2. 接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

3. 接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

4. 抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

:JDK 1.8 以后,接口里可以有静态方法方法体了。

5. 接口为什么引入静态方法?

  • 向后兼容 [Sort]

2. Funcation接口

序号接口 & 描述
1**BiConsumer(T,U)**代表了一个接受两个输入参数的操作,并且不返回任何结果
2**BiFunction(T,U,R)**代表了一个接受两个输入参数的方法,并且返回一个结果 ,继承
3**BinaryOperator(T)**代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果,BiFunction
4**BiPredicate(T,U)**代表了一个两个参数的boolean值方法
5BooleanSupplier代表了boolean值结果的提供方
6**Consumer(T)**代表了接受一个输入参数并且无返回的操作
7DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9**DoubleFunction(R>**代表接受一个double值参数的方法,并且返回结果
10DoublePredicate代表一个拥有double值参数的boolean值方法
11DoubleSupplier代表一个double值结构的提供方
12DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13DoubleToLongFunction接受一个double类型输入,返回一个long类型结果
14DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。
15**Function(T,R)**接受一个输入参数,返回一个结果。
16IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17IntConsumer接受一个int类型的输入参数,无返回值 。
18**IntFunction(R)**接受一个int类型输入参数,返回一个结果 。
19IntPredicate:接受一个int输入参数,返回一个布尔值的结果。
20IntSupplier无参数,返回一个int类型结果。
21IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。
22IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。
24LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25LongConsumer接受一个long类型的输入参数,无返回值。
26**LongFunction(R)**接受一个long类型输入参数,返回一个结果。
27LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28LongSupplier无参数,返回一个结果long类型的值。
29LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32**ObjDoubleConsumer(T)**接受一个object类型和一个double类型的输入参数,无返回值。
33**ObjIntConsumer(T)**接受一个object类型和一个int类型的输入参数,无返回值。
34**ObjLongConsumer(T)**接受一个object类型和一个long类型的输入参数,无返回值。
35**Predicate(T)**接受一个输入参数,返回一个布尔值结果。
36**Supplier(T)**无参数,返回一个结果。
37**ToDoubleBiFunction(T,U)**接受两个输入参数,返回一个double类型结果
38**ToDoubleFunction(T)**接受一个输入参数,返回一个double类型结果
39**ToIntBiFunction(T,U)**接受两个输入参数,返回一个int类型结果。
40**ToIntFunction(T)**接受一个输入参数,返回一个int类型结果。
41**ToLongBiFunction(T,U)**接受两个输入参数,返回一个long类型结果。
42**ToLongFunction(T)**接受一个输入参数,返回一个long类型结果。
43**UnaryOperator(T)**接受一个参数为类型T,返回值类型也为T。
注意:为避免markdown在文档页面格式冲突,上面返回类型中所有的()都是代表<> . 比如 List(T) 指的是 List<T>. 

2.1 Funcation apply (compose andThen) identity

接收一个参数,返回结果

lambda 行为

1. 接口

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);
	
    // before
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
	// after
    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. 入门案例

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 11:48
 * @Description: <h1> lambda传递的是"行为" </h1>
 */
public class FunctionTest {

    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
        //// 普通的加减乘除[方法写死]
        System.out.println(test.method1(1));
        System.out.println(test.method2(2));
        System.out.println(test.method3(3));

        // lambda 行为 function[灵活]
        // statement (return ....)
        System.out.println(test.compute(
                1, value -> {
                    return 2 * value; 
                }
        ));
        // 改进  expression .... (无花括号)
        System.out.println(test.compute(
                2, value -> 5 + value
        ));
        System.out.println(test.compute(
                3, value -> value * value
        ));

		 // 不同类型的数据运算
        System.out.println(test.convert(
                5, value -> String.valueOf(value + " hello word")
        ));
    }

    public int compute(int a, Function<Integer, Integer> function) {
        return function.apply(a);
    }

    public String convert(int a, Function<Integer, String> function) {
        return function.apply(a);
    }

    public int method1(int a) {
        return 2 * a;
    }
    public int method2(int a) {
        return 5 + a;
    }
    public int method3(int a) {
        return a * a;
    }
}

3. 深入 compose与andThen

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 12:27
 * @Description: <h1> Function使用 </h1>
 * 1. compose 先执行2在执行1 (先计算输入参数[2]的apply,把[2]的结果作为[1]的输入参数计算)
 * 2. andThen 先执行1在执行2
 */
public class FunctionTest2 {

    public static void main(String[] args) {

        FunctionTest2 test2 = new FunctionTest2();
        System.out.println(
                // 1.function1:  value * 3  2. function2: value * value
                // compose: 先执行 function2 2 * 2 = 4
                // 再执行 function1 4 * 3 = 12
                test2.compute(2, value -> value * 3, value -> value * value)
        );
        System.out.println(
                // andThen: 先执行 function1,后执行 function2
                // 2 * 3 = 6 ---> 6 * 6 = 36
                test2.compute2(2, value -> value * 3, value -> value * value)
        );

    }


   /**
     *  default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
     * compose 先执行function2在执行function1 (先计算输入参数[2]的apply,把[2]的结果作为[1]的输入参数计算)
     */
    public int compute(int a, Function<Integer, Integer> function1,
                       Function<Integer, Integer> function2) {
        // compose  function2
        return function1.compose(function2).apply(a);
    }

    /**
     * default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
     * andThen: 先执行 function1,后执行 function2
     */
    public int compute2(int a, Function<Integer, Integer> function1,
                        Function<Integer, Integer> function2) {
        // andThen
        return function1.andThen(function2).apply(a);
    }

}

2.2 BiFunction apply andThen

接收两个参数,返回一个结果

  • 没有compose: 由于BiFunction(接收两个参数,返回一个结果) 先执行后面方法时,返回的结果只有一个,不能作为BiFunction的参数[不成立]

1. 接口

@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

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

2. 用法示例

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 12:49
 * @Description: <h1> BiFunction </h1>
 * 接收两个参数,返回一个结果
 *  没有compose: 由于BiFunction(接收两个参数,返回一个结果) 先执行后面方法时,返回的结果只有一个,不能作为BiFunction的参数[不成立]
 */
public class BiFunctionTest {

    public static void main(String[] args) {

        BiFunctionTest test = new BiFunctionTest();
        // 1.
        System.out.println(
                // 和
//                test.compute(1, 2, (value1, value2) -> value1 + value2)
                test.compute(1, 2, Integer::sum)
        );
        System.out.println(
                // 差
//                test.compute(1, 2, (value1, value2) -> value1 - value2)
                test.compute(1, 2, (value1, value2) -> value1 - value2)
        );
        System.out.println(
                // 乘
                test.compute(1, 2, (value1, value2) -> value1 * value2)
        );
        System.out.println(
                // 除
                test.compute(1, 2, (value1, value2) -> value1 / value2)
        );

        // 2.
        System.out.println(
                // 和
//                test.compute(2, 3, (value1, value2) -> value1 + value2,, value -> value * value)
                // andThen: 先执行biFunction,在执行function 2+3=5 5*5=25
                test.compute2(2, 3, Integer::sum, value -> value * value)
        );
        System.out.println(
                // 差
                // andThen: 先执行biFunction,在执行function 2-3=-1 -1*-1=1
                test.compute2( 2, 3,(value1, value2) -> value1 - value2,value -> value * value)
        );
        System.out.println(
                // 乘
                // andThen: 先执行biFunction,在执行function 2*3=6 6*6=36
                test.compute2(2,3, (value1, value2) -> value1 * value2,value -> value * value)
        );
        System.out.println(
                // 除
                // andThen: 先执行biFunction,在执行function 2/3=2/3 (2/3)*(2/3)
                test.compute2( 2,3, (value1, value2) -> value1 / value2,value -> value * value)
        );

    }

    /**
     * BiFunction  接收两个参数,返回一个结果
     *
     * @param a          参数一
     * @param b          参数二
     * @param biFunction 函数
     * @return 结果
     */
    public int compute(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
        return biFunction.apply(a, b);
    }

     /**
     * default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)
     * andThen:  先执行biFunction,在执行function [接收两个参数,返回一个结果]
     */
    public int compute2(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {

        return biFunction.andThen(function).apply(a, b);
    }

    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }


}

3. 深入使用

person 实体类 第三种用法 BiFunction

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 16:46
 * @Description: <h1> BiFunction: 接收两个参数,返回一个结果 </h1>
 */
public class PersonTest {

    public static void main(String[] args) {

        Person person1 = new Person("zhangsan", 20);
        Person person2 = new Person("lisi", 30);
        Person person3 = new Person("wangwu", 40);
		// 将对象封装为集合
        List<Person> personList = Arrays.asList(person1, person2, person3);

        PersonTest test = new PersonTest();
        // 1. 根据名称获取[必须规定name]getPersonByUsername
        List<Person> personResult = test.getPersonByUsername("zhangsan", personList);
        personResult.forEach(person -> System.out.println(person.getUsername()));

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

        // 2. 选出大于指定年龄的[必须规定age]getPersonsByAge
        List<Person> personResult2 = test.getPersonsByAge(20, personList);
        personResult2.forEach(person -> System.out.println(person.getAge()));

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

        // 3. 选出大于指定年龄的 [灵活应用]getPersonsByAge2
        List<Person> personResult3 = test.getPersonsByAge2(
                // 3个参数: 1.age 2.personList 3. BiFunction: 接收两个参数,返回一个结果
                20, personList, (age, persons) -> persons.stream()
                        // 大于指定年龄
                        .filter(person -> person.getAge() > age)
                        .collect(Collectors.toList())
        );
        personResult3.forEach(person -> System.out.println(person.getAge()));

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

        // 4. 选出小于等于指定年龄的 [灵活应用]getPersonsByAge2
        List<Person> personResult4 = test.getPersonsByAge2(
                20, personList, (age, persons) -> persons.stream()
                        .filter(person -> person.getAge() <= age)
                        .collect(Collectors.toList())
        );
        personResult4.forEach(person -> System.out.println(person.getAge()));

    }

    public List<Person> getPersonByUsername(String username, List<Person> personList) {
        // stream 流
        return personList.stream()
                .filter(person -> person.getUsername().equals(username))
                // 转换为 list
                .collect(Collectors.toList());
    }

    /** stream流过滤 filter
     * 年龄大于给定参数(age)
     */
    public List<Person> getPersonsByAge(int age, List<Person> personList) {
        // BiFunction函数: 接收两个参数,返回一个结果
        // lambda:  [expression lambda]
        BiFunction<Integer, List<Person>, List<Person>> biFunction =
                (ageOfPerson, persons) -> persons.stream()
                        // 选择出 年龄大于给定的参数[age] person.getAge()>age
                        .filter(person -> person.getAge() > age)
                        .collect(Collectors.toList());
        // 应用
        return biFunction.apply(age, personList);
    }

    public List<Person> getPersonsByAge2(int age, List<Person> personList,
                                         BiFunction<Integer, List<Person>, List<Person>> biFunction) {
        return biFunction.apply(age, personList);
    }

}

2.3 Predicate接口 test (and or negate) isEqual

  • 接收参数,返回boolean值

1. 接口

**传递行为,**而不是传递值

@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. 入门案例

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 21:01
 * @Description: <h1> Predicate函数式接口 </h1>
 */
public class PredicateTest {

    public static void main(String[] args) {
        // 1. Predicate基本用法 boolean test(T t);  filter
        Predicate<String> predicate = p -> p.length() > 5;
        // 长度是否大于5  false
        System.out.println(predicate.test("hello"));
    }

}

3. 深入

/**
 * @Author: CHGGX
 * @Date: 2020/03/29 21:14
 * @Description: <h1> Predicate函数式接口  </h1>
 * 1. test(); 判断给定的为true/false
 * 2. and()
 * 3. negate 否定(与原来相反)
 * 4. or
 */
public class PredicateTest2 {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        PredicateTest2 test2 = new PredicateTest2();

        // 1. 获取当前集合的 偶数: 能被2整除
        test2.conditionFilter(list, item -> item % 2 == 0);
        System.out.println("-------------");

        // 2. 获取当前集合的 奇数: 不能被2整除,模1
        test2.conditionFilter(list, item -> item % 2 != 0);
        System.out.println("-------------");

        // 3. 集合中所有大于5的
        test2.conditionFilter(list, item -> item > 5);
        System.out.println("-------------");

        // 4. 集合中所有小于3的
        test2.conditionFilter(list, item -> item < 3);
        System.out.println("-------------");

        // 5. 打印出所有数据
        test2.conditionFilter(list, item -> true);
        System.out.println("-------------");

        // 6. 不打印出任何数据
        test2.conditionFilter(list, item -> false);
        System.out.println("-------------");

        System.out.println("------ and -------");

        // 7. 添加: 既要大于5, 又要为偶数 6 8 10
        // 大于5: item -> item > 5  ; 偶数:item -> item % 2 == 0
        test2.conditionFilter2(list, item -> item > 5, item -> item % 2 == 0);

        System.out.println("------ or -------");

        // 8. or 2 4 6 7 8 9 0
        // 大于5: item -> item > 5  ; 偶数:item -> item % 2 == 0
        test2.conditionFilter3(list, item -> item > 5, item -> item % 2 == 0);


        System.out.println("------ and -------");

        // 7. negate 1 2 3 4 5 7 9
        // 大于5: item -> item > 5  ; 偶数:item -> item % 2 == 0
        test2.conditionFilter4(list, item -> item > 5, item -> item % 2 == 0);

        System.out.println("------ isEqual -------");
        // 8. 判断2个参数是否相等
        System.out.println(test2.isEqual("test").test("test"));
    }

    /** boolean test(T t);
     * 函数式编程: 将参数处理,由方法体里面处理----->执行者里处理[处理逻辑有方法里面编写--->时下时编写体现]
     * <h2> Predicate: 方法 test(); </h2>
     */
    public void conditionFilter(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer integer : list) {
            // 判断是否为 true
            if (predicate.test(integer)) {
                // 是
                System.out.println(integer);
            }
        }
    }

    /**  default Predicate<T> and(Predicate<? super T> other)
     * <h2> 与或非:与and </h2>
     */
    public void conditionFilter2(List<Integer> list, Predicate<Integer> predicate1, Predicate<Integer> predicate2) {
        for (Integer integer : list) {
            // 给定集合,既要满足predicate1,又要满足predicate2
            if (predicate1.and(predicate2).test(integer)) {
                System.out.println(integer);
            }
        }
    }
    /**  default Predicate<T> or(Predicate<? super T> other)
     * <h2> 与或非: 或or </h2>
     */
    public void conditionFilter3(List<Integer> list, Predicate<Integer> predicate1, Predicate<Integer> predicate2) {
        for (Integer integer : list) {
            // 给定集合,既要满足predicate1,又要满足predicate2
            if (predicate1.or(predicate2).test(integer)) {
                System.out.println(integer);
            }
        }
    }

    /**  default Predicate<T> negate()
     * <h2> 与或非: 非negate </h2>
     */
    public void conditionFilter4(List<Integer> list, Predicate<Integer> predicate1, Predicate<Integer> predicate2) {
        for (Integer integer : list) {
            // 给定集合,既要满足predicate1,又要满足predicate2, negate: 取之前的相反结果
            if (predicate1.and(predicate2).negate().test(integer)) {
                System.out.println(integer);
            }
        }
    }
 
    /** static <T> Predicate<T> isEqual(Object targetRef)
     * <h2> 判断2个参数是否相等 </h2>
     */
    public Predicate<String> isEqual(Object o){
        return Predicate.isEqual(o);
    }
    // TODO 原来的面向对象编程,执行逻辑在方法体里面,方法写死.函数式接口,执行者实现.更加灵活

}

2.4 Supplier接口 get

  • 不接受参数,返回结果

1. 接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

2. 入门

package com.chggx.jdk8.supplier;

import java.util.function.Supplier;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 13:39
 * @Description: <h1> </h1>
 * supplier: 不接受参数,同时返回一个结果
 */
public class SupplierTest {

    /**
     *  T get();
     *  supplier: 不接受参数,同时返回一个结果
     */
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "hello word";
        System.out.println(supplier.get());
    }

}

3.深入

  • student实体类
package com.chggx.jdk8.pojo;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 13:46
 * @Description: <h1> </h1>
 */
public class Student {

    private String name = "zhangsan";

    private int age = 20;

    // 无参构造重点
    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 实现
package com.chggx.jdk8.java.util.function.supplier;

import com.chggx.jdk8.pojo.Student;

import java.util.function.Supplier;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 13:39
 * @Description: <h1> supplier应用 </h1>
 * supplier: 不接受参数,同时返回一个结果
 */
public class StudentTest {

    /**
     * T get();
     * supplier: 不接受参数,同时返回一个结果
     */
    public static void main(String[] args) {

        // 1. 以前方式
        Student student = new Student();
        String name = student.getName();
        int age = student.getAge();
        System.out.println("name: " + name + ", age: " + age);

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

        // 2. 现在Supplier函数式编程方式
        // lambda : () -> new Student();
        Supplier<Student> supplier = () -> new Student();
        System.out.println("name: " + supplier.get().getName() + ", age: " + supplier.get().getAge());

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

        // 3. 构造方法引用(无参构造) Student::new
        Supplier<Student> supplier1 = Student::new;
        System.out.println("name: " + supplier.get().getName() + ", age: " + supplier.get().getAge());
    }

}

2.5 BinaryOperator接口 继承BiFunction

package com.chggx.jdk8.java.util.function.binaryOperator;

import java.util.Comparator;
import java.util.function.BinaryOperator;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 14:14
 * @Description: <h1> </h1>
 * BinaryOperator: 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
 *  继承自BiFunction
 */
public class BinaryOperatorTest {

    public static void main(String[] args) {

        // 两个整数的运算
        BinaryOperatorTest binaryOperatorTest = new BinaryOperatorTest();
        // +
        System.out.println(binaryOperatorTest.compute(1, 2, Integer::sum));
        // -
        System.out.println(binaryOperatorTest.compute(1, 2, (a, b) -> a - b));
        // *
        System.out.println(binaryOperatorTest.compute(1, 2, (a, b) -> a * b));
        // /
        System.out.println(binaryOperatorTest.compute(1, 2, (a, b) -> a / b));

        // getShort 比较获取小的
        System.out.println(binaryOperatorTest.getShort("hello", "word", (a, b) ->a.length()-b.length()));
        System.out.println(binaryOperatorTest.getLong("hello", "word", (a, b) ->a.charAt(0)-b.charAt(0)));
        // getLong 比较获取大的
        System.out.println(binaryOperatorTest.getLong("hello", "word", (a, b) ->a.length()-b.length()));

    }

    /**
     * BinaryOperator
     * R apply(T t, U u);
     */
    public int compute(int a, int b, BinaryOperator<Integer> binaryOperator) {
        return binaryOperator.apply(a, b);
    }

    /**
     * 比较
     */
    public String getShort(String a, String b, Comparator<String> comparator) {
        return BinaryOperator.minBy(comparator).apply(a, b);
    }
    public String getLong(String a, String b, Comparator<String> comparator) {
        return BinaryOperator.maxBy(comparator).apply(a, b);
    }

}

3. Optional 函数式编程风格

  • 解决 NPE NullPointerException 空指针异常

3.1 源码

/**
 * Common instance for {@code empty()}.
 */
private static final Optional<?> EMPTY = new Optional<>();

/**
 * If non-null, the value; if null, indicates no value is present
 */
private final T value;

/**
 * Constructs an empty instance.
 *
 * @implNote Generally only one empty instance, {@link Optional#EMPTY},
 * should exist per VM.
 */
private Optional() {
    this.value = null;
}

/**
 * Returns an empty {@code Optional} instance.  No value is present for this
 * Optional.
 *
 * @apiNote Though it may be tempting to do so, avoid testing if an object
 * is empty by comparing with {@code ==} against instances returned by
 * {@code Option.empty()}. There is no guarantee that it is a singleton.
 * Instead, use {@link #isPresent()}.
 *
 * @param <T> Type of the non-existent value
 * @return an empty {@code Optional}
 */
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

/**
 * Constructs an instance with the value present.
 *
 * @param value the non-null value to be present
 * @throws NullPointerException if value is null
 */
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

/**
 * Returns an {@code Optional} with the specified present non-null value.
 *
 * @param <T> the class of the value
 * @param value the value to be present, which must be non-null
 * @return an {@code Optional} with the value present
 * @throws NullPointerException if value is null
 */
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

/**
 * Returns an {@code Optional} describing the specified value, if non-null,
 * otherwise returns an empty {@code Optional}.
 *
 * @param <T> the class of the value
 * @param value the possibly-null value to describe
 * @return an {@code Optional} with a present value if the specified value
 * is non-null, otherwise an empty {@code Optional}
 */
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

/**
 * If a value is present in this {@code Optional}, returns the value,
 * otherwise throws {@code NoSuchElementException}.
 *
 * @return the non-null value held by this {@code Optional}
 * @throws NoSuchElementException if there is no value present
 *
 * @see Optional#isPresent()
 */
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

/**
 * Return {@code true} if there is a value present, otherwise {@code false}.
 *
 * @return {@code true} if there is a value present, otherwise {@code false}
 */
public boolean isPresent() {
    return value != null;
}

/**
 * If a value is present, invoke the specified consumer with the value,
 * otherwise do nothing.
 *
 * @param consumer block to be executed if a value is present
 * @throws NullPointerException if value is present and {@code consumer} is
 * null
 */
public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

/**
 * If a value is present, and the value matches the given predicate,
 * return an {@code Optional} describing the value, otherwise return an
 * empty {@code Optional}.
 *
 * @param predicate a predicate to apply to the value, if present
 * @return an {@code Optional} describing the value of this {@code Optional}
 * if a value is present and the value matches the given predicate,
 * otherwise an empty {@code Optional}
 * @throws NullPointerException if the predicate is null
 */
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

/**
 * If a value is present, apply the provided mapping function to it,
 * and if the result is non-null, return an {@code Optional} describing the
 * result.  Otherwise return an empty {@code Optional}.
 *
 * @apiNote This method supports post-processing on optional values, without
 * the need to explicitly check for a return status.  For example, the
 * following code traverses a stream of file names, selects one that has
 * not yet been processed, and then opens that file, returning an
 * {@code Optional<FileInputStream>}:
 *
 * <pre>{@code
 *     Optional<FileInputStream> fis =
 *         names.stream().filter(name -> !isProcessedYet(name))
 *                       .findFirst()
 *                       .map(name -> new FileInputStream(name));
 * }</pre>
 *
 * Here, {@code findFirst} returns an {@code Optional<String>}, and then
 * {@code map} returns an {@code Optional<FileInputStream>} for the desired
 * file if one exists.
 *
 * @param <U> The type of the result of the mapping function
 * @param mapper a mapping function to apply to the value, if present
 * @return an {@code Optional} describing the result of applying a mapping
 * function to the value of this {@code Optional}, if a value is present,
 * otherwise an empty {@code Optional}
 * @throws NullPointerException if the mapping function is null
 */
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));
    }
}

/**
 * If a value is present, apply the provided {@code Optional}-bearing
 * mapping function to it, return that result, otherwise return an empty
 * {@code Optional}.  This method is similar to {@link #map(Function)},
 * but the provided mapper is one whose result is already an {@code Optional},
 * and if invoked, {@code flatMap} does not wrap it with an additional
 * {@code Optional}.
 *
 * @param <U> The type parameter to the {@code Optional} returned by
 * @param mapper a mapping function to apply to the value, if present
 *           the mapping function
 * @return the result of applying an {@code Optional}-bearing mapping
 * function to the value of this {@code Optional}, if a value is present,
 * otherwise an empty {@code Optional}
 * @throws NullPointerException if the mapping function is null or returns
 * a null result
 */
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));
    }
}

/**
 * Return the value if present, otherwise return {@code other}.
 *
 * @param other the value to be returned if there is no value present, may
 * be null
 * @return the value, if present, otherwise {@code other}
 */
public T orElse(T other) {
    return value != null ? value : other;
}

/**
 * Return the value if present, otherwise invoke {@code other} and return
 * the result of that invocation.
 *
 * @param other a {@code Supplier} whose result is returned if no value
 * is present
 * @return the value if present otherwise the result of {@code other.get()}
 * @throws NullPointerException if value is not present and {@code other} is
 * null
 */
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

/**
 * Return the contained value, if present, otherwise throw an exception
 * to be created by the provided supplier.
 *
 * @apiNote A method reference to the exception constructor with an empty
 * argument list can be used as the supplier. For example,
 * {@code IllegalStateException::new}
 *
 * @param <X> Type of the exception to be thrown
 * @param exceptionSupplier The supplier which will return the exception to
 * be thrown
 * @return the present value
 * @throws X if there is no value present
 * @throws NullPointerException if no value is present and
 * {@code exceptionSupplier} is null
 */
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

/**
 * Indicates whether some other object is "equal to" this Optional. The
 * other object is considered equal if:
 * <ul>
 * <li>it is also an {@code Optional} and;
 * <li>both instances have no value present or;
 * <li>the present values are "equal to" each other via {@code equals()}.
 * </ul>
 *
 * @param obj an object to be tested for equality
 * @return {code true} if the other object is "equal to" this object
 * otherwise {@code false}
 */
@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }

    if (!(obj instanceof Optional)) {
        return false;
    }

    Optional<?> other = (Optional<?>) obj;
    return Objects.equals(value, other.value);
}

/**
 * Returns the hash code value of the present value, if any, or 0 (zero) if
 * no value is present.
 *
 * @return hash code value of the present value or 0 if no value is present
 */
@Override
public int hashCode() {
    return Objects.hashCode(value);
}

/**
 * Returns a non-empty string representation of this Optional suitable for
 * debugging. The exact presentation format is unspecified and may vary
 * between implementations and versions.
 *
 * @implSpec If a value is present the result must include its string
 * representation in the result. Empty and present Optionals must be
 * unambiguously differentiable.
 *
 * @return the string representation of this instance
 */
@Override
public String toString() {
    return value != null
        ? String.format("Optional[%s]", value)
        : "Optional.empty";
}

3.2 入门

  • 工厂方法 of empty ofNullable
  • ifPresent orElse orElseGet
package com.chggx.jdk8.java.util.optional;

import java.util.Optional;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 17:26
 * @Description: <h1> optional测试 </h1>
 */
public class OptionalTest {

    public static void main(String[] args) {

        // 构造optional对象(不能new,私有的)
        // 1. of
//        Optional<String> optional = Optional.of("hello");
        // 2. empty
        Optional<String> optional = Optional.empty();

        // 如果存在
//        if (optional.isPresent()) {
//            System.out.println(optional.get());
//        }
        // 3. 函数式编程风格 optional.isPresent() -------> optional.ifPresent
        optional.ifPresent(item-> System.out.println(item));

        // 4. orElse 如果里面没有值,打印下面的值"world" (备选值)
        System.out.println(optional.orElse("world"));

        // 5. public T orElseGet(Supplier<? extends T> other)
        // 如果里面的值为空的话,接单后空的参数,输出结果
        System.out.println(optional.orElseGet(()->"nihao"));


    }

}

PS: 使用optional使用**函数式编程风格**写代码,不使用之前的方式

    // 如果存在
    if (optional.isPresent()) {
    	System.out.println(optional.get());   
    }else{
        .....
    }

3.3 深入

  • optional 没有 序列化
  • optional通常只作为方法的返回值,来规避空指针异常

员工表

package com.chggx.jdk8.pojo;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 17:50
 * @Description: <h1> 员工表 </h1>
 */
public class Employee {

    private String name;

    public Employee() {
    }

    public Employee(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

公司表

package com.chggx.jdk8.pojo;

import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 17:52
 * @Description: <h1> 公司表 </h1>
 */
public class Company {

    private String name;

    private List<Employee> employees;

    public Company() {
    }

    public Company(String name, List<Employee> employees) {
        this.name = name;
        this.employees = employees;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
}

示例

package com.chggx.jdk8.java.util.optional;

import com.chggx.jdk8.pojo.Company;
import com.chggx.jdk8.pojo.Employee;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 17:54
 * @Description: <h1> 测试一对多(employee,company) </h1>
 */
public class OptionalTest2 {

    public static void main(String[] args) {
        Employee employee = new Employee();
        employee.setName("zhangsan");

        Employee employee2 = new Employee();
        employee2.setName("lisi");

        Company company = new Company();
        company.setName("company1");
        List<Employee> employees = Arrays.asList(employee, employee2);
        // 1. employees不为空,调用optional的map获取结果
        // 2. 结果为空,调用optional的orElse返回空结果 []
        company.setEmployees(employees);

        // 传统方式
 //       List<Employee> list = company.getEmployees();
//        if (null != list) {
//            return list;
//        } else {
//            return new ArrayList<Employee>();
//        }

        // 函数式编程风格 Optional
        // 1. 构造工厂
        Optional<Company> optional = Optional.ofNullable(company);

        // map
        //  public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
        System.out.println(
                // map: 有结果情况
                optional.map(theCompany -> theCompany.getEmployees())
                        // 无结果情况 []
                        .orElse(Collections.emptyList()));

    }

}

4. 方法引用[method reference] ::

  • lambda表达式的一种语法糖(特殊情况),替换lambda,代码更精炼
 // 方法引用实现的接口 (接受一个参数,返回一个结果)
        Function<String,String> function = String::toUpperCase;
        System.out.println(function.getClass().getInterfaces()[0]);

4.1 概述

  1. 方法引用(method reference): lambda表达式的一种语法糖(特殊情况)

  2. 我们可以将"方法引用"看做是一个『函数式指针』,function pointer,指向另一个函数

  3. 方法引用的分类(4类)

    • 类名::静态方法名 classname::staticmethod 『函数式指针』
    • 引用名(对象名)::实例方法名
    • 类名::实例方法名

4.2 入门

  • 对比lambda
package com.chggx.jdk8.lambda.methodReference;

import java.util.Arrays;
import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:33
 * @Description: <h1> 方法引用 :: </h1>
 *  1. 方法引用(method reference): lambda表达式的一种语法糖(特殊情况)
 *  2. 我们可以将"方法引用"看做是一个『函数式指针』,function pointer,指向另一个函数
 *  3. 方法引用的分类(4类)
 *      1). 类名::静态方法名
 *      2). 引用名(对象名)::实例方法名
 */
public class MethodReferenceTest {

    public static void main(String[] args) {

        List<String> list = Arrays.asList("hello","world","hello world");

        // lambda方式
        list.forEach(item-> System.out.println(item));

        // 方法引用
        list.forEach(System.out::println);

    }

}

4.3 方法引用的分类

1. 类名::静态方法名 [静态方法在实体类里面]

  • 实体类Stu
package com.chggx.jdk8.lambda.methodReference;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:45
 * @Description: <h1> </h1>
 */
public class Stu {
    private String name;
    private int score;

    public Stu() {
    }

    public Stu(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    /**
     * 静态方法比较(分数)
     */
    public static int compareStuByScore(Stu stu1, Stu stu2) {
        return stu1.getScore() - stu2.getScore();
    }

    /**
     * 静态方法比较(名字)
     */
    public static int compareStuByName(Stu stu1, Stu stu2) {
        return stu1.getName().compareToIgnoreCase(stu2.getName());
    }

}
  • 测试
package com.chggx.jdk8.lambda.methodReference;

import java.util.Arrays;
import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:44
 * @Description: <h1> </h1>
 * 方法引用的第一类:  1). 类名::静态方法名 [静态方法在实体类里面]
 */
public class MethodReferenceTest2 {

    public static void main(String[] args) {
        Stu stu1 = new Stu("zhangsan", 10);
        Stu stu2 = new Stu("lisi", 90);
        Stu stu3 = new Stu("wangwu", 50);
        Stu stu4 = new Stu("zhaoliu", 40);

        List<Stu> stuList = Arrays.asList(stu1, stu2, stu3, stu4);

        // 1. 根据分数排序
        // 1.1 lambda
        // 排序(在原有集合上排序,不创建新集合) sort
        // 传递两个参数 (compare)
        stuList.sort((stuParam1, stuParam2) ->
                Stu.compareStuByScore(stuParam1, stuParam2));
        stuList.forEach(stu -> System.out.println(stu.getScore()));

        // 1.2 方法引用(Stu里的静态方法) [类名::静态方法名]
        // :: Comparator
        stuList.sort(Stu::compareStuByScore);
        stuList.forEach(stu -> System.out.println(stu.getScore()));

        // 2. 根据名字排序
        // 2.1 lambda
        // 排序(在原有集合上排序,不创建新集合) sort
        // 传递两个参数 (compare)
        stuList.sort((stuParam1, stuParam2) ->
                Stu.compareStuByName(stuParam1, stuParam2));
        stuList.forEach(stu -> System.out.println(stu.getName()));

        // 2.2 方法引用(Stu里的静态方法) [类名::静态方法名]
        // :: Comparator
        stuList.sort(Stu::compareStuByName);
        stuList.forEach(stu -> System.out.println(stu.getName()));


    }

}

2. 引用名(对象名)::实例方法名

  • 引用的类
package com.chggx.jdk8.lambda.methodReference;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 20:40
 * @Description: <h1> </h1>
 */
public class StuComparator {

    /**
     * 比较分数
     */
    public int comparatorStuByScore(Stu stu1, Stu stu2) {
        return stu1.getScore() - stu2.getScore();
    }

    /**
     * 比较分数
     */
    public int comparatorStuByName(Stu stu1, Stu stu2) {
        return stu1.getName().compareToIgnoreCase(stu2.getName());
    }

}
  • 测试
package com.chggx.jdk8.lambda.methodReference;

import java.util.Arrays;
import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:44
 * @Description: <h1> </h1>
 * 方法引用的第二类:  1). 引用名(对象名)::实例方法名
 */
public class MethodReferenceTest3 {
    public static void main(String[] args) {
        Stu stu1 = new Stu("zhangsan", 10);
        Stu stu2 = new Stu("lisi", 90);
        Stu stu3 = new Stu("wangwu", 50);
        Stu stu4 = new Stu("zhaoliu", 40);

        List<Stu> stuList = Arrays.asList(stu1, stu2, stu3, stu4);

        // 创建stu比较对象
        StuComparator stuComparator = new StuComparator();

        // 1. 排序 比较分数
        // 1.1 lambda
        stuList.sort((stuParam1, stuParam2) ->
                stuComparator.comparatorStuByScore(stuParam1, stuParam2));
        stuList.forEach(stu -> System.out.println(stu.getScore()));
        // 1.2 方法引用 stuComparator的方法 [引用名(对象名)::实例方法名]
        // :: Comparator
        stuList.sort(stuComparator::comparatorStuByScore);
        stuList.forEach(stu -> System.out.println(stu.getScore()));

        // 2. 排序 比较名字
        // 2.1 lambda
        stuList.sort((stuParam1, stuParam2) ->
                stuComparator.comparatorStuByName(stuParam1, stuParam2));
        stuList.forEach(stu -> System.out.println(stu.getName()));
        // 2.2 方法引用 stuComparator的方法 [引用名(对象名)::实例方法名]
        // :: Comparator
        stuList.sort(stuComparator::comparatorStuByName);
        stuList.forEach(stu -> System.out.println(stu.getName()));
    }
}

3. 类名::实例方法名

  • 实体类
package com.chggx.jdk8.lambda.methodReference;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:45
 * @Description: <h1> </h1>
 */
public class Stu {
    private String name;
    private int score;

    public Stu() {
    }

    public Stu(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    /**
     * 静态方法比较(分数)
     */
    public static int comparatorStuByScore(Stu stu1, Stu stu2) {
        return stu1.getScore() - stu2.getScore();
    }

    /**
     * 静态方法比较(名字)
     */
    public static int comparatorStuByName(Stu stu1, Stu stu2) {
        return stu1.getName().compareToIgnoreCase(stu2.getName());
    }

    //================================================================================================/

    /**
     * 改造上面的方法
     */
    public int comparatorByScore(Stu stu1) {
        return this.getScore() - stu1.getScore();
    }

    public int comparatorByName(Stu stu1) {
        return this.getName().compareToIgnoreCase(stu1.getName());
    }

}
  • 测试
package com.chggx.jdk8.lambda.methodReference;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:44
 * @Description: <h1> </h1>
 * 方法引用的第三类:  3). 类名::实例方法名
 */
public class MethodReferenceTest4 {
    public static void main(String[] args) {

        Stu stu1 = new Stu("zhangsan", 10);
        Stu stu2 = new Stu("lisi", 90);
        Stu stu3 = new Stu("wangwu", 50);
        Stu stu4 = new Stu("zhaoliu", 40);

        List<Stu> stuList = Arrays.asList(stu1, stu2, stu3, stu4);

        // 1. 方法引用  类名::实例方法名
        //  排序 比较分数
        // 类名::实例方法名 Stu::comparatorByScore [实例为类的非静态方法]
        stuList.sort(Stu::comparatorByScore);
        stuList.forEach(stu -> System.out.println(stu.getScore()));

        // 排序 比较分数
        // 类名::实例方法名 Stu::comparatorByScore [实例为类的非静态方法]
        stuList.sort(Stu::comparatorByName);
        stuList.forEach(stu -> System.out.println(stu.getName()));

        // 2. JDK自带排序
        List<String> cities = Arrays.asList("beijing", "shanghai", "tianjin", "chongqing");

        // 2.1 Collections.sort
        Collections.sort(cities, (cities1, cities2) -> cities1.compareToIgnoreCase(cities2));
        cities.forEach(city -> System.out.println(city));

        // 2.2 方法引用改进
        //  public int compareToIgnoreCase(String str)
        cities.sort(String::compareToIgnoreCase);
        cities.forEach(System.out::println);
    }
}

4. 构造方法引用: 类名::new

package com.chggx.jdk8.lambda.methodReference;

import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @Author: CHGGX
 * @Date: 2020/04/19 18:44
 * @Description: <h1> </h1>
 * 方法引用的第四类:  4). 构造方法: 类名::new
 */
public class MethodReferenceTest5 {

    public static void main(String[] args) {

        MethodReferenceTest5 test5 = new MethodReferenceTest5();
        // 1.
        // String::new : new String() 类名::new
        System.out.println(test5.getString(String::new));

        // 2. 
        System.out.println(test5.getString2("hello", String::new));


    }

    /**
     * Supplier<T>: 不接收参数,返回值
     */
    public String getString(Supplier<String> supplier) {
        return supplier.get() + "test";
    }

    /**
     * Function<T,R>:
     */
    public String getString2(String str, Function<String, String> function) {
        return function.apply(str);
    }
}

4. 案例

持续更新..........

5. Stream API

5.1 Stream简介

1. 什么是流

流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。

众所周知,集合操作非常麻烦,若要对集合进行筛选投影,需要写大量的代码,而流是以声明的形式操作集合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码

因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错的多线程代码了。

总结

流使用于操作数据源(集合.数组等)所生成的元素序列. 集合将的是数据,流讲的是计算.

注意

① Stream 自己不会存储元素 (不是数据结构,不会保存数据)

② Stream 不会改变源对象. 相反,他们会返回一个持有结果的新Stream.

③ Stream 操作是 延迟执行 的. 这意味着他们会等到需要结果的时候才执行.

2. 流的操作种类

创建 Stream

一个数据源 (集合,数组等) , 获取一个流.

中间操作

当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为 “中间操作”
中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线除非流水线上触发终止操作,否则中间操作不会执行任何的处理! 而在终止操作是一次性全部处理,成为 "惰性求值"

终端操作(终止操作)

当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。
终端操作将返回一个执行结果,这就是你想要的数据。

5.2 Stream 创建流

集合流

// 通过 Collection 系列集合提供的 stream() 或 parallelStream()
List<String> list = new ArrayList<>();
//获取一个顺序流
Stream<String> stream1 = list.stream();
//获取一个并行流
Stream<String> parallelStream = list.parallelStream(); 

数组流: Arrays.stream()

// 通过 Arrays 中的静态方法stream()获取
String[] strings = new String[10];
Stream<String> stream2 = Arrays.stream(strings);

Stream 类中的静态方法 of()、iterate()、generate()

// 通过 Stream 类中的静态方法 of
Stream<java.lang.String> stream3 = Stream.of("aa", "bb", "cc");
// 无限流
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6); // 迭代
stream2.forEach(System.out::println); 
Stream<Double> stream3 = Stream.generate(Math::random).limit(2); // 生成
stream3.forEach(System.out::println);

无限流

  1. 迭代 iterate()
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
  1. 生成 generate()
Stream.generate(Math::random).forEach(System.out::println);

文件生成流 BufferedReader.lines()

BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<java.lang.String> lineStream = reader.lines();
lineStream.forEach(System.out::println);

字符串分隔成流: Pattern.splitAsStream()

Pattern pattern = Pattern.compile(",");
Stream<java.lang.String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);

生成空流

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

数值范围生成流

// 生成0到10的int流
IntStream intStream = IntStream.rangeClosed(0, 10);
// 生成0到9的int流
IntStream intStream1 = IntStream.range(0, 10);

手动生成流

// 生成有字符串a和数字1的异构的流
Stream.builder().add("a").add(1).build().forEach(System.out::print);

5.3 Stream 中间操作

1. 筛选与切片

方法描述
filter(Predicate p)接收 Lambda , 从流中排除某些元素。
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量。
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素

filter——接收 Lambda , 从流中排除某些元素。

  1. 内部迭代
@Test
public void screeningAndSlicing1() {
    // 中间操作
    Stream<Employee> stream = employeeList.stream()
            // 年龄大于35
            .filter(e -> e.getAge() > 35);
    // 只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
    stream.forEach(System.out::println);
}
  1. 内部迭代
// 外部迭代
@Test
public void screeningAndSlicing2() {
    Iterator<Employee> it = employeeList.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

limit——截断流,使其元素不超过给定数量。

@Test
public void screeningAndSlicing3() {
    employeeList.stream()
            .filter(e -> {
                System.out.println("短路!"); // &&  ||
                return e.getSalary() > 5000;
            })
            .limit(2)
            .forEach(System.out::println);
}

skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

@Test
public void screeningAndSlicing4() {
    employeeList.stream()
            .filter(e -> e.getSalary() > 5000)
            .skip(2)
            .forEach(System.out::println);
}

distinct——筛选,通过流所生成元素的 hashCode()equals() 去除重复元素

@Test
public void screeningAndSlicing5() {
    // distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
    employeeList.stream()
            .filter(e -> e.getSalary() > 5000)
            .skip(2)
            .distinct()
            .forEach(System.out::println);
}

2. 映射

方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

map——接收 Lambda , 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
list.stream()
        // str -> str.toUpperCase() 转换为大写
        .map(String::toUpperCase)
        .forEach(System.out::println);

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

employeeList.stream()
        .map(Employee::getName)
        .forEach(System.out::println); 

flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

// Map
Stream<Stream<Character>> stream1 = list.stream()
        // 类名::静态方法
        .map(StreamApiTest02::filterCharacter);
stream1.forEach(sm -> {
    sm.forEach(System.out::print);
});
System.out.println();
System.out.println("------------");

// 优化上面
// flatMap
Stream<Character> stream2 = list.stream()
        // 类名::静态方法
        .flatMap(StreamApiTest02::filterCharacter);
stream2.forEach(System.out::print);

静态方法

public static Stream<Character> filterCharacter(String str) {
    List<Character> list = new ArrayList<>();
    for (Character ch : str.toCharArray()) {
        list.add(ch);
    }
    return list.stream();
}

3. 排序

方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator comp)产生一个新流,其中按比较器顺序排序

sorted()——自然排序(Comparable)

List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
// 自然排序
list.stream()
        .sorted()
        .forEach(System.out::println);

sorted(Comparator com)——定制排序(Comparator)

employeeList.stream()
        .sorted((e1, e2) -> {
            if (e1.getAge() == e2.getAge()) {
                return e1.getName().compareTo(e2.getName());
            } else {
                return Integer.compare(e1.getAge(), e2.getAge());
            }
        }).forEach(System.out::println);

5.4 Stream 终止操作

1 查找与匹配

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count返回流中元素总数
max(Comparator c)返回流中大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为 外部迭代。相反,Stream API 使用内部 迭代——它帮你把迭代做了)

allMatch——检查是否匹配所有元素

boolean b1 = employeeList.stream()
        .allMatch(e -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);

anyMatch——检查是否至少匹配一个元素

boolean b2 = employeeList.stream()
        .anyMatch(e -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);

noneMatch——检查是否没有匹配的元素

boolean b3 = employeeList.stream()
        .noneMatch(e -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);

findFirst——返回第一个元素

工资排序

Optional<Employee> opA = employeeList.stream()
        // 工资排序 正序
        .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
        .findFirst();
if (opA.isPresent()) {
    System.out.println(opA.get());
}
Optional<Employee> opD = employeeList.stream()
        // 工资排序  倒序
        .sorted((e1, e2) -> -Double.compare(e1.getSalary(), e2.getSalary()))
        .findFirst();
if (opD.isPresent()) {
    System.out.println(opD.get());
}

使用 max(),min() 优化

Optional<Employee> op1 = employeeList.stream().min(Comparator.comparingDouble(Employee::getSalary));
op1.ifPresent(System.out::println);
Optional<Employee> op2 = employeeList.stream().max(Comparator.comparingDouble(Employee::getSalary));
op2.ifPresent(System.out::println);

findAny——返回当前流中的任意元素

Optional<Employee> any = employeeList.stream()
        .filter(e -> e.getStatus().equals(Employee.Status.FREE))
        .findAny();
any.ifPresent(System.out::println);

count——返回流中元素的总个数

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

max——返回流中最大值

获取最大工资员工

Optional<Employee> max = employeeList.stream()
        .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
if (max.isPresent()){
    System.out.println(max.get());
}

优化:

  1. 方式一
Optional<Employee> max1 = employeeList.stream()
        .max(Comparator.comparingDouble(Employee::getSalary));
max1.ifPresent(System.out::println);
  1. 方式二 最大工资
Optional<Double> max2 = employeeList.stream()
        .map(Employee::getSalary)
        .max(Double::compare);
max2.ifPresent(System.out::println);

min——返回流中最小值

Optional<Employee> min = employeeList.stream()
        .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
if (max.isPresent()){
    System.out.println(min.get());
}

2. 规约

方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional<T>

reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream()
        // 参数一: 起始值
        // (x,y)-> x+y[求和] 转换为 Integer::sum
        .reduce(0, Integer::sum);
System.out.println(sum);

reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值,返回 Optional<T>

Optional<Double> reduce = employeeList.stream()
        .map(Employee::getSalary)
        .reduce(Double::sum);
reduce.ifPresent(System.out::println);

3. 收集

方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListList(T)把流中元素收集到List
toSetSet(T)把流中元素收集到Set
toCollectionCollection(T)把流中元素收集到创建的集合
countingLong计算流中元素的个数
averagingDoubleDouble平均值
averagingIntInteger
averagingLongLong
summingIntInteger对流中元素的整数属性求和
summingDoubleDouble
summingLongLong
summarizingDoubleDoubleSummaryStatistics用于int、long、double类型数据一个求总操作
summarizingInt
summarizingLong
joiningString连接流中每个字符串
maxByOptional(T)根据比较器选择最大值
minByOptional(T)根据比较器选择最小值
reducing归约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数
groupingByMap(K, List(T))根据某属性值对流分组,属性为K,结果为V
partitioningByMap(Boolean, List(T))根据true或false进行分区
注意:为避免markdown在文档页面格式冲突,上面返回类型中所有的()都是代表<> . 比如 List(T) 指的是 List<T>. 

toList

List<String> list = employeeList.stream()
        .map((Employee::getName))
        .collect(Collectors.toList());
list.forEach(System.out::println);

toSet

Set<String> set = employeeList.stream()
                .map((Employee::getName))
                .collect(Collectors.toSet());
        set.forEach(System.out::println);

toCollection

HashSet<String> collect = employeeList.stream()
        .map((Employee::getName))
        .collect(Collectors.toCollection(HashSet::new));
collect.forEach(System.out::println);

counting

Long count = employeeList.stream()
        .collect(Collectors.counting());
System.out.println(count);

averagingDouble 平均值

Double avg = employeeList.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);

summingDouble 总和

Double summ = employeeList.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(summ);

maxBy 最大值

Optional<Employee> max = employeeList.stream()
        .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
max.ifPresent(System.out::println);

minBy 最小值

Optional<Employee> min = employeeList.stream()
        .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
min.ifPresent(System.out::println);

groupingBy 分组

  1. 分组
Map<Employee.Status, List<Employee>> groupBy = employeeList.stream()
        .collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(groupBy);
  1. 多级分组
Map<Employee.Status, Map<String, List<Employee>>> map = employeeList.stream()
        .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
            if (e.getAge() <= 35) {
                return "青年";
            } else if (e.getAge() <= 50) {
                return "中年";
            } else {
                return "老年";
            }
        })));
System.out.println(map);

partitioningBy 分区

Map<Boolean, List<Employee>> partitioningBy = employeeList.stream()
        .collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
System.out.println(partitioningBy);

summarizingDouble

DoubleSummaryStatistics dss = employeeList.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("总和 : " + dss.getSum());
System.out.println("数量 : " + dss.getCount());
System.out.println("平均值 : " + dss.getAverage());
System.out.println("最大值 : " + dss.getMax());
System.out.println("最小值 : " + dss.getMin());

joining 连接

String str = employeeList.stream()
        .map(Employee::getName)
        .collect(Collectors.joining(",","===","==="));
System.out.println(str);

6. 日期API

参考:Calendar、Date

6.1 新旧版本API差别

1. 新版本计算准确(毫秒),旧版本有误差

public class dateApiTest01 {

    /**
     * 程序员小李出生于1995年12月26日,计算当前这个时间他已经出生多久了?
     */
    @Test
    public void oldApi() {
        Date d = new Date();
        // 获取时间
        long s1 = d.getTime();

        //
        Calendar c = Calendar.getInstance();
        // 月份从 0-11 12月为: 11
        c.set(1995, 11, 16);
        Date d2 = c.getTime();
        long s2 = d2.getTime();

        long intervalDay = (s1 - s2) / 1000 / 60 / 60 / 24;

        System.out.println("1995年12月距离现在已经过时了" + intervalDay + "天");
        // 9013
    }

    /**
     * 程序员小李出生于1995年12月26日,计算当前这个时间他已经出生多久了?
     *  Java8新日期API计算准确
     */
    @Test
    public void newApi() {

        // 使用Jav8新版本的API获取
        long intervalDay = ChronoUnit.DAYS.between(LocalDate.of(1995, 12, 16), LocalDate.now());

        System.out.println("1995年12月距离现在已经过时了" + intervalDay + "天");
        // 9014
    }

}

2. 老版本存在 线程不安全问题

SimpleDateFormat 类是线程不安全的,咋多线程的情况下,全局贡献一个 SimpleDateFormat 类中的 Calendar 对象有可能出现异常.

final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

/**
 * 创建10个线程,将字符串"2020-08-08 12:12:12"转换为Date对象后,打印到控制台上
 */
@Test
public void oldApi() {
    for (int i = 0; i < 10; i++) {
        // 创建线程(匿名内部类方式)
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Date date = SIMPLE_DATE_FORMAT.parse("2020-08-08 12:12:12");
                    System.out.println(date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

控制台错误:

 java.lang.NumberFormatException: For input string: ""

问题原因: 线程问题

解决: 锁

@Test
public void oldApi() {
    for (int i = 0; i < 10; i++) {
        // 创建线程(匿名内部类方式)
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 通过加锁解决线程安全问题
                    synchronized (SIMPLE_DATE_FORMAT) {
                        Date date = SIMPLE_DATE_FORMAT.parse("2020-08-08 12:12:12");
                        System.out.println(date);
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

3. 开发规范

不允许使用没有定义的魔法数字

public class dateApiTest03 {

    /**
     * 初始化Calendar对象,封装日期为2008年8月8日
     */
    @Test
    public void oldApi() {
        // 初始化Calendar对象
        Calendar calendar = Calendar.getInstance();
        // 设置年月日
        // 不允许使用没有定义的魔法数字
//        calendar.set(2008,7,8);
        /**
         * public final static int AUGUST = 7;
         */
        calendar.set(2008,Calendar.AUGUST,0); // 正确格式 august: 8月
    }

}

6.2 Date-Time API基本类

1. 常用类

常用类功能概述
InstantInstant类对时间轴上的单一瞬时点建模,可以用于记录应用程序中的事件时间戳,在之后的类型转换中,均可以使用Instant类作为中间类完成转换.
DurationDuration类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确度.
PeriodPeriod类表示一段时间的年,月,日.
LocalDateLocalDate是一个不可变的日期时间对象,表示日期,通常视为年月日.
LocalTimeLocalTime是一个不可变的日期时间对象,代表一个时间,通常看做是小时-秒,时间表示为纳秒精度.
LocalDateTimeLocalDateTime是一个不可变得日期时间对象,代表日期时间,通常视为年-月-日-时-分-秒.
ZonedDateTimeZonedDateTime是具有时区的日期的不可变表时,此类存储所有时间和时间字段,精确度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间.

2. now 方法在日期/时间类中的使用

Date-Time API 中的所有类均生成不可变的示例,它们是线程安全的,并且这些类不提供公共构造函数,也就是说没办法通过new的方式直接创建,需要曹勇工厂方法加以实例化.

  1. now方法可以根据当前日期或时间创建实例对象
public class TimeClassMethodTest01 {

    /**
     * now方法可以根据当前日期或时间创建实例对象
     * Instant,LocalDate,LocalTime,LocalDateTime,ZonedDateTime
     */
    @Test
    public void test1() {
        // 使用now方法创建Instant的实例方法对象
        Instant instant = Instant.now();
        // 使用now方法创建LocalDate的实例对象
        LocalDate localDateNow = LocalDate.now();
        // 使用now方法创建LocalTime的实例对象
        LocalTime localTimeNow = LocalTime.now();
        // 使用now方法创建LocalDateTime的实例对象
        LocalDateTime LocalDateTimeNow = LocalDateTime.now();
        // 使用now方法创建ZonedDateTime的实例对象
        ZonedDateTime zonedDateTimeNow = ZonedDateTime.now();

        // 将实例对象在到控制台输出
        System.out.println("instant: " + instant); // instant: 2020-08-20T05:05:47.339Z 标准时间(国际)
        System.out.println("localDateNow: " + localDateNow); // localDateNow: 2020-08-20
        System.out.println("localTimeNow: " + localTimeNow); // localTimeNow: 13:05:47.440 (440: 表示纳秒)
        System.out.println("LocalDateTimeNow: " + LocalDateTimeNow); // LocalDateTimeNow: 2020-08-20T13:05:47.440
        System.out.println("zonedDateTimeNow: " + zonedDateTimeNow); // zonedDateTimeNow: 2020-08-20T13:05:47.440+08:00[Asia/Shanghai] 东八区
    }

}
  1. Java8的Time包下其他使用now方法的类.
/**
 * 其他使用now方法的类
 * Year,YearMonth,MonthDay
 */
@Test
public void test2() {
    // 使用now方法创建Year的实例方法对象
    Year yearNow = Year.now();
    // 使用now方法创建Month的实例方法对象
    YearMonth yearMonthNow = YearMonth.now();
    // 使用now方法创建Day的实例方法对象
    MonthDay monthDayNow = MonthDay.now();

    // 将实例对象在到控制台输出
    System.out.println("yearNow: " + yearNow); // yearNow: 2020
    System.out.println("yearMonthNow: " + yearMonthNow); // yearMonthNow: 2020-08
    System.out.println("monthDayNow: " + monthDayNow); // monthDayNow: --08-20
}

now(): 获取当前时间日期.

3. of方法在日期/时间类中的应用

of方法可以根据给定的参数生成对相应的日期/时间,基本上每个基本类都有of方法用于生成的对应的对象,而且重载形式多变,可以根据不同的参数生成对应的数据.

  1. LocalDate.of 简单使用
/**
 * 初始化2020年8月8日
 */
@Test
public void test1() {
    LocalDate localDate = LocalDate.of(2020, 8, 8);
    System.out.println("localDate: "+ localDate); // localDate: 2020-08-08
}
  1. LocalTime.of 的使用

    LocalTime.of(int hour, int minute): 根据小时/分钟生成对象

    LocalTime.of(int hour, int minute,int second): 根据小时/分钟/秒生成对象

    LocalTime.of(int hour, int minute,int second, int nanoOfSecond): 根据小时/分钟/秒/纳秒生成对象.

/**
 * 初始化晚上8点0分0秒的" LocalTime " 对象  -> 如果是晚上的时间,需要将12.
 */
@Test
public void test2() {
    // 方式一
    LocalTime localTime1 = LocalTime.of(20, 0);
    // 方式二
    LocalTime localTime2 = LocalTime.of(20, 0,58);
    // 方式三
    LocalTime localTime3 = LocalTime.of(20, 0,34,1);
    System.out.println("localTime1: " + localTime1); // localTime1: 20:00
    System.out.println("localTime2: " + localTime2); // localTime2: 20:00:58
    System.out.println("localTime3: " + localTime3); // localTime3: 20:00:34.000000001
}
  1. LocalDateTime.of的使用

    LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute ,int second, int nanoOfSecond): 根据年/月/日/时/分/秒/纳秒生成对象

    LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute): 根据年/月/日/时/分生成对象

/**
 * 初始化2020年8月8日下午8点0分的 " LocalDateTime " 对象
 */
@Test
public void test3(){
    LocalDateTime localDateTime1 = LocalDateTime.of(2020, 8, 8, 20, 0);
    System.out.println("localDateTime1: " + localDateTime1); // localDateTime1: 2020-08-08T20:00
}

特殊情况: LocalDateTime.of(LocalDate date, LocalTime time)

/**
 * LocalDateTime: 特殊使用
 */
@Test
public void test4() {
    // localDate
    LocalDate localDate = LocalDate.of(2020, 8, 8);
    // localTime
    LocalTime localTime = LocalTime.of(20, 0);

    LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
    System.out.println("localDateTime: " + localDateTime); // localDateTime: 2020-08-08T20:00
}

4. LocalDateTime 时区信息

ZonedDateTime: 不仅封装有日期时间,并且还有偏移量 时区 (中国东八区)

  1. 时区的获取

    通过 ZoneId 类 提供的 getAvailableZoneIds() 可以获取一个Set集合,集合中封装了600个时区.

public class TimeClassMethodTest03 {

    @Test
    public void test1(){
        // 获取所有时区信息
        Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        for (String availableZoneId : availableZoneIds) {
//            System.out.println(availableZoneId);
        }
        // 获取当前系统默认的时区信息 -> 中国
        ZoneId zoneId = ZoneId.systemDefault();
        System.out.println(zoneId); // Asia/Shanghai
    }

}
  1. 添加时区信息 / 获取其他时区信息
@Test
public void test2(){
    // 1. 封装LocalDateTime对象,参数自定义->2020年11月11日 8点54分38秒
    LocalDateTime time = LocalDateTime.of(2020, 11, 11, 8, 54, 38);
    // 2. 封装完成后的time对象知识一个封装的时间对象,并没有时区相关的数据,所以添加时区到对象中,使用atZone()方法
    ZonedDateTime zonedDateTime = time.atZone(ZoneId.of("Asia/Shanghai"));
    System.out.println("zonedDateTime: " + zonedDateTime); // zonedDateTime: 2020-11-11T08:54:38+08:00[Asia/Shanghai]
    // 3. 更改时区查看其它时区的当前时间 通过 withZoneSameInstant() 即可更改
    ZonedDateTime AmericaZonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
    System.out.println("同一时间的日本时间: " + AmericaZonedDateTime);
    // 同一时间的日本时间: 2020-11-11T09:54:38+09:00[Asia/Tokyo]

}

5. Month枚举类的使用

java.time包中引入了Month的枚举,Month中包含标准日历中的12个月份的常量(1-12)月,也提供了一些方便的方法共我们使用.

推荐在初始化 LocalDate 和 LocalDateTime 对象的时候,月份使用枚举的方式传入,这样更简单易懂而且不易出错.(旧的API Calendar从0-11)

/**
 * Month月份枚举使用
 */
@Test
public void test(){
    // 1. 初始化 LocalDate 和 LocalDateTime 对象的时候,月份使用枚举的方式传入 -> 2020年7月26日11时11分11面
    LocalDateTime time = LocalDateTime.of(2020, Month.JULY, 26, 11, 11, 11);
    System.out.println(time); // 2020-07-26T11:11:11

    // 2. Month枚举类 -> of可以根据传入的数字返回对应月份的枚举
    Month month = Month.of(12);
    System.out.println(month); // DECEMBER
}

6. 测试

public class TimeClassMethodTest05 {

    /**
     * 创建当前时间(不带时区)
     */
    @Test
    public void test1(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now); // 2020-08-20T22:06:05.361
    }

    /**
     * 创建当前时间(只包含年月日)
     */
    @Test
    public void test2(){
        LocalDate now = LocalDate.now();
        System.out.println(now); // 2020-08-20
    }

    /**
     * 创建当前时间(包含年月日时分秒并且带有时区)
     */
    @Test
    public void test3(){
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println(now); // 2020-08-20T22:09:06.124+08:00[Asia/Shanghai]
    }

    /**
     * 创建2020年12日31日7时38分46秒的日期对象(月份使用枚举)
     */
    @Test
    public void test4(){
        LocalDateTime now = LocalDateTime.of(2020, Month.DECEMBER,31,7,38,46);
        System.out.println(now); // 2020-12-31T07:38:46
    }

    /**
     * 创建2020年12日31日的日期对象(月份使用枚举)
     */
    @Test
    public void test5(){
        LocalDate now = LocalDate.of(2020,Month.DECEMBER,31);
        System.out.println(now); // 2020-12-31
    }

    /**
     * 创建7时38分46秒的时间对象
     */
    @Test
    public void test6(){
        LocalTime now = LocalTime.of(7,38,46);
        System.out.println(now); // 07:38:46
    }

}

6.3 根据现有实例创建日期与时间对象

java8中的日期时间相关的API的所有实例都是不可变的,一旦创建 LocalDate,LocalTime,LocalDateTime 就无法修改(类似于Spring), 这对于线程安全非常有利

1. plus在 LocalDate,LocalTime 中的使用

想要修改某个日期/时间的现有实力是,我们可以使用plus/minus方法来完成操作.

  1. LocalDate中对日期进行递增/减操作的方法
方法作用
LocalDate plusDays(long days)增减天数
LocalDate plusWeeks(long weeks)增减周数
LocalDate plusMonths(long months)增减月数
LocalDate plusYears(long years)增减年数
/**
 * Plus方法在localDate中的使用
 * LocalDate plusDays(long days)增减天数
 * LocalDate plusWeeks(long weeks)增减周数
 * LocalDate plusMonths(long months)增减月数
 * LocalDate plusYears(long years)增减年数
 */
@Test
public void test1(){
    // 封装localDate对象,参数为2020年2月13日
    LocalDate localDate = LocalDate.of(2020, Month.FEBRUARY, 13);
    System.out.println("当前时间: " + localDate); // 当前时间: 2020-02-13

    // 计算当前时间的4天后的时间,并打印
    LocalDate date1 = localDate.plusDays(4);
    System.out.println("当前时间的4天后时间: " + date1); // 当前时间的4天后时间: 2020-02-17

    // 计算当前时间的3周后的时间,并打印
    LocalDate date2 = localDate.plusWeeks(3);
    System.out.println("当前时间的4周后时间: " + date2); // 当前时间的4周后时间: 2020-03-05

    // 计算当前时间的5个月后的时间,并打印
    LocalDate date3 = localDate.plusMonths(5);
    System.out.println("当前时间的5个月后时间: " + date3); // 当前时间的5个月后时间: 2020-07-13

    // 计算当前时间的2年后的时间,并打印
    LocalDate date = localDate.plusYears(3);
    System.out.println("当前时间的2年后时间: " + date); // 当前时间的2年后时间: 2023-02-13
}
  1. LocalTime中对时间进行递增/减操作的方法
方法作用
LocalTime plusNanos(long nanos)增加纳秒
LocalTime plusSeconds(long seconds)增加秒
LocalTime plusMenutes(long menutes)增加分钟
LocalTime plusHours(long hours)增加小时
/**
 * Plus方法在localTime中的使用
 * LocalTime plusNanos(long nanos)增加纳秒
 * LocalTime plusSeconds(long seconds)增加秒
 * LocalTime plusMenutes(long menutes)增加分钟
 * LocalTime plusHours(long hours)增加小时
 */
@Test
public void test2() {
    // 封装LocalTime对象参数为8点13分39秒218毫秒
    LocalTime localTime = LocalTime.of(8, 13, 39, 218);
    System.out.println("当前时间: " + localTime); // 当前时间: 08:13:39.000000218

    // 计算当前时间500纳秒后的时间,并打印
    LocalTime date1 = localTime.plusNanos(500);
    System.out.println("当前时间500纳秒后的时间: " + date1); // 当前时间500纳秒后的时间: 08:13:39.000000718

    // 计算当前时间45秒后的时间,并打印
    LocalTime date2 = localTime.plusSeconds(45);
    System.out.println("当前时间45秒后的时间: " + date2); // 当前时间45秒后的时间: 08:14:24.000000218


    // 计算当前时间19分钟后的时间,并打印
    LocalTime date3 = localTime.plusMinutes(19);
    System.out.println("当前时间19分钟后的时间: " + date3); // 当前时间19分钟后的时间: 08:32:39.000000218

    // 计算当前时间3小时后的时间,并打印
    LocalTime date4 = localTime.plusHours(3);
    System.out.println("当前时间3小时后的时间: " + date4); // 当前时间3小时后的时间: 11:13:39.000000218
}

2. minus方法在 LocalDate,LocalTime中的使用

  1. LocalDate中对日期进行递增/减操作的方法
方法作用
LocalDate minusDays(long daysToSubtract)增减天数
LocalDate minusWeeks(long weeksToSubtract)增减周数
LocalDate minusMonths(long monthsToSubtract)增减月数
LocalDate minusYears(long yearsToSubtract)增减年数
/**
 * Minus方法在localDate中的使用
 * LocalDate minusDays(long daysToSubtract)增减天数
 * LocalDate minusWeeks(long weeksToSubtract)增减周数
 * LocalDate minusMonths(long monthsToSubtract)增减月数
 * LocalDate minusYears(long yearsToSubtract)增减年数
 */
@Test
public void test1() {
    // 封装localDate对象,参数为2020年2月13日
    LocalDate localDate = LocalDate.of(2020, Month.FEBRUARY, 13);
    System.out.println("当前时间: " + localDate); // 当前时间: 2020-02-13

    // 计算当前时间的4天后的时间,并打印
    LocalDate date1 = localDate.minusDays(4);
    System.out.println("当前时间的4天前时间: " + date1); // 当前时间的4天前时间: 2020-02-09

    // 计算当前时间的3周后的时间,并打印
    LocalDate date2 = localDate.minusWeeks(3);
    System.out.println("当前时间的4周前时间: " + date2); // 当前时间的4周前时间: 2020-01-09

    // 计算当前时间的5个月后的时间,并打印
    LocalDate date3 = localDate.minusMonths(5);
    System.out.println("当前时间的5个月前时间: " + date3); // 当前时间的5个月前时间: 2019-09-13

    // 计算当前时间的2年后的时间,并打印
    LocalDate date = localDate.minusYears(2);
    System.out.println("当前时间的2年前时间: " + date); // 当前时间的2年前时间: 2018-02-13
}
  1. LocalTime中对时间进行递增/减操作的方法
方法作用
LocalTime minusNanos(long nanosToSubtract)增加纳秒
LocalTime minusSeconds(long secondsToSubtract)增加秒
LocalTime minusMinutes(long menutes)增加分钟
LocalTime minusHours(long hoursToSubtract)增加小时
/**
 * Minus方法在localTime中的使用
 * LocalTime minusNanos(long nanosToSubtract)增加纳秒
 * LocalTime minusSeconds(long secondsToSubtract)增加秒
 * LocalTime minusMinutes(long menutes)增加分钟
 * LocalTime minusHours(long hoursToSubtract)增加小时
 */
@Test
public void test2() {
    // 封装LocalTime对象参数为8点13分39秒218毫秒
    LocalTime localTime = LocalTime.of(8, 13, 39, 218);
    System.out.println("当前时间: " + localTime); // 当前时间: 08:13:39.000000218

    // 计算当前时间500纳秒后的时间,并打印
    LocalTime date1 = localTime.minusNanos(500);
    System.out.println("当前时间500纳秒后的时间: " + date1); // 当前时间500纳秒后的时间: 08:13:39.000000718

    // 计算当前时间45秒后的时间,并打印
    LocalTime date2 = localTime.minusSeconds(45);
    System.out.println("当前时间45秒后的时间: " + date2); // 当前时间45秒后的时间: 08:14:24.000000218


    // 计算当前时间19分钟后的时间,并打印
    LocalTime date3 = localTime.minusMinutes(19);
    System.out.println("当前时间19分钟后的时间: " + date3); // 当前时间19分钟后的时间: 08:32:39.000000218

    // 计算当前时间3小时后的时间,并打印
    LocalTime date4 = localTime.minusHours(3);
    System.out.println("当前时间3小时后的时间: " + date4); // 当前时间3小时后的时间: 11:13:39.000000218
}

3. plus/minus单独使用

  1. plus方法
方法作用
LocalDate/LocalTime/LocalDateTime plus(TemporalAmount amountToAdd)
LocalDate/LocalTime/LocalDateTime plus(long amountToAdd, TemporalUnit unit)
  • TemporalAmount 是一个接口,当借口作为方法的参数的时候,实际上传入的是接口的实现类对象,根据查看这个接口的体系,可以看到这个接口有一个实现类,名字叫做 Period (表示一段时间)

Period of(int years, int months, int days): Period.of(1,2,3) 返回对象表示即为1年2月3天

/**
 * 今天程序员小张查看自己的车辆的保险记录的时候看到还有2年3月8天就到期了,计算到期的时间是什么时候.
 * LocalDate plus(TemporalAmount amountToAdd)
 * Period.of(int years, int months, int days)
 */
@Test
public void test3(){

    // 1. 封装当前时间 -> now方法
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前时间: " + now); // 当前时间: 2020-08-21T06:34:36.627

    // 2. 在当前时间的基础上,进行+年月日操作,获取一个截止日期对象,这个对象就是保险的到期时间
    LocalDateTime endTime1 = now.plusYears(2).plusMonths(3).plusDays(8);
    System.out.println("保险到期时间: " + endTime1); // 保险到期时间: 2022-11-29T06:34:36.627

    // 3. 使用 plus 方法改进 TemporalAmount ---> Period(实现类)
    // Period.of(int years, int months, int days)
    Period period = Period.of(2, 3, 8);
    LocalDateTime endTime2 = now.plus(period); // 在当前时间的基础上增加
    System.out.println("保险到期时间: " + endTime2); // 保险到期时间: 2022-11-29T06:34:36.627
}
  • 在实际开发过程中可能还会更准确的操作日期或者说增加一些特殊的时间,比如说1个世纪,一个半天,一年...,Java8已经提供了这些日期的表示方法而不需要去单独进行计算了.

    TemporalUnit 是一个接口,通过查看体系接口发现,可以使用子类 ChronoUnit 来表示.

  • chronoUnit

    一组标准的日期时间单位。

    图片来源: jdk文档

/**
 * 结婚10年称为锡婚,2020年2月2日11点11分11秒称为对称日,很多情侣准备在那天结婚,如果在那天结婚了,那么锡婚会发生在什么时候?
 * LocalDate/LocalTime/LocalDateTime plus(long amountToAdd, TemporalUnit unit)
 */
@Test
public void test4() {
    // 封装日期 -> 表示结婚的时间点
    LocalDateTime marryTime = LocalDateTime.of(2020, Month.FEBRUARY, 2, 11, 11, 11);

    // 1. 使用 plus() 进行计算,加上一个十年  ChronoUnit.DECADES : 代表十年
    LocalDateTime time = marryTime.plus(1, ChronoUnit.DECADES);
    System.out.println("如果在: " + marryTime + " 结婚, 那么锡婚的时间是: " + time); // 如果在: 2020-02-02T11:11:11 结婚, 那么锡婚的时间是: 2030-02-02T11:11:11

    // 2. 如果锡婚后的半天,需要请所有的亲朋好友吃饭,计算吃饭的时间. ChronoUnit.HALF_DAYS : 代表半天
    LocalDateTime cTime = time.plus(1, ChronoUnit.HALF_DAYS);
    System.out.println("半天后, 请客吃放的时间为: " + cTime); // 半天后,请客吃放的时间为: 2030-02-02T23:11:11
}
  1. minus方法
方法作用
LocalDate/LocalTime/LocalDateTime minus(long amountToSubtract, TemporalUnit unit)
LocalDate/LocalTime/LocalDateTime minus(TemporalAmount amountToSubtract)

使用方法同 plus

4. with方法

如果不需要对日期进行加减而是直接修改日期的话,那么可以使用with方法,with方法提供了许多种修改时间的方法.

  1. 在LocalDateTime中的使用
方法作用
LocalDateTime withNano(int nanoOfSecond)修改纳秒
LocalDateTime withSecond(int second)修改秒
LocalDateTime withMinute(int minute)修改分钟
LocalDateTime withHour(int hour)修改小时
LocalDateTime withDayOfMonth(int dayOfMonth)修改日
LocalDateTime withDayOfYear(int dayOfYear)
LocalDateTime withMonth(int month)修改月
LocalDateTime withYear(int year)修改年
public static LocalDateTime getTime(){
    // 2020.12.12 12:12:0
    return LocalDateTime.of(1999, Month.DECEMBER,12,12,12,0);
}

/**
 *
 */
@Test
public void test1(){
    LocalDateTime time = getTime();
    // 使用 with 方法修改
    // 1. 经过使用发现 time 存在问题, 时间应为 1 号
    LocalDateTime resultTime1 = time.withDayOfMonth(1);
    System.out.println("修改前的错误时间: "+ time); // 修改前的错误时间: 1999-12-12T12:12
    System.out.println("修改后的正确时间1: "+ resultTime1); //  修改后的正确时间: 1999-12-01T12:12

    // 2. 修改为 2020.8.8 11:11:11:1
    LocalDateTime resultTime2 = time.withYear(2020).withMonth(8).withDayOfMonth(8).withHour(11).withMinute(11).withSecond(11).withNano(1);
    System.out.println("修改前的错误时间: "+ time); // 修改前的错误时间: 1999-12-12T12:12
    System.out.println("修改后的正确时间2: "+ resultTime2); // 修改后的正确时间2: 2020-08-08T11:11:11.000000001
}
  1. with单独使用
方法作用
with(TemporalField field, long newValue)
with(TemporalAdjuster adjuster)
  • TemporalField 是一个接口,通过查看体系结构,可以使用它的子类ChronoField

  • ChronoField中封装了一些日期时间中的组成部分,可以直接选择之后传入第二个参数进行修改.(参考open in new window)

    例; with(ChronoField.DAY_OF_MONTH,1): 将日期中的月份中的天数修改为1

/**
 * with(TemporalField field, long newValue)
 */
@Test
public void test2(){
    LocalDateTime time = getTime();
    // 使用 with 方法修改
    // 1. 经过使用发现 time 存在问题, 时间应为 1 号
    LocalDateTime resultTime1 = time.with(ChronoField.DAY_OF_MONTH, 1);
    System.out.println("修改前的错误时间: "+ time); // 修改前的错误时间: 1999-12-12T12:12
    System.out.println("修改后的正确时间1: "+ resultTime1); //  修改后的正确时间: 1999-12-01T12:12
}

5. 测试

/**
 * @Description: <h1> 测试plus/with </h1>
 * 使用三种方式计算2019年7月19日14是38分34秒后的3年7个月18天后是什么时候
 */
public class TimeClassMethodPlusWithTest {

    /**
     * plus() Period
     */
    @Test
    public void test1(){
        // 封装当前时间 -> of()
        LocalDateTime time = LocalDateTime.of(2019, Month.JULY, 19, 14, 38, 34);
        // 通过Period封装一个时间段表示: 3年7个月18天后
        Period period = Period.of(3, 7, 18);
        // 通过plus()增加 Period 是 TemporalAmount 的一个实现类
        LocalDateTime endTime = time.plus(period);
        System.out.println("当前时间是: " + time + ", 3年7个月18天后: " + endTime);
        // 当前时间是: 2019-07-19T14:38:34 3年7个月18天后: 2023-03-09T14:38:34
    }

    /**
     * plus()
     */
    @Test
    public void test2(){
        // 封装当前时间 -> of()
        LocalDateTime time = LocalDateTime.of(2019, Month.JULY, 19, 14, 38, 34);
        // 通过plus()增加 3年7个月18天后
        LocalDateTime endTime = time
                .plusYears(3)
                .withMonth(7)
                .withDayOfMonth(18);
        System.out.println("当前时间是: " + time + ", 3年7个月18天后: " + endTime);
        // 当前时间是: 2019-07-19T14:38:34 3年7个月18天后: 2023-03-09T14:38:34
    }

    /**
     * with()
     */
    @Test
    public void test3(){
        // 封装当前时间 -> of()
        LocalDateTime time = LocalDateTime.of(2019, Month.JULY, 19, 14, 38, 34);
        // 通过with 修改到 3年7个月18天后
        LocalDateTime endTime = time
                .withYear(2023)
                .withMonth(3)
                .withDayOfMonth(9);
        System.out.println("当前时间是: " + time + ", 3年7个月18天后: " + endTime);
        // 当前时间是: 2019-07-19T14:38:34 3年7个月18天后: 2023-03-09T14:38:34
    }

    /**
     * with()
     */
    @Test
    public void test4(){
        // 封装当前时间 -> of()
        LocalDateTime time = LocalDateTime.of(2019, Month.JULY, 19, 14, 38, 34);
        // 通过with 修改到 3年7个月18天后
        LocalDateTime endTime = time
                .with(ChronoField.YEAR,2023)
                .with(ChronoField.MONTH_OF_YEAR,3)
                .with(ChronoField.DAY_OF_MONTH,9);
        System.out.println("当前时间是: " + time + ", 3年7个月18天后: " + endTime);
        // 当前时间是: 2019-07-19T14:38:34 3年7个月18天后: 2023-03-09T14:38:34
    }

}

6.4 调节器TemporalAdjuster与查询TemporalQuery

1. 调节器TemporalAdjuster

在上一篇中介绍了,我们可以通过with方法修改日期时间对象中封装的数据,但是有一些时候可能会做一些复杂的操作,比如说将时间调整到下个周的周日,下一个工作日,又或者本月中的某一天,这个时候我们可以使用TemporalAdjuster来更方便的处理日期.

方法作用
with(TemporalAdjuster adjuster)

TemporalAdjuster: 是一个接口,with方法实际上传入的是这个接口的实现类对象, TemporalAdjusters并不是TemporalAdjuster的一个实现类,只不过TemporalAdjusters的静态方法石祥路TemporalAdjuster.并且将实现类对象返回了.

方法作用
static TemporalAdjuster firstDayOfMonth()当月的第一天
static TemporalAdjuster lastDayOfMonth()当月的第一天
static TemporalAdjuster firstDayOfNextMonth()下一个月的第一天
static TemporalAdjuster firstDayOfYear()当年的第一天
static TemporalAdjuster lastDayOfYear()当年的第一天
static TemporalAdjuster firstDayOfNextYear()下一年的第一天
static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)当月的第一个周x(通过参数确定)
static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)当月的最后一个周x(通过参数确定)
static TemporalAdjuster ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster)

TemporalAdjuster 是一个函数是接口,里面有一个抽象方法叫做 Temporal adjustInto(Temporal temporal) ;传入一个 Temporal 对象通过实现逻辑返回一个 Temporal 对象,Temporal 是 LocalDate,localTime相关日期类的父接口,

@Test
public void test1() {
    // 封装日期时间对象为当前对象
    LocalDate now = LocalDate.now();

    // 通过with()传入TemporalAdjuster类的实现类对象,就可以更改,实现类对象是由TemporalAdjuster s类的静态方法来提供.
    // 1. 修改时间为当月的第一天
    LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
    System.out.println("当月的第一天: " + firstDayOfMonth); // 当月的第一天: 2020-08-01

    // 2. 修改时间为当月的最后第一天
    LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
    System.out.println("当月的最后第一天: " + lastDayOfMonth); // 当月的最后第一天: 2020-08-31

    // 3. 修改时间为下一个月的第一天
    LocalDate firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
    System.out.println("下一个月的第一天: " + firstDayOfNextMonth); // 下一个月的第一天: 2020-09-01

    // 4. 修改时间为当年的第一天
    LocalDate firstDayOfYear = now.with(TemporalAdjusters.firstDayOfYear());
    System.out.println("当年的第一天: " + firstDayOfYear); // 当年的第一天: 2020-01-01

    // 5. 修改时间为下一年的第一天
    LocalDate firstDayOfNextYear = now.with(TemporalAdjusters.firstDayOfNextYear());
    System.out.println("下一年的第一天: " + firstDayOfNextYear); // 下一年的第一天: 2021-01-01

    // 6. 修改时间为当年的最后第一天
    LocalDate lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
    System.out.println("当年的最后第一天: " + lastDayOfYear); // 当年的最后第一天: 2020-12-31
}

2. DayOfWeek的使用

DayOfWeek是一周中星期几的枚举类,其中封装了从周一到周日.

/**
 * DayOfWeek
 * static TemporalAdjuster next(DayOfWeek dayOfWeek): 下一个周x
 * static TemporalAdjuster previous(DayOfWeek dayOfWeek): 上一个周x
 */
@Test
public void test2(){
    // 封装日期时间对象为当前对象
    LocalDate now = LocalDate.now();

    // 1. 将当前时间修改为下一月的周日
    LocalDate date1 = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println("当前日期: "+ now+ ", 下一月的周日; " + date1); // 当前日期: 2020-08-22, 下一月的周日; 2020-08-23

    // 2. 将当前时间修改为上一个周三
    LocalDate date2 = now.with(TemporalAdjusters.previous(DayOfWeek.WEDNESDAY));
    System.out.println("当前日期: "+ now+ ", 上一月的周三; " + date2); // 当前日期: 2020-08-22, 上一月的周三; 2020-08-19
}

3. 自定义TemporalAdjuster调节器

通过Java8本身提供的TemporalAdjusters中的方法可以完成一些常用的操作,如果需要自定义日期时间的更改逻辑,可以通过实现TemporalAdjuster类的接口中的方式来完成.

  • 创建类实现TemporalAdjuster接口
  • 实现TemporalAdjuster中的adjustInto方法,传入一个日期时间对象,完成逻辑之后日期时间对象
  • 通过with方法传入自定义调节器对象完成更改.
 /**
     * 例如员工一个月中领取工资,发薪日是每个月的15号,如果发薪日是周末,则调整为周五
     * <p>
     * 之后会传入一个日期类时间对象,判断日期类时间对象是不是15号,如果不是15号则修改为15号,如果是周六或者周日,则改为周五(上一个)
     */
    class PayDayAdjuster implements TemporalAdjuster {

        @Override
        public Temporal adjustInto(Temporal temporal) {
            // 1. Temporal: 日期时间类对象的父接口,实际上可以理解为传入的就是LocalDate或者LocalTime对象.需要将temporal转换为LocalDate对象
            LocalDate payDay = LocalDate.from(temporal);
            // 2. 判断日期类时间对象是不是15号,如果不是15号则修改为15号
            int day = 0;
            if (payDay.getDayOfMonth() != 15) {
                day = 15;
            } else {
                day = payDay.getDayOfMonth();
            }
            // with修改日期
            LocalDate realPayDay = payDay.withDayOfMonth(day);
            // 3. 判断readPayDay,如果是周六或者周日,则改为周五(上一个)
            if (realPayDay.getDayOfWeek() == DayOfWeek.SATURDAY || realPayDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
                // 是周六/周日,修改为上一个周五
                realPayDay = realPayDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
            }
            return realPayDay;
        }
    }

    /**
     * 测试上面类是否可用
     */
    @Test
    public void tes3() {
        // 封装localDate对象为2018年12月1日
        LocalDate payDay = LocalDate.of(2018, Month.DECEMBER, 1);

        // 计算payDay的真实发薪日
        Temporal temporal = new PayDayAdjuster().adjustInto(payDay);
        // 转换
        LocalDate localDate = LocalDate.from(temporal);
        System.out.println("预计的发薪日: " + payDay + ", 真是的发薪日: " + localDate);
        // 预计的发薪日: 2018-12-01, 真是的发薪日: 2018-12-14
    }

Temporal: 日期时间类对象的父接口,实际上可以理解为传入的就是LocalDate或者LocalTime对象.需要将temporal转换为LocalDate对象

LocalDate.from():

4. TemporalQuery的应用

学习的是时态类对象(LocalDate,LocalTime)都有一个方法叫做query,可以针对日期进行查询.

R query(TemporalQuery query)这个方法是一个泛型方法,返回的数据就是传入的泛型类的类型 TemporalQuery是一个泛型接口,包含一个抽象方法:

方法
R queryFrom(TemporalAccessor temporal)

TemporalAccessor: 是Temporal的父接口,实际上也就是LocalDate,LocalDateTime相关类的顶级父接口,这个queryFrom的方法的实现逻辑就是传入一个日期/时间通过自定义逻辑返回数据.

/**
 * @Description: <h1> TemporalQuery查询使用 </h1>
 * 获取某一天距离下一个劳动节相隔天数的实现
 */
public class TimeClassTemporalQueryTest implements TemporalQuery<Long> {

    @Override
    public Long queryFrom(TemporalAccessor temporal) {
        // 1. TemporalAccessor时localDate和localDateTime的顶级父接口,相当于localDate就是这个接口的实现类.将这个参数转换为LocalDate使用
        LocalDate now = LocalDate.from(temporal); // now > 2020.1.17
        // 2. 封装当年劳动节的时间 年份: now 月份: 5 日: 1
        LocalDate laborDay = LocalDate.of(now.getYear(), Month.MAY, 1);
        // 3. 判断当前时间是否已过当年的劳动节,则laborDay加一年 (判断月份是否为5月)
//        if (laborDay.getMonth()==Month.MAY)
        if (now.isAfter(laborDay)) { // isAfter() 现在的日期是否在当年5月1日之后
            laborDay = laborDay.plusYears(1); // plus() 年份+1
        }
        // 4. 通过ChronoUnit的between()计算两个时间之间的差值
        return ChronoUnit.DAYS.between(now, laborDay);
    }

    /**
     * 计算当前时间距离下个劳动节多少天
     *
     * @param args
     */
    public static void main(String[] args) {
        // 封装当前时间
        LocalDate now = LocalDate.now();
        // 调用now的query方法,然后将我们自己的实现类TimeClassTemporalQueryTest作为参数传入
        Long day = now.query(new TimeClassTemporalQueryTest());
        System.out.println("当前的时间是: " + now + ", 计算当前时间距离下个劳动节多少天: " + day);
        // 当前的时间是: 2020-08-22计算当前时间距离下个劳动节多少天: 252
    }

}

5. 测试

/**
 * @Description: <h1> 测试 </h1>
 * 计算任意时间与下一个圣诞季/儿童节/劳动节相差多少天?
 */
public class TemporalAdjusterAndTemporalQueryTest implements TemporalQuery<Long[]> {

    /**
     * @return 表示距离 圣诞季/儿童节/劳动节 三个节日的天数差额,0索引表示距离圣诞节,1索引表示距离儿童及,2索引表示劳动节
     */
    @Override
    public Long[] queryFrom(TemporalAccessor temporal) {
        // 1. 将temporal转换为localDate
        LocalDate now = LocalDate.from(temporal);

        // 2. 封装当年的 圣诞季/儿童节/劳动节 的日期对象 年: now的year 月+日: 各自对应日期
        LocalDate date1 = LocalDate.of(now.getYear(), Month.DECEMBER, 25);
        LocalDate date2 = LocalDate.of(now.getYear(), Month.JUNE, 1);
        LocalDate date3 = LocalDate.of(now.getYear(), Month.MAY, 1);

        // 3. 判断now是否超过了当 date1,date2,date3三个节日,如果超过则计算下一年的日期
        if (now.isAfter(date1)) {
            date1 = date1.plusYears(1); // +1
        }
        if (now.isAfter(date2)) {
            date2 = date2.plusYears(1); // +1
        }
        if (now.isAfter(date3)) {
            date3 = date3.plusYears(1); // +1
        }

        // 4. 通过ChronoUnit的between()计算两个时间之间的差值
        Long[] longs = {ChronoUnit.DAYS.between(now, date1), ChronoUnit.DAYS.between(now, date2), ChronoUnit.DAYS.between(now, date3)};
        return longs;
    }

    /**
     * 计算任意时间与下一个圣诞季/儿童节/劳动节相差多少天?
     *
     * @param args
     */
    public static void main(String[] args) {
        // 1. 封装任意日期
        LocalDate now = LocalDate.of(2020, Month.MAY, 30);

        // 2. 调用now的query的方法查询三个节日的差值
        Long[] longs = now.query(new TemporalAdjusterAndTemporalQueryTest());

        // 3. 打印结果
        System.out.println("当前时期: " + now + ", 距离下一个圣诞节: " + longs[0] + "天");
        System.out.println("当前时期: " + now + ", 距离下一个儿童节: " + longs[1] + "天");
        System.out.println("当前时期: " + now + ", 距离下一个劳动节: " + longs[2] + "天");
        // 当前时期: 2020-05-30, 距离下一个圣诞节: 209天
        // 当前时期: 2020-05-30, 距离下一个儿童节: 2天
        // 当前时期: 2020-05-30, 距离下一个劳动节: 336天 
    }
}

6.5 Date和LocalDate转换

Java8中的java.time包中并没有提供太多的内置方式类转换java.util包中用预处理标准日期和时间的类,我们可以使用Instant类作为中介,也可以使用java.sql.Date和java.sql.Timestamp类提供的方法进行转换.

1. java.util.Date类的转换

  1. 使用Instant将java.util.Date转换为java.time.LocalDate

java.time包中没有提供很多方式来进行直接转换,但是给之前的Date类,Calendar类在Java8都提供了一个新的方法: toInstant,可以将当前的对象,通过给Instant添加时区信息之后就可转换为LocalDate对象.

 /**
     * java.util.Date ---> LocalDate 对象之间的转换
     */
    @Test
    public void test1() {
        // 初始化Date对象
        java.util.Date date = new java.util.Date();
        // 1. 将Date对象转换为Instant对象 toInstant
        Instant instant = date.toInstant();
        // 2. Date类中国包含日期和时间信息,但是并不提供时区信息,和Instant类一样,通过Instant类的atZone()添加时区信息进行转换
        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
        // 3. 将ZonedDateTime通过toLocalDate()转换为LocalDate对象
        LocalDate localDate = zonedDateTime.toLocalDate();
        System.out.println("之前的Date对象: " + date + ", 转换之后LocalDate对象: " + localDate);
        // 之前的Date对象: Sat Aug 22 16:14:27 CST 2020, 转换之后LocalDate对象: 2020-08-22
    }

Date类中包含日期和时间信息,但是并不提供时区信息,和Instant类一样,通过Instant类的atZone()添加时区信息进行转换

  1. 第二种方式

java.util.Date -> java.sql.Date -> java.util.Date方法通过构造方法传入毫秒值 --> toLocalDate().(毫秒是可以通过java.util.Date的getTime方法类获取)

/**
 * java.util.Date ---> LocalDate 对象之间的转换 2
 */
@Test
public void test2() {
    // 初始化Date对象
    java.util.Date ud = new java.util.Date();
    // 1. 将 java.util.Date -> java.sql.Date  -> java.util.Date方法通过构造方法传入毫秒值 --> toLocalDate()
    java.sql.Date sd = new java.sql.Date(ud.getTime());
    LocalDate localDate = sd.toLocalDate();
    System.out.println("之前的java.util.Date对象: "+ ud);
    System.out.println("之前的java.sql.Date对象: "+ sd);
    System.out.println("之后的LocalDate对象: "+ localDate);
    // 之前的java.util.Date对象: Sat Aug 22 16:42:08 CST 2020
    // 之前的java.sql.Date对象: 2020-08-22
    // 之后的LocalDate对象: 2020-08-22
}

2. java.sql.Date类的转换

java.sql.Date类中,通过传入的一个毫秒值对象进行初始化,提供直接转换为localDate的方法: toLocalDate

/**
 * java.sql.Date ---> LocalDate
 */
@Test
public void test2(){
    // 初始化一个java.sql.Date对象
    java.sql.Date date = new java.sql.Date(System.currentTimeMillis());
    // java.sql.Date类中自带转换为LocalDate的方法,toLocalDate();
    LocalDate localDate = date.toLocalDate();
    System.out.println("之前的Date对象: " + date + ", 转换之后LocalDate对象: " + localDate);
    // 之前的Date对象: 2020-08-22, 转换之后LocalDate对象: 2020-08-22
}

3. java.sql.Timestamp类的转换

Timestamp是时间戳对象,通过传入的一个毫秒值对象进行初始化.提供直接转换为localDateTime的方法: toLocalDateTime

/**
 * java.sql.Timestamp ---> LocalDate
 */
@Test
public void test3(){
    // 初始化一个java.sql.timestamp对象
    Timestamp timestamp = new Timestamp(System.currentTimeMillis());
    // java.sql.Timestamp类中自带转换为LocalDate的方法,toLocalDateTime();
    LocalDateTime localDateTime = timestamp.toLocalDateTime();
    System.out.println("之前的Timestamp对象: " + timestamp + ", 转换之后LocalDate对象: " + localDateTime);
    // 之前的Timestamp对象: 2020-08-22 16:31:17.165, 转换之后LocalDate对象: 2020-08-22T16:31:17.165
}

4. java.util.Calendar类的转换

  1. Calendar转换为ZonedDateTime

Calendar对象字java1.1开始提供了一个方法获取时区对象的方法,getTimeZone,要将Calendar对象转换为ZonedDateTime需要现获取到时区对象,自java8开始TImeZone类提供了一个方法可以获取到ZoneId,获取到ZoneID之后就可以初始化ZonedDateTime对象了,ZonedDateTime类有一个ofInstant方法,可以将Instant对象和ZonedId对象作为参数传入构造一个ZonedDateTime对象.

方法作用
Calendar.getInstance()初始化Calendar对象
TimeZone getTimeZone()获取时区
ZoneId toZoneId()根据时区获取 ZoneId
ZonedDateTime ofInstant(Instant instant, ZoneId zone)将一个Instant对象和ZoneId队形封装为ZonedDateTime
/**
 * Calendar ---> ZonedDateTime
 */
@Test
public void test1(){
    // 1. 初始化Calendar对象 (Date和Calendar提供 转换为Instant的方法: toInstant)
    Calendar calendar = Calendar.getInstance();
    // 2. Calendar对象自java1.1提供了一个方法用于获取时区对象getTimeZone(),要将Calendar对象转换为ZonedDateTime对象要现获取到市区对象
    TimeZone timeZone = calendar.getTimeZone();
    // 3. TimeZone从1.8开始提供一个方法(toZoneId)获取到ZoneId -> 根据ZoneId构建ZonedDateTime对象
    ZoneId zoneId = timeZone.toZoneId();
    // 4. ZonedDateTime类有一个ofInstant方法,可以将一个Instant对象和ZoneId队形封装为ZonedDateTime
    ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), zoneId);
    System.out.println("之前的Calendar对象: " + calendar);
    System.out.println("之后的ZonedDateTime对象: " + zonedDateTime);
    // 之前的Calendar对象: java.util.GregorianCalendar[time=1598087373926,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2020,MONTH=7,WEEK_OF_YEAR=34,WEEK_OF_MONTH=4,DAY_OF_MONTH=22,DAY_OF_YEAR=235,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=9,SECOND=33,MILLISECOND=926,ZONE_OFFSET=28800000,DST_OFFSET=0]
    // 之后的ZonedDateTime对象: 2020-08-22T17:09:33.926+08:00[Asia/Shanghai]
}
  1. Calendar转换为LocalDateTime

Calendar对象可以获取到年月日时分秒的信息,这些信息可以作为LocalDateTime构造方法的参数

/**
 * Calendar ---> LocalDateTime
 */
@Test
public void test2() {
    // 1. 初始化Calendar对象 (Date和Calendar提供 转换为Instant的方法: toInstant)
    Calendar calendar = Calendar.getInstance();
    // 2. 通过get()获取Calendar中封装的数
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DAY_OF_MONTH);
    int hor = calendar.get(Calendar.HOUR_OF_DAY);
    int minute = calendar.get(Calendar.MINUTE);
    int second = calendar.get(Calendar.SECOND);
    // 3. 将上述结果作为LocalDateTime的of()的参数 注意: Calendar的月份 需要+1
    LocalDateTime localDateTime = LocalDateTime.of(year, month + 1, day, hor, minute, second);
    System.out.println("之前的Calendar对象: " + calendar);
    System.out.println("之后的ZonedDateTime对象: " + localDateTime);
    // 之前的Calendar对象: java.util.GregorianCalendar[time=1598088161179,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2020,MONTH=7,WEEK_OF_YEAR=34,WEEK_OF_MONTH=4,DAY_OF_MONTH=22,DAY_OF_YEAR=235,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=22,SECOND=41,MILLISECOND=179,ZONE_OFFSET=28800000,DST_OFFSET=0]
    // 之后的ZonedDateTime对象: 2020-08-22T17:25:28
}

5. 日期的解析与格式化DateTimeFormatter

SimpleDateFormat类是线程不安全的,Java8提供了新的格式化类 DateTimeFormatter.

DateTimeFormatter: 提供了大量预定义格式化器,包括常量(如: ISO_LOCAL_DATE),模式字母(如: yyyy-MM-dd)以及本地化样式.

与SimpleDateFormat不同的是,新版本的日期/时间API的格式化与解析不需要创建转换器对象在进行转换了,通过日期时间对象的parse/format方法可以直接尽心转化.

  1. LocalDateTime的parse/format
@Override  // override for Javadoc and performance
public String format(DateTimeFormatter formatter) {
    Objects.requireNonNull(formatter, "formatter");
    return formatter.format(this);
}

format方法需要传入一个DateTimeFormatter对象.

/**
 * 对LocalDateTime进行格式化和解析
 */
@Test
public void test1(){
    // 初始化LocalDateTime对象
    LocalDateTime now = LocalDateTime.now();

    // now可以直接调用format进行格式化
    String s1 = now.format(DateTimeFormatter.ISO_DATE_TIME);
    String s2 = now.format(DateTimeFormatter.ISO_DATE);
    System.out.println("ISO_DATE_TIME格式化后: " + s1);
    System.out.println("ISO_DATE格式化后: " + s2);
    // ISO_DATE_TIME格式化后: 2020-08-22T17:39:37.916
    // ISO_DATE格式化后: 2020-08-22

    // 解析parse
    LocalDateTime localDateTime1 = LocalDateTime.parse(s1);
    System.out.println(localDateTime1); // 2020-08-22T17:41:06.634
}
  1. DateTimeFormatter的ofLocalizedDate()
public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) {
    Objects.requireNonNull(dateStyle, "dateStyle");
    return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null)
            .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
}

此方法需要传入一个FormStyle类对象,查看后发现FormatStyle对象是一个枚举类:

描述
FULL全显示(年月日+星期)
LONG全显示(年月日)
MEDIUM缩略显示(没有年月日汉字)
SHORT精简显示(精简年+月日)
/**
 * ofLocalizedDate()使用
 */
@Test
public void test2(){
    // 初始化LocalDateTime对象
    LocalDateTime now = LocalDateTime.now();

    // 通过DateTimeFormatter的ofLocalizedDate指定解析格式
    String s1 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
    String s2 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
    String s3 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
    String s4 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));

    System.out.println("FULL: " + s1);
    System.out.println("LONG: " + s2);
    System.out.println("MEDIUM: " + s3);
    System.out.println("SHORT: " + s4);
    // FULL: 2020年8月22日 星期六
    // LONG: 2020年8月22日
    // MEDIUM: 2020-8-22
    // SHORT: 20-8-22
}

注意: 此种方式在不同时区的显示方式不一样,在其他时区不会显示中文,会根据当先系统默认的时区类进行区别显示.

  1. 自定义格式化日期

除了系统自带的方式之外,也可以通过DateTimeFormatter类提供的orPattern方式创建自定义的格式化器,格式化的写法与之前使用到的SimpleDateFormat相同

/**
 * 自定义格式化器
 */
@Test
public void test3() {
    // 初始化LocalDateTime对象
    LocalDateTime now = LocalDateTime.now();

    String s = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss:SSS"));

    System.out.println("LocalDateTime格式化后的内容:  " + s);
    // LocalDateTime格式化后的内容:  2020/08/22 18:01:44:189
}
  1. 测试
/**
 * 将1998年3月18日17时19分28秒格式化为以下格式化和或或或或i:1998-03月-8---17:19分:28秒
 * <p>
 * 格式: yyyy-MM月-dd---HH:mm分:ss秒
 */
@Test
public void test4() {
    // 初始化LocalDateTime对象
    LocalDateTime now = LocalDateTime.of(1998,
            Month.MARCH,
            18,
            17,
            19,
            28);
    String s = now.format(DateTimeFormatter.ofPattern("yyyy-MM月-dd---HH:mm分:ss秒"));
    System.out.println("之前: " + now);
    System.out.println("之后: " + s);
    // 之前: 1998-03-18T17:19:28
    // 之后: 1998-03月-18---17:19分:28秒
}