目录

JDK1.8的新特性

接口(interface)的默认方法与静态方法

  • 接口的代码示例
public interface JDK8Interface {
    // 1. 定义属性,默认修饰符为: public static final(无法修改)
    int color = 10;

    // 2. 定义方法,默认修饰符为:public abstract(无法修改)
    void show();

    // 3. jdk8新特性,定义接口默认方法,默认加了public修饰(无法修改)
    default void defaultMethod(){
        System.out.println("defaultMethod");
    }

    // 4. jdk8新特性,定义接口静态方法,可以通过类名.的方式调用,默认加了public修饰(无法修改)
    static void staticMethod() {
        System.out.println("staticMethod");
    }
}

  • 实现类代码实例
public class JDK8InterfaceImpl implements JDK8Interface {
    @Override
    public void show() {
        System.out.println("show");
    }
}
  • Test测试类方法
public class Test {
    public static void main(String[] args) {
        JDK8InterfaceImpl jdk8Interface = new JDK8InterfaceImpl();

        // 1. 调用实现的方法
        jdk8Interface.show();

        // 2. 调用接口的default方法
        jdk8Interface.defaultMethod();

        // 3. 调用接口的静态方法, 不可以通过实现类的对象调用
        JDK8Interface.staticMethod();
    }
}

lambda表达式(是一个匿名函数)

为什么需要使用lambda表达式

lambda表达式是为了能够简化实现匿名内部类的代码。

举个例子,创建一个线程并调用,采用匿名内部类和lambda表达式的方式

public class Test {
    public static void main(String[] args) {
        // 1. 匿名内部类的方式,一共6行代码
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "-- 线程正在运行 --");
            }
        }).start();

        // 2.lambda表达式的方式
        new Thread(() -> System.out.println(Thread.currentThread().getName() + "-- 线程正在运行 --")).start();
    }
}

lambda表达式的使用条件

  • 要将匿名内部类简化为lambda表达式,那么该匿名内部类的类型必须为一个函数接口类型

应用场景

  1. 写在方法中作为一个函数式接口的返回值

  2. 成为方法的参数或局部变量(前提是类型为一个函数式接口)

函数接口 及 @FuncationInterface

  1. 该接口中必须有且只能有一个抽象方法

  2. 可以在接口中添加@FuncationInterface(函数式接口),一旦加了该注释,那么该接口中就必须符合上一点的规范

  3. 该接口中定义Object类中的方法

  4. 该接口中可以定义default和static方法

lambda表达式基本语法

lambda表达式的三个组成部分 : () -> {}
1. ( ) 参数列表,不需要写参数类型
2. -> 参数列表与方法体的连接符
3. { } 方法体

lambda的简化规则
  1. 当参数列表的参数数量为1时候,可以省略小括号

  2. 当方法体只有一条语句时,可以省略大括号

匿名内部类的原理与lambda表达式的区别

所需类型不一样

  • 匿名内部类的类型可以是类、抽象类、接口

  • lambda表达式的类型只能是接口

类型的抽象方法数量不一样

  • 匿名内部类的类型可以有多个抽象方法

  • lambda表达式的类型有且只能有一个抽象方法

实现原理不一样

  • 匿名内部类

    • 会创建一个类来实现当前的接口并重写方法,重写的方法中包含我们实际需要运行的代码(我们自己写的那一段方法体)。
  • lambda表达式

    1. 会在当前类中创建一个方法,其中包含我们需要运行的代码

    2. 会创建一个类来实现该接口,该接口会调用在当前类中生成的方法

常用的4个函数式接口

-  Consumer<T> : void accept(T t)  消费型接口

-  Supplier<T> : T get()  提供型接口

-  Function<T,R> : R apply(T t)  函数型接口

-  Predicate<T> : boolean test(T t)  断言型接口

方法引用(代码的简化,主要是函数式接口与需要引用的方法返回值与参数类型及数量一致)

方法引用的三种格式

  1. 对象::成员方法名

    • 当函数式接口的方法返回值、形参数量 与 需要调用的对象的成员方法一致
// Consumer的方法: void accept(T t);因为用了泛型,所以T相当于String
// println方法:public void println(String x) {}
        Consumer<String> consumer1 = s -> System.out.println(s);

        Consumer<String> consumer2 = System.out::println;
  1. 类::静态方法名
    • 当函数式接口的方法返回值、形参数量 与 需要调用的静态方法一致
