JDK1.8 引入了一个非常方便的值处理类 —— Optional 类,它除了用于解决臭名昭著的空指针异常,还有更多不止于此的用途。
本质上来说,Optional 是用来包装其他对象的一个包装类。通过包装它提供了空值校验,默认值设置,值转换等功能。而且搭配 JDK1.8 的函数式编程,可以帮助我们实现一些更强劲的功能。
本文代码参见:cn.mingfer.demo.utils.optional.OptionalTest
方法概览
静态方法
方法名 | 说明 |
---|---|
Optional.empty(); | 获取到一个空的 Optional 对象,里面没有存储任何的实际值 |
Optional.of(T t); | 包装对象 t,t 不能为空,为空则抛出 NullPointException |
Optional.ofNullable(T t); | 包装对象 t,t 可以为空,为空时返回一个 Optional.empty(); |
这三个静态方法均可以用来初始化一个 Optional 实例,具体的用法我们会在后面说明。
成员方法
方法名 | 说明 |
---|---|
get() | 获取包装的对象值 t |
isPresent() | 判断包装的对象是否是 null 值,不是为 true |
ifPresent(Consumer<? super T> consumer) | 如果对象不为 null 就执行传入的方法 |
filter(Predicate<? super T> predicate) | 过滤对象,如果对象满足 predicate 方法中的条件就返回该对象,否则返回 Optional.empty() |
map(Function<? super T, ? extends U> mapper) | 当包装的对象不为空的时候,执行 mapper 方法操作包装的对象并返回一个 Optional 对象。如果这里 mapper 返回 Optional 对象,那么会被包装两次变成 Optional<Optional |
flatMap(Function<? super T, Optional> mapper) | 当包装的对象不为空的时候,执行 mapper 方法操作包装的对象并返回一个 Optional 对象,注意这里返回的 mapper 对象是 mapper 方法返回的。 |
orElse(T other) | 如果当前包装的值为 null,那么返回备选值 other |
orElseGet(Supplier<? extends T> other) | 如果当前包装的值为 null,那么返回备选值 other |
orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果当前包装的值为 null,那么抛出异常 X |
使用技巧
空值校验
不允许通过 of
方法设置 null
值:
1
2
3
4
@Test(expected = NullPointerException.class)
public void ofNull() {
Optional.of(null);
}
通过 isPresent
判断包装的值是否为空值。大多数时候我们无法确定某个方法返回的值是否是 null
的时候,可以使用 ofNullable
包装对应的返回值,然后使用 isPresent
判断返回值是否为 null
。
1
2
3
4
5
@Test
public void ofNullable() {
Assert.assertFalse(Optional.ofNullable(null).isPresent());
Assert.assertTrue(Optional.ofNullable("test data").isPresent());
}
如果包装的值为 null
,在尝试获取值的时候回抛出 NoSuchElementException
异常。
1
2
3
4
@Test(expected = NoSuchElementException.class)
public void ofNullableTheGetNullValue() {
Optional.ofNullable(null).get();
}
值访问
上面已经有说过,Optional 通过 get
方法获取包装的值:
1
2
3
4
5
6
@Test
public void get() {
final Entity entity = new Entity("test-entity", "entity");
final Optional<Entity> optionalEntity = Optional.of(entity);
Assert.assertEquals("entity", optionalEntity.get().getName());
}
设置默认值
当我们在获取某个值,但是该值可能为 null
,又希望有个默认值去替换的时候。比如:
1
2
3
4
5
6
7
8
@Test
public void getDefaultValue() {
final Entity entity = new Entity("test", null);
Assert.assertNull(entity.getName());
Assert.assertEquals("test", Optional.ofNullable(entity.getName()).orElse("test"));
Assert.assertNotNull(Optional.empty().orElseGet(() -> new Entity("test", null)));
Assert.assertNotNull(Optional.ofNullable(entity).orElseGet(() -> new Entity("test", null)));
}
上边的例子中第二个断言当 entity
的 name
属性为 null
时使用默认值test
。
orElse
和 orElseGet
都是用于设置默认值的。区别在于 orElseGet
接收一个 Supplier
参数,当包装的值不为 null 的时候,orElseGet
不会执行 new Entity
去构造一个实例,但是 orElse
会先构造 new Entity
实例。如果这段代码需要频繁调用或者 new 一个实例成本比较高的时候,在性能上的区别是非常大的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Entity(String id, String name) {
System.out.println("execute construction.");
this.id = id;
this.name = name;
}
@Test
public void orElseAndOrElseGet() {
final Entity entity = new Entity("test", null);
Assert.assertNotNull(Optional.ofNullable(entity).orElse(new Entity("test", null)));
Assert.assertNotNull(Optional.ofNullable(entity).orElseGet(() -> new Entity("test", null)));
}
# 结果
execute construction.
execute construction.
如果我们在包装的值不为空的时候不想设置默认值,而是直接抛出一个不允许为空的异常:
1
2
3
4
5
6
@Test(expected = IllegalArgumentException.class)
public void orThrow(){
final Entity entity = new Entity("test", null);
Optional.ofNullable(entity.getName())
.orElseThrow(() -> new IllegalArgumentException("Entity.name must not be null."));
}
这里 orElseThrow
也是传入一个 Supplier
封装的异常,避免了在值不为空的时候也创建异常对象,创建异常对象的时候由于需要 thread dump
性能是非常低的。
值转换
Optional 中 map
和 flatMap
用于转换 Optional 包装的值的类型。
map
和 flatMap
的区别在于:map
会对执行结果进行 Optional 包装,flatMap
要求执行的结果必须是一个 Optional 对象:
下面的例子是:我们需要获取 entity 的 Name 属性,但是不知道 name 是否为 null,如果为 null 我们希望返回默认值 mingfer。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void mapAndFlatMap() {
final Entity entity = new Entity("test", null);
Optional<String> name = Optional.of(entity).map(Entity::getName);
Assert.assertEquals("mingfer", name.orElse("mingfer"));
Optional<Optional<String>> optionalName = Optional.of(entity).map(Entity::getOptionalName);
Assert.assertEquals("mingfer", optionalName.map(Optional::get).orElse(Optional.of("mingfer").get()));
Optional<String> nameByFlatMap = Optional.of(entity).flatMap(Entity::getOptionalName);
Assert.assertEquals("mingfer", nameByFlatMap.orElse("mingfer"));
}
值过滤
Optional 中 filter
用于过滤包装的值,filter
接口接受一个 Predicate
参数,如果包装的值满足 Predicate 条件,返回该值,否则返回一个 empty 的 Optional 对象。
1
2
3
4
5
6
@Test
public void filter() {
final Entity entity = new Entity("test", "mingfer");
Assert.assertTrue(Optional.of(entity).filter(e -> "test".equals(e.getId())).isPresent());
Assert.assertFalse(Optional.of(entity).filter(e -> "test-2".equals(e.getId())).isPresent());
}
使用 Optional 的优势
不用 Optional 的时候,进行对象属性访问:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void disuseOptional() {
final User user = new User(new User.Address(new User.Country("China")));
// 可能出现空指针异常的调用方式
String countryName = user.getAddress().getCountry().getName().toUpperCase();
Assert.assertEquals("CHINA", countryName);
// 为了避免空指针异常需要做的判断
if (user != null) {
User.Address address = user.getAddress();
if (address != null) {
User.Country country = address.getCountry();
if (country != null) {
String name = country.getName();
if (name != null) {
countryName = countryName.toUpperCase();
}
}
}
}
Assert.assertEquals("CHINA", countryName);
}
使用 Optional 进行对象的属性访问:
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void useOptional() {
final User user = new User(new User.Address(new User.Country("China")));
String result =
Optional.ofNullable(user)
.map(User::getAddress)
.map(User.Address::getCountry)
.map(User.Country::getName)
.map(String::toLowerCase)
.orElse("CHINA");
Assert.assertEquals("CHINA", result);
}
对比两份代码可以看到 Optional 明显的减少了代码量。