jdk8新特性

一、Lambda表达式

1 问题分析

当我们需要开辟一条线程执行语句时

1
2
3
4
5
6
7
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程执行:"+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程执行:"+Thread.currentThread().getName());

代码分析:

  1. 当我们想要创建一个线程时需要实现一个Runnable接口
  2. 为了简化Runnable的实现需要写一个匿名内部类
  3. 而实现这个接口需要重写一个run方法,而且需要保证其返回值、类名和参数都要按照要求
  4. 只有在这个方法体中才能写我们最终需要执行的代码
  5. 然而我们最终的目的只是想要开辟一条线程执行一条语句而已

那我如何对这段代码进行简化呢?

2 初体验

为了简化匿名内部类的写法,我们可以使用Lambda表达式

1
2
3
new Thread(() -> {
System.out.println("新线程执行:"+Thread.currentThread().getName());
}).start();

Lambda表达式优点:解决了匿名内部类的大量代码冗余,不在拘束于其中的条条框框,极大程度上简化和美化了代码

为了更好的理解,我们可以把lambda表达式理解为一段可以传递的代码。当然带来的缺点就是可读性变差了

3 语法规则

1
2
3
(参数类型 参数名) -> {
代码块
}

结构解析:

  • (参数类型 参数名):里面存放参数列表
  • {代码块}:里面是需要执行的方法
  • ->:用来分隔参数列表以及代码块

3.1 练习1(无参无返)

定义一个接口:

1
2
3
public interface UserService {
public void show();
}

然后创建一个主方法使用:

1
2
3
4
5
6
7
8
9
10
11
public class LambdaDemo02 {
public static void main(String[] args) {
getShow(()->{
System.out.println("Lambda表达式被执行");
});
}

public static void getShow(UserService userService){
userService.show();
}
}

输出:

1
Lambda表达式被执行

3.2 练习2(有参有返)

定义一个类:

1
2
3
4
5
public class User {
private Integer id;
private String username;
private String password;
}

实现一个根据uid逆序排的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
List<User> list = new ArrayList<>();

list.add(new User(1,"zhangsan","123"));
list.add(new User(2,"lisi","123"));
list.add(new User(3,"wanhwu","123"));
list.add(new User(4,"zhaoliu","123"));

Collections.sort(list,(User o1,User o2) -> {
return o2.getId() - o1.getId();
});

for (User user : list) {
System.out.println(user);
}
}

输出:

1
2
3
4
User{id=4, username='zhaoliu', password='123'}
User{id=3, username='wanhwu', password='123'}
User{id=2, username='lisi', password='123'}
User{id=1, username='zhangsan', password='123'}

3.3 注意点(重要)

Lambda使用的接口必须只能有一个抽象方法,如果有多个时会报错,可以使用@FunctionalInterface注解来规定该接口只能有一个抽象方法

4 原理分析

匿名内部类在运行的过程中会生成一个class文件

Lambda表达式在程序运行时会形成一个类

  1. 在类中新建一个方法,这个方法的方法体就是lambda里面的内容
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法
  3. 在接口中重写方法会调用新生成的方法

5 省略写法

在原有的语法基础上,还可以进一步的进行省略

  1. 参数的类型可以省略
  2. 当有且仅有一个参数时,括号可以省略
  3. 当函数体中有且仅有一条语句时,可以省略return、花括号以及分号

最简写法:

1
2
3
public static void main(String[] args) {
getShow(id -> System.out.println("zhangsan"));
}

6 使用前提

Lambda表达式的语法非常的简洁,但是他的使用要求非常高,需要满足以下条件才能使用

  1. 方法的参数或局部变量必须是抽象接口才能使用
  2. 接口类必须只有一个抽象方法式才可以使用

7 与匿名内部类的对比

Lambda与匿名内部类的对比

  1. 所需的类型不一样
    • 匿名内部类的类型可以是 类,抽象类,接口
    • Lambda表达式需要的类型必须是接口
  2. 抽象方法的数量不一样
    • 匿名内部类的抽象方法数量不做要求,可以无数个
    • Lambda表达式所需的接口之能有一个抽象方法
  3. 实现原理不一样
    • 匿名内部类是在编译后形成一个class
    • Lambda表达式是在程序运行的时候动态生成class

二、接口中新增的方法

1 jdk 8中接口新增

jdk8在接口方面又新增了一些方法,在jdk8之前

1
2
3
4
interface 接口名 {
静态常量;
抽象方法;
}

在jdk8之后,新增了默认方法和静态方法

1
2
3
4
5
6
interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法;
}

