一、整体概述

1.1 生态

  • Lambda 表达式
  • 函数式接口
  • 方法引用 / 构造器引用
  • Stream API
  • 接口中的默认方法 / 静态方法
  • 新时间
  • 日期 API
  • 其他新特性

    1.2 新特性

  • 速度更快

  • 代码更少
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常 Optional (Kotlin ?)

文章开头.jpg

二、Lambda

2.1 Lambda概念

Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升。

2.2 从匿名方法到Lambda的转换

  1. @Test
  2. /**
  3. * 原来的使用匿名内部类方式
  4. */
  5. public void test() {
  6. Comparator<Integer> comparator = new Comparator<Integer>() {
  7. @Override
  8. public int compare(Integer o1, Integer o2) {
  9. return Integer.compare(o1, o2);
  10. }
  11. };
  12. TreeSet<Integer> set = new TreeSet<Integer>(comparator);
  13. }
  14. /**
  15. * 使用lambda表达式
  16. */
  17. @Test
  18. public void testLambda() {
  19. Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
  20. TreeSet<Integer> set = new TreeSet<Integer>(comparator);
  21. }


2.3 Lambda 表达式语法

2.3.1 基础语法

Lambda 表达式的基础语法:
Java8种引入了一个新的操作符“->” ,改操作符称为箭头操作符或者 Lambda 操作符号箭头操作符将 Lambda 拆分成两部分
左侧:Lambda 的参数列表
右侧:Lambda 表达式中的需要执行的功能 ,即 Lambda 体

2.3.2 语法格式

(1) 语法格式一: 无参数,无返回值
() -> System.out.println(“hello Lambda!”)

    @Test
    public void test1(){
        int num = 0;//jdk 1.7 之前必须是final jdk1.8他底层给你加了final 其实还是有的 但是可以不写
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world!"+num);
            }
        };
        runnable.run();
        System.out.println("------------------------");
        Runnable runnable1 = () -> System.out.println("hello Lambda!"+num);
        runnable1.run();
    }

(2) 语法格式二: 一个参数,无返回值
Consumer consumer = (x) -> System.out.println(x);

    @Test
    public void test2(){
        Consumer<String> consumer = (x) -> System.out.println(x);
        consumer.accept("我是你爸爸!!");
        System.out.println("-------------------------------");
        Consumer<String> consumer1 = x -> System.out.println(x);
        consumer1.accept("参数1,小括号可以省略不写");
    }

(3) 语法格式三:若只有一个参数,小括号可以省略不写
Consumer consumer1 = x -> System.out.println(x);
(4) 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator comparator = (x,y) -> {
System.out.println(“x: “+x);
System.out.println(“y: “+y);
return Integer.compare(x,y);
};

    @Test
    public void test3(){
        Comparator<Integer> comparator = (x,y) -> {
            System.out.println("x: "+x);
            System.out.println("y: "+y);
            return Integer.compare(x,y);
        };
        int compare = comparator.compare(1, 2);
        System.out.println(compare);
    }

(5) 语法格式五: 有两个以上的参数,有返回值,但是 Lambda 中有一条语句 return 和 {} 都可以省略不写
Comparator comparator = (x,y) -> Integer.compare(x,y);

 @Test
    public void test4(){
        Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
        int compare = comparator.compare(1, 2);
        System.out.println(compare);
    }

2.3.3 总结

左右遇一括号省
左侧推断类型省

2.3.4 简单练习

 //需求:对一个数进行运算
    @Test
    public void test5(){
        System.out.println("Lambda 直接实现函数接口,然后手动调用方法:");
        MyFunction myFunction = (x) -> {
            System.out.println(x);
            return x;
        };
        myFunction.getValue(12);
        System.out.println("-----------------------------");
        System.out.println("封装operation方法,完成直接调用和Lambda函数接口的实现:");
        Integer operation1 = operation(100, x -> x * x);
        System.out.println("1.值平方:"+operation1);
        Integer operation = operation(100, (x) -> x);
        System.out.println("2.值打印:"+operation);
        System.out.println("3.加法:"+operation(100,z -> z+200));

    }

    public Integer operation(Integer num,MyFunction myFunction){
        return myFunction.getValue(num);
    }

2.4 Lambda 表达式需要“函数式接口”的支持

函数式接口:接口中有一个抽象方法的接口,称为函数式接口。可以使用@FunctionalInterface修饰可以检查是否是函数式接口

2.5 类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型类型,程序依然可以编译,这是一位内 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖与上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”

三、函数式接口

  1. 函数式接口:只包含一个抽象方法的接口
  2. 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出就一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
  3. 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口

    3.1 自定义函数式接口

    ```java @FuntionalInterface public interface MyNumber{ public double getValue(); }

函数式接口中使用泛型: @FuntionalInterface public interface MyFunc{ public T getValue(T t); }

3.2 作为参数传递 Lambda 表达式
```java
public String toUpperString(Myfunc<String> fun,String str){
    return fun.getValue(str);
}
作为参数传递 Lambda 表达式:
String newStr = toUpperString(
    (str) -> str.toUpperCase(),"abcdef"
    );
System.out.println(newStr);

注意:作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须与该 Lambda 表达式兼容的函数式接口的类型。

