分类: Java
2014-08-17 01:12:17
在Java8中,引入了一个新的类java.util.Optional 。这个类专门用来解决空引用的问题。有了它,对于一个方法的返回值什么的,都不需要再判断是不是null了。同时,这个类和lambda表达式和函数式编程也可以比较好的整合在一起使用。
这里就来看一下这个类的使用。
知道Scala的同学可以类比一下 scala.Option[T] ,或者Haskell的Data.Maybe。都有类似的功能。
Optional是一个容器,里面放着我们的返回值(就是真正要用的东西),但是这个真正要用的东西可能是null呀,所以每次都要判断,但是Optional不可能是null。所以不要在判断是否为null了。
1
2 3 4 5 6 |
public void print(String s) {
System.out.println(s); } String x = //... Optional<String> opt = //... |
上面代码中,x可能是null,opt不可能是null。
首先让我们来看一下如何构造Optional对象。Optional对象有一系列静态工厂方法创建出来。
1
2 3 |
opt = Optional.of(notNull);
opt = Optional.ofNullable(mayBeNull); opt = Optional.empty(); |
以上3个方法,第一个of()从一个非null的对象创建Optional,如果传给他null,就有异常NullPointerException。
ofNullable()可以由任意对象(包括null)创建Optional。empty方法总是返回一个空的Optional。
Optional在使用的时候,有一些主要的方法,首先是ifPresent。顾名思义,即使对象是否存在的意思。就是说里面装的东西是不是null,这个和判断对象是否为null基本上也是等价的。
1
2 3 |
if (x != null) {
print(x); } |
以上代码等价于,下面的任何一句话。
1
2 |
opt.ifPresent(x -> print(x));
opt.ifPresent(this::print); |
另外一个方法是filter()。就是可以做一些条件判断,判断Optional里面的东西,是否满足某一个条件,满足了,就可以做一些额外的操作。
1
2 3 |
if (x != null && x.contains("ab")) {
print(x); } |
等价于:
1
2 3 |
opt.
filter(x -> x.contains("ab")). ifPresent(this::print); |
如果不喜欢这种函数式的风格,也可以这么写,更好懂:
1
2 3 |
if(opt.isPresent() && opt.get().contains("ab")) {
print(opt.get()); } |
上面的get()方法就是把Optional的东西拿出来了。
map()方法可以对值做一些处理,将一个操作作用于里面的东西。比如,字符串去掉首尾空格后打印出来:
1
2 3 4 5 6 |
if (x != null) {
String t = x.trim(); if (t.length() > 1) { print(t); } } |
等价于:
1
2 3 4 |
opt.
map(String::trim). filter(t -> t.length() > 1). ifPresent(this::print); |
map()方法会对Optional里的内容做处理,但如果Optional里的是null,他什么都不做(不会有异常哦),只返回empty。
map()还有一个特点是,它是类型安全的。
1
2 |
Optional<String> opt = //...
Optional<Integer> len = opt.map(String::length); |
一个String的Optional,取出他们的长度,变成了Integer。map()之后的数据类型就是Integer了。
如果你在拿到Optional的数据后,发现是他null,你想做一些默认的返回值,执行类似下面的操作:
1
|
int len = (x != null)? x.length() : -1;
|
可以这么写:
1
|
int len = opt.map(String::length).orElse(-1);
|
如果默认值需要被动态计算出来,就是可以用orElseGet(),它接受一个Supplier:
1
2 3 |
int len = opt.
map(String::length). orElseGet(() -> slowDefault()); //orElseGet(this::slowDefault) |
flatMap()方法和map()类似,不同点是,map可以返回任意类型,系统会自动包装为Optional,但是flatMap必须返回Optional,系统不会自动做包装。
1
2 3 |
//返回值是Optional
public Optional<String> tryFindSimilar(String s) Optional<String> similar = opt.flatMap(this::tryFindSimilar); |
以下代码等价,一看便知:
1
2 3 4 5 6 7 8 9 10 11 |
public char firstChar(String s) {
if (s != null && !s.isEmpty()) return s.charAt(0); else throw new IllegalArgumentException(); } opt. filter(s -> !s.isEmpty()). map(s -> s.charAt(0)). orElseThrow(IllegalArgumentException::new); |
假如有一个Person 对象,它有一个Address 属性,而Address属性还嵌套一个validFrom的日期。所有的值都可能是null。
好了,现在我们来判断一下Person 的这些属性是否是Valid的。
1
2 3 4 5 6 7 8 9 10 |
private boolean validAddress(NullPerson person) {
if (person != null) { if (person.getAddress() != null) { final Instant validFrom = person.getAddress().getValidFrom(); return validFrom != null && validFrom.isBefore(now()); } else return false; } else return false; } |
或者也可以这么写:
1
2 3 4 |
return person != null &&
person.getAddress() != null && person.getAddress().getValidFrom() != null && person.getAddress().getValidFrom().isBefore(now()); |
总之 都不是很好看。
但如果这些属性都是Optional,那么看起来会稍微舒服一点。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Person {
private final Optional<Address> address; public Optional<Address> getAddress() { return address; } //... } class Address { private final Optional<Instant> validFrom; public Optional<Instant> getValidFrom() { return validFrom; } //... } |
以下代码就实现了一样的判断:
1
2 3 4 5 |
return person.
flatMap(Person::getAddress). flatMap(Address::getValidFrom). filter(x -> x.before(now())). isPresent(); |
使用Optional以后,NullPointerException 就从此消失了。
Optional是一个集合,虽然里面只有0或者1个元素,但它一样是一个集合。如果要转为List或者Set,一般的写法可以是:
1
2 3 4 5 |
public static <T> List<T> toList(Optional<T> option) {
return option. map(Collections::singletonList). orElse(Collections.emptyList()); } |
或者更传统的写法:
1
2 3 4 5 6 |
public static <T> List<T> toList(Optional<T> option) {
if (option.isPresent()) return Collections.singletonList(option.get()); else return Collections.emptyList(); } |
但是在java8里,其实只需要这么写:
1
2 3 4 5 |
import static java.util.stream.Collectors.*;
//转为List List<String> list = collect(opt, toList()); //转为Set Set<String> set = collect(opt, toSet()); |
本文代码内容来自于: