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,介绍给Stream
s。这个应用编程接口很可怕,但是可以理解:
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 -从理论上看两者可能和顺序抽象是单子,这就是它们共享一些功能的原因