Java8新特性之Optional正确理解和用法

一、前言

Optional在Java 8中引入,目的是解决 NullPointerExceptions的问题,Optional 是一个可以为null的容器对象。它可以保存类型T的值,或者仅仅保存null。如果值存在,isPresent()方法会返回true,调用get()方法会返回该对象。

在Java 8之前,程序员将返回null而不是Optional,这种方法有一些缺点。一种是没有明确的方法来表示null可能是一个特殊值。

相比之下,在API中返回Optional是明确的声明,其中可能没有值。如果我们要确保不会出现空指针异常,则需要对每个引用进行显式的空检查,如下所示,我们之前的代码都这样写的:

MiniProgram miniProgram = accountProperties.getMiniProgram();
  if(Objects.nonNull(miniProgram)){
   WxMpTemplateMessage wxMpTemplateMessage = miniProgram.getWxMpTemplateMessage();
   if(Objects.nonNull(wxMpTemplateMessage)){
    ....
   }
  }

Optional 的好处在于可以简化平日里一系列判断 null 的操作,使得用起来的时候看着不需要判断 null,纵享丝滑,表现出来好像用 Optional 就不需要关心空指针的情况。Optional 其实就是一个壳,里面放着原先的值,至于这个值是不是 null 另说,反正拿到的这个壳肯定不是 null。

Java8新特性之Optional正确理解和用法

使用了optional语法简化如下:

String templateId= Optional.ofNullable(accountProperties.getMiniProgram())
    .map(MiniProgram::getWxMpTemplateMessage)
    .map(WxMpTemplateMessage::getTemplateId).orElse("");

可以看到,如果用了 Optional,代码里不需要判空的操作,即使 miniProgram 、wxMpTemplateMessage为空的话,也不会产生空指针错误,这就是 Optional 带来的好处!

二、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 对象可以通过以下方法构建:

2.1 empty( ):

返回一个空的 Optional 对象。

    private static final Optional<?> EMPTY = new Optional<>();
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

返回一个空的Optional实例,如果对象为空,请避免与Option.empty()返回的实例的{==}比较。因为不能保证它是一个单例。

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

2.2 of( ):

返回一个包含给定值的 Optional 对象,如果给定值为 null,则触发异常NullPointerException。

  public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

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

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

2.3 ofNullable( ):

返回一个描述给定值的 Optional 对象,如果不为 null,则返回一个包含该值的 Optional 对象,否则返回一个空的 Optional 对象。

 public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
Optional<String> optional = Optional.ofNullable(name());
 
  private  String  name(){
 
  String name = "Java";
 
  return (name.length() > 5) ? name : null;}

Optional 提供了以下主要方法:

2.4 isPresent():

检查 Optional 对象是否包含值,如果包含,则返回 true,否则返回 false。如果所包含的对象不为null,则返回true,反之返回false。

  public boolean isPresent() {        return value != null;    }

通常在对对象执行任何其他操作之前,先在Optional上调用此方法。

Optional<String> optional = Optional.of("java");
  if (optional.isPresent()) {
   //Do something, normally a get
  }

2.5 get():

获取 Optional 对象中的值(如果存在)。如果 Optional 对象为空,则抛出 NoSuchElementException 异常。这就需要 orElse() 方法来配合使用。

 public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
public static void main(String[] args) {
  Optional<String> optional1 = Optional.of("java");
  if (optional1.isPresent()){
   String value = optional1.get();
   System.out.println(value);
  }
 }

2.6 orElse( ):

返回 Optional 对象中的值(如果存在),否则返回一个默认值。

 public T orElse(T other) {
        return value != null ? value : other;
    }

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

public static void main(String[] args) {
  //orElse
  String nullName = null;
  String name = Optional.ofNullable(nullName).orElse("default_name");
  System.out.println(name);
 }

2.7 orElseGet( ):

返回 Optional 对象中的值(如果存在),否则获取一个可计算的值。

  public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

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

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

orElseGet 和 orElse 说明:

Optional 里有 orElseGet  和 orElse 这两个看起来挺相似的方法,都是处理当值为 null 时的兜底逻辑。可能你也在一些文章上看到说用 orElseGet 不要用 orElse ,因为在 Optional 有值时候 orElse 仍然会调用方法,所以后者性能比较差。其实从上面分析我们知道不论 Optional 是否有值,orElse 和 orElseGet  都会被执行,所以是怎么回事呢?

看下这个代码:Java8新特性之Optional正确理解和用法上面的的代码只有orElse()方法输出了字符,orElseGet ()方法并没有输出,难道结论有误。orElse 确实性能会差?

我们来看下源码。Java8新特性之Optional正确理解和用法可以看到两者的入参不同,一个就是普通参数,一个是 Supplier。我们已经得知不论Optional.ofNullable 返回的是否是空 Optional,下面的逻辑还是会执行,所以 orElse 和 orElseGet 这两个方法无论如何都会执行。

因此 orElse(getName1()) 会被执行,在参数入栈之前,执行了 getName1 方法得到结果,然后入栈,而 orElseGet 的参数是 Supplier,所以直接入栈,然后在调用 other.get 的时候,getName 方法才会被触发执行,这就是两者的区别之处。

所以才会造成上面表现出的性能问题,因此不是 BUG,也不是有些文章说的 Optional 有值 orElse 也会被执行而 orElseGet 不会执行这样不准确的说法,相信现在你的心里很有数了。

2.8 orElseThrow( ):

返回 Optional 对象中的值(如果存在),否则抛出一个异常。

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }

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

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

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

2.9 ifPresent()

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

public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

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

public static void main(String[] args) {
  Optional<String> optional = Optional.of("java");

  optional.ifPresent(s -> System.out.println(s.length()));
 }

3.0 filter()过滤值:

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

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

 public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

下面来看一个根据基本的账号密码登录验证来决定接受或拒绝 User(用户) 的示例:

public static void main(String[] args) {
  User user = new User("anna@gmail.com""1234", Lists.newArrayList());
  Optional<User> result = Optional.of(user)
    .filter(u -> u.getPassword() != null && u.getUsername().contains("@"));

  assertTrue(result.isPresent());
  System.out.println(result.get());
 }

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

3.1 map()转换值:

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

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}
public static void main(String[] args) {
  User user = new User("anna@gmail.com""1234", Lists.newArrayList());
  String result = Optional.of(user)
    .map(User::getUsername).orElse("account");
  System.out.println(result);
 }

3.2 flatMap():

Optional.flatMap() 是Java 8中引入的一个方法,用于处理Optional对象。这个方法接受一个函数作为参数,该函数应用于Optional对象中的值(如果存在),并返回一个新的Optional对象。

Optional.flatMap的作用是将一个Optional类型的对象映射为一个新的Optional类型的对象,可以同时去掉原对象中的空值。

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

例如,使用它可以将一个Optional类型的对象中的Optional类型的对象转换为另一个Optional类型的对象,避免空指针异常!

 public static void main(String[] args) {
  User user = new User("anna@gmail.com""1234", Lists.newArrayList());
  String result = Optional.of(user)
    .flatMap(s->Optional.of("aa"))
    .orElse("account");
  System.out.println(result);
 }

三、总结

Optional 是一个可以为null的容器对象,主要用于处理可能为null的情况,以避免出现空指针异常。Optional 类的使用场景非常广泛,例如:

  1. 方法返回值:当一个方法的返回值可能为null时,我们可以将方法的返回值类型改为Optional,这样就可以明确告诉调用者该方法可能返回一个空值。调用者在使用该方法时,就必须显式地处理Optional对象,从而避免空指针异常的发生。
  2. 集合操作:当我们对一个集合进行操作时,有时会遇到集合为空的情况。使用Optional可以避免这种情况的发生。我们可以将集合对象包装成Optional对象,这样就可以在集合为空时返回一个空的Optional对象,而不是null。调用者在使用该集合时,就必须显式地处理Optional对象,从而避免空指针异常的发生。

总的来说,Optional 是一个非常有用的工具,它可以为我们提供更优雅、更安全的代码实现。

原文始发于微信公众号(明月予我):Java8新特性之Optional正确理解和用法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272780.html

(0)
明月予我的头像明月予我bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!