// Comparator的方法:int compare(T o1, T o2);因为用了泛型,所以T相当于String
// Integer的方法:public int compareTo(Integer anotherInteger);

        Comparator<Integer> comparator1 = (o1,o2) -> o1 - o2;

        Comparator<Integer> comparator2 = Integer::compareTo;
  1. 类::成员方法名
    • x作为方法的调用者,y作为方法的实际参数时
// BiFunction的方法:R apply(T t, U u);由于传递了泛型的参数类型,所以T和U都相当于String,R相当于Boolean
// equals方法:public boolean equals(Object anObject);
public class Test {
    public static void main(String[] args) {
//        BiFunction<String,String,Boolean> bif = (x,y) -> x.equals(y);

        BiFunction<String,String,Boolean> bif = String::equals;

        Boolean apply = bif.apply("abc", "abc");

        System.out.println(apply);
    }
}
  1. 类::new
    • 这一种的适用条件其实跟第一种、第二种是一样的,只不过方法返回值变成了一个对象而已。
  2. 类[]::new
    • 同上

stream流

什么需要使用stream流、stream流的思想是怎么样的

  • 平时我们操作集合获取其中的某些元素时,总是不可避免的需要对集合进行遍历遍历再遍历,这就使得代码量不断扩大。而stream流可以帮我们解决这一系列问题,让我们的代码变得更简洁,操作也更加方便

  • stream流的思想与io中的输入输出流不是一个概念,这个stream流指的是将数据像流水线一样进行加工处理,最终再给予最终的结果。

Stream流的分类及操作次数提示

  • Stream流中的方法共分为两类,分别为终结方法(返回最终结果)与非终结方法(返回一个处理后的Stream流)。若是一个Stream流没有执行终结方法,则非终结方法不会被执行

  • 一个Stream流只能被操作一次(!!!),意思是只要一个流你调用过任何方法,那么该流就无法被再次使用(该流调用后返回的流除外),会抛出异常:IllegalStateException: stream has already been operated upon or closed

Stream流的三种获取方式

public class StreamTest {
    public static void main(String[] args) {
        // 1. 通过Collection接口中的默认方法stream()获取,Collection接口的子类有List和Set
        Stream<String> stream1 = new ArrayList<String>().stream();
        Stream<String> stream2 = new HashSet<String>().stream();

        // 2. 通过Stream接口的静态方法  Stream<T> of(T t)
        Stream<Integer> stream3 = Stream.of(1235);

        // 3. 通过Stream接口的静态方法  Stream<T> of(T... values),
           // 注意:如果直接传递一个数组的话,不能为基本数据类型的数组
        Stream<Integer> stream4 = Stream.of(5, 98, 55, 11, 77, 35, 36);
    }
}

Stream流中的常用方法

forEach:消费Stream流中的每个元素

  • forEach的方法定义如下:
    void forEach(Consumer<? super T> action);

  • forEach方法的基本使用

    Stream<String> stream = Arrays.asList("张三", "李四", "王五", "赵六").stream();
    stream.forEach((s) -> System.out.println(s));
  • 运行结果如下
张三
李四
王五
赵六

count:统计Stream流中元素的个数

  • count方法的定义如下
    long count();

  • count方法的基本使用

    Stream<String> stream = Arrays.asList("张三", "李四", "王五", "赵六").stream();
    long count = stream.count();
    System.out.println(count); // 结果为4

filter : 过滤Stream流中的数据

  • 方法的定义如下
    Stream<T> filter(Predicate<? super T> predicate);

  • filter方法的基本使用

    Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();

    // 过滤出所有字符串长度为3的数据并输出
    stream.filter(s -> s.length() == 3).forEach(System.out::println);

limit:获取前几个元素 和 skip跳过前几个元素,都会返回一个新的Stream流

  • 方法定义
    Stream<T> limit(long maxSize);
    Stream<T> skip(long n);
  • limit的基本使用
    Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();

    // limit:获取流中的前2个元素并输出,结果为:张三峰、李四
    stream.limit(2).forEach(System.out::println);
  • skip的基本使用
    Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
    
    // skip: 跳过流中的前2个元素,只获取之后的元素,结果为:王五米、赵六
    stream.skip(2).forEach(System.out::println);