3.2 Java 内置四大核心函数式接口

** 参数类型 返回类型 用途
核心接口
Consumer 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t);
Supplier 供给型接口 T 返回类型为T的对象,包含方法:T get();
Function 函数型接口 T R 对类型为T的对象应用操作,并返回R类型的结果的对象。包含方法:R apply(T t);
Predicate 断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t);
其他接口
BiConsumer T, U void 对类型为T,U参数应用操作。包含方法为:R accept(T t,U u);
BiFuncation T,U R 对类型为 T, U 参数应用操作,返回 T 类型的结果。包含方法为: R apply(T t, U u);
UnaryOperator (Function 子接口) T T 对类型为 T 的对象进行一元运行。并返回 T 类型的结果。包含方法为:T apply(T t);
BinaryOperator (BiFuncation 子接口) T, T T 对类型为 T 的对象进行二元运算,并返回T类型的接口。包含方法为:T apply(T t1,T t2);
ToIntFunction
ToLongFunction
ToDoubleFunction
T int
long
double
分别计算int 、long、double 值的函数
IntFunction
LongFunction
DoubleFunction
int
long
double
R 参数分别为int、long、double 类型的函数

3.3 练习

package lambda;

/**
 * @author : [Zara-cat]
 * @version : [v1.0]
 * @className : Lambda03
 * @description : [java8 内置的四大函数式接口]
 * @createTime : [2021/10/31 23:30]
 * @updateUser : [Zara-cat]
 * @updateTime : [2021/10/31 23:30]
 * @updateRemark : [描述说明本次修改内容]
 */

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Consumer<T>:消费型接口
 *          void accept(T t);
 * Supplier<T>:供给型接口
 *          T get();
 * Function<T,R>:函数型接口
 *          R apply(T t);
 * Predicate: 断言型接口
 *          boolean test(T t);
 */
public class Lambda03 {
    // Consumer<T>:消费型接口
    @Test
    public void test1(){
        happy(10000,(x) -> System.out.println("大保健消费了"+x+"元"));
    }
    public void happy(double money, Consumer<Double> consumer){
        consumer.accept(money);
    }

    //Supplier<T>:供给型接口
    @Test
    public void test2(){
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
        numList.stream().forEach(System.out::println);
    }
    public List<Integer> getNumList(int sum, Supplier<Integer> supplier){
        List<Integer> integers = new ArrayList<>();
        for (int i = 0; i < sum; i++) {
            Integer integer = supplier.get();
            integers.add(integer);
        }
        return integers;
    }

    //Function<T,R>:函数型接口
    @Test
    public void test3(){
        String s1 = strHandler("\t\t\t\t\t 我爱王小明", (s) -> s.trim());
        System.out.println(s1);
        String s2 = strHandler("我爱王小明", (s -> s.substring(2, s.length())));
        System.out.println(s2);

    }
    public String strHandler(String str, Function<String,String> function){
        return function.apply(str);
    }

    //Predicate: 断言型接口
    @Test
    public void test4(){
        List<String> list = Arrays.asList("hello","Zara","ok","Lambda","www");
        List<String> strings = filterStr(list, (s) -> s.length() > 3);
        strings.stream().forEach(System.out::println);
    }
    public List<String> filterStr(List<String> strings , Predicate<String> pre){
        List<String> list = new ArrayList<>();
        for (String str : strings){
            if (pre.test(str)){
                list.add(str);
            }
        }
        return list;
    }

}

四、方法引用与构造器引用

4.1 方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!
方法引用:使用操作符“::”将方法名与对象或类的名字分隔开来。

主要有 3 种语法格式
对象 :: 实例方法名
类 :: 静态方法名
类 :: 实例方法名

注意
(1). Lambda 体中调用方法的参数列表和返回值类型,要与函数式接口中的抽象方法的参数列表和返回值类型保持一致
(2). 若 Lambda 参数列表中的第一个参数列表是实例方法的调用者,而第二个参数是实例方法的参数时,可是使用ClassName :: methodName

 //对象 :: 实例方法名
    @Test
    public void test1(){
        Consumer<String> consumer = (x) -> System.out.println(x);

        PrintStream ps = System.out;
        Consumer<String> consumer1 = ps ::println;

        Consumer<String> consumer2 = ps ::println;
        consumer2.accept("aa");
    }

    @Test
    public void test2(){
        Employee employee = new Employee();
        employee.setName("哈哈哈");
        Supplier<String> sup = () -> employee.getName();
        String str = sup.get();
        System.out.println(str);

        Supplier<Integer> sup2 = employee::getAge;
        Integer integer = sup2.get();
        System.out.println(integer);
    }
    //类 :: 静态方法名
    @Test
    public void test3(){
        Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);

        Comparator<Integer> comparator1 = Integer::compare;
        int compare = comparator1.compare(5, 4);
        System.out.println(compare);
    }
    //类 :: 实例方法名
    @Test
    public void test4(){
        BiPredicate<String,String> biPredicate = (x,y) -> x.equals(y);
        BiPredicate<String,String> biPredicate1 = String :: equals;
    }

4.2 构造器引用