2 默认方法

2.1 为什么要新增默认方法

新建一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InterfaceDemo01 {
public static void main(String[] args) {
A b = new B();
A c = new C();
}
}

interface A {
void test();
}

class B implements A{
@Override
public void test() {}
}

class C implements A{
@Override
public void test() {}
}

我们可以看到两个类B,C都继承了A接口并实现了抽象方法,可是当我们增加A的抽象方法时

B和C都会报错,那是因为B和C还没有实现A新增的抽象方法。于是我们便发现一个问题,当A被多个类继承时,其实就不利于A接口的扩展了,比如Map接口就多达164个实现类

image-20211209192552142

这个时候默认方法就可以很好的解决这个问题

2.2 语法格式

接口中默认方法的语法格式:

1
2
3
4
5
interface 接口名 {
修饰符 default 返回值类型 接口名 {
方法体;
}
}

2.3 默认方法的特点

  1. 默认方法不是抽象方法
  2. 子类也能调用该方法
  3. 当然也可以对他进行重写
  4. 最终要的是方便了接口功能的扩展

3 静态方法

静态方法的作用与默认方法一样都是为了接口的扩展

3.1 语法格式

接口中使用静态方法的语法格式:

1
2
3
4
5
interface 接口名{
修饰符 static 返回值类型 方法名(){
代码块;
}
}

3.2 静态方法的特点

  1. 静态方法不是抽象方法
  2. 子类不能调用该方法
  3. 子类不能重写该方法
  4. 静态方法只能通过 接口名.静态方法名() 进行调用

默认方法和静态方法的区别很明显,但他们的目的却又都一样,只有灵活搭配才能发挥其特性

三、函数式接口

​ 在JDK中帮我们提供的函数式接口,一般都在Java.util.function 包中

1 由来

​ 我们知道使用Lambda表达式的前提需要有函数式接口,而Lambda并不在乎抽象方法名和接口名,只在乎他的参数列表和返回值,而JDK专门为其提供了Lambda表达式可以直接使用的接口来丰富其功能

2 介绍

2.1 Supplier

Supplier函数接口:无参有返 ,用来生产数据

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {

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

使用:计算一到九的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SupplierTest {
public static void main(String[] args) {
test(() ->{
int[] arr = {1,2,3,4,5,6,7,8,9};
int max = 0;
for (int i : arr) {
max += i;
}
return max;
});
}

public static void test(Supplier<Integer> supplier){
int max = supplier.get();
System.out.println(max);
}
}

2.2 Consurmer

Consumer函数接口:有参无返回值 故名思意是用来消耗数据的

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);

default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

使用:将传入的字符串转换成大写

1
2
3
4
5
6
7
8
9
10
11
public class ConsumerTest {
public static void main(String[] args) {
test((msg) -> {
System.out.println(msg + "转化成为大写————>"+msg.toUpperCase(Locale.ROOT));
});
}

public static void test(Consumer<String> consumer){
consumer.accept("Hello world");
}
}

2.3 function

有参有返,不做描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

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

static <T> Function<T, T> identity() {
return t -> t;
}
}

2.4 Predicate

有参返回值为Boolean类型,一般用于判断(在下面的stream流中条件语句类型多为这个)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

default Predicate<T> negate() {
return (t) -> !test(t);
}

default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

四、方法引用

1 什么是方法引用

方法引用可以看成Lambda表达式的深层次的表达。可以说方法引用就是Lambda表达式,但我认为是Lambda表达式的另外一种写法,而他最大的作用其实还为了进一步的解决Lambda表达式的缺陷,减少Lambda表达式的代码冗余

观察使用方法引用和不使用方法引用的差别:

1.不使用方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
int arr[] = {5,1,21,53,11};
Function<int[],Integer> function = (arr1) -> {
int max = 0;
for (int i : arr) {
if(max < i){
max = i;
}
}
return max;
};
function.apply(arr);
}

2.使用方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
Function<int[],Integer> function = Test::printMax;
System.out.println(function.apply(arr));
}

public static int printMax(int[] arr){
int max = 0;
for (int i : arr) {
if(max < i){
max = i;
}
}
return max;
}

通过以上两段代码我们可以发现:

  1. 函数接口的方法方法实现被抽离出来,而不在是以内部类的形似去实现了
  2. 而抽离出来有一个最大的好处就是实现了代码的复用,及该代码块不仅仅作用在抽象方法的实现
  3. 当然也可以直接使用实现了该功能的方法,近一步减少了Lambda表达式的冗余

2 使用要求