match:判断Stream流中的元素是否满足匹配要求,3个方法

  • 方法定义
    boolean allMatch(Predicate<? super T> predicate);
    boolean anyMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
  • match的基本使用
        Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();
        // 1. 判断Stream流中的字符串元素是否全都满足长度为3,满足返回true,否则为false
        // System.out.println(stream.allMatch(s -> s.length() == 3)); // false

        // 2. 判断Stream流中的字符串元素是否有任意一个满足长度为3,满足返回true,否则为false
        // System.out.println(stream.anyMatch(s -> s.length() == 3)); // true

        // 3. 判断Stream流中的字符串元素是否全都 不满足长度为3,满足返回true,否则为false
        System.out.println(stream.noneMatch(s -> s.length() == 3)); // fasle

find:返回Stream流中匹配要求的元素

  • 方法定义
    Optional<T> findFirst();
    Optional<T> findAny(); 串行时基本返回第一个元素,并行就未必了。
  • find的基本使用
    Stream<String> stream = Arrays.asList("张三峰", "李四", "王五米", "赵六").stream();

    // 1. 获取Stream流中的第一个元素
    // Optional<String> first = stream.findFirst();
    // System.out.println(first.get());

    // 2. 获取Stream流中的任意一个元素, 串行、或者数据量小时可能会一直返回第一个元素
    Optional<String> any = stream.findFirst();
    System.out.println(any.get());

max:返回最大值,min:返回最小值

  • 方法定义
    Optional<T> max(Comparator<? super T> comparator);
    Optional<T> min(Comparator<? super T> comparator);

  • max的基本使用

    Stream<String> stream = Arrays.asList("z张三峰", "b李四", "a王五米", "c赵六").stream();

    Optional<String> max = stream.max((s1, s2) -> s1.compareTo(s2));
    System.out.println(max.get()); // 输出结果为z张三峰
  • min的基本使用
    Stream<Integer> stream = Arrays.asList(23, 44, 95, 11).stream();

    Optional<Integer> min = stream.min((i1, i2) -> i1.compareTo(i2));
    System.out.println(min.get()); // 输出结果为11

reduce:对Stream流中的元素进行汇总,最终返回一个值

  • 方法定义

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner); 暂不演示
– 解释:第一个参数为初始值,第二个函数化接口父类中的接口及方法的定义如下:

- 接口:`public interface BinaryOperator<T> extends BiFunction<T,T,T>`
- 父类BiFunction中的方法:`R apply(T t, U u);`
- 因此可以看出,是传入2个同类型的参数,再返回一个相同类型的参数。
  • reduce的基本使用
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();

    // 获取Stream流中所有整数之和
    // 1. 使用 T reduce(T identity, BinaryOperator<T> accumulator) 实现数据的增加统计
    //    这里的x值为0,每次都会把x + y 的值赋给x
    //    其中的y就是每次遍历出来的整数
    // Integer sum = stream.reduce(2, (x, y) -> x + y);
    // System.out.println(sum); // 12

    // 2. 使用 Optional<T> reduce(BinaryOperator<T> accumulator) 实现数据的增加统计
    Optional<Integer> sum = stream.reduce((x, y) -> x + y);
    System.out.println(sum.get()); // 10

map:将Stream流中的元素转换为另一种类型

  • 方法定义
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  • map的基本使用
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
    
    // 将strem流中的数据类型转换为String
    stream.map((i) -> String.valueOf(i)).forEach(s -> {
        System.out.println("当前的数据类型为:" + s.getClass().getSimpleName()); // String
    });

mapToInt:将一个Stream流转换为IntStream流

  • 存在的意义:会提高处理Int数据的速度,当Stream流中不仅是Int数据且数据量大时可以考虑。

  • 方法定义
    IntStream mapToInt(ToIntFunction<? super T> mapper);

  • mapToInt的基本使用

    Stream<String> stream1 = Arrays.asList("1", "2", "3", "4").stream();

    // 将Stream流转换为IntStream流
    IntStream intStream = stream1.mapToInt(s -> Integer.parseInt(s));
    intStream.forEach(System.out::println);

Stream静态方法concat:将多个流中的元素合并到一个流中

  • 方法定义
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

  • map的基本使用

    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
    
    // 将strem流中的数据类型转换为String
    stream.map((i) -> String.valueOf(i)).forEach(s -> {
        System.out.println("当前的数据类型为:" + s.getClass().getSimpleName()); // String
    });

Stream流中的数据收集

  • 方法定义
    <R, A> R collect(Collector<? super T, A, R> collector);
    <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); 暂不演示