格式ClassName :: new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

    //构造器引用
    @Test
    public void test5(){
        Supplier<Employee> sup = () -> new Employee();
        Employee employee = sup.get();

        //构造器引用方式
        Supplier<Employee> sup2 = Employee:: new;
        Employee employee1 = sup2.get();
        System.out.println(employee1);
    }
    @Test
    public void test6(){
        Function<String,Employee> function = (x) -> new Employee(x);

        Function<String,Employee> function1 = Employee::new;
        Employee em = function1.apply("某某");
        System.out.println(em);

        BiFunction<String,Double,Employee> function2 = Employee::new;

    }

4.3 数组引用

格式type[] :: new

  //数组引用
    @Test
    public void test7(){
        Function<Integer,String[]> fun = (x) ->new String[x];
        String[] apply = fun.apply(10);
        System.out.println(apply.length);

        Function<Integer,String[]> fun2 = String[]::new;
        System.out.println(fun2.apply(20).length);
    }

五、Stream API

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另一个则是 Stream API(java.util.stream.*)

5.1 了解Stream

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作,使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式

5.2 什么是 Stream

流(Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”

注意:
(1) Stream 自己不会存储元素
(2) Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
(3) Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

5.3 Stream的操作三个步骤

  • 创建 Stream

一个数据源(如:集合、数组),获取一个流

  • 中间操作

一个中间操作链,对数据源的数据进行处理

  • 终止操作(终端操作)

一个终止操作,执行中间操作链,并产生结果

image.png

5.3.1 创建 Stream

Java8 中的 collection 接口被扩展,提供了两个获取流的方法

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array) : 返回一个流
  • 重载形式,能够处理对应基本类型的数组
  • public static IntStream stream(int[] array);
  • public static LongStream stream(long[] array);
  • public static DoubleStream stream(double[] array);

由值创建流,可以使用静态方法 Stream.of() ,通过显示值创建一个流。它可以接受任意数量的参数:

  • public static Stream of(T… values) : 返回一个流

创建无限流:
可以使用静态方法 Stream.iterate() 和 Stream.generate() , 创建无限流。

  • 迭代

public static Stream interate(final T send, final UnaryOperator f)

  • 生成

pubicl static Stream generate(Supplier s)

 @Test
    public void test1(){
        //1.可以通过Collection 系列集合提供的stream()方法 或 parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //2.可以通过 Arrays 中的静态方法 stream() 获取数组流
        Employee[] employees = new Employee[10];
        Stream<Employee> stream1 = Arrays.stream(employees);

        //3.通过 Steam 类中静态方法 of()
        Stream<String> stream2 = Stream.of("aa", "bb", "cc");

        //4.创建无限流
        //迭代
        Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
        //stream3.limit(10).forEach(System.out::println);
        //生成
        Stream.generate(() ->(int)(Math.random()*100))
                .limit(5)
                .forEach(System.out::println);
    }

5.3.2 Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

a. 筛选与切片

方法 描述
filter(Predicate p) 接收 Lambda ,从流中排除某些约束
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量。
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个 ,则返回一个空流。与 limit(n) 互补。
 List<Employee> list = Arrays.asList(
            new Employee("张山", 18, 9999.99),
            new Employee("李四", 38, 9955.99),
            new Employee("王五", 50, 669.99),
            new Employee("赵六", 16, 339.99),
            new Employee("田七", 8, 9977.99),
            new Employee("田七", 8, 9977.99),
            new Employee("田七", 8, 9977.99)
    );   
    /*
        筛选与切片
        filter:接收Lambda,从流中排除某些元素;
        limit:截断流,使其元素不超过给定数量
        skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不够 n 个,则返回一个空流,与limit(n) 互补
        distinct:筛选,通过流所生成的hashCode() 和 equals() 去除重复元素
     */
    @Test
    public void test1(){、
        //filter()
        Stream<Employee> s = list.stream()
                                 .filter(x -> x.getAge() > 30);//内置迭代操作
        s.forEach(System.out::println);
    }
    @Test
    public void test2(){
        //limit会形成短路,满足条件后不再迭代遍历,提高效率
        Stream<Employee> stream = list.stream().filter((e) -> e.getSalary() > 1000).limit(2);
        stream.forEach(System.out::println);
    }
    @Test
    public void test3(){
        //skip()
        list.stream().filter((e) ->e.getSalary()>1000)
                .skip(2)
                .forEach(System.out::println);
    }
    @Test
    public void test4(){
        //distinct()
        list.stream().filter((e) ->e.getSalary()>1000)
                .skip(2)
                .distinct()
                .forEach(System.out::println);
    }

b. 映射

