嗨,您好! 我今天想讨论一些基本的Java主题-Optional类的用法,并将其与Vavr库中的替代方法进行比较。 可选的方法最初是在Java 8中引入的,并且被定义为“可能包含也可能不包含非空值值的容器对象”。 开发人员利用Optionals以避免执行代码时对位置进行空值检查,这不是结果,而是空值价值,它可以导致空指针异常。 在这种情况下,Optional为我们提供了一些精美的功能,但并非所有功能都在第8版中引入,某些功能需要Java11。处理这些问题的另一种方法是Vavr的Option类。
在本文中,我们将学习如何使用Java Optional类,然后将其与Vavr Option类进行比较。 请注意,该代码至少需要Java 11并已通过Vavr进行了测试0。10。2。
Introducing Java Optional
Optional的概念并不是什么新鲜事物,并且已经在功能编程语言(例如Haskell或Scala)中实现。 在对方法调用可能返回未知值或不存在的值(例如null)的情况进行建模时,它被证明非常有用。 让我们看看如何处理它。
Creation of 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()方法。 您可以注意到,四不是整数,而是容器,在里面保存整数。 当我们确定该值在里面时,我们可以使用得到()方法。 具有讽刺意味的是,如果我们使用得到()无需检查,我们可以以NoSuchElementException结尾。
Another way to obtain an Optional is using streams. Several terminal stream's methods return Optionals, so we can manipulate them and checking existence or absence, for example:
- findAnyfindFirst最高分减少
检查以下代码片段:
Optional<Car> car = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
下一个选项是从代码创建Optional,可能产生空值,例如 来自Nullable:
Optional<Integer> nullable = Optional.ofNullable(client.getRequestData());
最后,我们可以创建一个空的可选的:
Optional<Integer> nothing = Optional.empty();
How to use Optional
只要获得了Optional,就可以使用它。 一种最普遍的情况是在Spring存储库中使用它通过id查找一条记录,因此我们可以在Optional上构建逻辑并避免进行空检查(顺便说一句,Spring还支持Vavr Options)。 假设我们有一个图书库,想找到一本书。
Optional<Book> book = repository.findOne("some id");
首先,我们可以执行一些逻辑,以防万一,被表达。 我们在上一节中使用if-else做到了这一点,但是我们不需要:Optional为我们提供了一种接受消费者与对象:
repository.findOne("some id").ifPresent(book -> {});
甚至更简单,我们可以使用方法引用来编写它:
repository.findOne("some id").ifPresent(System.out::println);
如果存储库中没有书籍,则可以使用isPresent OrElseGet方法:
repository.findOne("some id").ifPresentOrElseGet(book->{
// if value is presented
}, ()->{
// if value is absent
});
另外,如果没有给出运算结果,我们可以得到另一个值:
Book result = repository.findOne("some id").orElse(defaultBook);
但是,使用Optionals,我们需要记住可能存在的缺点。 在最后一个示例中,我们自己“保证”我们无论如何都会得到一本书,无论是呈现在底层存储库中还是来自要不然。 但是,如果默认值不是不变,还需要一些复杂的方法吗? 首先,Java无论如何评估找一个。 然后,它必须处理要不然 method. Yes, if it is just a default 不变 value it is ok, but it can be as I said before time-consuming as well.
Let have an example
让我们创建一个简单的示例来检查实际上如何使用Optional和Option类。 我们会有一个汽车储存库会根据提供的ID(例如,车牌号)找到一辆汽车。 然后,我们将看到如何操作Optional和Options。
Add some code first
从POJO类开始汽车。 它遵循一成不变的模式,因此所有字段都是最终字段,我们只有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;
}
}
第二件事是创建汽车储存库类。 按ID查找汽车将有两个选择-使用可能产生空结果的旧方法以及使用诸如Spring存储库的Optional。
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"));
}
}
注意,在初始化过程中,我们还会用一些模拟汽车填充存储库,因此我们没有任何底层数据库。 我们需要避免复杂性,让我们专注于Optional和Option,而不是存储库。
Finding cars with Java Optional
Create a new test with JUnit (I also have an excellent tutorial on JUnit 5 if you don't have experience with it).
@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并检查它是否存在。 然后我们发现一辆缺少的车,因为它有俄罗斯的车牌,而我们的仓库里只有捷克的车。 它是空值,因此可能会导致NullPointerException。
接下来,让我们转到Java Optionals。 第一步是使用返回Optional的专用方法从存储库获取Optional实例:
@Test
void getCarByIdWithOptional(){
Optional<Car> tesla = repository.findCarByIdWithOptional("1A9 4321");
tesla.ifPresent(System.out::println);
}
在这种情况下,我们使用findCarByIdWithOptional方法,然后打印汽车(如果有)。 如果运行它,将得到以下输出:
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可以返回空值如果找不到车。 我们手动使用否则带有牌照的所需汽车时抛出NoSuchElementException的方法5T1 0965存在。 另一种情况是使用要不然带有一些默认值,如果请求的数据在存储库中不可用:
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");
}
好的,我们的车库里没有银色的奥迪,所以我们必须叫出租车!
Finding cars with 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 Optional创建Option! 看一下下面的代码片段:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
使用Vavr Option,我们可以使用与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? 我们可以通过几种方式做到这一点。 有一个平等的方法存在在Optional中调用被定义为:
if (result.isDefined()){
// do something
}
但是,我们使用Option摆脱了if-else构造。 能否以与Optional相同的方式浮动? 我们可以根据存在与否执行操作窥视:
result.peek(val -> System.out.println(val)).onEmpty(() -> System.out.println("Result is missed"));
Also, there are some other very useful methods in Vavr Option that gonna make your code much more functional, than with built-in Optional class. So I encourage you to take some time and explore Vavr Option javadocs and experiment with these APIs. I can note some cool things like map, narrow, isLazy and when that you definetly need to check out. Also, Vavr Option is a part of Vavr family and is heavily integrated with Vavr other classes, and make such comparasion with Optional in vaccuum, e.g. in abscence of such classes is not correct. So, I would also write posts on other Vavr topics, like Try, Collections, Streams. Don't forget to sign up for updates!
Conclusion
在本文中,我们讨论了Java中的Optional类。 Optional的概念并不是什么新鲜事物,并且已经在功能编程语言(例如Haskell或Scala)中实现。 在对方法调用可能返回未知值或不存在的值(例如null)的情况进行建模时,它被证明非常有用。 让我们看看如何处理它。 我们探索了其API,并提供了一些示例,以查找汽车并使用Optional逻辑处理结果。 然后,我们发现了Optional-Vavr's Option的替代方法,并描述了其方法。
Read more
- José Paumard. Optionals: Patterns and Good Practices (2016) Oracle Community Directory, read here
- Mervyn McCreight and Mehmet Emin Tok. A look at the Optional datatype in Java and some anti-patterns when using it (2019) FreeCodeCamp, read here
- Radosław Nowak. Functional programming in Java with Vavr (2018) Pragmatists, read here