收集到List、ArrayList集合当中
    Stream<String> stream = Arrays.asList("1", "2", "3", "4").stream();

    // 1. 收集到List集合中
    // List<String> collect = stream.collect(Collectors.toList());
    
    // 2. 收集到ArrayList集合中
    ArrayList<String> collect = stream.collect(Collectors.toCollection(() -> new ArrayList<>()));
收集到Set、HashSet集合中
    Stream<String> stream = Arrays.asList("1", "2", "3", "4").stream();

    // 1. 收集到Set集合中
    // Set<String> collect = stream.collect(Collectors.toSet());

    // 2. 收集到HashSet集合中
    HashSet<String> collect = stream.collect(Collectors.toCollection(() -> new HashSet<>()));
收集到数组当中,Object与或任意引用类型
    Stream<String> stream = Arrays.asList("aa", "cc", "bb", "dd").stream();

    // 1. 获取Object类型的数组
    // Object[] objects = stream.toArray();

    // 2. 获取任意类型的数组,只能是引用类型
    // String[] strings = stream.toArray(s -> new String[4]); // 必须手动指定长度
    String[] strings = stream.toArray(String[]::new);

Stream流中的数据聚合,对已经处理过的数据进行聚合计算

当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对数据的某个字段进行聚合操作。最大值、最小值、求和、平均值、统计数量。

最大值
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();

    // 1. 最大值
    Optional<Integer> max = stream.collect(Collectors.maxBy((i1, i2) -> Integer.compare(i1, i2)));
    System.out.println(max.get()); // 4
最小值
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
	
    // 2. 最小值
    Optional<Integer> min = stream.collect(Collectors.minBy((i1, i2) -> Integer.compare(i1, i2)));
    System.out.println(min.get()); // 1
求和
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
	
    // 3. 求和
    int sum = stream.collect(Collectors.summingInt(i -> i));
    System.out.println(sum); // 10
平均值
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
	
    // 4. 平均值
    Double average = stream.collect(Collectors.averagingInt(i -> i));
    System.out.println(average); // 2.5
统计数量
    Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream();
	
    // 5. 统计数量
    Long count = stream.collect(Collectors.counting());
    System.out.println(count); // 4

Stream将数据进行分组

单级分组
    ArrayList<Person> list = new ArrayList<>();
    list.add(new Person("张三",15));
    list.add(new Person("李四",26));
    list.add(new Person("王五",15));
    list.add(new Person("名吉",15));
    list.add(new Person("赵六",12));

    Stream<Person> stream = list.stream();

    // 1. 按照年龄进行分组
    Map<Integer, List<Person>> ageGroupList = stream.collect(Collectors.groupingBy(p -> p.getAge()));

    ageGroupList.forEach((k, v) -> System.out.println("年龄为:" + k + ",该组人员有:" + v));

多级分组(可以分无限级)
        // 1. 按照年龄进行分组、再将分组后的数据按照姓名分组
        Map<Integer, Map<String, List<Person>>> groups = stream.collect
                (
                    Collectors.groupingBy
                        (
                            p -> p.getAge(), 
                            Collectors.groupingBy(p -> p.getName())
                        )
                );
    
        groups.forEach((k,v) -> {
            System.out.println("按照年龄分组:" + k + ",分组内容为:" + v);
            v.forEach((k2,v2) -> System.out.println("按照姓名分组:" + k2 + ",分组内容为:" + v2));
            System.out.println("-----------------------------------");
        });
分组聚合,分组的同时统计每组有多少人
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",15));
        list.add(new Person("李四",26));
        list.add(new Person("王五",15));
        list.add(new Person("名吉",15));
        list.add(new Person("赵六",12));

        Stream<Person> stream = list.stream();

        // 1. 按照年龄进行分组,并统计有多少人
        Map<Integer, Long> collect = stream.collect(Collectors.groupingBy(p -> p.getAge(), Collectors.counting()));

        collect.forEach((k, v) -> System.out.println("年龄为:" + k + ",该组人数有:" + v));

Stream流中的分区操作

    ArrayList<Person> list = new ArrayList<>();
    list.add(new Person("张三",15));
    list.add(new Person("李四",26));
    list.add(new Person("王五",15));
    list.add(new Person("张三",15));
    list.add(new Person("赵六",12));

    Stream<Person> stream = list.stream();

    // 将年龄小于15岁的与年龄不小于15岁的人分为2组,分别为true组和false组
    Map<Boolean, List<Person>> collect = stream.collect(Collectors.partitioningBy(p -> p.getAge() < 15));

    collect.forEach((k,v) -> System.out.println(k + "组的成员有:" + v));