方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
    /**
     * 映射
     * map:接收 Lambda ,将元素转换成其他形式或提取信息。接受一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素
     * flatMap:接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有的流链接成一个流
     */
    @Test
    public void test5(){
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
        list.stream()
                .map((str) -> str.toUpperCase())
                .forEach(System.out::println);
        System.out.println("-----------------------------");
        this.list.stream()
                .distinct()
                .map(Employee::getName)
                .forEach(System.out::println);
        System.out.println("-----------------------------");
        Stream<Stream<Character>> streamStream = list.stream()
                .map(StreamAPI02::filterStr);
        streamStream.forEach((stream) ->stream.forEach(System.out::println));
        System.out.println("-----------------------------");
        Stream<Character> characterStream = list.stream()
                .flatMap(StreamAPI02::filterStr);
        characterStream.forEach(System.out::println);
        System.out.println("++++++++++++++++++++++++++++++++++");
        List list1 = new ArrayList();
        list1.add(1);
        list1.add("你好");
        list1.addAll(list);
        list1.add(list);
        System.out.println(list1);
        Iterator iterator = list1.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    //将一个字符串转换成一个字符流
    public static Stream<Character> filterStr(String str){
        List<Character> list = new ArrayList<>();
        for(Character character : str.toCharArray()){
            list.add(character);
        }
        return list.stream();
    }

c. 排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序
    /**
     * 排序:
     *  sorted() :自然排序(Comparable)
     *  sorted(Comparator com) : 定制排序(Comparator)
     */
    @Test
    public void test6(){
        List<String> list = Arrays.asList("ccc","eee","bbb","aaa","ddd");
        list.stream()
                .sorted()
                .forEach(System.out::println);
        System.out.println("---------------------");
        this.list.stream()
                .sorted((e1,e2) ->{
                    if (e1.getAge() == e2.getAge()){
                        return e1.getName().compareTo(e2.getName());
                    }else {
                        Integer age = e1.getAge();
                        return age.compareTo((Integer) e2.getAge());
                    }
                })
                .forEach(System.out::println);
    }

5.3.3 Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至 是 void。

a. 查找与匹配

方法 描述
allMatch(Predicate p)
参数:断言函数式接口
检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素得总数
max(Comparator c)
Comparator :比较器
返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c)
Consumer :消费型函数式接口
内部迭代使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了
    List<Employee> list = Arrays.asList(
            new Employee("张山", 18, 9999.99, Employee.Status.FREE),
            new Employee("李四", 38, 9955.99, Employee.Status.BUSY),
            new Employee("王五", 50, 669.99, Employee.Status.VOCATION),
            new Employee("赵六", 16, 339.99, Employee.Status.FREE),
            new Employee("田七", 8, 9977.99, Employee.Status.BUSY)
    );
    /**
     * 查找与匹配:
     *      allMatch: 检查是否匹配所有元素
     *      anyMatch: 检查是否至少匹配一个元素
     *      noneMatch: 检查是否没有匹配所有元素  流里是不是没有元素能够匹配指定的规则
     *      findFirst: 返回第一个元素
     *      findAny: 返回当前流中的任意元素
     *      count: 返回流中元素的个数
     *      max: 返回流中最大值
     *      min: 返回流中最小值
     */
    @Test
    public void test1(){
        boolean b = list.stream()
                .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b);// fase

        boolean b1 = list.stream()
                .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1); //true

        boolean b2 = list.stream()
                .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2);//false

        Optional<Employee> first = list.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findFirst();
        Employee employee = first.get();
        System.out.println(employee);//Employee{name='赵六', age=16, salary=339.99, status=FREE}

        Optional<Employee> any = list.stream()
                .filter(employee1 -> employee1.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(any.get());//Employee{name='张山', age=18, salary=9999.99, status=FREE}
    }
    @Test
    public void test2(){
        long count = list.stream()
                .count();
        System.out.println(count);//5

        Optional<Employee> max = list.stream()
                .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(max.get());//Employee{name='张山', age=18, salary=9999.99, status=FREE}

        Optional<Double> min = list.stream()
                .map(Employee::getSalary)
                .min(Double::compare);
                //.min((e1, e2) -> Double.compare(e1, e2));
        System.out.println(min.get());//339.99
    }

b. 归约

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

备注:map和reduce的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

    /**
     * 规约
     * reduce(T identity,BinaryOperator) / reduce(BinaryOperator) :可以将流中元素反复结合起来,得到一个值
     */
    @Test
    public void test3(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer reduce = list.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(reduce);
        System.out.println("-----------------------------------");
        Optional<Double> reduce1 = this.list.stream()
                .map(Employee::getSalary)
                .reduce(Double::sum);
        System.out.println(reduce1.get());
    }

c. 收集

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

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

