对于Java程序猿来说,NullPointerException肯定不会陌生,因为我们实在太讨厌他了,他可能是无处不在的。

String upperCaseName= user.getAddress()
                  .getCountry()
                  .getCountryName()
                  .toUpperCase();

当我们见到上面的代码,对于经验丰富的程序员来说肯定一眼就能看出这其中潜在空指针异常,如果想要解决就要

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String name= country.getCountryName();
            if (name!= null) {
                String upperCaseName= name.toUpperCase();
            }
        }
    }
}

这样一来解决了问题,但是代码的可读性就变得很差。Java8为我们引进了Optional类。我们可以看一下Optional的写法:

Optional.ofNullable(user)
       .map(User::getAddress)
       .map(Address::getCountry)
       .map(Country::getCountryName)
       .map(String::toUpperCase)
       .orElse(null);

可以看出来这种写法相当优雅漂亮可读性也很好。

什么是Optional

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空” 的Optional对象,由方法Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。你可能还有疑惑,null引用和Optional.empty() 有什么本质的区别吗?从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大: 如果你尝试解引用一个null ,一定会触发NullPointerException ,不过使用 Optional.empty()就完全没事儿,它是类的一个有效对象,多种场景都能调用,非常有用。

创建Optional对象

  1. 声明一个空的Optional

通过静态工厂方法Optional.empty,创建一个空的Optional 对象:

Optional<Car> optCar = Optional.empty();
  1. 依据一个非空值创建Optional

你还可以使用静态工厂方法Optional.of,依据一个非空值创建一个Optional对象:

Optional<Car> optCar = Optional.of(car);

如果car是一个null,这段代码会立即抛出一个NullPointerException,而不是等到你试图访问car的属性值时才返回一个错误。

  1. 可接受nullOptional

最后,使用静态工厂方法Optional.ofNullable,你可以创建一个允许null值的Optional对象:

Optional<Car> optCar = Optional.ofNullable(car);

如果carnull,那么得到的Optional对象就是个空对象。

API

isPresent():

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

如果值存在,返回 true,否则返回false,和get()搭配使用

map(Function):

如果实例非空,调用 Function Lambda 表达式。如果Optional包含一个值,那函数就将该值作为参数传递给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));
        }
    }

这里我们可以注意,如果mapper.apply()的结果是null那么会返回empty(),也就是Optional包装的空对象不用担心map吐出去一个空指针!如果有orElse()之类的直接会进入orElse()

flatMap(Function):

如果实例非空,调用 Function Lambda 表达式

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

map貌似和flatMap有点像,那么他们区别是什么?

Map()flatMap()
map方法的mapping函数返回值可以是任何类型T map中获取的返回值自动被Optional包装flatMap方法的mapping函数必须是Optional latMap中返回值保持不变,但必须是Optional类型
Optional.ofNullable(mapper.apply(value))Objects.requireNonNull(mapper.apply(value))

我们可以思考一下流当中的flatMap是不是也是这样类似呢。

filter(Predicate):

过滤方法,相当于if操作,满足条件往下执行不满足返回空Optional即跳到诸如orElse的操作中

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

举个例子:

        List<User> users = Lists.newArrayList();
        String filter = Optional.ofNullable(users)
                .filter(CollectionUtils::isNotEmpty)
                .map(users1 -> {
                    System.out.println("进入map");
                    return "map";
                })
                .orElse("进入orelse");
        //等同于
        String filterEqual = Optional.ofNullable(users)
                .map(users1 -> {
                    if (CollectionUtils.isEmpty(users1)) {
                        return null;
                    }
                    System.out.println("进入map");
                    return "map";
                })
                .orElse("进入orElse");

读取Optional实例中的变量值

get()

get()最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。 所以,除非你非常确定Optional 变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于嵌套式的null检查,也并未体现出多大的改进。

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

orElse():

如果Optional对象保存的值不是null,则返回原来的值,否则返回value。它允许你在Optional对象不包含值时提供一个默认值。

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

orElseGet():

orElse方法的延迟调用版,Supplier 方法只有在Optional对象不含值时才执行调用。如果创建默认值是件耗时费力的工作, 你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。

orElseThrow():

get方法非常类似, 它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。

ifPresent(Consumer)

变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

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

两个Optional对象的组合

现在,我们假设你有这样一个方法,它接受一个Person和一个Car对象,并以此为条件对外部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:

public Insurance findCheapestInsurance(Person person, Car car) {
// 不同的保险公司提供的查询服务
// 对比所有数据
return cheapestCompany;
}

我们还假设你想要该方法的一个null-安全的版本,它接受两个Optional对象作为参数, 返回值是一个Optional<Insurance>对象,如果传入的任何一个参数值为空,它的返回值亦为空。Optional类还提供了一个isPresent方法,如果Optional对象包含值,该方法就返回true, 所以你的第一想法可能是通过下面这种方式实现该方法:

public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) {
        if (person.isPresent() && car.isPresent()) {

            return Optional.of(findCheapestInsurance(person.get(), car.get()));
        } else {
            return Optional.empty();
        }
    }

这个方法具有明显的优势,我们从它的签名就能非常清楚地知道无论是person还是car,它的值都有可能为空,出现这种情况时,方法的返回值也不会包含任何值。不幸的是,该方法的具体实现和你之前曾经实现的null检查太相似了:方法接受一个Person和一个Car对象作为参数, 而二者都有可能为null。利用Optional类提供的特性,有没有更好或更地道的方式来实现这个方法呢?

public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) {
        return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
    }

这段代码中,你对第一个Optional对象调用flatMap方法,如果它是个空值,传递给它的Lambda表达式不会执行,这次调用会直接返回一个空的Optional对象。反之,如果person 对象存在,这次调用就会将其作为函数Function的输入,并按照与flatMap方法的约定返回一个Optional<Insurance>对象。这个函数的函数体会对第二个Optional对象执行map操作, 如果第二个对象不包含car,函数Function就返回一个空的Optional对象, 整个nullSafeFindCheapestInsuranc方法的返回值也是一个空的Optional对象。最后,如果personcar对象都存在,作为参数传递给map方法的Lambda表达式能够使用这两个值安全地调用原始的findCheapestInsurance方法,完成期望的操作。

流与Optional

我们开发过程中有时候经常会用到OptionalStream的结合。我们知道当list为空时是不会进入流执行,但是为null的时候调用stream是会有空指针异常的。所以我们就在orElse()上new一个list返回这样就可以将整个流程串起来了!

List<User> users = Lists.newArrayList();
        Optional.ofNullable(users)
                .filter(CollectionUtils::isNotEmpty)
                .orElse(Lists.newArrayList())
                .stream()
                .map(User::getAddress)
                .collect(Collectors.toList());

Optional使用建议

1.调用get()注意和isPresent()方法配合食用,否则可能会抛出异常

2.尽量不要将Optional类型用作属性或是方法参数,Optional 类型不可被序列化, 因为它也并未实现Serializable接口,所以用作字段类型可能会出问题

3.尽量进行链式调用,简化代码