Java8 Optional用法

  根据Oracle文档,Optional是一个容器对象,可以包含也可以不包含非null值。Optional在Java 8中引入,目的是解决 NullPointerExceptions的问题。本质上,Optional是一个包装器类,其中包含对其他对象的引用。在这种情况下,对象只是指向内存位置的指针,并且也可以指向任何内容。从其它角度看,Optional提供一种类型级解决方案来表示可选值而不是空引用。
  在Java 8之前,程序员将返回null而不是Optional。这种方法有一些缺点。一种是没有明确的方法来表示null可能是一个特殊值。相比之下,在API中返回Optional是明确的声明,其中可能没有值。如果我们要确保不会出现空指针异常,则需要对每个引用进行显式的空检查,如下所示,我们都同意这是很多样板。
  
// Life before Optional
    private void getIsoCode( User user){
        if (user != null) {
            Address address = user.getAddress();
            if (address != null) {
                Country country = address.getCountry();
                if (country != null) {
                    String isocode = country.getIsocode();
                    if (isocode != null) {
                        isocode = isocode.toUpperCase();
                    }
                }
            }
        }
    }

  Optional的特性

public final class Optional<T> {
    //Null指针的封装
    private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();
    //内部包含的值对象
    private final T value;
    private Optional() ;
    //返回EMPTY对象
    public static<T> java.util.Optional<T> empty() ;
    //构造函数,但是value为null,会报NPE
    private Optional(T value);
    //静态工厂方法,但是value为null,会报NPE
    public static <T> java.util.Optional<T> of(T value);
    //静态工厂方法,value可以为null
    public static <T> java.util.Optional<T> ofNullable(T value) ;
    //获取value,但是value为null,会报NoSuchElementException
    public T get() ;
    //返回value是否为null
    public boolean isPresent();
    //如果value不为null,则执行consumer式的函数,为null不做事
    public void ifPresent(Consumer<? super T> consumer) ;
     //过滤,如果value不为null,则根据条件过滤,为null不做事
    public java.util.Optional<T> filter(Predicate<? super T> predicate) ;
     //转换,在其外面封装Optional,如果value不为null,则map转换,为null不做事
    public<U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper);
     //转换,如果value不为null,则map转换,为null不做事
    public<U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) ;
    //value为null时,默认提供other值
    public T orElse(T other);
      //value为null时,默认提供other值
    public T orElseGet(Supplier<? extends T> other);
      //value为null时,默认提供other值
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) ;
}

  

Optional类提供了大约10种方法,我们可以使用它们来创建和使用Optional类,下面将介绍如何使用它们。

创建一个Optional类

这是用于创建可选实例的三种创建方法。

  1. static <T> [Optional]<T> [empty]()

返回一个空的Optional实例。

// Creating an empty optional
Optional<String> empty = Optional.empty();

在返回一个空的{Optional}实例时,Optional的值不存在。不过,这样做可能很有诱惑力,如果对象为空,请避免与Option.empty()返回的实例的{==}比较 。因为不能保证它是一个单例,反之,应该使用isPresent()。

  1. static <T> [Optional]<T> [of](T value)

返回特定的非空值Optional。

// Creating an optional using of

String name = "java";

Optional<String> opt = Optional.of(name);

静态方法需要一个非null参数;否则,将引发空指针异常。因此,如果我们不知道参数是否为null,那就是我们使用 ofNullable的时候,下面将对此进行介绍。

  1. static <T> [Optional]<T> [of](T value)

返回描述指定值的Optional,如果非空,则返回空值。

// Possible null value

 Optional<String> optional = Optional.ofNullable(name());

  private  String  name(){

  String name = "Java";

  return (name.length() > 5) ? name : null;

 }

如果我们传入一个空引用,它不会抛出异常,而是返回一个空的Optional对象:

所以这就是动态或手动创建Optional的三种方法。下一组方法用于检查值的存在。

  1. 布尔值[isPresent]()