方法 返回类型 作用
toList List 把流中元素收集到List
toSet Set 把流中元素收集到Set
toCollection Collection 把流中元素收集到创建的集合
counting Long 计算流中元素的个数
summingInt Integer 对流中元素的整数属性求和
averagingInt Double 对流中元素Integer属性的平均值
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值
joining String 连接流中每个字符串
maxBy Optional 根据比较器选择最大值
minBy Optional 根据比较器选择最小值
reducing 规约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator 与流中元素逐个结合,从而归约成单个值
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转换函数
groupingBy Map> 根据某个属性值对流分组,属性为 K,结果为V
partitioningBy Map> 根据 true 或 false 进行分区
    /**
     * 收集
     * collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中的元素做汇总的方法
     */
    @Test
    public void test4(){
        List<String> collect = this.list.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        collect.stream()
                .forEach(System.out::println);
        //collect.forEach(System.out::println);
        System.out.println("-----------------------------------");
        Set<String> collect1 = this.list.stream()
                .map((x) -> x.getName())
                .collect(Collectors.toSet());
        collect1.forEach(System.out::println);
        System.out.println("------------------------------------");
        HashSet<String> collect2 = this.list.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        collect2.forEach(System.out::println);
        System.out.println("-----------------------------------");
        LinkedList<String> collect3 = this.list.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(LinkedList::new));
        collect3.forEach(System.out::println);
    }
    @Test
    public void test5(){
        //总数
        Long collect = this.list.stream()
                .collect(Collectors.counting());
        long count = this.list.stream()
                .count();
        System.out.println(count +" "+count +"  ");
        System.out.println(count == collect);
        System.out.println("--------------------------");
        //平均值
        Double avg = this.list.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avg);
        System.out.println("--------------------------");
        //总和
        Double SUM = this.list.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(SUM);
        System.out.println("---------------------------");
        //工资最大的员工信息
        Optional<Employee> collect1 = this.list.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(collect1.get());
        //最大的工资
        Optional<Double> collect2 = this.list.stream()
                .map(Employee::getSalary)
                .collect(Collectors.maxBy(Double::compare));
        System.out.println(collect2.get());
        //最小工资
        Optional<Double> collect3 = this.list.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(collect3.get());
        System.out.println("----------------------------");
    }
 @Test
    public void test6(){
        //分组
        Map<Employee.Status, List<Employee>> map = this.list.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(map);
        System.out.println("-----------------------------");
        //多级分组
        Map<Employee.Status, Map<String, List<Employee>>> mapMap = this.list.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(mapMap);
    }
    @Test
    public void test7(){
        //分区(分为满足条件的和不满足条件的)
        Map<Boolean, List<Employee>> map = this.list.stream()
                .collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
        System.out.println(map);
    }

    @Test
    public void test8(){
        DoubleSummaryStatistics dss = this.list.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));
        System.out.println(dss.getMax());
        System.out.println(dss.getMin());
        System.out.println(dss.getSum());
        System.out.println(dss.getAverage());
        System.out.println(dss.getCount());
    }
    @Test
    public void test9(){
        String nameJoin= this.list.stream()
                .map(Employee::getName)
                //.collect(Collectors.joining());
                //.collect(Collectors.joining(","));
                .collect(Collectors.joining(",","===","==="));
        System.out.println(nameJoin);
    }

5.4 Stream练习

Trader交易员类

//交易员类
public class Trader {

    private String name;
    private String city;

    public Trader() {
    }

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Trader [name=" + name + ", city=" + city + "]";
    }

}

Transaction交易类

//交易类
public class Transaction {

    private Trader trader;
    private int year;
    private int value;

    public Transaction() {
    }

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return trader;
    }

    public void setTrader(Trader trader) {
        this.trader = trader;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Transaction [trader=" + trader + ", year=" + year + ", value="
                + value + "]";
    }

}

test类

package stream.eq;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Before;
import org.junit.Test;

import javax.crypto.spec.PSource;

public class TestTransaction {

    List<Transaction> transactions = null;

    @Before
    public void before(){
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
    }

    //1. 找出2011年发生的所有交易, 并按交易额排序(从低到高)
    @Test
    public void test1(){
        transactions.stream()
                .filter((t) -> t.getYear() == 2011)
                .sorted((t1,t2) -> Integer.compare(t1.getValue(),t2.getValue()))
                .forEach(System.out::println);
    }

    //2. 交易员都在哪些不同的城市工作过?
    @Test
    public void test2(){
        transactions.stream()
                .map((t) -> t.getTrader().getCity())
                .distinct()//去重
                .forEach(System.out::println);
    }

    //3. 查找所有来自剑桥的交易员,并按姓名排序
    @Test
    public void test3(){
        transactions.stream()
                .filter((t) -> t.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getTrader)
                .sorted((t1,t2) -> t1.getName().compareTo(t2.getName()))
                .distinct()
                .forEach(System.out::println);
    }

    //4. 返回所有交易员的姓名字符串,按字母顺序排序
    @Test
    public void test4() {
        transactions.stream()
                .map((e) -> e.getTrader().getName())
                .distinct()
                .sorted((t1,t2) -> t1.compareTo(t2))
                .forEach(System.out::println);
        System.out.println("---------------------------------------");
        String str = transactions.stream()
                .map((e) -> e.getTrader().getName())
                .sorted()
                .distinct()
                .reduce("", String::concat);
        System.out.println(str);
        System.out.println("-----------------------------");
        transactions.stream()
                .map((e) -> e.getTrader().getName())
                .distinct()
                .flatMap(TestTransaction::filterCharacter)
                .sorted()
                .forEach(System.out::print);
    }
    public static Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for(Character character : str.toCharArray()){
            list.add(character);
        }
        return list.stream();
    }

    //5. 有没有交易员是在米兰工作的?
    @Test
    public void test5(){
        boolean bl = transactions.stream()
                .anyMatch((t) -> t.getTrader().getCity().equals("Milan"));
        System.out.println(bl);
    }


    //6. 打印生活在剑桥的交易员的所有交易额
    @Test
    public void test6(){
        Optional<Integer> valueSum = transactions.stream()
                .filter((e) -> e.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getValue)
                .reduce(Integer::sum);
        System.out.println(valueSum.get());
    }


    //7. 所有交易中,最高的交易额是多少
    @Test
    public void test7(){
        Optional<Integer> max = transactions.stream()
                .map(Transaction::getValue)
                .collect(Collectors.maxBy(Integer::compare));
        System.out.println(max.get());
        System.out.println("-----------------------");
        Optional<Integer> max2 = transactions.stream()
                .map(Transaction::getValue)
                .sorted((t1,t2) -> -Integer.compare(t1,t2))
                .findFirst();
        System.out.println(max2.get());
        System.out.println("-----------------------");
        Optional<Integer> max1 = transactions.stream()
                .map(Transaction::getValue)
                .max(Integer::compare);
        System.out.println(max1.get());
    }

    //8. 找到交易额最小的交易
    @Test
    public void test8(){
        Optional<Transaction> min = transactions.stream()
                .min((e1, e2) -> Integer.compare(e1.getValue(), e2.getValue()));
        System.out.println(min.get());
        System.out.println("-----------------------------------");
        Optional<Transaction> min2 = transactions.stream()
                .collect(Collectors.minBy((e1, e2) -> Integer.compare(e1.getValue(), e2.getValue())));
        System.out.println(min2.get());

    }

}