实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的 方法的参数列表和返回值类型保持一致!(方法名可以不同),并且引用的方法必须存在

3 语法格式

符号表示:::

符号说明:双冒号为方法引用运算符,而他所在的表达式被称为方法引用表达式

使用场景:

  • 当Lambda表达式想要实现的功能,在其他方法中已经实现时,我们可以去使用方法引用
  • 当Lambda表达式实现的功能需要使用到多个地方时,把该功能抽离出方法实现代码的复用

常见的引用方式如下

3.1 对象名::方法名

3.2 类名::静态方法名

3.3 类名::引用实例方法名

3.4 类名::构造方法

3.5 数组::构造器

五、Stream API

1 集合处理的弊端

当我们对集合中的元素进行操作时,除了必须的添加,删除,获取外,最典型的操作就是集合的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
List<String> list = Arrays.asList("张三","李四","王五","张二狗");
List<String> list1 = new ArrayList<>();
for (String s : list) {
//找出姓张和名字长度为2的姓名
if(s.startsWith("张") && s.length() == 2){
list1.add(s);
}
}
for (String s : list1) {
System.out.println(s);
}
}

我们可以看到,其实对集合中的元素筛选其实很不方便,而stream给我们带来了简便的写法

1
2
3
4
5
6
public static void main(String[] args) {
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 2)
.forEach(System.out::println);
}

实现功能:过滤留下了张性,两个字的名字并且遍历打印

2 Stream流式思想

我们要注意将Stream流于IO流区别出来,Stream流不等于IO流,所以不能使用IO流的特性去理解Stream流,Stream流有点类似于工厂的流水线,集合中的元素会像商品一样经过一次一次又一次的过滤和筛选,留下最终符合要求的元素

stream图

3 获取方法

3.1 通过Collection获取

在Collection中实现了这个默认方法

1
2
3
4
5
6
7
8
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Vector<String> vector = new Vector<>();
list.stream();
set.stream();
vector.stream();
}

map集合的获取方式:

1
2
3
4
5
6
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.keySet().stream();
map.values().stream();
map.entrySet().stream();
}

3.2 通过Stream.of 获取

1
2
3
4
5
6
public static void main(String[] args) {
Integer[] arr = {1,2,3,4,5};
Stream.of(arr)
.filter(val -> val < 4)
.forEach(System.out::println);
}

注意: 使用的数组必须是包装类型,基础类型的数组会被当成一个数据,无法遍历(以下是基础类型数组)

1
2
3
4
5
public static void main(String[] args) {
int[] arr2 = {1,2,3,4,5};//基本类型的数组
Stream.of(arr2)
.forEach(System.out::println);
}

输出:

1
[I@3b9a45b3

4 常用方法

Stream有许多可以使用的方法,下面只介绍一些常用的API。这些方法通常可以分为两类:

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 遍历元素 void 终结
filter 过滤 Stream 非终结
limit 取前面n个元素 Stream 非终结
skip 跳过前面n个元素 Stream 非终结
map 对数据类型转换 Stream 非终结
concat 将两个流合并 Stream 非终结

终结方法:返回值不在是Stream类型,不在支持链式调用,一般用在调用链的结尾

非终结方法:返回值仍是Stream类型,支持链式调用。(除了终结方法,其他均为非终结方法)

需要注意:

  1. Stream只能调用一次
  2. Stream方法返回的是新的流
  3. Stream执行的过程中不可逆
  4. 如果Stream的调用链结尾中没有终止方法,那么整条调用链都不会执行(重点)

综合案例:

案例

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StreamTest03 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("孙小美", "阿土伯", "小明", "钱夫人", "小红", "小菜","李傻子");
List<String> list2 = Arrays.asList("王熙凤","王五","张柳","李三麻","张二狗","张天爱","张麻子");

Stream<String> stream1 = list1.stream()
.filter(s -> s.length() == 3)
.limit(3);
Stream<String> stream2 = list2.stream()
.filter(s -> s.startsWith("张"))
.skip(2);
Stream.concat(stream1, stream2)
.map(User::new)
.forEach(System.out::println);
}
}

执行结果:

1
2
3
4
5
User{id=null, username='孙小美', password='null'}
User{id=null, username='阿土伯', password='null'}
User{id=null, username='钱夫人', password='null'}
User{id=null, username='张天爱', password='null'}
User{id=null, username='张麻子', password='null'}

5 结果集的收集

5.1 结果收集到集合中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test01(){
List<String> list = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toList());
System.out.println(list);
Set<String> set = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toSet());
System.out.println(set);