Stream 并行流与串行流

  • Stream串行流:单个线程执行Stream流中的处理操作

  • Stream并行流:多个线程执行Stream流中的处理操作(forkjoin框架,任务窃取算法)

获得并行流的2种方式

  • 通过Collection的实现类直接获取,例如
Stream<Integer> parallelStream = Arrays.asList(1, 4, 5, 1, 6).parallelStream();
  • 对一个串行流调用parallel()方法来获取一个并行流
    Stream<Integer> stream = Arrays.asList(1, 4, 5, 1, 6).stream();
    // 转换为一个并行流
    Stream<Integer> parallelStream = stream.parallel();

解决并行流在执行过程中线程安全问题的4种办法

演示并行流的线程安全问题
    // 1. 创建一个ArrayList<Integer> 集合,往里面存入1000个数据
    ArrayList<Integer> oldList = new ArrayList<>();
    for (int i = 1; i <= 1000; i++) {
        oldList.add(i);
    }

    // 2. 获取一个并行流
    Stream<Integer> parallelStream = oldList.parallelStream();

    // 3. 创建一个新的集合
    ArrayList<Integer> newList = new ArrayList<>();

    // 4. 使用并行流将oldList集合中的数据添加到newList集合中
    parallelStream.forEach(i -> newList.add(i));

    // 5. 输出newList集合的元素数量
    System.out.println(newList.size()); // 数量不定的变换,或者直接抛出异常,出现了线程安全问题
}
解决方案1:为操作数据的代码添加同步代码块
    // 4. 使用并行流将oldList集合中的数据添加到newList集合中
    //  这里使用了当前的测试类.class作为同步锁
    parallelStream.forEach(i -> {
        synchronized (StreamTest.class) {
            newList.add(i);
        }
    });
解决方案2:使用线程安全的容器,比如说Vector
    // 3. 创建一个新的集合
    Vector<Integer> newList = new Vector<>();
解决方案3:使用Collections的静态方法将一个容器转换为线程安全的
    // 3. 创建一个新的集合
    ArrayList<Integer> newList1 = new ArrayList<>();
    List<Integer> newList = Collections.synchronizedList(newList1);
解决方案4:使用Stream流中的collect或toArray进行操作,则会保证线程安全
    ArrayList<Integer> newList = parallelStream.collect(Collectors.toCollection(() -> new ArrayList<>()));

                                       或者

    Integer[] integers = parallelStream.toArray(Integer[]::new);

Optional 可以很好的解决空指针异常

Optional类的创建方式

public static <T> Optional<T> of(T value)
public static <T> Optional<T> ofNullable(T value)
public static<T> Optional<T> empty()

    // 1. 通过Optional类的静态方法of创建一个Optional对象,不能传null
    Optional<Person> op1 = Optional.of(new Person("zhangsan", 15));
    
    // 2. 通过Optional类的静态方法ofNullable创建一个Optional对象,可以传null空对象
    Optional<Person> op2 = Optional.ofNullable(null);
    
    // 3. 通过Optional类的静态方法empty()获取一个为空对象的Optional对象
    Optional<Object> op3 = Optional.empty();

Optional类的常用方法

get()

获取Optional类中存储的对象,如果为null则会抛出异常

    Optional<Person> op1 = Optional.of(new Person("zhangsan", 15));

    Person person = op1.get();
    System.out.println(person); // Person{name='zhangsan', age=15}

isPresent()

判断存储的对象是否包含值,包含返回true、不包含返回flase

    Optional<Person> op1 = Optional.ofNullable(null);

    boolean present = op1.isPresent();

    System.out.println(present); // false

orElse(T t)

如果Optional中的对象为null,返回该方法传递的值,否则返回原Optional对象中的值

    Optional<Person> op1 = Optional.ofNullable(null);

    Person person = op1.orElse(new Person("zhang", 15));

    System.out.println(person); // new Person("zhang", 15)

orElseGet(Supplier s)

同上方法一致,不过是将T换成了一个函数式接口

    Optional<Person> op1 = Optional.ofNullable(new Person("lis",11));

    Person person = op1.orElseGet(() -> new Person("lisi", 55));

    System.out.println(person); // Person{name='lis', age=11}

ifPresent(Consumer<? super T> consumer)

如果Optional中的对象不为空,则执行一段代码

    Optional<Person> op1 = Optional.ofNullable(null);

    // 当对象为null时不执行
    op1.ifPresent(p -> System.out.println("今天天气真好" + p));