六、新时间日期API

6.1 使用LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime类的实例是不可变对象,分别表示使用ISO-8601日历系统的时期、时间、日期和时间。他们提供了简单的日期或时间,并包括当前的时间信息。也不包括与时区相关的信息。

注:ISO-8601 日历系统是国际标准化组织制定的现代公民的日期和时间的表示法
image.png

    //1.LocalDate、LocalTime、LocalDateTime 类的实例是 不可变的对象 ,分别表示使用ISO-8601 日历系统的日期、时间、日期和时间。
    //  他们提供了简单的日期或时间,并不包括当前的时间信息,也不包括与时区相关的信息(我们人读的时间和日期)
    @Test
    public void test1(){
        LocalDateTime localTime = LocalDateTime.now();
        System.out.println(localTime);//2021-11-05T13:45:53.216 获取当前时间
        System.out.println("---------------------------------------------------");
        LocalDateTime localDateTime = LocalDateTime.of(2099, 9, 9, 9, 9, 9);
        System.out.println(localDateTime);//2099-09-09T09:09:09
        System.out.println("---------------------------------------------------");
        LocalDateTime newTime = localTime.plusYears(2);
        System.out.println(newTime);//2099-09-09T09:09:09 加两年
        System.out.println("---------------------------------------------------");
        LocalDateTime newTime2 = localTime.minusDays(5);
        System.out.println(newTime2);//2021-10-31T13:50:46.337 减5天
        System.out.println("---------------------------------------------------");
        System.out.println(localTime.getYear());//年
        System.out.println(localTime.getMonthValue());//月
        System.out.println(localTime.getDayOfMonth());//日
        System.out.println(localTime.getHour());//小时
        System.out.println(localTime.getMinute());//分钟
        System.out.println(localTime.getSecond());//秒
    }

运行结果:
image.png

6.2 Instant 时间戳

用于“时间戳”的运算。他是以Unix元年(传统的设定为UTC时区 1970 年 1 月 1 日 午夜时分)开始所经历的描述进行运算

 //2.Instant:时间戳(以 Unix 元年:1970年1月1日 00:00:00 到某个时间之间到毫秒值)
    @Test
    public void test2(){
        Instant instant = Instant.now();//默认获取 UTC 时区             UTC:世界协调时间 和中国相差8个时差
        System.out.println("UTC:"+instant);
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        System.out.println("中国:"+offsetDateTime);

        System.out.println(instant.toEpochMilli());

        System.out.println(Instant.ofEpochSecond(6)); //1970-01-01T00:00:06Z  1970年1月1日 00:00:00后的6秒钟
    }

运行结果:
image.png

6.3 Duration 和 Period

  • Duration: 计算两个“时间” 之间的间隔
  • Period: 计算两个“日期”之间的间隔


    @Test
    public void test3(){
        Instant ins1 = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Instant ins2 = Instant.now();
        Duration d = Duration.between(ins1, ins2);
        System.out.println(d.getSeconds());//秒
        System.out.println("---------------------------------------------------");
        LocalTime lt1 = LocalTime.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LocalTime lt2 = LocalTime.now();
        System.out.println(Duration.between(lt1,lt2).toMillis());//毫秒
    }
    @Test
    public void test4(){
        LocalDate ld1 = LocalDate.of(2021,1,1);
        LocalDate ld2 = LocalDate.now();
        System.out.println(Period.between(ld1,ld2).getMonths());
        System.out.println(Period.between(ld1,ld2).getDays());
    }

运行结果:
test3:
image.png
test4:
image.png

