Java 8备忘单中的可选内容


java.util.Optional<T>在Java 8中是scala.Option[T]Data.Maybe in Haskell。但这并不意味着它没用。如果这个概念对你来说是新的,想象一下:

Optional

作为可能包含或不包含某些值的容器。就像Java中的所有引用都可以指向某个对象或者是

null

,

Option

可能会包含一些(非空!)引用或为空。


事实证明,两者之间的类比

Optional

可空引用是非常明智的。

Optional

是在Java 8中引入的,所以很明显,它没有在整个标准的Java库中使用——并且由于向后兼容的原因,它永远不会被使用。但是我建议您至少尝试一下,并且在有可空引用的时候使用它。

Optional

而不是普通的

null

在编译时进行静态检查,因为它清楚地表明给定变量是否存在。当然,这需要一些纪律——你永远不应该分配

null

任何变量。


的用法选择权可能)模式颇有争议,我不打算参与讨论。相反,我向您展示了几个用例

null

以及如何改造它们

Optional<T>

。在以下示例中,使用了给定的变量和类型:


public void print(String s) {
System.out.println(s);
}
String x = //...
Optional<String> opt = //...
x

是一个字符串可能

null

,

opt

从来没有

null

,但可能包含也可能不包含某些值(礼物或者空的)。创造的方式很少

Optional

opt = Optional.of(notNull);
opt = Optional.ofNullable(mayBeNull);
opt = Optional.empty();

在第一种情况下

Optional

 必须不包含

null

值,并且在以下情况下将引发异常

null

通过了。

ofNullable()

将返回空的或当前的(集合)

Optional

empty()

总是空着返回

Optional

,对应于

null

。这是单胞胎,因为

Optional<T>

是不可改变的。

ifPresent()-什么时候做点什么Optional已设置

沉闷的

if

声明:

if (x != null) {
print(x);
}

可以用高阶函数代替

ifPresent()

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

当lambda参数(

String x

)匹配函数形式参数。


filter()-拒绝(过滤掉)某些Optional价值观。

有时,您不仅希望在设置引用时执行某些操作,还希望在引用满足特定条件时执行某些操作:


if (x != null && x.contains("ab")) {
print(x);
}

这可以替换为

Optional.filter()

呈现(设置)

Optional

清空

Optional

如果基础值不符合给定的谓词。如果输入

Optional

为空,则按原样返回:


opt.
filter(x -> x.contains("ab")).
ifPresent(this::print);

这相当于更迫切:


if(opt.isPresent() && opt.get().contains("ab")) {
print(opt.get());
}

map()-转换值(如果存在)

通常你需要对一个值进行一些转换,但前提是它不是

null

(避免

NullPointerException

):


if (x != null) {
String t = x.trim();
if (t.length() > 1) {
print(t);
}
}

这可以用更具声明性的方式来完成

map()


opt.
map(String::trim).
filter(t -> t.length() > 1).
ifPresent(this::print);

这变得很棘手。

Optional.map()

对内部值应用给定的函数

Optional

-但前提是

Optional

存在。否则什么都不会发生

empty()

被返回。请记住,转换是类型安全的——请在这里查看泛型:


Optional<String>  opt = //...
Optional<Integer> len = opt.map(String::length);

如果

Optional<String>

存在

Optional<Integer> len

也存在,包装长度为

String

。但是如果

opt

是空的,

map()

除了改变泛型类型,它什么也不做。


orElse()/orElseGet()-变空Optional<T>默认T

在某些时候,你可能希望打开包装

Optional

并从中获得真正的价值。但是你不能这样做,如果

Optional

为空。这里有一种处理这种情况的Java 8之前的方法:


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

随着

Optional

我们可以说:


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

还有一个版本接受Supplier<T>如果计算默认值缓慢、昂贵或有副作用:


intlen = opt.
map(String::length).
orElseGet(() -> slowDefault());  //orElseGet(this::slowDefault)

flatMap()-我们需要更深入

假设你有一个不接受的函数

null

但是可能会产生一个:


public String findSimilar(@NotNullString s) //...

使用起来有点麻烦:


String similarOrNull = x != null? findSimilar(x) : null;

随着

Optional

这就有点直截了当了:


Optional<String> similar = opt.map(this::findSimilar);

如果函数我们

map()

超额收益

null

,的结果

map()

是空的

Optional

。否则,它是用(present)包装的所述函数的结果

Optional

。到目前为止还不错,但是我们为什么要回来

null

-如果我们有价值

Optional


public Optional<String> tryFindSimilar(String s)  //...

我们的意图是明确的,但使用

map()

无法生成正确的类型。相反,我们必须使用

flatMap()


Optional<Optional<String>> bad = opt.map(this::tryFindSimilar);
Optional<String> similar =  opt.flatMap(this::tryFindSimilar);

你看到双份了吗

Optional<Optional<...>>

?绝对不是我们想要的。如果您正在映射一个返回

Optional

,使用

flatMap

相反。以下是该功能的简化实现:


public<U> Optional<U> flatMap(Function<T, Optional<U>> mapper) {
if(!isPresent())
returnempty();
else{
returnmapper.apply(value);
}
}

orElseThrow()-懒洋洋地在空上抛出异常Optional

如果值不可用,我们通常会抛出异常:


public char firstChar(String s) {
if(s != null&& !s.isEmpty())
returns.charAt(0);
else
throw new IllegalArgumentException();
}

这整个方法可以用下面的成语来代替:


opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

我们不想提前创建异常的实例,因为creating an exception has significant cost


更大的例子

想象我们有一个

Person

Address

那有一个

validFrom

约会。所有这些都可以

null

。我们想知道是否

validFrom

是集和过去:


private boolean validAddress(NullPerson person) {
if(person != null) {
if(person.getAddress() != null) {
finalInstant validFrom = person.getAddress().getValidFrom();
returnvalidFrom != null&& validFrom.isBefore(now());
} else
return false;
} else
return false;
}

相当丑陋和防御性的。或者但仍然丑陋:


returnperson != null&&
person.getAddress() != null&&
person.getAddress().getValidFrom() != null&&
person.getAddress().getValidFrom().isBefore(now());

现在想象所有这些(

person

,

getAddress()

,

getValidFrom()

)是

Optional

适当类型的,清楚地表明它们可能没有设置;


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;
}
//...
}

突然间,计算变得更加简单:


returnperson.
flatMap(Person::getAddress).
flatMap(Address::getValidFrom).
filter(x -> x.before(now())).
isPresent();

它更易读吗?很难说。但至少它是不可能生产的

NullPointerException

当...的时候

Optional

始终如一地使用。


转变Optional<T>List<T>

我有时喜欢思考

Optional

作为一个集合1有0或1个元素的。这可能使理解

map()

flatMap()

更容易。不幸地

Optional

没有

toList()

方法,但是很容易实现一个:


public static<T> List<T> toList(Optional<T> option) {
return option.
map(Collections::singletonList).
orElse(Collections.emptyList());
}

或者不那么地道:


public static<T> List<T> toList(Optional<T> option) {
if(option.isPresent())
return Collections.singletonList(option.get());
else
return Collections.emptyList();
}

但是为什么要限制我们自己

List<T>

?怎么样

Set<T>

和其他收藏品?Java 8已经抽象出通过Collectors API,介绍给Streams。这个应用编程接口很可怕,但是可以理解:


public static<R, A, T> R collect(Optional<T> option, Collector<? superT, A, R> collector) {
finalA container = collector.supplier().get();
option.ifPresent(v -> collector.accumulator().accept(container, v));
return collector.finisher().apply(container);
}

我们现在可以说:


import static java.util.stream.Collectors.*;
List<String> list = collect(opt, toList());
Set<String>  set  = collect(opt, toSet());

摘要

Optional<T>

远不如

Option[T]

在斯卡拉(但至少它doesn’t allow wrapping null)。应用编程接口不像

null

-处理,可能要慢得多。但是编译时检查的好处加上可读性和文档价值

Optional

持续使用大大优于缺点。此外,它可能会取代几乎相同的com.google.common.base.Optional<T> from Guava


1 -从理论上看两者可能顺序抽象是单子,这就是它们共享一些功能的原因