作者:王甜甜(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 接口方法可以用 default
或 static
修饰,默认是 public abstract 修饰的,抽象方法(顺便提一嘴,变量是默认 public static final 修饰的)。
被上面的两个修饰符修饰的方法分别是普通方法和静态方法,他们可以拥有方法体,实现类不必重写此方法。
default
修饰的方法,是普通实例方法,可以用 this 调用,可以被子类继承、重写。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 语句。
它的源数据可以是 Collection
、 Array
等。由于它的方法参数都是函数式接口类型,所以一般和 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 操作时延迟执行的,这意味着他们会等到需要结果的时候才执行
体系图:
# 流类型
1.stream 串行流
2.parallelStream 并行流,可多线程执行
Stream 的操作三个步骤:
1. 创建 Stream
一个数据源(如:集合、数组),获取一个流
2. 中间操作
一个中间操作,对数据源的数据进行处理
3. 终止操作(终端操作
一旦执行终止操作, 就执行中间操作
(延迟执行),并产生结果。之后,不会再被使用。
# 步骤一 创建 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); | |
} |
# 步骤二 中间操作
筛选与切片、映射、排序
- 中间操作:筛选与切片
方法 | 描述 |
---|---|
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);// 会去掉重复数据 |
- 中间操作:映射
方法 | 描述 |
---|---|
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] | |
} |
- 中间操作:排序
方法 | 描述 |
---|---|
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); | |
} |
# 步骤三 终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List
、 Integer
,甚至是 void
流进行了终止操作之后,不能再次使用
匹配与查找、归约、收集
- 终止操作:匹配与查找
方法 | 描述 |
---|---|
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); | |
} |
- 终止操作:归约
方法 | 描述 |
---|---|
reduce(T iden,BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
map
和 reduce
的连接通常称为 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); | |
} |
- 终止操作:收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法 |
Collector
接口中方法的实现决定了如何对流执行收集的操作(如收集到 List
、 Set
、 Map
)
Collectors
实用类提供了很多静态方法,可以方便地创建常见收集器实例具体方法与实例如下表:
代码示例:
//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
常用方法 🤌
上面的例子基本都有展示的啦~~~
# 小结
从源码和实例中我们可以总结出一些 stream 的特点
- 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
- 方法参数都是函数式接口类型
- 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
- 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 的方法。
它的结构图如下:
- 创建 Optional 类对象
Optional.of(T t) : 创建一个 Optional 实例,t 必须非空;
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t): t 可以为 null
- 判断 Optional 容器是否包含对象
boolean isPresent(): 判断是否包含对象
void ifPresent(Consumer<? super T> consumer): 如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。
- 获取 Optional 容器里装的对象
T get():如果调用对象包含值,返回该值,否则抛异常
T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象
T orElseGet(Supplier<? extends t> other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。
T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常。
- 搭配使用
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
结构图:
# LocalDate (本地日期)
LocalDate 表示了一个确切的日期,比如 2022-04-18。该对象值是不可变的,用起来和 LocalTime 基本一致。
结构图:没有显示继承依赖等信息
例子展示:如何给 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"); |