6.4 TemporalAdjuster日期的操控

  • TemporalAdjuster : 时间矫正器。有时我们可能需要获取列如:将日期调整到“下一个周日” 等操作
  • TemporalAdjusters: 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现

    //TemporalAdjuster : 时间矫正器。有时我们可能需要获取列如:将日期调整到“下一个周日” 等操作
      //TemporalAdjusters: 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现
      @Test
      public void test1(){
          LocalDateTime localDateTime = LocalDateTime.now();
          System.out.println(localDateTime);
    
          LocalDateTime newLdt = localDateTime.withDayOfMonth(10);//指定当前时间为本月的10号
          System.out.println(newLdt);
          //2021-11-05T15:19:23.534
          //2021-11-10T15:19:23.534
    
          LocalDateTime nextSUNDAY = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
          System.out.println("下一个周日:"+nextSUNDAY);
    
          //自定义: 下一个工作日是什么时候
          LocalDateTime nextWorkTime = localDateTime.with((e) -> {
              //public LocalDateTime with(TemporalAdjuster adjuster) {}
              //TemporalAdjuster是一个函数式接口,我们可以使用Lambda表达式进行实现
              //这里把参数强转成我们想要的类型
              /**
               * public LocalDateTime with(TemporalAdjuster adjuster) {
               *         // optimizations
               *         if (adjuster instanceof LocalDate) {
               *             return with((LocalDate) adjuster, time);
               *         } else if (adjuster instanceof LocalTime) {
               *             return with(date, (LocalTime) adjuster);
               *         } else if (adjuster instanceof LocalDateTime) {
               *             return (LocalDateTime) adjuster;
               *         }
               *         return (LocalDateTime) adjuster.adjustInto(this);
               *     }
               */
              LocalDateTime ldt = (LocalDateTime) e; //我们就是用LocalDateTime
              DayOfWeek dayOfWeek = ldt.getDayOfWeek();//获取当前日期是周几
              if (dayOfWeek.equals(DayOfWeek.FRIDAY)) { //如果当前是星期5
                  return ldt.plusDays(3);//加3天
              } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
                  return ldt.plusDays(2);//加2天
              } else if (dayOfWeek.equals(DayOfWeek.SUNDAY)) {
                  return ldt.plusDays(1);//加1天
              } else {
                  return ldt.plusDays(1);
              }
          });
          System.out.println("下一个工作日是:"+nextWorkTime);
      }
    

    TemporalAdjusters的方法:
    image.png

    6.5 解析与格式化

    java.time.format.DateTimeFormatter类:该类提供了三种
    格式化方法:
    预定义的标准格式
    语言环境相关的格式
    自定义的格式

     //DateTimeFormatter: 格式化时间/日期
      @Test
      public void test2(){
          DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
          LocalDateTime localDateTime = LocalDateTime.now();
          String strDate = localDateTime.format(dtf);
          System.out.println(strDate);
          System.out.println("------------------------------");
          DateTimeFormatter dtf1 = DateTimeFormatter.ISO_DATE;
          LocalDateTime localDateTime1 = LocalDateTime.now();
          String strDate1 = localDateTime1.format(dtf1);
          System.out.println(strDate1);
          System.out.println("------------------------------");
          //DateTimeFormatter.ofPattern();
          DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy年-MM月dd日 HH:mm:ss");
          String strDate3 = dtf3.format(localDateTime);
          System.out.println(strDate3);
          //localDateTime.parse(param1,param2);
          LocalDateTime parse = localDateTime.parse(strDate3,dtf3); //解析回来str -=》date  参数1:str 参数2:
          System.out.println(parse);
    
      }
    

    6.6 时区的处理

    Java8 中加入了对时区的支持,带时区的时间为分别为:
    ZonedDate、ZonedTime、ZonedDateTime
    其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式 。例如 :Asia/Shanghai 等
    ZoneId:该类中包含了所有的时区信息
    getAvailableZoneIds() : 可以获取所有时区时区信息
    of(id) : 用指定的时区信息获取ZoneId 对象


    //ZonedDate ZonedTime ZonedDateTime
    @Test
    public void test3(){
        //支持多少个时区
        Set<String> set = ZoneId.getAvailableZoneIds();
        set.forEach(System.out::println);
    }
    @Test
    public void test4(){
        LocalDateTime now = LocalDateTime.now(ZoneId.of("Europe/London"));
        System.out.println(now);
        LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        ZonedDateTime zonedDateTime = now2.atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println(zonedDateTime);
    }

6.6 与传统日期处理的转换

image.png

七、接口中的默认方法与静态方法

7.1 接口中的默认方法

Java 8 中允许接口中包含具有实现的方法,该方法称为”默认方法”,默认方法使用 default 关键字修饰

例如:

interface MyInterface<T>{
    T func(int a);

    default String getName(){
        return "hello Java 8!"
    }
}

我们都知道Java 是一个单继承,多实现的。
若一个接口中定义了一个默认方法,而另一个父类或接口中又定义了一个同名的方法时。
我们使用代码进行实验一下:

//接口1
public interface MyInterface {
    default String getName(){
        return "林志玲";
    }
    public static void show(){
        System.out.println("接口中的静态方法");
    }
}
//接口2
public interface MyInterface2 {
    default String getName(){
        return "王振宇";
    }
}
//父类
public class MyClass {
    public String getName(){
        return "黄勃";
    }
}
//实现类1----》多实现
public class SubClass implements MyInterface,MyInterface2{
    @Override
    public String getName() {
        return MyInterface2.super.getName();
    }    
}
//实现类1---》继承 实现
public class SubClass1 implements MyInterface extends MyClass{

}
//测试类---》SubClass
public class TestDefaultInterface {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        System.out.println(subClass.getName()); //王振宇
        MyInterface.show();
    }
}
//测试类2---》SubClass1
public class TestDefaultInterface2 {
    public static void main(String[] args) {
        SubClass1 subClass = new SubClass1();
        System.out.println(subClass.getName()); //黄渤
        MyInterface.show();
    }
}