如果存在值,则返回true;反之,返回false。如果所包含的对象不为null,则返回true,反之返回false。通常在对对象执行任何其他操作之前,先在Optional上调用此方法。

//ispresent

Optional<String> optional1 = Optional.of("javaone");

if (optional1.isPresent()){

//Do something, normally a get

}

布尔值[isEmpty()]

如果存在值,则返回false;否则,返回ture。这与isPresent 相反, 并且仅在Java 11及更高版本中可用。

//isempty

Optional<String> optional1 = Optional.of("javaone");

if (optional1.isEmpty()){

  //Do something

}
  1. void [ifPresent]([Consumer]<? super [T]> consumer)

如果存在值,则使用该值调用指定的使用者;否则,什么都不做。

如果您不熟悉Java 8,那么您可能会想知道:什么是消费者?简单来说,消费者是一种接受参数且不返回任何内容的方法。当使用 ifPresent时,这个方法就是一石二鸟。我们可以执行值存在性检查并使用一种方法执行预期的操作,如下所示。

//ifpresent

Optional<String> optional1 = Optional.of("javaone");

optional1.ifPresent(s -> System.out.println(s.length()));
可选类提供了另一组用于获取可选值的方法。
  1. T[get]()

如果此Optional中存在值,则返回该值,否则抛出 NoSuchElementException。在这之后,我们想要的是存储在Optional中的值,我们可以通过get()来获取它。但是,当该值为null时,此方法将引发异常。这就需要 orElse() 方法来紧急救援。

//get
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){ 
  String value = optional1.get();
}
  1. [T ][orElse][T]其他)

返回值(如果存在);反之,返回其他。

该 orElse() 方法用于检索包装在Optional实例内的值。它采用一个充当默认值的参数。该 orElse() 方法返回包装的值(如果存在)及其参数,反之:

 //orElse
        String nullName = null;
        String name = Optional.ofNullable(nullName).orElse("default_name");

如果这还不够,那么Optional类将继续提供另一种获取值的方法,即使该方法的null称为 orElseGet()。

  1. [T][orElseGet]([Supplier]<? extends [T]> other)

返回值(如果存在);否则,调用other并返回该调用的结果。

该orElseGet() 方法类似于 orElse()。但是,如果没有Optional值,则不采用返回值,而是采用供应商功能接口,该接口将被调用并返回调用的值:

 //orElseGet
        String name = Optional.ofNullable(nullName).orElseGet(() -> "john");

那么,orElse() 和orElseGet()之间有什么区别。

乍一看,这两种方法似乎具有相同的效果。但是,事实并非如此。让我们创建一些示例,以突出两者之间的相似性和行为差异。

首先,让我们看看它们在对象为空时的行为:

String text = null;
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue);
defaultText = Optional.ofNullable(text).orElse(getDefaultValue());
public String getDefaultValue() {
    System.out.println("Getting Default Value");
    return "Default Value";
}
在上面的示例中,我们在Optional对象中包装了一个空文本,然后尝试使用两种方法中的每一种来获取包装后的值。副作用如下:
Getting default value...
Getting default value...
在每种情况下都会调用默认方法。碰巧的是,当不存在包装的值时,两者 orElse() 和的 orElseGet() 工作方式完全相同。

现在,让我们运行另一个该值存在测试,理想情况下,甚至不应创建默认值:

在这个简单的示例中,创建默认对象不会花费很多成本,因为JVM知道如何处理此类对象。但是,当诸如此类的方法 default 必须进行Web服务调用或者查询数据库时,则成本变得非常明显。

1,创建 Optional 实例
重申一下,这个类型的对象可能包含值,也可能为空。你可以使用同名方法创建一个空的 Optional。

Optional<User> emptyOpt = Optional.empty();
emptyOpt.get();

毫不奇怪,尝试访问 emptyOpt 变量的值会导致 NoSuchElementException。

你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:

Optional<User> opt = Optional.of(user);

因此,你应该明确对象不为 null 的时候使用 of()。

如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

