Optional之第三篇总结


1、简介

Optional 是一个对象容器,具有以下两个特点:

提示用户要注意该对象有可能为null
简化if else代码

2、使用介绍

  1. 创建:

    Optional.empty(): 创建一个空的 Optional 实例

    Optional.of(T t):创建一个 Optional 实例,当 t为null时抛出异常

    Optional.ofNullable(T t):创建一个 Optional 实例,但当 t为null时不会抛出异常,而是返回一个空的实例

  2. 获取:

    get():获取optional实例中的对象,当optional 容器为空时报错

  3. 判断:

    isPresent():判断optional是否为空,如果空则返回false,否则返回true

    ifPresent(Consumer c):如果optional不为空,则将optional中的对象传给Comsumer函数

    orElse(T other):如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值

    orElseGet(Supplier other):如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other

    orElseThrow(Supplier exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常

  4. 过滤:

    filter(Predicate p):如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional

  5. 映射:

    map(Function mapper):如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中。

    flatMap(Function< T,Optional> mapper):跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional

    区别:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optional

3、分析

3.1、创建源码:

// Optional的空实例对象,可以看到此时的value是为null的。但是这个Optional对象是不为null的,只是其中的属性value是为null的
private static final Optional<?> EMPTY = new Optional<>();

private final T value;

// 私有构造,不允许外界创建Optional对象

private Optional() {
    this.value = null;
}


private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

// 创建一个Optional空实例对象
public static Optional empty() {
    @SuppressWarnings("unchecked")
    Optional t = (Optional) EMPTY;
    return t;
}


// 静态方法创建实例。如果value为null,那么将会爆出空指针异常;不为空的时候,才会返回一个value不为null的Optional对象
public static  Optional of(T value) {
    return new Optional<>(value);
}


// 如果value为null,那么返回的是一个Optional对象,但是其value属性是为null,但是Optional对象不为null
// 如果value不为null,那么返回的是一个Optional对象,其value实不为空的,就是传进来的value值。所以推荐使用的是这个
public static  Optional ofNullable(T value) {
    return value == null ? empty() : of(value);
}

创建源码小结

创建对象的时候,因为构造方法是用private修饰的,所以无法在外界来进行构造对象。是能够利用当前类中的方法来进行调用。

使用empty方法,创建出来的是Optional对象,但是其中的value是null;

使用of方法,但是前提是value不能够为null;

使用ofNuallable方法,此时根据传入进来的value值分为两种情况:1、value不为空,那么创建出来的Optional对象的value不为null(有参构造会进行检查);2、value为null,会创建出来一个Optional对象,但是其属性value是为null的。

3.2、获取源码

public T get() {
    // 如果获取的时候是为空的,那么报错
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    // 否则返回Optional对象的value值
    return value;
}

小结:这个方法就很鸡肋了,但是使用的时候,可以配置isPresent方法,先进行判断是否有值,有值的时候在进行get,没有值的时候就不来执行这个方法。

3.3、判断源码

是对当前的value值进行判断

isPresent()

// 如果Optional对象的属性value是不为null的,那么直接返回true;否则返回false
public boolean isPresent() {
    return value != null;
}

ifPresent(Consumer c)

// 参数是lambda表达式,表示的是要对当前传递进来的匿名对象要进行什么样子的操作
public void ifPresent(Consumer<? super T> consumer) {
    // 如果当前Optional对象的value属性不为null,那么就进行消费,消费方式开发者自己来进行定义
    if (value != null)
        consumer.accept(value);
}

3.4、默认方法

orElse(T other)

// 如果当前Optional对象的value属性是不为null的,那么直接返回当前Optional对象;如果value是为null的,那么将value赋值一个指定的属性
public T orElse(T other) {
    return value != null ? value : other;
}

orElseGet(Supplier<? extends T> other)

public T orElseGet(Supplier<? extends T> other) {
    // 当前value不为null,直接返回当前value值,否则给当前value赋值一个指定值
    return value != null ? value : other.get();
}

orElseThrow(Supplier<? extends X> exceptionSupplier):

public  T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    // 如果当前Optional对象的value不为null,那么直接返回这个值;如果为null,那么爆出指定的异常
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

3.5、过滤方法

filter(Predicate<? super T> predicate)

public Optional filter(Predicate<? super T> predicate) {
    // 首先检查传递进来的匿名是否是为空的
    Objects.requireNonNull(predicate);
	// 首先判断当前Optional对象的value是否是为null的,如果为null,那么isPresent为fasle,!isPresent返回的是true,那么直接返回Optional对象
    // 如果optional对象的value值是为null,那么这里就直接返回,不为null,那么就会走下面的方法
    if (!isPresent())
        return this;
    else
        // 对这里的value值来进行断言:这里的断言是自己来进行定义的:如果返回的是true,那么返回当前对象,为false的时候,返回的是一个Optional对象
        // 其value属性是为null的
        return predicate.test(value) ? this : empty();
}

那么从这里可以看到,这里的过滤方法只是说做一个判断而已,如果Optional对象中的value属性为null的时候,过滤方法还是会将其进行返回了。进入到下一步的操作中去。但是在stream流中,filter方法却是将符合条件的留在流中,但是这里的filter操作相当于还是返回了,但是此时此刻,如果要想来进行使用的话,那么还是需要结合其他方法,如isPresent方法来进行使用

3.6、映射方法

map(Function<? super T, ? extends U> mapper)方法

public Optional map(Function<? super T, ? extends U> mapper) {
    // 首先检查是否为null
    Objects.requireNonNull(mapper);
    // 如果optional对象中的value是为null,那么为false,!isPresent判断后为true,直接返Optional对象value属性为null
    if (!isPresent())
        return empty();
    else {
        // 如果不为null,那么就开始进行映射方法。这里返回的对象是optional对象,但是其value属性不一定是有值的。
        return Optional.ofNullable(mapper.apply(value));
    }
}

那么这里在进行多次map的时候,可能会有多个Optional对象其value是为null的情况,那么就直接走return empty()的步骤了。

flatMap(Function<? super T, Optional> mapper)

public Optional flatMap(Function<? super T, Optional> mapper) {
    // 检查匿名对象
    Objects.requireNonNull(mapper);
    // 如果optional对象中的value不为null,那么将走else;否则走true
    if (!isPresent())
        return empty();
    else {
        // 消费方法之后,返回的是一个Optional对象
        return Objects.requireNonNull(mapper.apply(value));
    }
}

4、例子

4.1、Demo1-filter方法

需求:学校想从一批学生中,选出年龄大于等于18,参加过考试并且成绩大于80的人去参加比赛。

    @Test
    public void testOptionalOne() {
        Student s1 = new Student("张三", 19, 80);
        Student s2 = new Student("李四", 19, 50);
        Student s3 = new Student("王五", 23, null);
        Student s4 = new Student("赵六", 16, 90);
        Student s5 = new Student("钱七", 18, 99);
        Student s6 = new Student("孙八", 20, 40);
        Student s7 = new Student("吴九", 21, 88);
        List students = Arrays.asList(s1, s2, s3, s4, s5, s6, s7);
        List collect = students.stream()
                .filter(student -> Optional.ofNullable(student.getAge()).filter(age -> age >= 18).isPresent())
                .filter(student -> Optional.ofNullable(student.getScore()).filter(score -> score > 80).isPresent()).collect(Collectors.toList());
        collect.forEach(System.out::println);
    }

注意到这段代码:

Optional.ofNullable(student.getAge()).filter(age -> age >= 18).isPresent()

如果不加后面的isPresent方法,那么返回的是可能是两种结果:optional对象的value属性有value=null,也有value!=null的,但是这里很明显,需要过滤掉value=null的情况。结合着其他情况来进行使用。

4.2、Demo2-orElse-orElseGet

测试一下orElse和orElseGet方法,这里会有一个很有意思的问题:

定义一个User类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String email;
    private String id;
}