接口默认方法的“类优先”原则:
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时:
1.选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
2.接口冲突。如果一个父接口提供了一个默认方法,而另一个接口也提供了一个具有相同名称的参数列表的方法(不管方法是否是默认方法,那么必须覆盖该方法来解决冲突)

7.2 接口中的静态方法

public interface MyInterface {
    default String getName(){
        return "林志玲";
    }
    //接口中的静态方法
    public static void show(){
        System.out.println("接口中的静态方法");
    }
}

八、Optional类

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

/**
 * @author : [Zara-cat]
 * @version : [v1.0]
 * @className : TestOptional
 * @description : [optional对null针对介绍]
 * @createTime : [2021/11/4 13:37]
 * @updateUser : [Zara-cat]
 * @updateTime : [2021/11/4 13:37]
 * @updateRemark : [描述说明本次修改内容]
 */
public class TestOptional {
    /**
     * Optional 容器类的常用方法:
     * Optional.of(T t) : 创建一个Optional 实例
     * Optional.empty() : 创建一个空的 Optional 实例
     * Optional.ofNullable(T t) : 若 t 不为 null,创建 Optional 实例,否者创建空的实例
     * isPresent() : 判断是否包含值
     * orElse(T t) : 如果调用对象包含值,返回该值,否者返回 t
     * orElseGet(Supplier s) : 如果调用对象包含值,返回该值,否者返回 s 获取的值
     * map(Function f) : 如果有值对其处理,并返回处理后的 Optional ,否者返回 Optional。empty()
     * flatMap(Function mapper) : 与 map 类似,要求返回值必须是 Optional
     */
    @Test
    public void test1(){
        //Optional.of(T t) : 创建一个Optional 实例
        Optional<Employee> opEmployee = Optional.of(new Employee());
        Employee employee = opEmployee.get();
        System.out.println(employee);
    }
    @Test
    public void test2(){
        //Optional.empty() : 创建一个空的 Optional 实例
        Optional<Employee> opEmployee = Optional.empty();
        System.out.println(opEmployee.get());
    }
    @Test
    public void test3(){
        // Optional.ofNullable(T t) : 若 t 不为 null,创建 Optional 实例,否者创建空的实例
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee());
        System.out.println(opEmployee.get());
        Optional<Employee> opEmployee2 = Optional.ofNullable(null);
        System.out.println(opEmployee2.get());
    }
    @Test
    public void test4(){
        //isPresent() : 判断是否包含值
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee());
        if (opEmployee.isPresent()){
            System.out.println(opEmployee.get());
        }
        Optional<Employee> opEmployee2 = Optional.ofNullable(null);
        if (opEmployee2.isPresent()){
            System.out.println(opEmployee.get());
        }else {
            System.out.println("2=Optional中没有值");
        }
    }
    @Test
    public void test5(){
        //orElse(T t) : 如果调用对象包含值,返回该值,否者返回 t
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee());
        Employee employee = opEmployee.orElse(new Employee("张山", 18, 8888.88, Employee.Status.BUSY));
        System.out.println(employee);


        Optional<Employee> opEmployee2 = Optional.ofNullable(null);
        Employee employee2 = opEmployee2.orElse(new Employee("张山", 18, 8888.88, Employee.Status.BUSY));
        System.out.println(employee2);
    }
    @Test
    public void test6(){
        //orElseGet(Supplier s) : 如果调用对象包含值,返回该值,否者返回 s 获取的值
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee());
        Employee employee = opEmployee.orElseGet(() -> {
            //这里可以写自己的处理逻辑
            return new Employee();
        });
        System.out.println(employee);
    }
    @Test
    public void test7(){
        //map(Function f) : 如果有值对其处理,并返回处理后的 Optional ,否者返回 Optional。empty()
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee("张山", 18, 8888.88, Employee.Status.BUSY));
        Optional<String> opName = opEmployee.map((e) -> e.getName());
        System.out.println(opName.get());
    }
    @Test
    public void test8(){
        //flatMap(Function mapper) : 与 map 类似,要求返回值必须是 Optional
        Optional<Employee> opEmployee = Optional.ofNullable(new Employee("张山", 18, 8888.88, Employee.Status.BUSY));
        Optional<String> name = opEmployee.flatMap(e -> Optional.of(e.getName()));
        System.out.println(name.get());
    }

九、附件1

/**
 * @author : [Zara-cat]
 * @version : [v1.0]
 * @className : Employee
 * @description : [描述说明该类的功能]
 * @createTime : [2021/10/27 23:26]
 * @updateUser : [Zara-cat]
 * @updateTime : [2021/10/27 23:26]
 * @updateRemark : [描述说明本次修改内容]
 */
public class Employee {
    private String name;
    private int age;
    private double salary;
    private Status status;

    public Employee() {
    }
    public Employee(String name){
        this.name = name;
    }
    public Employee(String name,double money){
        this.name = name;
        this.salary = money;
    }

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(String name, int age, double salary, Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Double.compare(employee.salary, salary) == 0 && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                ", status=" + status +
                '}';
    }

    public enum Status{
            FREE,
            BUSY,
            VOCATION;
    }
}