讨论一个重要的Java主题-Optional类的用法-并将其与Vavr库中的替代方法进行比较。可选最初在Java 8中引入,并定义为“可能包含也可能不包含非null值的容器对象”。开发人员利用Optionals来避免在代NullPointerException。在这种情况下,Optional为我们提供了一些精美的功能,但并非所有功能都在第8版中引入,某些功能需要Java11。另一种解决这些问题的方法是使用Vavr的Option类。在本文中,我们将学习如何使用Java的Optional类,然后将其与Vavr的Option类进行比较。注意:此代码需要Java 11 +
Java Optional 简介
已经以功能性编程语言(如Haskell或Scala)实现了。在对方法调用可能返回未知值或不存在的值(例如null)的情况进行建模时,它被证明非常有用。
创建 Optional
首先,我们需要创建Optional的实例。有几种方法可以实现。我们还可以创建一个空的Optional。看看第一种方法,这非常简单:
Optional<Integer> four = Optional.of(Integer.valueOf(4));
if (four.isPresent){
System.out.println("Hoorayy! We have a value");
} else {
System.out.println("No value");
}
我们从4的Integer值构建一个Optional,这意味着始终应该有一个值并且它不能为null,但这只是一个例子。我们使用ifPresent()方法检查值是否存在。您可以注意到四个不是整数;它是一个容器,里面装有整数。当我们确定该值在内部时,可以使用get()获取值。如果我们不进行检查就使用get(),则可能NoSuchElementException结尾。获得可选参数的另一种方法是使用流。几种终端流的方法返回Optional,因此我们可以对其进行操作并检查其存在与否,例如:
- findAny
- findFirst
- max
- min
- reduce
代码片段
Optional<Car> car = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
创建 Nullable:
Optional<Integer> nullable = Optional.ofNullable(client.getRequestData());
创建 空 Optional:
Optional<Integer> nothing = Optional.empty();
使用 Optional
一种最普遍的情况是在Spring存储库中使用它来通过ID查找一条记录,因此我们可以在Optional上构建逻辑并避免空检查(顺便说一句,Spring还支持Vavr Options)。假设我们有一个图书库,想找到一本书。
Optional<Book> book = repository.findOne("some id");
首先,如果有这本书,我们可以执行一些逻辑。我们在上一节中使用if-else做到了这一点,但是我们不需要:可选为我们提供了一种接受对象的消费者的方法:
repository.findOne("some id").ifPresent(book -> System.out.println(book));
或者,我们可以使其更简单。我们可以使用方法引用:
repository.findOne("some id").ifPresent(System.out::println);
如果存储库中没有书籍,则可以使用theifPresentOrElseGet方法提供替代回调
repository.findOne("some id").ifPresentOrElseGet(book->{
// if value is presented
}, ()->{
// if value is absent
});
另外,如果未显示运算结果,我们可以设置默认值:
Book result = repository.findOne("some id").orElse(defaultBook);
但是,使用Optionals,我们需要记住可能的缺点。在最后一个示例中,我们“保证”自己无论如何都能获得一本书。它显示存储或来自orElse。但是,如果此默认值不是恒定的,但又需要一些复杂的方法怎么办?首先,Java无论如何都会评估findOne。然后,它必须处理orElse方法。是的,如果它只是一个默认常数,就可以了。
另一个实例
让我们创建一个简单的示例来检查如何实际使用Optional和Option类。我们将有一个CarRepository,可以根据提供的ID(例如在车牌号上)找到一辆汽车。然后,我们将看到如何操作Optional和Options。
首先,添加基础代码
它遵循不可变模式,因此所有字段都是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;
}
}
The second thing is to create the CarRepository class. It requires two options for finding car by Id — using the old way with a possible null result and using Optional, as we do in Spring repositories.
第二,是创建CarRepository类。findCarById 可能返回null.
public class CarRepository {
private List<Car> cars;
public CarRepository(){
getSomeCars();
}
Car findCarById(String id){
for (Car car: cars){
if (car.getId().equalsIgnoreCase(id)){
return car;
}
}
return null;
}
Optional<Car> 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"));
}
}
Java Optional 查询Car
测试代码片段
@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的汽车,并检查了该汽车的存在。
@Test
void getCarByIdWithOptional(){
Optional<Car> tesla = repository.findCarByIdWithOptional("1A9 4321");
tesla.ifPresent(System.out::println);
}
在这种情况下,我们使用findCarByIdWithOptional方法,然后打印Car(如果有的话)。如果运行它,将得到以下输出:
Car tesla with license id 1A9 4321 and of color red
但是,如果我们的代码中没有该特殊方法,该怎么办?在这种情况下,我们可以从可能返回空值的方法中获取Optional。它被称为可空的。
Optional<Car> 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 Option是处理这些任务的另一种方法。首先,使用依赖项管理在您的项目中安装Vavr(我使用Maven):
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.2</version>
</dependency>
简而言之,Vavr具有类似的API可创建Option实例。我们可以从nullable创建Option,如下所示:
Option<Car> nothing = Option.of(repository.findCarById("T 543 KK"));
或者我们可以使用一个静态方法创建一个空容器:
Option<Car> nullable = Option.none();
此外,还有一种方法可以从Java创建Option(可选)!看一下下面的代码片段:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
借助Vavr选项,我们可以使用与Optional相同的API来完成上述任务。例如,我们可以通过类似的方式设置默认值:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
Car skoda = result.getOrElse(new Car("skoda", "5E2 4232", "pink"));
System.out.println(skoda);
抛出异常
Option<Car> nullable = Option.none();
Assertions.assertThrows(NoSuchElementException.class, ()->{
nullable.getOrElseThrow(()->new NoSuchElementException());
});
或者,当数据不可用时,我们可以执行以下操作
nullable.onEmpty(()->{
///runnable
});
如果我们需要根据数据的存在来执行操作,就像我们使用Optional的ifPresent一样,该怎么办?我们可以通过几种方式做到这一点。在Option中有一个相等的方法isPresent,在Option中被称为isDefined:
if (result.isDefined()){
// do something
}
但是,我们使用Option来摆脱if-else构造。我们可以以与Optional相同的方式浮动吗?我们可以通过窥视的存在来执行操作:
result.peek(val -> System.out.println(val)).onEmpty(() -> System.out.println("Result is missed"));
此外,Vavr Option中还有其他一些非常有用的方法,这些方法可以使您的代码比内置的Optional类具有更多功能。因此,我鼓励您花一些时间来探索Vavr Option Javadocs并尝试使用这些API。
总结
在本文中,我们讨论了Java中的Optional类。可选的概念并不是什么新鲜事物,已经在其他功能编程语言(例如Haskell和Scala)中实现了。在对方法调用可能返回未知值或不存在的值(例如null)的情况进行建模时,它被证明非常有用。然后,我们探索了它的API,并创建了一些示例,这些示例使用可选逻辑查找汽车并处理结果。最后,我们找到了Optional-Vavr的Option的替代方法并描述了其方法。
希望你喜欢!在评论中留下想法或问题。