然后写两个测试:

测试一:

    private User createNewUser() {
        log.info("Creating New User");
        return new User("extra@gmail.com", "1234555");
    }


    @Test
    public void testOrElseAndOrElseGetOne() {
        User user = null;
        log.info("Using orElse");
        User result = Optional.ofNullable(user).orElse(createNewUser());
        log.info("Using orElseGet");
        User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
    }

如果user为null,那么看下orElse和orElseGet方式取值:

2021-11-20 11:18:21.031  INFO 17328 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Using orElse
2021-11-20 11:18:21.031  INFO 17328 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Creating New User
2021-11-20 11:18:21.031  INFO 17328 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Using orElseGet
2021-11-20 11:18:21.031  INFO 17328 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Creating New User

可以看到,user为空的时候,两个都会走createNewUser方法。

测试二:

    private User createNewUser() {
        log.info("Creating New User");
        return new User("extra@gmail.com", "1234555");
    }

    @Test
    public void testOrElseAndOrElseGetTwo() {
        User user = new User("john@gmail.com", "1234");
        log.info("Using orElse");
        User result = Optional.ofNullable(user).orElse(createNewUser());
        log.info("Using orElseGet");
        User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
    }

当user不为空的时候,那么继续来进行测试:

2021-11-20 11:20:09.274  INFO 29156 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Using orElse
2021-11-20 11:20:09.274  INFO 29156 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Creating New User
2021-11-20 11:20:09.274  INFO 29156 --- [           main] c.e.s.SpringbootMvcDemo1ApplicationTests : Using orElseGet

