ITPub博客

首页 > 应用开发 > Java > Vavr Option:Java Optional 的另一个选项

Vavr Option:Java Optional 的另一个选项

原创 Java 作者:专注的阿熊 时间:2019-11-06 10:57:12 0 删除 编辑

每当涉及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<Integer> 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 

查看下面的代码段:

Optional<Car> car = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();

方法三,使用 Nullable 新建 Optional。可能产生 null:

Optional<Integer> nullable = Optional.ofNullable(client.getRequestData());

最后,可以新建一个 empty Optional:

Optional<Integer> nothing = Optional.empty();

如何使用 Optional

获得 Optional 对象后即可使用。一种典型的场景是在 Spring 仓库中根据 Id 查找记录。可以使用 Optional 实现代码逻辑,避免 null 检查(顺便提一下,Spring 也支持 Vavr Option)。比如,从图书仓库里查找一本书。

Optional<Book> book = 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<Car> 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<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。

使用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<Car> 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<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 OptionOption提供了另一种解决办法。首先,在项目中添加依赖,(使用 Maven)安装 Vavr:

<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"));

也可以用 none 静态方法创建一个empty容器:

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 中 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 并通过示例进行了介绍。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69946337/viewspace-2662859/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-08-23

  • 博文量
    38
  • 访问量
    18412