JDK1.8新特性(四):Optional类

概述

针对我们熟悉又深恶痛绝的空指针异常 NullPointException,JDK8 新增了一个用于处理可能出现的空对象的类 Optionalt ,也就意味着。他可以理解为对对象的包装类,对原本的类进行了增强,结合 Lambda 表达式和函数式接口,我们可以安心的操作一些可能是 null 的对象,避免繁琐的空值检验。

举个例子:

我们常见过一些处理对象的方法,有些时候经常能见到这样的写法:

1
2
3
4
5
public static Optional<TestBean> getBean() {
Optional<TestBean> bean = null;
// 业务逻辑
return bean;
}

有些时候可能是try-catch块的限制,或者一些其他什么原因,我们的方法可能会返回一个 null,这样对于调用方来说,如果没有做空值检验就很有可能报 NPE,使用 Optional,我们可以显示的声明返回值可能是 null,并且返回一个 Option 类,调用方可以直接使用内置的方法进行处理

1
2
3
4
5
6
7
8
public Optional<TestBean> getStr() {
Optional<TestBean> bean = null;
// 业务逻辑
return bean;
}

// 调用方
TestClass.getBean().isPresent();

一、创建 Optional 对象

在开始了解如何创建 Optional 对象之前,我们要明确,和 Integer 一样,Option 分为类本身与成员变量 value,其中 value 是可能为 null。

创建 Optional 对象有三种方法:

  • Optional.empty()方法:创建一个 value 为 null 的空值 Optional

    1
    Optional<String> emptyOptional = Optional.empty();
  • Optional.of()方法:创建一个有默认值的 Optional

    1
    Optional<String> defaultOptional = Optional.of("这是一个默认值");
  • Optional.ofNullable()方法:创建一个可能是空值的 Optional。当有值的时候等同于Optional.of(),无值的时候等同于Optional.empty()

    1
    Optional<String> nullableOption = Optional.ofNullable(null);

Optional 的构造器是私有的,即无法也不应该通过构造方法去创建一个 Optional 实例。

下文的示例将基于空值对象 emptyOptional,非空值对象 defaultOptional 进行演示,

二、Optional 基本使用

1.判空

  • isPresent() 方法:判空用于判断 Optional 类所包装的 value 是否非空.

    1
    2
    3
    System.out.println(emptyOptional.isPresent()); // false
    System.out.println(defaultOptional.isPresent()); // true
    System.out.println(nullableOption.isPresent()); // false
  • ifPresent()方法:用于指定若值为不为空的情况下的处理方法,可以看做 ifPresent()用法的延伸

    1
    emptyOptional.ifPresent(s -> System.out.println("这玩意不是null"));

2.获取值

获取值的方法:

  • orElse()方法:参数是一个变量,允许传入空值而不会报 NPE。

    1
    2
    System.out.println(emptyOptional.orElse(null)); // null
    System.out.println(emptyOptional.orElse("这是空的")); // 这是空的
  • orElseGet()方法:参数是一个无参有返回值的函数式接口 Supplier,不允许传入空值,当传入空值的时候会报 NPE 异常。

    1
    2
    System.out.println(emptyOptional.orElseGet(null)); // java.lang.NullPointerException
    System.out.println(emptyOptional.orElseGet(() -> "aaa")); // aaa

    由于Supplier是无参的,所以更推荐使用 Class::method这样形式的静态方法引用来传入方法。

  • orElseThrow()方法:与 orElseGet()方法有点像,不过在传入的一个抛出异常的方法,简而言之,就是如果空值就抛异常,传入 null 就抛出 NPE,否则就按传入的自定义异常的构造函数来跑异常。

    1
    2
    System.out.println(nullableOption.orElseThrow(null)); // java.lang.NullPointerException
    System.out.println(nullableOption.orElseThrow(MyException::new)); // MyException
  • get()方法:Option 的 get()是一个奇葩,当 value 是 null 的时候,他不会抛出 NPE,但是会抛出 NoSuchElementException,实乃脱裤子放屁之举。所以一般不通过 get()去取值,只在内部用于其他方法的实现。

    1
    System.out.println(emptyOptional.get()); // java.util.NoSuchElementException

orElse()方法和 orElseGet()方法用法看起来有点相近,但是参数决定了两个方法使用场景的不同。

就我个人理解,orElse()适用于检验参数的非空,比如一些非必要参数可以通过该方法保证默认值;而orElseGet()因为传入的是一个函数式接口的实现,适用于在使用前需要对可能为空的值进行预选处理的场合,比如日志系统记录空查询这样的情况。

另外,值得一提的是,toString未尝不能看做是一种特别的取值行为。Optional 重写了 toStirng()方法,当 value 为 null 的时候返回的是字符串 "Optional.empty",否则返回 "Optional[默认值]"形式的字符串

1
2
// 空值打印 Optional.empty,否则打印 Optional[这是一个默认值]
System.out.println(nullableOption.toString());

三、Optional 过滤和转换

1.过滤

Optional 提供了 filter()方法用于过滤数据。它和 Stream 的 filter()方法一样,都是使用了有一个参数返回boolean值的函数式接口 Predicate作为参数,通过 Lambda 表达式直接传入实现类,从而实现快速过滤的目的。

举个例子:

1
2
3
4
// emptyOptional里面字符串长度并没有大于5
Optional<String> s = emptyOptional.filter(t -> t.length() > 5);
System.out.println(s); // Optional.empty
System.out.println(s.isPresent()); // false

由于默认会进行一次判空,所以filter我们可以看成下面代码的简化:

1
2
3
4
5
if (conditionStr != null) {
if (conditionStr.length() > 5) {
System.out.println(emptyStr);
}
}

2.转换

map()用于值的转换,这个方法同样与 Stream 的 map()异曲同工。通过传入拥有一个参数和一个返回值的函数式接口 Function 的实现类,从而实现 value 的转换。

举个例子:

1
2
3
// defaultOptional的value为“这是一个默认值”
Optional<String[]> s = defaultOptional.map(t -> t.split(""));
System.out.println(Arrays.toString(s.get())); // [这, 是, 一, 个, 默, 认, 值]

四、总结

网上对 Optional 类的评价有褒有贬,公司里因为有专门用于处理各种类型的参数的空值工具类,所以也不太常见到过它。但是我在知乎上曾经看到过一个对 Optional 类的很贴切的总结:

Optional的核心思想就是我明确告诉你可能会返回null,你一定要处理。所以,现在模块间提供给其他人的接口,如果有可能返回为null都要声明为Optional。

0%