//当需要指定具体的实现类型时
ArrayList<String> list1 = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(list1);
HashSet<String> set1 = Stream.of("aa", "bb", "cc", "aa")
.collect(Collectors.toCollection(HashSet::new));
System.out.println(set1);
}

输出:

1
2
3
4
[aa, bb, cc, aa]
[aa, bb, cc]
[aa, bb, cc, aa]
[aa, bb, cc]

5.2 结果收集到数组中

toArray方法可以将结果收集到Object集合中,如果传参还可以确定数组的类型

1
2
3
4
5
6
7
8
9
10
@Test
public void test02(){
Object[] objects = Stream.of("aa", "bb", "cc", "aa")
.toArray();
System.out.println(Arrays.toString(objects));
//当需要指定具体的放回值时
String[] strings = Stream.of("aa", "bb", "cc", "aa")
.toArray(String[]::new);
System.out.println(Arrays.toString(strings));
}

输出

1
2
[aa, bb, cc, aa]
[aa, bb, cc, aa]

5.3 聚合运算

跟数据库里的聚合运算一样,Stream流也提供与其一样的功能,更加方便的操作某个字段实现求最大值,最小值,和,平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Test
public void test03(){
//求最大值
Optional<User> max = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println(max);

//求最小值
Optional<User> min = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println(min);

//求和
Integer sum = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.summingInt(User::getAge));
System.out.println(sum);

//求平均值
Double avg = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.averagingInt(User::getAge));
System.out.println(avg);

//计数
Long count = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.counting());
System.out.println(count);
}

输出:

1
2
3
4
5
Optional[User{name=lisi, age=23}]
Optional[User{name=zhaoliu, age=11}]
69
17.25
4

5.4 分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 实现分组功能
*/
@Test
public void test04(){
Map<String, List<User>> collect = Stream.of(
new User("zhangsan", 17)
, new User("lisi", 23)
, new User("zhangsan", 21)
, new User("lisi", 16)
, new User("zhangsan", 22)
, new User("lisi", 13)
).collect(Collectors.groupingBy(user -> user.getAge() <= 18 ? "未成年" : "成年"));
collect.forEach((k,v) ->{
System.out.println(k + "=" +v);
});
}

输出

1
2
未成年=[User{name=zhangsan, age=17}, User{name=lisi, age=16}, User{name=lisi, age=13}]
成年=[User{name=lisi, age=23}, User{name=zhangsan, age=21}, User{name=zhangsan, age=22}]

多层分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现多层分组
*/
@Test
public void test05(){
Map<String, Map<String, List<User>>> collect = Stream.of(
new User("zhangsan", 17)
, new User("lisi", 23)
, new User("zhangsan", 21)
, new User("lisi", 16)
, new User("zhangsan", 22)
, new User("lisi", 13)
).collect(Collectors.groupingBy(User::getUsername, Collectors.groupingBy(user -> user.getAge() >= 18 ? "成年" : "未成年")));

collect.forEach((k,v)->{
System.out.println(k);
v.forEach((k1,v1)->{
System.out.println("\t"+k1+"="+v1);
});
});
}

输出

1
2
3
4
5
6
lisi
未成年=[User{name=lisi, age=16}, User{name=lisi, age=13}]
成年=[User{name=lisi, age=23}]
zhangsan
未成年=[User{name=zhangsan, age=17}]
成年=[User{name=zhangsan, age=21}, User{name=zhangsan, age=22}]

5.5 数据分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 将数据按false和true进行分区
*/
@Test
public void test06(){
Map<Boolean, List<User>> collect = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).collect(Collectors.partitioningBy(user -> user.getAge() >= 18));//根据这个判断分区
collect.forEach((k,v)->{
System.out.println(k + "=" + v);
});
}

输出

1
2
false=[User{name=wangwu, age=17}, User{name=zhaoliu, age=11}]
true=[User{name=zhangsan, age=18}, User{name=lisi, age=23}]

5.6 数据拼接

Collectors.joining()可以指定分割符将所有数据拼接成一个字符串

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test07(){
String collect = Stream.of(
new User("zhangsan", 18)
, new User("lisi", 23)
, new User("wangwu", 17)
, new User("zhaoliu", 11)
).map(User::getUsername)
.collect(Collectors.joining("_","开始:"," 结束"));
System.out.println(collect);
}

输出

1
开始:zhangsan_lisi_wangwu_zhaoliu 结束

6 并行流

6.1 串行的Stream流

我们前面使用的都是串行流,也就是在一条线程上执行的。

6.2 并行的Stream流