可以看到即使是user不为null的时候,orElse也会走后面的方法来创建对象,但是orElseGet方法不会。

所以说value属性无论是null,orElse后面的方法都会执行;而对于orElseGet来说,满足了条件就不会走;

那么如果对于执行较为密集的情况下,如果使用了orElse方法,会降低性能;所以为了方便起见,推荐使用orElseGet方法。

我看了一下两个方法源码:

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

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

我在这里有一个疑问:方法进栈了之后就会开始执行对应的方法,那么为什么第一个执行了,第二个没有执行??

如果orElse方法中只是固定值,那么可以直接写,但是如果是方法的话,那么建议慎用。

4.3、Demo3-orElseThrow

需求:如果用户的email邮箱为空,那么抛出异常

首先自定义一个运行时异常:

public class BussinussException extends RuntimeException{
    public BussinussException() {
    }

    public BussinussException(String message) {
        super(message);
    }
}

测试代码:

@Test
public void testOrElseThrow(){
    User user = null;
    String email = Optional.ofNullable(user).map(user1 -> user.getEmail()).orElseThrow(() -> new BussinussException("email为空"));
    log.info("email的值是:{}",email);
}

控制台:

com.example.springbootmvcdemo1.BussinussException: email为空

这个在做参数校验的时候,也是很好来进行使用的。

4.4、Demo4-map和flatMap

测试map和flatMap

测试一:

需求:判断下面代码的执行顺序

Optional.ofNullable(getYes())
        .map(a -> a.getAddress())
        .map(p -> p.getProvince())
        .map(n -> n.getName())
        .orElseThrow(NoSuchElementException::new);

这里其实只需要根据map的映射方法的源码就可以知道。

    public Optional map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        // value为null的时候,直接返回optional对象,但是其中的value是为null的
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

所以上面的代码中应该是每个都会执行,只不过执行到了orElseThrow的时候:

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

在这里发现了value是为null的,那么将会报出异常。

测试二:

public class User {
    private Address address;
 
    public Optional
getAddress() { return Optional.ofNullable(address); } // ... } public class Address { private Country country; public Optional getCountry() { return Optional.ofNullable(country); } // ... }

测试代码:

@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");
 
    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");
 
    assertEquals(result, "default");
}

那么看一下flatMap源码:

    public Optional flatMap(Function<? super T, Optional> mapper) {
        Objects.requireNonNull(mapper);
        // 如果optional对象中得value为null,那么就直接返回空对象
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

上面的操作中的转换步骤是:Optional类型数据,然后进行flatMap的时候,会拿到value,也就是user,进行getAddress操作,获取得到Optional

操作,然后拿到address对象,来进行获取得到Country对象,也就是Optional对象,然后获取得到对应的值之后,再来进行操作。

但是如果返回的不是Optional类型的,那么就不需要用到这个方法了。

5、总结

可以看到Optional类中的方法,对于map、filter等方法,返回的都是一个Optional对象,但是都是包裹起来的,无论value是有值的还是为null的,所以这里可以很方便我们来进行下一步操作。上面这句话很重要