Java8 中的Optional类详解

在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对象。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。

ifPresent()方法:真的存在吗?

Optional在使用的时候,有一些主要的方法,首先是ifPresent。顾名思义,即使对象是否存在的意思。就是说里面装的东西是不是null,这个和判断对象是否为null基本上也是等价的。

1
2
3
if (x != null) {
print(x);
}

以上代码等价于,下面的

任何一句话。

1
2
opt.ifPresent(x -> print(x));
opt.ifPresent(this::print);

filter()方法:过滤一下吧

另外一个方法是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()方法:执行一些操作

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了。

orElse()/orElseGet() 需要一个默认值吗?

如果你在拿到Optional的数据后,发现是他null,你想做一些默认的返回值,执行类似下面的操作:

1
int len = (x != null)? x.length() : 1;

可以这么写:

1
int len = opt.map(String::length).orElse(1);

如果默认值需要被动态计算出来,就是可以用orElseGet(), 它接受一个Supplier. A important difference is the expression in orElse will be executed no matter whehter the caller Optional has value or not. So it would be more efficient to use orElseGet if the expression require resources or is I/O bound. Expamle in this SO question.

1
2
3
int len = opt.
map(String::length).
orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

flatMap() 不会有Null的map

flatMap()方法和map()类似,不同点是,map可以返回任意类型,系统会自动包装为Optional,但是flatMap必须返回Optional,系统不会自动做包装。

1
2
3
//返回值是Optional
public Optional<String> tryFindSimilar(String s)
Optional<String> similar =       opt.flatMap(this::tryFindSimilar);

orElseThrow() 如果值不存在,抛一个异常吧!

以下代码等价,一看便知:

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转为List或者Set

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());

本文代码内容来自于:http://www.nurkiewicz.com/2013/08/optional-in-java-8-cheat-sheet.html

FROM HERE

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s