parallel Stream其实就是一个并行执行的流,她通过默认的ForkjoinRool,可以提高多线程任务的效率

6.2.1 并行流的两种获取方法
  1. 通过List接口的parallelStream方法获取并行流

  2. 通过parallel方法将串行流转化成并行流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test08(){
    //通过list接口
    List<Integer> list = new ArrayList<>();
    Stream<Integer> integerStream = list.parallelStream();

    //将串行流改成并行流
    long count = Stream.of("1", "2", "3").parallel().count();
    }
6.2.1 并行流的执行
1
2
3
4
5
6
7
8
9
10
@Test
public void test09() {
Stream.of("1","2","3","4","5","6","7","8","9")
.parallel()
.filter(s ->{
System.out.println(Thread.currentThread() + "s=" +s );
return Integer.valueOf(s) > 3;
})
.count();
}

输出

1
2
3
4
5
6
7
8
9
Thread[ForkJoinPool.commonPool-worker-15,5,main]s=4
Thread[ForkJoinPool.commonPool-worker-8,5,main]s=5
Thread[main,5,main]s=6
Thread[ForkJoinPool.commonPool-worker-11,5,main]s=2
Thread[ForkJoinPool.commonPool-worker-6,5,main]s=1
Thread[ForkJoinPool.commonPool-worker-13,5,main]s=9
Thread[ForkJoinPool.commonPool-worker-9,5,main]s=3
Thread[ForkJoinPool.commonPool-worker-4,5,main]s=7
Thread[ForkJoinPool.commonPool-worker-2,5,main]s=8

我们可以看到数据的输出并不是按顺序的,说明所用元素的过滤都是同时进行的,这样速度会快一些

六、Optional类

1 常规对null值的处理

1
2
3
4
5
6
String s = null;
if(s != null){
System.out.println("字符串的长度为:"+s.length());
}else{
System.out.println("该字符串为null");
}

在常规的开发过程中经常需要对各种各样的变量做非空判断

2 Optional

Optional是一个工具类,是jdk8中的新特性。它是一个值可以为NULL的容器对象,其主要目的是避免null检查,防止NullPointerException

3 Optional对象的基本使用

3.1 创建的三种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Optional对象的创建方式
*/
@Test
public void test02(){
//第一种 用of方法创建 但不能添加null值
Optional<String> op1 = Optional.of("zhangsan");
//Optional<Object> op2 = Optional.of(null);

//第二种 用ofNullable 可以添加null值
Optional<String> op3 = Optional.ofNullable("lisi");
Optional<Object> op4 = Optional.ofNullable(null);

//第三种 用empty 直接创建一个空的容器对象
Optional<Object> op5 = Optional.empty();
}

七、新时间日期API

1 旧版本的缺陷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 旧版本的时间日期缺陷
*/
@Test
public void test01(){
//1. 设计不合理
Date date = new Date(2021,12,11);
System.out.println(date);

SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sf.format(date));

//不是线程安全的
new Thread(()->{
try {
System.out.println(sf.parse("2021-12-11"));
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
  1. 设计不合理,在Java.util和Java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而Java.sql.Date仅仅包含日期,此外用于格式化和解析的类在Java.text包下。
  2. 非线程安全,java.util.Date是飞线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
  3. 时区处理麻烦,日期类并不提供国际化,没有时区支持。

2 新版本的时间日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* jdk8日期操作
*/
@Test
public void test2(){
//获取指定日期的时间对象
LocalDate date = LocalDate.of(2022,1,1);
System.out.println(date);

//获取当前日期
LocalDate now = LocalDate.now();
System.out.println(now);

//根据LocalDate获取指定的日期数据
System.out.println(now.getYear());
System.out.println(now.getMonth().getValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getDayOfWeek().getValue());
}

/**
* 时间对象
*/
@Test
public void test3(){
//获取指定时间的时间对象
LocalTime localTime = LocalTime.of(12,01,01,4315);
System.out.println(localTime);

//获取当前时间
LocalTime now = LocalTime.now();
System.out.println(now);

//根据LocalTime获取指定的时间数据
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
}

/**
* 时间日期对象
*/
@Test
public void test03() {
//获取指定日期时间的时间对象
LocalDateTime date = LocalDateTime.of(2022,1,1,12,01,01,4315);
System.out.println(date);

//获取当前日期
LocalDateTime now = LocalDateTime.now();
System.out.println(now);

//根据LocalDateTime获取指定的时间日期数据
System.out.println(now.getYear());
System.out.println(now.getMonth().getValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getDayOfWeek().getValue());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
}