2,访问 Optional 对象的值

从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

String name = "John";
Optional<String> opt = Optional.ofNullable(name);

assertEquals("John", opt.get());

不过,你看到了,这个方法会在值为 null的时候抛出异常。要避免异常,你可以选择首先验证是否有值:

User user = new User("john@gmail.com", "1234");
Optional<User> opt = Optional.ofNullable(user);
assertTrue(opt.isPresent());

assertEquals(user.getEmail(), opt.get().getEmail());

检查是否有值的另一个选择是 ifPresent() 方法。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

这个例子中,只有 user 用户不为 null 的时候才会执行断言。

接下来,我们来看看提供空值的方法。

3,返回默认值
Optional类提供了API用以返回对象值,或者在对象为空的时候返回默认值:orElse(),
如果有值则返回该值,否则返回传递给它的参数值:

User user2 = new User("anna@gmail.com", "1234");
User result = Optional.ofNullable(user).orElse(user2);

assertEquals(user2.getEmail(), result.getEmail());

这里 user 对象是空的,所以返回了作为默认值的 user2。

如果对象的初始值不是 null,那么默认值会被忽略:

User user = new User("john@gmail.com","1234");
User user2 = new User("anna@gmail.com", "1234");
User result = Optional.ofNullable(user).orElse(user2);

assertEquals("john@gmail.com", result.getEmail());

第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

4,orElse() 和 orElseGet() 的不同之处

乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。

我们先来看看对象为空时他们的行为:

复制代码
User user = null
logger.debug("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.debug("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());


private User createNewUser() {
logger.debug("Creating New User");
return new User("extra@gmail.com", "1234");
}
复制代码

上面的代码中,两种方法都调用了 createNewUser() 方法,这个方法会记录一个消息并返回 User 对象。

代码输出如下:

Using orElse
Creating New User
Using orElseGet
Creating New User

由此可见,当对象为空而返回默认对象时,行为并无差异。

我们接下来看一个类似的示例,但这里 Optional 不为空:

复制代码
@Test
public void givenPresentValue_whenCompare_thenOk() {
  User user = new User("john@gmail.com", "1234");
  logger.info("Using orElse");
  User result = Optional.ofNullable(user).orElse(createNewUser());
  logger.info("Using orElseGet");
  User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
复制代码

这次的输出:

Using orElse
Creating New User
Using orElseGet

这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。

在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。

5,返回异常
除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:

User result = Optional.ofNullable(user).orElseThrow( () -> new IllegalArgumentException());

这里,如果 user 值为 null,会抛出 IllegalArgumentException。

这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。

现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。

6,转换值
有很多种方法可以转换 Optional 的值。我们从 map() 和 flatMap() 方法开始。

先来看一个使用 map() API 的例子:

User user = new User("anna@gmail.com", "1234");
String email = Optional.ofNullable(user)
.map(u -> u.getEmail()).orElse("default@gmail.com");

assertEquals(email, user.getEmail());

map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能 —— 这里的下一环就是 orElse()。

相比这下,flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果。

下面的操作中,我们给 User 类添加了一个方法,用来返回 Optional:

复制代码
public class User { 
    private String position;

    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }

//...
}        
复制代码

既然 getter 方法返回 String 值的 Optional,你可以在对 User 的 Optional 对象调用 flatMap() 时,用它作为参数。其返回的值是解除包装的 String 值:

User user = new User("anna@gmail.com", "1234");
user.setPosition("Developer");
String position = Optional.ofNullable(user)
.flatMap(u -> u.getPosition()).orElse("default");

assertEquals(position, user.getPosition().get());

7,过滤值

除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。

filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。

来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:

User user = new User("anna@gmail.com", "1234");
Optional<User> result = Optional.ofNullable(user)
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

assertTrue(result.isPresent());

如果通过过滤器测试,result 对象会包含非空值。

8,Optional 类的链式方法
为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。

我们使用 Optional 重写最早介绍的示例。