filter

判断Optional中的对象是否满足条件,满足则返回该对象,不满足返回null

    Optional<Person> op1 = Optional.ofNullable(null);

    // 当对象为null时不执行
    op1.ifPresent(p -> System.out.println("今天天气真好" + p));

map

将Optional中的对象转换为另一个类型

    Optional<Person> op1 = Optional.ofNullable(new Person(null,15));

    // 返回的Optional中的对象值为null
    Optional<String> opStr = op1.map(p -> p.getName());
    
    // 返回的Optional中的对象值为15
    Optional<Integer> opInt = op1.map(p -> p.getAge()); 

Optional小案例(帮助理解下源码)

/**
 * @author codeStars
 * @date 2022/8/6 10:13
 * 返回Person对象中的name并且转换为大写,如果name值为空,则返回 name值为null
 */
public class StreamTest {
    public static void main(String[] args) {
        Optional<Person> op1 = Optional.ofNullable(new Person(null,15));

        String name = getUpperCaseName(op1);
        System.out.println(name);


    }

    /**
     * 返回Person对象中的name并且转换为大写,如果name值为空,则返回 name值为null
     * @param op1
     * @return
     */
    private static String getUpperCaseName(Optional<Person> op1) {
        String msg = "";

        if(op1.isPresent()) {
            /*
                注意这里的toUpperCase不会抛空指针异常。因为op1.map(Person::getName)执行后,
                得到的Optional中的对象就已经为空对象了
                再调用map方法时,会先判断Optional中的对象是否为空对象
                源码如下:
                             if (!isPresent())
                                return empty();
                            else {
                                return Optional.ofNullable(mapper.apply(value));
                            }
                所以如果Person中的name为空的话,根本就执行不到toUpperCase
            */
            msg =  op1.map(Person::getName).map(String::toUpperCase).orElse("name值为null");
        }
        return msg;
    }
}

class Person {
    private String name;
    private int age;
	// 省略 get/set/toString/全参construction
}

新增的时间日期API

以前的日期时间API有哪些问题,新的API解决了什么

  • 以前的日期时间API设计不合理

    • 在java.util中有一个Date类,用来存储年月日时分秒
  • 在java.sql中有一个Date类,用来存储年月日

    • 在java.text包中存储了格式化日期的SimpleDateFormat类
    • 多个处理日期时间的类分别在不同的包中
  • 以前的Date是线程不安全的,导致在多线程情况下出现数据异常等问题。

  • 新的API解决了线程安全问题,因为它的每一个日期时间类都是固定不变的,每次对其进行任何修改时,都会返回一个新的日期时间类,因此可以保证在多线程的情况下不会出现线程安全问题。

日期时间的信息获取(年月日、时分秒)

    // 获取指定的日期
    LocalDate localDate = LocalDate.of(2022, 8, 5);
    // 获取当前的日期
    LocalDate nowDate = LocalDate.now();

    // 1. 获取年
    int year = localDate.getYear();
    // 2. 获取月
    int month = localDate.getMonth().getValue();
    // 3. 获取日
    int dayOfMonth = localDate.getDayOfMonth();
    System.out.println("year = " + year);
    System.out.println("month = " + month);
    System.out.println("dayOfMonth = " + dayOfMonth);

    // 获取指定的时间
    LocalTime localTime = LocalTime.of(21,24,50,716000000);
    // 获取当前的时间
    LocalTime nowTime = LocalTime.now();

    // 1. 获取小时
    int hour = localTime.getHour();
    // 2. 获取分钟
    int minute = localTime.getMinute();
    // 3. 获取秒
    int second = localTime.getSecond();
    // 4. 获取纳秒
    int nano = localTime.getNano();
    System.out.println("hour = " + hour);
    System.out.println("minute = " + minute);
    System.out.println("second = " + second);
    System.out.println("nano = " + nano);

日期时间的修改与比较

增加年月日时分秒

    // 获取指定的日期时间
    LocalDateTime localDateTime =
            LocalDateTime.of(2022, 8, 6 ,19, 30, 30);

    // 增加年月日时分秒, // 2022-08-06T19:30:30
    System.out.println("未增加前的时间:" + localDateTime);
    // 1. 增加一年
    localDateTime = localDateTime.plusYears(1);
    // 2. 增加一个月
    localDateTime = localDateTime.plusMonths(1);
    // 3. 增加一天
    localDateTime = localDateTime.plusDays(1);
    // 4. 增加一个小时
    localDateTime = localDateTime.plusHours(1);
    // 5. 增加5分钟
    localDateTime = localDateTime.plusMinutes(5);
    // 6. 增加10秒
    localDateTime = localDateTime.plusSeconds(10);
    // 结果:2022-08-06T19:30:30
    System.out.println("增加后的时间:" + localDateTime);

