Skip to content

Commit 03d1879

Browse files
committed
Java 8 Optional优雅的处理空指针
1 parent cfcd88e commit 03d1879

1 file changed

Lines changed: 358 additions & 0 deletions

File tree

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
---
2+
title: Jdk14都要出了,还不能使用 Optional优雅的处理空指针?
3+
# toc: false
4+
date: 2019-11-04 08:01:01
5+
url: jdk/jdk8-optional
6+
tags:
7+
- Java8
8+
- Optional
9+
categories:
10+
- Java 新特性
11+
---
12+
13+
# 1. 前言
14+
15+
> 如果你没有处理过空指针,那么你不是一位真正的 Java 程序员。
16+
17+
空指针确实会产生很多问题,我们经常遇到空的引用,然后又想从这个空的引用上去获取其他的值,接着理所当然的碰到了 `NullPointException`。这是你可能会想,这报错很好处理,然后你看了眼报错行数,对比了下代码。脑海里瞬间闪过 ”对对对,这里有可能为空“,然后加上 `null check`轻松处理。然而你不知道这已经是你处理的第多少个空指针异常了。
18+
<!-- more -->
19+
为了解决上面的问题,在 Java SE8 中引入了一个新类 `java.util.Optional`,这个类可以**缓解**上面的问题。
20+
21+
你可能已经发现了,上面我用的是**缓解**而不是**解决**。这也是很多人理解不太对的地方,以为 Java SE8 中的 `Optional` 类可以解决空指针问题。其实 Optional 类的的使用只是**提示**你这里可能存在空值,需要特殊处理,并提供了一些特殊处理的方法。如果你把 `Optional` 类当作空指针的救命稻草而不加思考的使用,那么依旧会碰到错误。
22+
23+
因为 `Optional` 是的 Java SE8 中引入的,因此本文中难免会有一些 JDK8 中的语法,如 **Lambda** 表达式,流处理等,但是都是基本形式,不会有过于复杂的案例。
24+
25+
# 2. Optional 创建
26+
27+
Optional 的创建一共有三种方式。
28+
29+
```java
30+
/**
31+
* 创建一个 Optional
32+
*/
33+
@Test
34+
public void createOptionalTest() {
35+
// Optional 构造方式1 - of 传入的值不能为 null
36+
Optional<String> helloOption = Optional.of("hello");
37+
38+
// Optional 构造方式2 - empty 一个空 optional
39+
Optional<String> emptyOptional = Optional.empty();
40+
41+
// Optional 构造方式3 - ofNullable 支持传入 null 值的 optional
42+
Optional<String> nullOptional = Optional.ofNullable(null);
43+
}
44+
```
45+
46+
其中构造方式1中 `of` 方法,如果传入的值会空,会报出 `NullPointerException` 异常。
47+
48+
# 3. Optional 判断
49+
50+
Optional 只是一个包装对象,想要判断里面有没有值可以使用 `isPresent` 方法检查其中是否有值 。
51+
52+
```java
53+
/**
54+
* 检查是否有值
55+
*/
56+
@Test
57+
public void checkOptionalTest() {
58+
Optional<String> helloOptional = Optional.of("Hello");
59+
System.out.println(helloOptional.isPresent());
60+
61+
Optional<Object> emptyOptional = Optional.empty();
62+
System.out.println(emptyOptional.isPresent());
63+
}
64+
```
65+
66+
得到的输出:
67+
68+
```java
69+
true
70+
false
71+
```
72+
73+
从 JDK11 开始,提供了 `isEmpty`方法用来检查相反的结果:是否为空。
74+
75+
如果想要在有值的时候进行一下操作。可以使用 `ifPresent`方法。
76+
77+
```java
78+
/**
79+
* 如果有值,输出长度
80+
*/
81+
@Test
82+
public void whenIsPresent() {
83+
// 如果没有值,获取默认值
84+
Optional<String> helloOptional = Optional.of("Hello");
85+
Optional<String> emptyOptional = Optional.empty();
86+
helloOptional.ifPresent(s -> System.out.println(s.length()));
87+
emptyOptional.ifPresent(s -> System.out.println(s.length()));
88+
}
89+
```
90+
91+
输出结果:
92+
93+
```java
94+
5
95+
```
96+
97+
# 4. Optional 获取值
98+
99+
使用 `get`方法可以获取值,但是如果值不存在,会抛出 `NoSuchElementException` 异常。
100+
101+
```java
102+
/**
103+
* 如果没有值,会抛异常
104+
*/
105+
@Test
106+
public void getTest() {
107+
Optional<String> stringOptional = Optional.of("hello");
108+
System.out.println(stringOptional.get());
109+
// 如果没有值,会抛异常
110+
Optional<String> emptyOptional = Optional.empty();
111+
System.out.println(emptyOptional.get());
112+
}
113+
```
114+
115+
得到结果:
116+
117+
```java
118+
hello
119+
120+
java.util.NoSuchElementException: No value present
121+
at java.util.Optional.get(Optional.java:135)
122+
at net.codingme.feature.jdk8.Jdk8Optional.getTest(Jdk8Optional.java:91)
123+
```
124+
125+
126+
127+
# 5. Optional 默认值
128+
129+
使用 `orElse`, `orElseGet` 方法可以在没有值的情况下获取给定的默认值。
130+
131+
```java
132+
/**
133+
* 如果没有值,获取默认值
134+
*/
135+
@Test
136+
public void whenIsNullGetTest() {
137+
// 如果没有值,获取默认值
138+
Optional<String> emptyOptional = Optional.empty();
139+
String orElse = emptyOptional.orElse("orElse default");
140+
String orElseGet = emptyOptional.orElseGet(() -> "orElseGet default");
141+
System.out.println(orElse);
142+
System.out.println(orElseGet);
143+
}
144+
```
145+
得到的结果:
146+
```log
147+
orElse default
148+
orElseGet default
149+
```
150+
看到这里你可能会有些疑惑了,这两个方法看起来效果是一模一样的,为什么会提供两个呢?下面再看一个例子,你会发现两者的区别。
151+
```java
152+
/**
153+
* orElse 和 orElseGet 的区别
154+
*/
155+
@Test
156+
public void orElseAndOrElseGetTest() {
157+
// 如果没有值,默认值
158+
Optional<String> emptyOptional = Optional.empty();
159+
System.out.println("空Optional.orElse");
160+
String orElse = emptyOptional.orElse(getDefault());
161+
System.out.println("空Optional.orElseGet");
162+
String orElseGet = emptyOptional.orElseGet(() -> getDefault());
163+
System.out.println("空Optional.orElse结果:"+orElse);
164+
System.out.println("空Optional.orElseGet结果:"+orElseGet);
165+
System.out.println("--------------------------------");
166+
// 如果没有值,默认值
167+
Optional<String> stringOptional = Optional.of("hello");
168+
System.out.println("有值Optional.orElse");
169+
orElse = stringOptional.orElse(getDefault());
170+
System.out.println("有值Optional.orElseGet");
171+
orElseGet = stringOptional.orElseGet(() -> getDefault());
172+
System.out.println("有值Optional.orElse结果:"+orElse);
173+
System.out.println("有值Optional.orElseGet结果:"+orElseGet);
174+
}
175+
176+
public String getDefault() {
177+
System.out.println(" 获取默认值中..run getDeafult method");
178+
return "hello";
179+
}
180+
```
181+
182+
得到的输出:
183+
184+
```log
185+
空Optional.orElse
186+
获取默认值中..run getDeafult method
187+
空Optional.orElseGet
188+
获取默认值中..run getDeafult method
189+
空Optional.orElse结果:hello
190+
空Optional.orElseGet结果:hello
191+
--------------------------------
192+
有值Optional.orElse
193+
获取默认值中..run getDeafult method
194+
有值Optional.orElseGet
195+
有值Optional.orElse结果:hello
196+
有值Optional.orElseGet结果:hello
197+
```
198+
199+
在这个例子中会发现 `orElseGet` 传入的方法在有值的情况下并不会运行。而 `orElse`却都会运行。
200+
201+
# 6. Optional 异常
202+
203+
使用 `orElseThrow` 在没有值的时候抛出异常
204+
205+
```java
206+
/**
207+
* 如果没有值,抛出异常
208+
*/
209+
@Test
210+
public void whenIsNullThrowExceTest() throws Exception {
211+
// 如果没有值,抛出异常
212+
Optional<String> emptyOptional = Optional.empty();
213+
String value = emptyOptional.orElseThrow(() -> new Exception("发现空值"));
214+
System.out.println(value);
215+
}
216+
```
217+
218+
得到结果:
219+
220+
```java
221+
java.lang.Exception: 发现空值
222+
at net.codingme.feature.jdk8.Jdk8Optional.lambda$whenIsNullThrowExceTest$7(Jdk8Optional.java:118)
223+
at java.util.Optional.orElseThrow(Optional.java:290)
224+
at net.codingme.feature.jdk8.Jdk8Optional.whenIsNullThrowExceTest(Jdk8Optional.java:118)
225+
```
226+
227+
# 7. Optional 函数接口
228+
229+
`Optional` 随 JDK8 一同出现,必然会有一些 JDK8 中的新特性,比如函数接口。`Optional` 中主要有三个传入函数接口的方法,分别是`filter``map``flatMap`。这里面的实现其实是 JDK8 的另一个新特性了,因此这里只是简单演示,不做解释。后面放到其他 JDK8 新特性文章里介绍。
230+
231+
```java
232+
@Test
233+
public void functionTest() {
234+
// filter 过滤
235+
Optional<Integer> optional123 = Optional.of(123);
236+
optional123.filter(num -> num == 123).ifPresent(num -> System.out.println(num));
237+
238+
Optional<Integer> optional456 = Optional.of(456);
239+
optional456.filter(num -> num == 123).ifPresent(num -> System.out.println(num));
240+
241+
// map 转换
242+
Optional<Integer> optional789 = Optional.of(789);
243+
optional789.map(String::valueOf).map(String::length).ifPresent(length -> System.out.println(length));
244+
}
245+
```
246+
247+
得到结果:
248+
249+
```java
250+
123
251+
3
252+
```
253+
254+
# 8. Optional 案例
255+
256+
假设有计算机、声卡、usb 三种硬件(下面的代码中使用了 `Lombok``@Data` 注解)。
257+
258+
```java
259+
/**
260+
* 计算机
261+
*/
262+
@Data
263+
class Computer {
264+
private Optional<SoundCard> soundCard;
265+
}
266+
267+
/**
268+
* 声卡
269+
*/
270+
@Data
271+
class SoundCard {
272+
private Optional<Usb> usb;
273+
}
274+
275+
/**
276+
* USB
277+
*/
278+
@Data
279+
class Usb {
280+
private String version;
281+
}
282+
```
283+
284+
计算机可能会有声卡,声卡可能会有 usb。那么怎么取得 usb 版本呢?
285+
286+
```java
287+
/**
288+
* 电脑里【有可能】有声卡
289+
* 声卡【有可能】有USB接口
290+
*/
291+
@Test
292+
public void optionalTest() {
293+
// 没有声卡,没有 Usb 的电脑
294+
Computer computerNoUsb = new Computer();
295+
computerNoUsb.setSoundCard(Optional.empty());
296+
// 获取 usb 版本
297+
Optional<Computer> computerOptional = Optional.ofNullable(computerNoUsb);
298+
String version = computerOptional.flatMap(Computer::getSoundCard).flatMap(SoundCard::getUsb)
299+
.map(Usb::getVersion).orElse("UNKNOWN");
300+
System.out.println(version);
301+
System.out.println("-----------------");
302+
303+
// 如果有值,则输出
304+
SoundCard soundCard = new SoundCard();
305+
Usb usb = new Usb();
306+
usb.setVersion("2.0");
307+
soundCard.setUsb(Optional.ofNullable(usb));
308+
Optional<SoundCard> optionalSoundCard = Optional.ofNullable(soundCard);
309+
optionalSoundCard.ifPresent(System.out::println);
310+
// 如果有值,则输出
311+
if (optionalSoundCard.isPresent()) {
312+
System.out.println(optionalSoundCard.get());
313+
}
314+
315+
// 输出没有值,则没有输出
316+
Optional<SoundCard> optionalSoundCardEmpty = Optional.ofNullable(null);
317+
optionalSoundCardEmpty.ifPresent(System.out::println);
318+
System.out.println("-----------------");
319+
320+
// 筛选 Usb2.0
321+
optionalSoundCard.map(SoundCard::getUsb)
322+
.filter(usb1 -> "3.0".equals(usb1.map(Usb::getVersion)
323+
.orElse("UBKNOW")))
324+
.ifPresent(System.out::println);
325+
}
326+
```
327+
328+
得到结果:
329+
330+
```java
331+
332+
UNKNOWN
333+
-----------------
334+
SoundCard(usb=Optional[Usb(version=2.0)])
335+
SoundCard(usb=Optional[Usb(version=2.0)])
336+
-----------------
337+
```
338+
339+
# 9. Optional 总结
340+
341+
在本文中,我们看到了如何使用 Java SE8 的 `java.util.Optional` 类。`Optional` 类的目的不是为了替换代码中的每个空引用,而是为了帮助更好的设计程序,让使用者可以仅通过观察属性类型就可以知道会不会有空值。另外,`Optional`不提供直接获取值的方法,使用时会强迫你处理不存在的情况。间接的让你的程序免受空指针的影响。
342+
343+
344+
345+
文中代码已经上传 [Github](https://github.com/niumoo/jdk-feature)
346+
347+
https://github.com/niumoo/jdk-feature
348+
349+
350+
351+
352+
353+
354+
355+
356+
357+
358+

0 commit comments

Comments
 (0)