首先,重构类,使其 getter 方法返回 Optional 引用:

复制代码
public class User {
    private Address address;

    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }

    // ...
}
public class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }

 // ...
}
复制代码

现在可以删除 null 检查,替换为 Optional 的方法:

复制代码
@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");

    String result = Optional.ofNullable(user)
    .flatMap(u -> u.getAddress())
    .flatMap(a -> a.getCountry())
    .map(c -> c.getIsocode())
    .orElse("default");

    assertEquals(result, "default");
}
复制代码

上面的代码可以通过方法引用进一步缩减:

String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");

结果现在的代码看起来比之前采用条件分支的冗长代码简洁多了。

四,Java 9 增强
我们介绍了 Java 8 的特性,Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()。

or() 方法与 orElse() 和 orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。

如果对象包含值,则 Lambda 表达式不会执行: 

User result = Optional.ofNullable(user)
.or( () -> Optional.of(new User("default","1234"))).get();

assertEquals(result.getEmail(), "default"); 

上面的示例中,如果 user 变量是 null,它会返回一个 Optional,它所包含的 User 对象,其电子邮件为 “default”。

ifPresentOrElse() 方法需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable。


如果你想在有值的时候执行某个动作,或者只是跟踪是否定义了某个值,那么这个方法非常有用:

Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),
() -> logger.info("User not found"));

最后介绍的是新的 stream() 方法,它通过把实例转换为 Stream 对象,让你从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。

我们来看一个把 Optional 处理成 Stream 的例子: 

复制代码
User user = new User("john@gmail.com", "1234");
List<String> emails = Optional.ofNullable(user)
.stream()
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
.map( u -> u.getEmail())
.collect(Collectors.toList());

assertTrue(emails.size() == 1);
assertEquals(emails.get(0), user.getEmail());
复制代码

这里对 Stream 的使用带来了其 filter()、map() 和 collect() 接口,以获取 List。

使用Optional最佳实践

就像编程语言的任何其他功能一样,它可以正确使用或被滥用。为了了解使用Optional类的最佳方法,需要了解以下内容:

1.它解决的问题

Optional的方法是尝试通过增加构建更具表现力的API的可能性来减少Java系统中空指针异常的情况,这些API解释了有时缺少返回值的可能性。

如果从一开始就存在Optional,那么大多数库和应用程序可能会更好地处理缺少的返回值,从而减少了空指针异常的数量以及总体上的错误总数。

2.它不解决的问题

Optional并不意味着是一种避免所有类型的空指针的机制。例如,它仍然必须测试方法和构造函数的强制输入参数。

像使用null时一样,Optional不能帮助传达缺失值的含义。以类似的方式,null可能意味着很多不同的东西(找不到值等),因此缺少Optional值也可以。

该方法的调用方仍然需要检查该方法的JavaDoc以理解缺省选项的含义,以便正确地处理它。

同样,以一种类似的方式,可以将检查的异常捕获在一个空块中,没有什么阻止调用方进行调用 get() 并继续进行。

3.何时使用

Optional的预期用途主要是作为返回类型。获取此类型的实例后,可以提取该值(如果存在)或提供其他行为(如果不存在)。

Optional类的一个非常有用的用例是将其与流或返回Optional值以构建流畅的API的其他方法结合。请参见下面的代码段

User user = users.stream().findFirst().orElse(new User("default", "1234"));
4.什么时候不使用

a)不要将其用作类中的字段,因为它不可序列化

如果确实需要序列化包含Optional值的对象,则Jackson库提供了将Optionals视为普通对象的支持。这意味着Jackson将空对象视为空,将具有值的对象视为包含该值的字段。可以在jackson-modules-java8项目中找到此功能。

b)不要将其用作构造函数和方法的参数,因为这会导致不必要的复杂代码。

User user = new User("john@gmail.com", "1234", Optional.empty());

  



转载链接:https://www.jianshu.com/p/362010f310b9
原文地址:https://www.cnblogs.com/yuarvin/p/13555143.html