减少年月日时分秒

    // 获取指定的日期时间
    LocalDateTime localDateTime =
            LocalDateTime.of(2022, 8, 6 ,19, 30, 30);

    // 减少年月日时分秒, 2022-08-06T19:30:30
    System.out.println("未减少前的时间:" + localDateTime);
    // 1. 减少一年
    localDateTime = localDateTime.minusYears(1);
    // 2. 减少一个月
    localDateTime = localDateTime.minusMonths(1);
    // 3. 减少一天
    localDateTime = localDateTime.minusDays(1);
    // 4. 减少一个小时
    localDateTime = localDateTime.minusHours(1);
    // 5. 减少5分钟
    localDateTime = localDateTime.minusMinutes(5);
    // 6. 减少10秒
    localDateTime = localDateTime.minusSeconds(10);
    // 结果: 2021-07-05T18:25:20
    System.out.println("减少后的时间:" + localDateTime);

修改为指定具体的年月日时分秒

    // 获取指定的日期时间
    LocalDateTime localDateTime =
            LocalDateTime.of(2022, 8, 6 ,19, 30, 30);

    // 修改指定的年月日时分秒, // 2022-08-06T19:30:30
    System.out.println("未修改前的时间:" + localDateTime);
    // 1. 修改年份为2000年
    localDateTime = localDateTime.withYear(2000);
    // 2. 修改月份为1月
    localDateTime = localDateTime.withMonth(1);
    // 3. 修改天份为20日
    localDateTime = localDateTime.withDayOfMonth(20);
    // 4. 修改小时为早上6点
    localDateTime = localDateTime.withHour(6);
    // 5. 修改分钟为30分钟
    localDateTime = localDateTime.withMinute(30);
    // 6. 修改秒为30秒
    localDateTime = localDateTime.withSecond(30);
    // 结果:
    System.out.println("修改后的时间:" + localDateTime);

比较2个日期(之前、之后、相等)

    // 创建2个日期
    LocalDate date1 = LocalDate.of(2020,5 , 10);
    LocalDate date2 = LocalDate.of(2019,5 , 10);

    // date1是否在date2之前, // false
    boolean before = date1.isBefore(date2);

    // date1是否在date2之后, // true
    boolean after = date1.isAfter(date2);

    // date1 与 date2是否一致, // false
    boolean equal = date1.isEqual(date2);

    System.out.println("before = " + before);
    System.out.println("after = " + after);
    System.out.println("equal = " + equal);

格式化及解析操作

创建格式化类的2种方式

    // 1. 创建指定的格式化类
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
    // 2. 创建JDK中提供好的格式化类
    DateTimeFormatter basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE;

将LocalDateTime格式化为一个String类型(2种方式)

    // 1. 创建指定的格式化类
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

    // 2. 创建一个日期时间类
    LocalDateTime localDateTime =
            LocalDateTime.of(2022, 8, 6, 19, 30, 30);

    // 3. 将日期时间格式化为指定格式的字符串,(1)通过DateTimeFormatter对象格式化
    String formatDateTime1 = dateTimeFormatter.format(localDateTime);
    System.out.println("formatDateTime1 = " + formatDateTime1);

    // 4. 将日期时间格式化为指定格式的字符串,(2)通过LocalDateTime对象格式化
    String formatDateTime2 = localDateTime.format(dateTimeFormatter);
    System.out.println("formatDateTime2 = " + formatDateTime2);

将一个String字符串转换为LocalDateTime对象

    // 1. 创建指定的格式化类
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // 2. 通过LocalDateTime的parse静态方法
    LocalDateTime dateTime1 = LocalDateTime.parse("2020-10-10 20:30:59", dateTimeFormatter);
    System.out.println("dateTime1 = " + dateTime1);

Instant时间戳类(精确到纳秒)

    // 创建一个时间戳
    Instant oldInstant = Instant.now();
    System.out.println(oldInstant);

    // 为该时间戳增加2天、2小时、2分钟,注意:不支持Period的月和年
    Instant newInstance = oldInstant.plus(Duration.ofDays(2).plusHours(2).plusMinutes(2));
    System.out.println(newInstance);
    
    // 其余的减少、比较与LocalDateTime等大同小异

