作者:王甜甜(dabing)

视频: 宋红康 - 全网最全 Java 零基础入门教程

# Java8 新特性

简介:

  • 速度更快
  • 代码更少(增加了新的语法:Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn 引擎,允许在 JVM 上运行 js 应用

# 1 - Lambda 表达式

Lambda 表达式是一个匿名函数,Java8 允许把函数作为参数传递进方法中。

一个新的语;法要求。说白了就看到别人的这样用很好,也抄一抄借鉴借鉴

语法格式:📒

(parameters)->experssion 或
(parameters)->{statements;}

Lambda 实战:✍

/**
 * Lambda 表达式的使用
 * 1. 举例:(o1,o2)-> Integer.compare (o1,o2)
 * 2. 格式:
 *      -> :Lambda 操作符 或 箭头操作符
 *      左边:Lambda 形参列表 (其实就是接口种的抽象方法的形参列表)
 *      右边:Lambda 体 (其实就是重写的抽象方法的方法体)
 * 3.Lambda 表达式的使用  :分 6 种情况
 *   总结:
 *   -> 左边:Lambda 形参列表的参数类型可以省略(类型推断);如果 Lambda 形参列表只有一个参数,其 () 也可以省略
 *   -> 右边:Lambda 体应该使用一对 {} 包裹;如果 Lambda 体只有一条执行语句(可能时 return 语句),可以省略这一对 {} 和 return 关键字
 *
 * 4.Lambda 表达式的本质:作为函数式接口的实例对象
 */
Runnable r2 = () -> System.out.println("我爱java");// 无参,无返回
Consumer<String> con1=(String s)-> System.out.println(s);// 有参,无返回
Consumer<String> con1=(s)-> System.out.println(s);// 类型推断,可省略数据类型
Consumer<String> con1=s-> System.out.println(s);// 一个参数,省略 ()
Comparator<Integer>com1 =(o1,o2)->{
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };// 多条执行语句,用 {} 包裹
Comparator<Integer> com = (o1, o2) -> o1.compareTo(o2);// 一条语句,{} 和 return 均可省略

# 2 - Interface

新的 interface 接口方法可以用 defaultstatic 修饰,默认是 public abstract 修饰的,抽象方法(顺便提一嘴,变量是默认 public static final 修饰的)。

被上面的两个修饰符修饰的方法分别是普通方法和静态方法,他们可以拥有方法体,实现类不必重写此方法。

  1. default 修饰的方法,是普通实例方法,可以用 this 调用,可以被子类继承、重写。
  2. static 修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用 Interface 调用

如果有一个类实现了两个接口,而两个接口有相同的一个方法,则实现类必须重写该方法。不然的话,编译的时候就会报错。

另外,如果一个接口只有一个抽象方法,那这个接口被叫做函数式接口,函数式接口一般可以用 Lambda 表达式进行书写。✍

# 3 - functional interface 函数式接口

定义:也称为 SAM 接口,即 Single Abstract Method interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口。

在 java 8 中专门有一个包放函数式接口 java.util.function ,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有 @FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有该注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。

# 4 - 方法引用

使用情景:当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!

方法应用可以看作是 Lambda 表达式深层次的表达,换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个 实例 。通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖。

要求:实现接口的抽象方法的 参数列表返回值类型 ,必须与方法引用的方法的参数列表和返回值类型保持一致!(针对情况 1 和情况 2)

格式:使用操作符 :: 将类(或对象)与方法名分割开来。

如下三种主要使用情况:

1. 对象::实例方法名

2. 类::静态方法名

3. 类::实例方法名

如:

Comparator<Integer> comparator2 = Integer::compare;
//Comparator 的 int compare (Integer o1,Integer o2)
//Integer 的 int compare (Integer o1,Integer o2)

代码说明:

// 情况一:对象::实例方法
//Consumer 中的 void accept (T t);
//PrintStream 中的 void println (T t)
@Test
public void test1(){
    // 原 Lambda 写法
    Consumer<String> con1=str-> System.out.println(str);
    // 用方法引用 对象::实例方法
    PrintStream ps=System.out;
    Consumer<String> con2=ps::println;
}
// 情况二:类::静态方法
@Test
public void test2(){
    // 原 Lambda 写法
    Comparator<Integer> comparator1=(o1, o2)-> Integer.compare(o1,o2);
    // 用方法引用 类::静态方法
    Comparator<Integer> comparator2 = Integer::compare;
    // 传统写法
    Function<Double,Long> func1=new Function<Double, Long>() {
        @Override
        public Long apply(Double aDouble) {
            return Math.round(aDouble);
        }
    };
    //Lambda 原写法
    Function<Double,Long> func2=d-> Math.round(d);
    // 方法引用写法
    Function<Double,Long> func3=Math::round;
}
// 情况二:类::实例方法
//Comparator 中的 int compare (T t1,T t2);
//String 中的 int t1.compareTo (t2)
@Test
public void test3(){
    // 原 Lambda 写法
    Comparator<String> com1=(o1, o2)-> o1.compareTo(o2);
    // 用方法引用 类::实例方法
    Comparator<String> com2 = String::compareTo;
}

# 5 - 构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。

抽象方法的返回值里类型即为构造器所属的类的类型

代码说明:

// 构造器引用
    @Test
    public void test4(){
        // 传统写法
        Function<Integer, User> fun1=new Function<Integer, User>() {
            @Override
            public User apply(Integer integer) {
                return new User();
            }
        };
        // 原 Lambda 写法
        Function<Integer, User> fun2=i->new User(i);
        // 用方法引用 类::实例方法
        Function<Integer, User> fun3=User::new;
    }

# 6 - Stream

java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如 java.io.FileInputStream ,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何 CRUD

Stream 依然不存储数据,不同的是它可以检索 (Retrieve) 和逻辑处理 集合 数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。

它的源数据可以是 CollectionArray 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用

为什么要使用 Stream API?

实际开发中,项目中多数数据源都来自于 mysql、oracle 等,但现在数据源可以更多了,有 mongDB、redis 等,而这些 NoSql 的数据就需要 Java 层面去处理。这是就使用到了我们的 Stream 在 Java 层进行数据的处理。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

“集合讲的是数据,Stream 讲的是计算”

注意:

1.Stream 自己不会存储元素

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

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

体系图:

image-20230405214110053

# 流类型

1.stream 串行流

2.parallelStream 并行流,可多线程执行

Stream 的操作三个步骤:

1. 创建 Stream

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

2. 中间操作

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

3. 终止操作(终端操作

一旦执行终止操作, 就执行中间操作 (延迟执行),并产生结果。之后,不会再被使用。

image-20221020234129381

# 步骤一 创建 Stream

// 创建 Stream 方式一:通过集合
    @Test
    public void test1(){
        List<User> list = new ArrayList<>();
        list.add(new User(1));
        list.add(new User(2));
        list.add(new User(3));
        //default Stream<E> stream (): 返回一个顺序流
        Stream<User> stream = list.stream();
        //default Stream<E> parallelStream (): 返回一个并行流
        Stream<User> userStream = list.parallelStream();
    }
    // 创建 Stream 方式二:通过数组
    @Test
    public void test2(){
        int[] arr=new int[]{1,2,3,4,5,6};
        // 调用 Arrays 类的 static<T> Stream<T> stream (T [] array): 返回一个流
        IntStream stream = Arrays.stream(arr);
    }
    // 创建 Stream 方式三:通过 Stream 的 of ()
    @Test
    public void test3(){
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    }
    // 方式四:创建无限流,了解即可
    @Test
    public void test4(){
        // 迭代
        //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T>f)
        // 遍历前 10 个偶数
        Stream.iterate(0,t ->t+2).limit(10).forEach(System.out::println);
        // 生成
        //public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }

# 步骤二 中间操作

筛选与切片、映射、排序

  1. 中间操作:筛选与切片
方法 描述
filter(Predicate p) 用于通过设置的条件过滤出元素
distinct() 筛选,通过流所生成元系的 hashCode () 和 equals () 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定的数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回空流

代码示例:

// 数据
List<Employee>list new ArrayList<>();
1ist.add(new Emp1oyee(1001,"马化騰",34,6000.38);
list.add(new Employee(1002,"马云",12,9876.12);
list.add(new Employee(1003,"刘强东",33,388.82);
list.add(new Employee(1004,"雷军",26,7657.37)):
list.add(new Employee(1005,"李彦宏",65,5555.32));
list.add(new Employee(1006,"比尔盖茨",42,956.43));
list.add(new Employee(1007,"任正非",26,4333.32);
list.add(new Employee(1008,"扎克伯格",35,2508.32);
//1. 筛选与切片
// 创建流
Stream<Employee>stream =list.stream();
//filter--- 过滤数据,中间操作:查询员工表中薪资大于 7000 的员工信息
stream.filter(e->e.getsalary()>7000).forEach(System.out::println); // 循环输出为最终操作
         
//limit (n)---- 截断流,使其元素不超过给定的数量
// 这里注意要重新获取一次流,Stream 流只用一次就关闭了
list.stream().limit(3).forEach(System.out::println); // 前 3 条记录    
         
//skip (n)---- 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回空流
list.stream().skip(3).forEach(System.out::println); // 从第四条数据开始输出
         
//distinct ()--- 筛选,通过流所生成元系的 hashCode () 和 equals () 去除重复元素
1ist.add(new Employee(1009,"刘强东",40,8000));
1ist.add(new Employee(1009,"刘强东",40,8000));
list.stream().distinct().forEach(System.out::println);// 会去掉重复数据
  1. 中间操作:映射
方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素(以前学函数的时候就先学的映射嘛)
mapToDouble(ToDoubleFunction f) 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
mapToInt(ToIntFunction f) 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
mapToIong(ToLongFunction f) 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
flatMap(Function f) 接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

代码示例:

//2 - 映射
@Test
public void test2(){
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    //map (Function f)—— 接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
    // 练习 1:获取员工姓名长度大于 3 的员工的姓名。
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> nameStream = employees.stream().map(Employee::getName);
    nameStream.filter(name -> name.length() >3).forEach(System.out::println);
    System.out.println();
    // 练习 2:使用 map () 中间操作实现 flatMap () 中间操作方法
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream);
    streamStream.forEach(s ->{
        s.forEach(System.out::println);
    });
    System.out.println();
    //flatMap (Function f)—— 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::fromStringToStream);
    characterStream.forEach(System.out::println);
}
// 将字符串中的多个字符构成的集合转换为对应的 Stream 的实例
public static Stream<Character>fromStringToStream(String str){
    ArrayList<Character> list = new ArrayList<>();
    for (Character c :
         str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}
//map () 和 flatMap () 方法类似于 List 中的 add () 和 addAll () 方法
@Test
public void test(){
    ArrayList<Object> list1 = new ArrayList<>();
    list1.add(1);
    list1.add(2);
    list1.add(3);
    list1.add(4);
    ArrayList<Object> list2 = new ArrayList<>();
    list2.add(5);
    list2.add(6);
    list2.add(7);
    list2.add(8);
    list1.add(list2);
    System.out.println(list1);//[1, 2, 3, 4, [5, 6, 7, 8]]
    list1.addAll(list2);
    System.out.println(list1);//[1, 2, 3, 4, [5, 6, 7, 8], 5, 6, 7, 8]
}
  1. 中间操作:排序
方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

代码示例:

//3 - 排序
@Test
public void test3(){
    //sorted ()—— 自然排序
    List<Integer> list = Arrays.asList(12, 34, 54, 65, 32);
    list.stream().sorted().forEach(System.out::println);
    // 抛异常,原因:Employee 没有实现 Comparable 接口
    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted().forEach(System.out::println);
    //sorted (Comparator com)—— 定制排序
    List<Employee> employees1 = EmployeeData.getEmployees();
    employees1.stream().sorted((e1,e2)->{
        int ageValue = Integer.compare(e1.getAge(), e2.getAge());
        if (ageValue != 0){
            return ageValue;
        }else {
            return -Double.compare(e1.getSalary(),e2.getSalary());
        }
    }).forEach(System.out::println);
}

# 步骤三 终止操作

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

流进行了终止操作之后,不能再次使用

匹配与查找、归约、收集

  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 使用内部迭代 —— 它帮你把迭代做了)

代码示例:

//1 - 匹配与查找
@Test
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    //allMatch (Predicate p)—— 检查是否匹配所有元素。
    // 练习:是否所有的员工的年龄都大于 18
    boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println(allMatch);
    //anyMatch (Predicate p)—— 检查是否至少匹配一个元素。
    // 练习:是否存在员工的工资大于 5000
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 5000);
    System.out.println(anyMatch);
    //noneMatch (Predicate p)—— 检查是否没有匹配的元素。
    // 练习:是否存在员工姓 “雷”
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println(noneMatch);
    //findFirst—— 返回第一个元素
    Optional<Employee> first = employees.stream().findFirst();
    System.out.println(first);
    //findAny—— 返回当前流中的任意元素
    Optional<Employee> employee = employees.parallelStream().findAny();
    System.out.println(employee);
}
@Test
public void test2(){
    List<Employee> employees = EmployeeData.getEmployees();
    //count—— 返回流中元素的总个数
    long count = employees.stream().filter(e -> e.getSalary()>5000).count();
    System.out.println(count);
    //max (Comparator c)—— 返回流中最大值
    // 练习:返回最高的工资
    Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
    Optional<Double> maxSalary = salaryStream.max(Double::compareTo);
    System.out.println(maxSalary);
    //min (Comparator c)—— 返回流中最小值
    // 练习:返回最低工资的员工
    Optional<Double> minSalary = employees.stream().map(e -> e.getSalary()).min(Double::compareTo);
    System.out.println(minSalary);
    //forEach (Consumer c)—— 内部迭代
    employees.stream().forEach(System.out::println);
    System.out.println();
    // 使用集合的遍历操作
    employees.forEach(System.out::println);
}
  1. 终止操作:归约
方法 描述
reduce(T iden,BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional

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

代码示例:

//2 - 归约
@Test
public void test3(){
    //reduce (T identity, BinaryOperator)—— 可以将流中元素反复结合起来,得到一个值。返回 T
    // 练习 1:计算 1-10 的自然数的和
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = list.stream().reduce(0, Integer::sum);
    System.out.println(sum);
    //reduce (BinaryOperator) —— 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
    // 练习 2:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Double> sumSalary = employees.stream().map(e -> e.getSalary()).reduce(Double::sum);
    System.out.println(sumSalary);
}
  1. 终止操作:收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 ListSetMap

Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例具体方法与实例如下表:

image-20221020234242661

image-20221020234301928

代码示例:

//3 - 收集
@Test
public void test4(){
    //collect (Collector c)—— 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法
    // 练习 1:查找工资大于 6000 的员工,结果返回为一个 List 或 Set
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
    employeeList.forEach(System.out::println);
    System.out.println();
    Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
    employeeSet.forEach(System.out::println);
}

# 常用方法

接下来我们看 java.util.stream.Stream 常用方法 🤌

上面的例子基本都有展示的啦~~~

image-20221020233658581

# 小结

从源码和实例中我们可以总结出一些 stream 的特点

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
  2. 方法参数都是函数式接口类型
  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
  4. Stream 不保存数据,不改变数据源

# 7 - Optional

阿里巴巴开发手册关于 Optional 的介绍中这样写到

防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE

反例:public int f (){return Integer 对象},如果为 null,自动拆箱会抛 NPE

2)数据库的查询结果可能为 null

3)集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null

4)远程调用返回对象时,一律要求进行空指针判断,防止 NPE

5)对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针

6)级联调用 obj.getA ().getB ().getC ();一连串调用,易产生 NPE

正例:使用 JDK8 的 Optional 类来防止 NPE 问题

Optional 在包 java.util.Optional 内,就是为防止 NPE 而生,其中可以包含空值或非空值。

补充一下:

什么是 NPE?

NPE(java.lang.NullPointerException) : 空指针异常。

怎么减少 NPE 的发生?

1. 遵守代码规范

2. 使用 Optional 类 😃

3. 空值检测

# 一个例子

假设有一个 Zoo 类,里面有个属性 Dog,需求要获取 Dog 的 age。

class Zoo {
   private Dog dog;
}
class Dog {
   private int age;
}

传统防止 NPE 的方式:

Zoo zoo = getZoo();
if(zoo != null){
   Dog dog = zoo.getDog();
   if(dog != null){
      int age = dog.getAge();
      System.out.println(age);
   }
}

其实传统方式虽然一层一层的判空,代码多,但是挺直观的。

Optional 是这样的实现的:

Optional.ofNullable(zoo).map(o->o.getDog()).map(d->d.getAge()).ifPresent(age->
      System.out.println(age)                                                                      
);// 对应着下面的方法说明很容易看懂的啦~~

# Optional 类的方法

上例的 Optional.ofNullable 只是其中的一种创建 Optional 的方法。

它的结构图如下:

image-20221020233544717

  1. 创建 Optional 类对象

Optional.of(T t) : 创建一个 Optional 实例,t 必须非空;

Optional.empty() : 创建一个空的 Optional 实例

Optional.ofNullable(T t): t 可以为 null

  1. 判断 Optional 容器是否包含对象

boolean isPresent(): 判断是否包含对象

void ifPresent(Consumer<? super T> consumer): 如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。

  1. 获取 Optional 容器里装的对象

T get():如果调用对象包含值,返回该值,否则抛异常

T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象

T orElseGet(Supplier<? extends t> other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。

T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常。

  1. 搭配使用

of()get() 方法搭配使用,明确对象非空

ofNullable()orElse() 搭配使用,不确定对象非空

# 代码示例:

public class OptionalTest {
    @Test
    public void test1() {
        //empty (): 创建的 Optional 对象内部的 value = null
        Optional<Object> op1 = Optional.empty();
        if (!op1.isPresent()){//Optional 封装的数据是否包含数据
            System.out.println("数据为空");
        }
        System.out.println(op1);
        System.out.println(op1.isPresent());
        // 如果 Optional 封装的数据 value 为空,则 get () 报错。否则,value 不为空时,返回 value.
        System.out.println(op1.get());
    }
    @Test
    public void test2(){
        String str = "hello";
//        str = null;
        //of (T t): 封装数据 t 生成 Optional 对象。要求 t 非空,否则报错。
        Optional<String> op1 = Optional.of(str);
        //get () 通常与 of () 方法搭配使用。用于获取内部的封装的数据 value
        String str1 = op1.get();
        System.out.println(str1);
    }
    @Test
    public void test3(){
        String str ="Beijing";
        str = null;
        //ofNullable (T t) :封装数据 t 赋给 Optional 内部的 value。不要求 t 非空
        Optional<String> op1 = Optional.ofNullable(str);
        System.out.println(op1);
        //orElse (T t1): 如果 Optional 内部的 value 非空,则返回此 value 值。如果
        //value 为空,则返回 t1.
        String str2 = op1.orElse("shanghai");
        System.out.println(str2);
    }
}

使用 Optional 类避免产生空指针异常

public class GirlBoyOptionalTest {
    // 使用原始方法进行非空检验
    public String getGrilName1(Boy boy){
        if (boy != null){
            Girl girl = boy.getGirl();
            if (girl != null){
                return girl.getName();
            }
        }
        return null;
    }
    // 使用 Optional 类的 getGirlName () 进行非空检验
    public String getGirlName2(Boy boy){
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        // 此时的 boy1 一定非空,boy 为空是返回 “迪丽热巴”
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
        Girl girl = boy1.getGirl();
        //girl1 一定非空,girl 为空时返回 “古力娜扎”
        Optional<Girl> girlOptional = Optional.ofNullable(girl);
        Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
        return girl1.getName();
    }
    // 测试手动写的控制检测
    @Test
    public void test1(){
        Boy boy = null;
        System.out.println(getGrilName1(boy));
        boy = new Boy();
        System.out.println(getGrilName1(boy));
        boy = new Boy(new Girl("杨幂"));
        System.out.println(getGrilName1(boy));
    }
    // 测试用 Optional 类写的控制检测
    @Test
    public void test2(){
        Boy boy = null;
        System.out.println(getGirlName2(boy));
        boy = new Boy();
        System.out.println(getGirlName2(boy));
        boy = new Boy(new Girl("杨幂"));
        System.out.println(getGirlName2(boy));
    }
}

# 8 - Date-Time API

Java8 在 java.time 包下包含一个全新的日期和时间 API,这是对 java.util.Date 的强有力补充,解决了 Date 类的大部分痛点:

1. 非线程安全

2. 时区处理麻烦

3. 各种格式化、时间计算繁琐

4. 设计有缺陷,Date 类同时包括日期和时间;还有一个 java.sql.Date,容易混淆

Clock、ZoneId、LocalTime、LocalDate、

java.time 常用的类:而在旧版本中是 java.util.Date 既包含日期又包含时间

ocalDateTime.class // 日期 + 时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class // 日期 format: yyyy-MM-dd
LocalTime.class // 时间 format: HH:mm:ss

# LocalTime (本地时间)

LocalTime 定义了一个没有时区信息的时间,例如 晚上 10 点或者 17:30:15

结构图

image-20221020233419554

# LocalDate (本地日期)

LocalDate 表示了一个确切的日期,比如 2022-04-18。该对象值是不可变的,用起来和 LocalTime 基本一致。

结构图:没有显示继承依赖等信息

image-20221020233438253

例子展示:如何给 Date 对象加减天 / 月 / 年。另外注意这些对象是不可变得,操作返回的总是给一个新实例。

LocalDate today=LocalDate.now();// 获取现在的日期
System.out.println("今天的日期:"+today);//2022-04-18
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);// 明天的日期
System.out.println("明天的日期:"+tomorrow);//2022-04-19
LocalDate yersterday1 = today.minusDays(1);
LocalDate yersterday2 = today.minus(1,ChronoUnit.DAYS);
System.out.println("昨天的日期1:"+yersterday1);
System.out.println("昨天的日期2:"+yersterday2);
// 有很多种实现方式
LocalDate oneDay = LocalDate.of(2022, Month.JANUARY, 1);//2022-01-01
DayOfWeek dayOfWeek=oneDay.getDayOfWeek();// 获取那天是周几,有很多方法
System.out.println("2022-01-01是周几:"+dayOfWeek);

# LocalDateTime (本地日期时间)

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime 还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek1 = sylvester.getDayOfWeek();
System.out.println(dayOfWeek1);      // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439  59h*60+59=1439min

# 格式化

格式化 LocalDateTime 和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

DateTimeFormatter formatter =DateTimeFormatter.ofPattern("MM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("09 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // 09 03, 2014 - 07:13

# 解析字符串

1. 字符串 —> 时间日期 parse

2. 时间日期 —> 字符串 format

如果是默认格式,不需要用到 DateTimeFormatter 直接 parse 即可,

非默认格式,使用 DateTimeFormatter 解析字符串:

String str1="2022==04==18 01时06分09秒";
DateTimeFormatter formatter1=DateTimeFormatter.ofPattern("yyyy==MM==dd HH时mm分ss秒");
LocalDateTime dateTime1 = LocalDateTime.parse(str1, formatter1);
System.out.println(dateTime1);// 输出  2022-04-18T01:06:09
String str2 = "2014$$$四月$$$13 20小时";
DateTimeFormatter fomatter2 = DateTimeFormatter
        .ofPattern("yyy$$$MMM$$$dd HH小时");  // 这里还不太明白
LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);
System.out.println(dt2); // 输出 2014-04-13T20:00
// 默认的格式解析不用 famtter,直接 parse 即可
LocalDate.parse("2021-01-26");
LocalDateTime.parse("2021-01-26T12:12:22");
LocalTime.parse("12:12:22");
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Dabing-He 微信支付

微信支付