Optional是JDK8引入的新特性,主要用于解决Java程序中对于null的处理。很多时候,调用一个方法获取的返回值为null,不能直接作为参数去调用其他方法,因此需要使用大量的非空判断,特别是在"."运算符中。但往往我们并不能保证100%去对一个值做非空判断,即使都使用判断,也可能会影响代码本身的质量。JDK8引入的Optional则很好的解决了这个问题。

Javadoc中是这样表述Optional类的:

这是一个可以为null的容器对象。如果值存在,则isPresent()方法返回true,并且调用get()方法可以返回这个值。

    下面,我将根据源码和例程详细介绍一下Optional的功能和用途。首先需要知道的是,Optional的构造方法是私有的,所以不能使用new Optional<T>()的形式创建Optional对象,Optional中创建了一个EMPTY的单例对象,这个对象没有指定值。

一、Optional主要方法


1. empty()

    源码如下:

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

    返回一个空的Optional对象,这个对象没有值。

2. of()

    源码如下:

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

    根据所传入的value返回一个Optional对象,如果value为null,则会跑出空指针异常NullPointException。

3. ofNullable()

    源码如下:

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

    根据value的值返回一个Optional对象,如果value为null,则返回一个没有值的Optional对象EMPTY。

4. get()

    源码如下:

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

    如果Optional有值,则返回这个值;否则抛出NoSuchElementException异常。

5. isPresent()

    源码如下:

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

    如果Optional对象的值存在,则返回true,否则返回false。

6. ifPresent()

    源码如下:

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

    如果Optional对象有值则为其调用consumer,否则不做任何处理。Consumer是一个接口,包含一个accept()方法和default域的方法,accept()方法对传入的值进行处理,但没有返回值。Java8中支持不用接口直接通过lambda表达式传入参数。

7. filter()

    源码如下:

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

    这个方法通过传入的限定条件对Optional对象的值进行过滤。如果Optional有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。可以传入实现了Predicate接口的lambda表达式。

8. map()

    源码如下:



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

    如果有值,则对其执行调用mapping函数得到返回值;如果返回值不为null,则创建包含mapping返回值的Optional作为map()的返回值,否则返回空Optional。

9. flatMap()

    源码如下:

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

    如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap()和map()类似,区别在于flatMap()中的mapper必须是Optional,调用结束后,flatMap()不会对结果用Optional封装。

10. orElse()

    源码如下:

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

    如果Optional对象有值,则返回该值;否则返回指定的值。

11. orElseGet()

    源码如下:

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

    该方法与orElse()方法类似,其区别在于得到默认值的方法。orElse()是直接传入特定的值作为默认值,而orElseGet()方法可以通过实现Supplier接口的lambda表达式来生成默认值。

12. orElseThrow()

    源码如下:

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

    如果Optional对象有值,则将其返回;否则抛出Supplier接口创建的异常。

    以上就是Optional主要的方法,还需要注意的是,Optional还覆写了equals()、hashCode()、toString()方法。

    源码如下:

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }

    if (!(obj instanceof Optional)) {
        return false;
    }

    Optional<?> other = (Optional<?>) obj;
    return Objects.equals(value, other.value);
}

@Override
public int hashCode() {
    return Objects.hashCode(value);
}
@Override
public String toString() {
    return value != null
        ? String.format("Optional[%s]", value)
        : "Optional.empty";
}

    以上介绍了Optional中的所有的方法及其实现。下面将介绍一下如何较好的使用Optional解决NullPointException问题。这里,需要注意的是,如何巧妙的使用Optional解决问题。

二、Optional的使用


     首先我们看下面一个例子:



@Test
public void _test_badUsed() {
    OptionalEntity oe = new OptionalEntity();
    Optional<String> value = Optional.ofNullable(oe.getValue());
    if (value.isPresent()) {
        System.out.println(value.get());
    } else {
        System.out.println("null");
    }
}

    在这个例子中,OptionalEntity类定义了一个String类型的value,现在使用Optional的ofNullable获取这个value值,并判断value是否存在,如果存在,则输出这个值。看起来,这个程序并没有什么错误,运行起来也可以通过。但其实这和直接判断value的值是否为空并没有什么区别,显然,这并不是我们想要的。那如何较好的使用Optional呢?接下来,我将来详细介绍Optional的运用。

1. 存在即返回,无则返回默认值。

OptionalEntity oe = new OptionalEntity();
Optional<String> value = Optional.ofNullable(oe.getValue());
String name = value.orElse("default");
System.out.println(name);

2. 存在即返回,无则由lambda表达式生成。

String name1 = value.orElseGet(() ->  "default");
System.out.println(name1);

3. 存在才对它做点儿什么。

OptionalEntity oe1 = new OptionalEntity();
Optional<String> value1 = Optional.ofNullable(oe1.getValue());
OptionalEntity oe2 = new OptionalEntity("Hello world!");
Optional<String> value2 = Optional.ofNullable(oe2.getValue());
value1.ifPresent(System.out::println);
value2.ifPresent(System.out::println);

4. map()和flatMap()函数的运用

OptionalEntity oe1 = new OptionalEntity("test");
List<String> values = new ArrayList<>();
values.add("hello");
values.add("world");
values.add("java");
oe1.setValues(values);
Optional<OptionalEntity> opt1 = Optional.of(oe1);
List<String> valueList = opt1.map(oe -> oe.getValues())
	.orElse(Collections.emptyList());
valueList.stream().forEach(System.out::println);

String value = opt1.flatMap(oe -> Optional.of(oe.getValue()))
	.map(v -> v.toUpperCase()).orElse("null");
System.out.println(value);

    注意map()和flatMap()的区别。

5. filter()函数的使用

OptionalEntity oe1 = new OptionalEntity("test");
Optional<String> opt1 = Optional.ofNullable(oe1.getValue());
opt1.filter(v -> v.length() > 6)
	.ifPresent(System.out::println);
OptionalEntity oe2 = new OptionalEntity("Hello world, java!");
Optional<String> opt2 = Optional.ofNullable(oe2.getValue());
opt2.filter(v -> v.length() > 6)
	.ifPresent(System.out::println);

    以上就是我对Optional的总结。在上面的例子中,可以看到Optional经常与Stream、lambda表达式结合使用,这也是JDK8的新特性,而Stream和lambda表达式将在以后进行总结。