计算日期时间差

计算日期差Period

    // 1. 创建2个日期
    LocalDate localDate1 = LocalDate.of(2020, 8, 5);
    LocalDate localDate2 = LocalDate.of(2018, 6, 4);

    // 2. 创建Period,获取时是:第二个参数 - 第一个参数
    Period period = Period.between(localDate2, localDate1);

    // 3. 获取相差的年
    int years = period.getYears();
    System.out.println("years = " + years);

    // 4. 获取相差的月
    int months = period.getMonths();
    System.out.println("months = " + months);

    // 5. 获取相差的天数
    int days = period.getDays();
    System.out.println("days = " + days);

计算时间差Duration

    // 1. 创建2个日期
    LocalTime localTime1 = LocalTime.of(20,30,30);
    LocalTime localTime2 = LocalTime.of(21,31,35);

    // 2. 创建duration,获取时是:第二个参数 - 第一个参数
    Duration duration = Duration.between(localTime1, localTime2);
    
    // 3. 获取相差的小时
    long hours = duration.toHours();
    System.out.println("hours = " + hours);

    // 4. 获取相差的分钟
    long minutes = duration.toMinutes();
    System.out.println("minutes = " + minutes);

    // 5. 获取相差的秒数
    long seconds = duration.getSeconds();
    System.out.println("seconds = " + seconds);

计算LocalDateTime之间的时间差,ChronoUnit

    // 获取指定的日期时间
    LocalDateTime localDateTime1 =
            LocalDateTime.of(2022, 8, 6 ,19, 30, 30);

    LocalDateTime localDateTime2 =
            LocalDateTime.of(2023, 8, 6 ,19, 30, 30);

    // 1. 获取相差的年
    long years = ChronoUnit.YEARS.between(localDateTime1, localDateTime2);
    System.out.println("years = " + years);

    // 2. 获取相差的月
    long months = ChronoUnit.MONTHS.between(localDateTime1, localDateTime2);
    System.out.println("months = " + months);

    // 3. 获取相差的日
    long days = ChronoUnit.DAYS.between(localDateTime1, localDateTime2);
    System.out.println("days = " + days);

    // 4. 获取相差的小时
    long hours = ChronoUnit.HOURS.between(localDateTime1, localDateTime2);
    System.out.println("hours = " + hours);

    // 5. 获取相差的分钟
    long minutes = ChronoUnit.MINUTES.between(localDateTime1, localDateTime2);
    System.out.println("minutes = " + minutes);

    // 6. 获取相差的秒
    long seconds = ChronoUnit.SECONDS.between(localDateTime1, localDateTime2);
    System.out.println("seconds = " + seconds);

时间矫正器(2种)

  • 使用自定义矫正器
    LocalDate localDate = LocalDate.of(2000, 10, 20);
    System.out.println("矫正前localDate = " + localDate); // 矫正前localDate = 2000-10-20

    LocalDate localDate2 = localDate.with(new TemporalAdjuster() {
        @Override
        public Temporal adjustInto(Temporal temporal) {
            LocalDate adjust1 = (LocalDate) temporal;
            // 矫正增加2年
            adjust1 = adjust1.plusYears(2);
            return adjust1;
        }
    });

    System.out.println("矫正后localDate2 = " + localDate2); // 矫正后localDate2 = 2002-10-20
  • 使用JDK提供的矫正器
    LocalDate localDate = LocalDate.of(2000, 10, 20);
    System.out.println("矫正前localDate = " + localDate);// 矫正前localDate = 2000-10-20

    // 例如明年的第一天
    LocalDate localDate2 = localDate.with(TemporalAdjusters.firstDayOfNextYear());

    System.out.println("矫正后localDate2 = " + localDate2); // 矫正后localDate2 = 2001-01-01

日期时间的时区(理解为带了时区的日期时间)

    // 1. 输出所有时区
    ZoneId.getAvailableZoneIds().stream().forEach(System.out::println);

    // 2. 创建了标准时间。我们中国比标准时间晚了8个小时
    ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC());
    System.out.println(now);

    // 3. 根据指定的时区创建时间
    ZonedDateTime now1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(now1);
    
    // 4. 其他的增加、减少、修改、比较、计算时间差等都跟LocalDateTime一模一样