这是我参与8月更文挑战的第8天,活动详情查看: 8月更文挑战
想必这种错误,大家应该随时随地都可以见到吧?
Exception in thread "main" java.lang.NullPointerException
at com.silly.demo.jdk8.OptionalDemo.main(OptionalDemo.java:10)
不管你是青铜小白,还是骨灰级的王者,这个异常类:NullPointerException 应该是经常见到,而且让人头疼,特别是遇到线上环境,日志打印不是非常的完善,突然给你单单的来一下上面那样的空指针异常,估计此时的你一定在抓狂吧。 为了防止空指针异常的出现,Java8中引入了一个新类Optional,在使用Stream流式编程的时候,这个类比较常见。在使用的时候,将结果封装到Optional类中,然后通过提供的API操作,获取值,或者进行空值判断,如果有值的时候就返回值,没有值的时候就返回Empty。下面我们就一起来看怎么去使用它和它的实现原理。 看这个类整个的源码才70行,可能有时候你自己写个方法都不止70行吧,大概看一眼基本上都能够看明白它的实现原理,接的我们一步步来分析里面的每个API的使用方法及用途。
创建对象
要向使用这个对象,得先创造出这个对象,不然咋个去使用它呢?首先想到的是new创建,但是一看构造函数是private的,没法new,那肯定只能通过它提供的静态方法来创建对象了。
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
看源码,提供了3个方法用于创建对象,第一个empty(),只是返回一个空的Optional实例,内部没有存在任何值;ofNullable方法内部对传入的值做了个判断,如果值为空,则创建一个空的optional,不为空则调用of()来创建对象;在看of() 方法,直接用了new来创建对象,我接着进入构造函数来查看
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
of中的构造函数赋值的时候调用了requireNonNull来对传入的值进行空值判断,如果传入的值为空,则抛出NullPointerException,从这里就可以看出of创建的对象不允许为空,如果为空在创建的时候就抛出了异常,而ofNullable是允许为空的存在。
空值判断
在对象创建的时候,如果传入的值为空,那肯定在使用的时候需要判断是否为空值,该怎么操作呢?不要急,我们接着源码继续找对应的方法,总共也就70行代码,刚刚创建对象都去了10多行了。
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
看这两个方法只有一个字的差别,但是用却比较大,isPresent 只是相当于我们的if(obj == null) 这样的实现逻辑,ifPresent的实现中对value不等于空的时候,执行我们传入的函数接口方法逻辑,简化了我们if else的写法
Optional.ofNullable(student).ifPresent(s -> System.out.println("传入对象不为空"));
值获取
更多时候我们需要拿到里面的值,进行后续的业务逻辑操作,Optional提供了get()来获取值
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
get()的使用非常简单,但不安全,因为其在获取值的时候,如果值存在,则直接返回封装在Optional中的值,如果不存在,则抛出NoSuchElementException。因此它的使用前提是已经确定Optional中有值,否则没有使用意义。在更多时候我们需要自定义异常信息,然后通过AOP来对异常进行统一的处理,get抛出的异常只有NoSuchElementException,有些时候无法来区分异常处理,这个时候就需要用到**orElseThrow()**来自定义异常信息
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
orElseThrow()与get()一样都是用于取值,但是当Optional中没有值时,get()会直接抛出NoSuchElementException,这样的话,就存在了一定的局限性,因为有时可能需要抛出自定义异常。此时就可以使用orElseThrow(),它在取值时,如果Optional中没有值时,可以抛出自定义异常。 又有另外一种情况,获取不到值,想自己重新创建一个对象的话,也提供了对应的方法
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
Java8最大的亮点我觉得就是链式编程的思想,在Optional中也体现了链式编程的思想,它也有map这样的方法,从值中获取它的某一个属性,map的用于与Stream一样
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));
}
}
通过map的话,我们可以取得学生的姓名这样的属性,但是存在多级对象嵌套的时候,比如:学生-->学校-->班级-->班主任,这个多级链式获取,map就无能为力了,肯定这个时候想到的就是map().map().map() 这样来调用,但是在使用的时候这段代码事无法编译通过的,因为map在每次调用的时候,都会对Optional泛型进行改变,最终会产生多次Optional嵌套的结构。对于这样的问题,Optional提供了另外一种方法法flatMap()。它本身用于多层调用,同时对于结果它不会形成多个Optional,而是将结果处理成最终的一个类型的Optional。但是通过flatMap获取的返回值必须是Optional类型。而map可以返回直接的数据类型。
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类还提供了其他的方法,例如:filter来对属性的值进行条件判断,由于用起来及源码都比较简单,就不一一去举例,总之就是Optional比较简单,用起来也比较上手,里面的源码一看就懂。