Optional使用总结

我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。如下代码:

Author author = geAuthor();
if(author != null){
    System.out.println(author.getName());
}

尤其是对象的属性还是一个对象的情况下,这种判断会更多,而过多的判断语句会让我们的代码显得臃肿不堪。所以在 JDK8 中引入了 Optional 来解决空指针异常的问题,养成使用 Optional 的习惯后你可以写出更优雅的代码来避免空指针异常。并且在很多函数式编程相关的 API 中也都用到了 Optional ,学习 Optional 也对我们使用函数式编程有益。

开始使用

创建对象

java.util.Optional 是一个包装类,可以把具体的数据封装到 Optional 对象内部,然后我们去使用 Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。

ofNullable 方法

ofNullable方法用来存放对象,一般使用 Optional 的静态方法 ofNullable 来把数据封装成一个 Optional对象,无论传入的参数是否为 null 都不会出现问题。如下代码:

Person person = null;
Optional<Person> optional = Optional.ofNullable(person);

分析一下源码,当传入一个对象时会判断是否为空,如果为空就调用 empty 方法,不为空则调用 of 方法,of方法稍后会讲到,如下代码:

public final class Optional<T> {
  public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
  }
}

其中empty 静态方法相当于返回一个空的 Optional 对象,如下代码:

public final class Optional<T> {
  private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();
}

of方法

开发时如果确定一个对象不为空则可以使用 Optional 的静态方法 of 来把数据封装成 Optional 对象,如下代码:

Person person = new Person();
Optional<Person> optional = Optional.of(person);

但是一定要注意,如果使用 of 方法的时候传入的参数必须不为 null 。如果为 null,则会报空指针异常。如下代码:

Person person = null;
Optional<Person> optional = Optional.of(person);

异常错误:
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:221)
	at java.base/java.util.Optional.<init>(Optional.java:107)
	at java.base/java.util.Optional.of(Optional.java:120)
	at com.example.didipark.Application.main(Application.java:12)

分析源码可以看到在Optional.of方法中对入参做了非空判断,如下代码:

public final class Optional<T> {
  private Optional(T value) {
    this.value = Objects.requireNonNull(value);
  }
  
  public static <T> java.util.Optional<T> of(T value) {
    return new java.util.Optional<>(value);
  }
}

使用ofNullable 方法,当传入 null 时,也会使用 empty 方法返回一个空的 Optional 方法,所以推荐使用 ofNullable 方法而不是 empty 方法,这样省的我们再判断对象是否为空了,ofNullable 已经帮我们做了判空操作。

安全的消费值

我们获取到一个 Optional 对象后肯定需要对其中的数据进行使用,这个时候我们可以使用 ifPresent 方法来消费其中的值。

Person person = new Person();
Optional<Person> optional = Optional.of(person);
optional.ifPresent(
	new Consumer<Person>() {
	  @Override
	  public void accept(Person person) {
		log.info("person: {}", person);
	  }
	}
);

或者使用lambda表达式,如下代码:

Person person = new Person();
Optional<Person> optional = Optional.of(person);
optional.ifPresent(
		person1 -> log.info("person: {}", person1)
);

再或者使用isPresent()方法进行验证后使用,如下代码:

Person person = new Person();
Optional<Person> optional = Optional.of(person);
if(optional.isPresent()){
  // todo 
}

这了要注意区分ifPresentisPresent

安全的获取值

不推荐的做法

当我们想要获取 Optional 封装的对象自己进行处理可以使用 Optional 对象提供的 get 方法进行获取,但是不推荐。因为当 Optional 内部的数据为空的时候会抛出异常,如下代码:

Person person = null;
Optional<Person> optional = Optional.of(person);
Person dbPerson = optional.get();

异常错误:
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:221)
at java.base/java.util.Optional.<init>(Optional.java:107)
at java.base/java.util.Optional.of(Optional.java:120)
at com.example.didipark.Application.main(Application.java:12)

如果一定要使用get方法进行获取,可以在get之前使用isPresent判断。

通过分析get源代码后得知,在get方法中会对取出的值做判断,如果为空则抛出NoSuchElementException异常,如下代码:

public class Optional<T> {
  public T get() {
    if (value == null) {
      throw new NoSuchElementException("No value present");
    }
    return value;
  }
}

推荐的做法

如果我们期望安全的获取值,不推荐使用 get 方法,而是使用 Optional 提供的orElseGetorElseorElseThrow方法。

orElseGet

获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据你传入的参数创建对象作为默认值返回,如下代码:

Person person = null;
Optional<Person> optional = Optional.ofNullable(person);
Person dbPerson =
	optional.orElseGet(
		new Supplier<Person>() {
		  @Override
		  public Person get() {
			return new Person(2, "lisi");
		  }
		}
	);
log.info("dbPerson: {}", dbPerson);

[main] INFO com.example.didipark.Application - dbPerson: Person(id=2, name=lisi)

分析一下源码,当使用 orElseGet 方法获取值时,会进行判断。如果值为空,则返回我们实现Supplier 接口中的 get 方法的返回值(默认值);如果不为空返回真正的值,如下代码:

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

orElse

获取数据时直接传入默认值,当数据为空时返回默认值,如下代码:

Person person = null;
Optional<Person> optional = Optional.ofNullable(person);
Person dbPerson = optional.orElse(new Person(2, "lisi"));
log.info("dbPerson: {}", dbPerson);

比较一下 orElseGetorElseorElseGet 方法需要实现 get 方法,其返回值作为默认值,而 orElse 更直接,直接传入值作为默认值。但是通过 orElseGet 方法我们可以进行一些处理返回默认值。两者的使用情况可以根据使用场景来决定。如下orElse源码实现:

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

orElseThrow

获取数据,如果数据不为空就能获取到数据,如果为空则根据你传入的参数来抛出异常。如下代码所示:

Person person = null;
Optional<Person> optional = Optional.ofNullable(person);
Person dbPerson = optional.orElseThrow();
log.info("dbPerson: {}", dbPerson);

同时orElseThrow还支持自定义异常方式,如下代码:

Person person = null;
Optional<Person> optional = Optional.ofNullable(person);
Person dbPerson =
	optional.orElseThrow(
			(Supplier<Throwable>) () -> new RuntimeException("异常了!!")
	);
log.info("dbPerson: {}", dbPerson);

为什么有orElseThrow这个方法呢?

比如我们在使用 Spring 框架的时候,我们会进行异常的统一处理,使用 orElseThrow 方法,当值为空时抛出异常,统一异常处理器捕获到异常,封装成特定信息,返回给前端,便于我们的控制和扩展。

filter

我们使用 filter 方法对数据进行过滤,用来判断数据是否满足要求,如下代码:

Person person = new Person(1, "lisi");
Optional<Person> optional = Optional.ofNullable(person);
optional
	.filter(
		new Predicate<Person>() {
		  @Override
		  public boolean test(Person person) {
			return person.getName().equals("lisi");
		  }
		}
	)
	.ifPresent(
		new Consumer<Person>() {
		  @Override
		  public void accept(Person person) {
			  log.info("lisi yes!");
		  }
		}
	);

上面代码意思是在Optional<Person>中过滤name是否等于lisi,如果等于lisi则输出lisi yes,否则不输出任何内容。如下所示filter核心代码:

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

映射

Optional中提供了两种映射mapflatMap,它们的目的就是从对象中再次取出一个映射结果。

map

map 可以让我们对数据进行转换,并且转换得到的数据还是被 Optional 包装好的,保证了我们的使用安全。map 相当于一种映射。如下代码:

List<Book> list = new ArrayList<>();
list.add(Book.builder().id(1).name("语文").build());
list.add(Book.builder().id(2).name("数学").build());
Person person = new Person(1, "lisi", list);
Optional<Person> optional = Optional.ofNullable(person);
Optional<List<Book>> optionalBooks =
	optional.map(
		new Function<Person, List<Book>>() {
		  @Override
		  public List<Book> apply(Person person) {
			return person.getBooks();
		  }
		});
List<Book> books = optionalBooks.get();
log.info(">> books: {}", books);

调用 map 方法,首先会通过 Objects.requireNonNull 方法判断是否实现了 Function 接口,然后再通过 isPresent 方法判断 Optional 对象中是否有值,没有值的话,就调用 empty 方法返回一个空的 Optional 对象;有值的话,就执行我们实现的 apply 返回一个由 ofNullable 包装的 Optional 对象。如下代码:

public class Optional<T> {
    public <U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return java.util.Optional.ofNullable(mapper.apply(value));
        }
    }
}

flatMap

flatMap和map的作用基本上一样,也都是提供数据转换和映射功能,在源代码实现上有略微差别,如下代码:

1、基于flatMap实现:

Person person = new Person(1, "lisi");
Optional<Person> optional = Optional.ofNullable(person);
Optional<Integer> resultOption = optional.flatMap(new Function<Person, Optional<Integer>>() {
  @Override
  public Optional<Integer> apply(Person person) {
	return Optional.ofNullable(person.getId());
  }
});
log.info(">> userId: {}", resultOption.orElseGet(() -> 0));

2、基于map实现:

Person person = new Person(1, "lisi");
Optional<Person> optional = Optional.ofNullable(person);
Optional<Integer> resultOption =
	optional.map(
		new Function<Person, Integer>() {
		  @Override
		  public Integer apply(Person person) {
			return person.getId();
		  }
		});
log.info(">> userId: {}", resultOption.orElseGet(() -> 0));

flatMapmap唯一的区别就是在apply方法中,flatMap需要使用Optional进行包装,而map则直接返回。

如下所示flatMap代码实现:

public class Optional<T> {
  public <U> java.util.Optional<U> flatMap(
      Function<? super T, ? extends java.util.Optional<? extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
      return empty();
    } else {
      @SuppressWarnings("unchecked")
      java.util.Optional<U> r = (java.util.Optional<U>) mapper.apply(value);
      return Objects.requireNonNull(r);
    }
  }
}

总结

Optional 帮我们优雅的解决了 Java 的空指针异常问题,让我们的程序更健壮。

经过上述的分析,Optional 的使用不是非常复杂,只需要知道如何使用 Optional 创建对象,安全的消费值、获取值,就能在大多数场景使用了。

完!!


版权声明:本文为qusikao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>