每当涉及Java,总会有很多选项。 这篇文章讨论了 Java 基础类 Optional 用法,与 Vavr 中的对应方法进行比较。Java 8最早引入了 Optional,把它定义为“一种容器对象,可以存储 null 或非 null 值”。
通常,在返回值可能为null的地方,会出现NullPointerException。开发人员可以使用 Optional 避免 null 值检查。在这种情况下,Optional 提供了一些方便的功能。但可惜的是,Java 8并没有包含所有功能。Optional中的某些功能需要使用 Java 11。要解决这类问题还可以使用 Vavr Option类。
本文将介绍如何使用 Java Optional类,并与 Vavr Option 进行比较。注意:示例代码要求使用Java 11及更高版本。所有代码在 Vavr0.10.2环境下完成测试。
让我们开始吧。
Java Optional 简介
Optional 并不是什么新概念,像 Haskell、Scala 这样的函数式编程语言已经提供了实现。调用方法后,返回值未知或者不存在(比如 null)的情况下,用 Optional 处理非常好用。下面通过实例进行介绍。
新建 Optional 实例
首先,需要获得 Optional 实例,有以下几种方法可以新建 Optional 实例。不仅如此,还可以创建empty Optional。方法一,通过 value 创建,过程非常简单:
Optional four = Optional.of(Integer.valueOf(
4));
if (four.isPresent){
System.out.println(
"Hoorayy! We have a value");
}
else {
System.out.println(
"No value");
}
为Integer 4 新建一个Optional实例。这种方法得到的 Optional 始终包含一个 value 且不为 null,例如上面这个示例。使用 ifPresent() 可以检查value是否存在。可以注意到 four 不是 Integer,而是一个装有整数的容器。如果确认 value 存在,可以用 get() 方法执行拆箱操作。具有讽刺意味的是,调用 get() 前如果不进行检查,可能会抛出 NoSuchElementException。
方法二,得到 Optional 对象的另一种方法是使用 stream。Stream提供的一些方法会返回Optional,可以用来检查结果是否存在,例如:
-
findAny
-
findFirst
-
max
-
min
-
reduce
查看下面的代码段:
Optionalcar = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
方法三,使用 Nullable 新建 Optional。可能产生 null:
Optionalnullable = Optional.ofNullable(client.getRequestData());
最后,可以新建一个 empty Optional:
Optionalnothing = Optional.empty();
如何使用 Optional
获得 Optional 对象后即可使用。一种典型的场景是在 Spring 仓库中根据 Id 查找记录。可以使用 Optional 实现代码逻辑,避免 null 检查(顺便提一下,Spring 也支持 Vavr Option)。比如,从图书仓库里查找一本书。
Optionalbook = repository.findOne("some id");
首先,如果有这本书,可以继续执行对应的业务逻辑。在前面的章节用 if-else实现了功能。当然,还有其他办法:Optional 提供了一个方法,接收 Consumer 对象作为输入:
repository.findOne("some id").ifPresent(book -> System.out.println(book));
还可以直接使用方法引用,看起来更简单:
repository.findOne("some id").ifPresent(System.out::println);
如果仓库中没有该书,可以用ifPresentOrElseGet提供回调函数:
repository.findOne(
"some id").ifPresentOrElseGet(book->{
// 如果 value 存在
}, ()->{
// 如果 value 不存在
});
如果结果不存在,可以返回另一个value:
Book result = repository.findOne("some id").orElse(defaultBook);
但是,Optional 也有缺点,使用时需要注意。最后一个例子中,“确保”无论如何都能获得一本书,可能在仓库中,也可能来自 orElse。但如果默认的返回值不是常量或者需要支持一些复杂方法该怎么办?首先,Java 无论如何都会执行 findOne,然后调用 orElse方法。默认返回值可以为常量,但正如我之前所说那样,执行过程比较耗时。
另一个示例
下面用一个简单的示例介绍如何实际使用 Optional 和 Option 类。有一个 CarRepository,可以根据提供的 ID(比如车牌号)查找汽车,接下来用这个示例介绍如何使用 Optional 和 Option。
首先,加入下面代码
从 POJO 类 Car 开始。它遵循 immutable 模式,所有字段都标记为 final,只包含 getter 没有 setter。初始化时提供所有数据:
public
class Car {
private final String name;
private final String id;
private final String color;
public
Car
(String name, String id, String color){
this.name = name;
this.id = id;
this.color = color;
}
public String
getId
(){
return id;
}
public String
getColor
() {
return color;
}
public String
getName
() {
return name;
}
@
Override
public String
toString
() {
return
"Car "+name+
" with license id "+id+
" and of color "+color;
}
}
接下来创建 CarRepository类。提供两种方法根据Id查找汽车:一种是老办法,使用 Optional。和之前在 Spring 仓库的做法类似,结果可能为 null。
public
class
CarRepository {
private
List cars;
public
CarRepository
()
{
getSomeCars();
}
Car
findCarById
(String id)
{
for
(Car car: cars){
function(){ //外汇跟单www.gendan5.com
if
(car.getId().equalsIgnoreCase(id)){
return
car;
}
}
return
null;
}
Optional findCarByIdWithOptional(String id){
return
cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
}
private
void
getSomeCars
()
{
cars =
new
ArrayList<>();
cars.add(
new
Car(
"tesla"
,
"1A9 4321"
,
"red"
));
cars.add(
new
Car(
"volkswagen"
,
"2B1 1292"
,
"blue"
));
cars.add(
new
Car(
"skoda"
,
"5C9 9984"
,
"green"
));
cars.add(
new
Car(
"audi"
,
"8E4 4321"
,
"silver"
));
cars.add(
new
Car(
"mercedes"
,
"3B4 5555"
,
"black"
));
cars.add(
new
Car(
"seat"
,
"6U5 3123"
,
"white"
));
}
}
注意:初始化过程会在仓库中添加一些汽车模拟数据,便于演示。为了突出重点,避免问题复杂化,下面的讨论专注于 Optional 和 Option。
使用Java Optional
使用JUnit创建一个新测试:
@
Test
void
getCarById
(){
Car car = repository.findCarById(
"1A9 4321");
Assertions.assertNotNull(car);
Car nullCar = repository.findCarById(
"M 432 KT");
Assertions.assertThrows(NullPointerException.
class, ()->{
if (nullCar == null){
throw
new NullPointerException();
}
});
}
上面的代码段采用了之前的老办法。查找捷克牌照 1A9 4321对应的汽车,检查该车是否存在。输入俄罗斯车牌找不到对应的汽车,因为仓库中只有捷克车。结果为 null 可能会抛出 NullPointerException。
接下来用Java Optional。第一步,获得 Optional 实例,从存储库中使用指定方法返回 Optional:
@
Test
void
getCarByIdWithOptional
(){
Optional tesla = repository.findCarByIdWithOptional(
"1A9 4321");
tesla.ifPresent(System.out::println);
}
这时调用findCarByIdWithOptional方法打印车辆信息(如果有的话)。运行程序,得到以下结果:
Car tesla with license id 1A9 4321 and of color red
但是,如果代码中没有特定方法该怎么办?这种情况可以从方法返回可能包含 null 的 Optional,称为nullable。
Optional nothing = Optional.ofNullable(repository.findCarById(
"5T1 0965"));
Assertions.assertThrows(NoSuchElementException.
class, ()->{
Car car = nothing.orElseThrow(()->
new NoSuchElementException());
});
上面这段代码段中,我们发现了另一种方法。通过 findCarById 创建 Optional,如果未找到汽车可以返回 null。没有找到车牌号 5T1 0965汽车时,可以用 orElseThrow 手动抛出 NoSuchElementException。另一种情况,如果请求的数据不在仓库中,可以用orElse返回默认值:
Car audi = repository.findCarByIdWithOptional(
"8E4 4311")
.orElse(
new Car(
"audi",
"1W3 4212",
"yellow"));
if (audi.getColor().equalsIgnoreCase(
"silver")){
System.out.println(
"We have silver audi in garage!");
}
else {
System.out.println(
"Sorry, there is no silver audi, but we called you a taxi");
}
好的,车库里没有找到银色奥迪,只好打车了!
使用 Vavr Option
Vavr OptionOption提供了另一种解决办法。首先,在项目中添加依赖,(使用 Maven)安装 Vavr:
io.vavr
vavr
0.10.2
简而言之,Vavr 提供了类似的 API 新建 Option 实例。可以从 nullable 新建 Option 实例,像下面这样:
Optionnothing = Option.of(repository.findCarById("T 543 KK"));
也可以用 none 静态方法创建一个empty容器:
Optionnullable = Option.none();
此外,还有一种方法可以用 Java Optional 新建 Option。看下面这段代码:
Optionresult = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
使用 Vavr Option,可以使用与 Optional相同的 API 来完成上述任务。例如,设置默认值:
Option result = Option.ofOptional(repository.findCarByIdWithOptional(
"5C9 9984"));
Car skoda = result.getOrElse(
new Car(
"skoda",
"5E2 4232",
"pink"));
System.out.println(skoda);
或者,请求的数据不存在时可以抛出异常:
Option nullable = Option.none();
Assertions.assertThrows(NoSuchElementException.
class, ()->{
nullable.getOrElseThrow(()->
new NoSuchElementException());
});
另外,当数据不可用时,可以执行以下操作:
nullable.onEmpty(()->{
///runnable
});
如何根据数据是否存在来执行相应操作,类似 Optional 中 ifPresent?有几种实现方式。与 Optional 中 isPresent 类似,在 Option 中对应的方法称为 isDefined:
if (result.isDefined()){
// 实现功能
}
然而,使用 Option能摆脱 if-else。 是否可以用Optional相同的方式完成? 使用 peek 操作:
result.peek(val -> System.out.println(val)).onEmpty(() -> System.out.println("Result is missed"));
此外,Vavr Option还提供了一些其他非常有用的方法,在函数式编程上比Optional类效果更好。 因此,建议您花一些时间来探索 Vavr Option javadocs尝试使用这些API。 我会持续跟进一些类似 map、narrow、isLazy 和 when 这样有趣的功能。
另外,Option只是 Vavr 开发库的一部分,其中还包含了许多其他关联类。 不考虑这些类直接与 Optional 比较是不对的。 接下来我会继续编写 Vavr 主题的系列文章,介绍 Vavr 相关技术例如 Try、Collections 和 Streams。 敬请关注!
总结
本文中,我们讨论了 Java Optional 类。 Optional 并不是什么新概念,像 Haskell、Scala这样的函数式编程语言已经提供了实现。 调用方法后,返回值未知或者不存在(比如 null)的情况下,Optional 非常有用。 然后,介绍了 Optional API,并设计了一个汽车搜索示例进行说明。 最后,介绍了 Optional 的另一种替代方案 Vavr Option 并通过示例进行了介绍。