JDK笔记之 Optional

228 阅读7分钟

Optional


1. null在程序中带来的问题

相信“NullPointerException”是java程序员最常见的Exception之一。

其主要常见原因是访问null引用对象属性值时导致。

因此针对该Exception要求我们在编程时需要特别注意对有可能为null对象属性的访问。举例如下:

import lombok.Data;

public class OptionalTest {
    public static void main(String[] args) {
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        A a = new A();
        try {
            System.out.println("访问对象实例a的属性实例b:" + a.getB());
            System.out.println(a.getB().getC());
        } catch (Exception exception) {
            System.out.println("访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c。异常内容:" + exception);
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        try {
            B b = new B();
            b.setTestTwo("testTwo");
            a.setB(b);
            System.out.println("访问对象实例a的B属性:" + a.getB().toString());
            System.out.println("访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c:" + a.getB().getC());
            System.out.println(a.getB().getC().getTest());
        } catch (Exception exception) {
            System.out.println("访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c,最后访问实例c的属性test,异常内容:" + exception);
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    }
}

@Data
class A {
    private String testOne;
    private B b;
}

@Data
class B {
    private String testTwo;
    private C c;
}

@Data
class C {
    private String testThree;
    private String test;
}

运行结果:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

访问对象实例a的B属性:null 访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c。异常内容:java.lang.NullPointerException

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 访问对象实例a的B属性:B(testTwo=testTwo, c=null) 访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c:null 访问对象实例a的属性实例b,然后继续访问实例对象b的属性实例c,最后访问实例c的属性test,异常内容:java.lang.NullPointerException

分析:对null引用对象属性值的获取会引起:java.lang.NullPointerException

常用解决方案:

如果希望从实例对象a中能成功的访问到对象实例c的test属性值而不出现异常,常用的解决方案:

依次判断防止null导致程序出现NullPointerException。(方案不唯一,但主要思想都是依赖于if依次判断)

if (null != a.getB() && null != a.getB().getC()) {
    System.out.println(a.getB().getC().getTest());
}else{
   System.out.println("无法获取值"); 
}

总结:

1.未知null引用需要特别注意,导致程序异常风险较高;

2.null的if检查导致程序较为臃肿;

2. Optional可以null的容器对象

JDK8开始提供了java.util.Optional类我们先看一下 JDK注解。

A container object which may or may not contain a non-null value. If a value is present, {@code isPresent()} will return {@code true} and {@code get()} will return the value.

这是一个可以包含非null或者null值的容器对象。如果值存在,isPresent()方法返回true,并且get()方法返回该值。

由此可知Optional类是一个可以包含null的容器。这样就可以将包含值为null的Optional实例赋值给变量,而不是将null直接赋值给变量。

那么现在有会有两个问题需要思考:

1.如何将null迁移到Optional(如何使用Optional)?---Optional实例

2.Optional如何帮助我们更好的处理null以及延伸了那些新功能?---Optiinal API源码分析

3.Optional实例

根据上面例子进行改造最终实现一种无需if判断便可以依次访问包含null变量解决方案。

package com.wcq.jdk.sty.optional;

import lombok.Data;

import java.util.Optional;

public class OptionalTest {
    public static void main(String[] args) {
        System.out.println(">>>>>>>>>>>>> 当 AA 为空 >>>>>>>>>>>>>>>>>>>>>");
        Optional<AA> aaOptional_01 = Optional.empty();
        System.out.println(aaOptional_01.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElse("空值01"));
        System.out.println(">>>>>>>>>>>>> 当 BB 为空 >>>>>>>>>>>>>>>>>>>>>");
        AA aa = new AA();
        aa.setTestOne("one");
        aa.setBb(Optional.empty());
        Optional<AA> aaOptional_02 = Optional.ofNullable(aa);
        System.out.println(aaOptional_02.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElse("空值02"));
        System.out.println(">>>>>>>>>>>>> 当 CC 为空 >>>>>>>>>>>>>>>>>>>>>");
        BB bb = new BB();
        bb.setTestTwo("two");
        bb.setCc(Optional.empty());
        Optional<AA> aaOptional_03 = Optional.ofNullable(aa);
        System.out.println(aaOptional_03.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElse("空值03"));
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    }
}

@Data
class AA {
    private String TestOne;
    private Optional<BB> bb;
}

@Data
class BB {
    private String TestTwo;
    private Optional<CC> cc;
}

@Data
class CC {
    private String test;
}

4.Optional API源码分析

PS:基于jdk版本:1.8.0_251

4.1 empty()

声明一个空的Optional

源码:

// 定义一个私有公共实体类,赋值为一个空的构造函数。为empty()方法调用
private static final Optional<?> EMPTY = new Optional<>(); 
// 私有 空的构造函数
private Optional() {
        this.value = null;
    }
// 静态方法:返回一个空的构造函数
public static<T> Optional<T> empty() {
      @SuppressWarnings("unchecked")
      Optional<T> t = (Optional<T>) EMPTY; // 赋值为公共实体类(一个空的构造函数)
      return t;
 }

样例:声明一个空的Optional

Optional<AA> aaOptional=Optional.empty();

4.2 of()

创建一个非空值Optional

源码:

// 创建Optional时如果不为空就会将值赋值给改私有的final变量
private final T value;
// 带参数私有构造函数
private Optional(T value) {
        this.value = Objects.requireNonNull(value); // 调用java.util.Objects的requireNonNull判断参数不允许为空不然NullPointerException
    }
// 静态方法:创建一个非空值Optional,参数为null抛出NullPointerException
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

样例:

AA aa = new AA();
aa.setTestOne("one");
Optional<AA> aaOptional = Optional.of(aa); // 创建非空Optional
Optional<AA> aaOptionalException = Optional.of(null); // 会抛出NullPointerException

4.3 ofNullable()

创建一个允许为非空值Optional

结合:empty()和of可以很好的理解该方法源码

源码:

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value); // 对参数进行了非空判断选择调用:empty或者of
}

样例:

AA aa = new AA();
aa.setTestOne("one");
Optional<AA> aaOptional = Optional.ofNullable(aa);
Optional<AA> aaOptionalException = Optional.ofNullable(null); // 不会抛出异常

4.4 get()

返回封装的变量值(不安全)

当封装的变量值不为空,返回该变量值;

当封装的变量值为空,抛出NoSuchElementException;

源码:

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

4.5 isPresent()

判断封装变量值是否为null

源码:

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

4.6 ifPresent(Consumer<? super T> consumer)

如果存在值,则使用该值调用指定的方法,否则什么都不做

源码:

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

样例:

AA aa = new AA();
aa.setTestOne("one");
Optional<AA> aaOptional = Optional.of(aa);
aaOptional.ifPresent(item-> System.out.println(item.getTestOne())); // 控制台打印 :one     

4.7 filter(Predicate<? super T> predicate)

如果值存在并且满足筛选条件,返回包含该值的Optional,否则返回EMPTY

源码:

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate); // 谓词为空校验
    if (!isPresent()) // 值为空判断
        return this;
    else
        return predicate.test(value) ? this : empty();
}

样例:

AA aa = new AA();
aa.setTestOne("one");
Optional<AA> aaOptional = Optional.of(aa);
System.out.println(aaOptional.filter(item->item.getTestOne().equals("one"))); 
System.out.println(aaOptional.filter(item->item.getTestOne().equals("two")));
System.out.println(aaOptional.filter(null));

返回结果: Optional[AA(TestOne=one, bb=Optional.empty)] Optional.empty

Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at java.util.Optional.filter(Optional.java:174) at com.wcq.jdk.sty.optional.OptionalTest.main(OptionalTest.java:31)

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

如果值存在则对该值执行函数(mapping)调用返回包含该值的Optional,否则返回EMPTY

PS:类比stream的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));
    }
}

样例:

AA aa = new AA();
aa.setTestOne("one");
Optional<AA> aaOptional = Optional.of(aa);
System.out.println(aaOptional.map(AA::getTestOne));
System.out.println(aaOptional.map(AA::getTestOne).get());

返回结果:

Optional[one] one

PS: 根据源码分析以下代码:

aaOptional.map(AA::getBb).map(BB::getCc).map(CC::getTest).get()

这段代码在编译时就会失败,原因:分析源码map方法返回的是一个Optinal。getBb返回的是一个Optional类型的对象,因此第二个map操作的参数一个Optional<Optional>这样的参数实例是非法的。如果想进行上述函数式编程需要使用flatMap方法。

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

如果值存在则对该值执行函数(mapping)调用返回Optional类型的值,否则返回EMPTY

源码:

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

样例:

AA aa = new AA();
BB bb = new BB();
CC cc = new CC();
cc.setTest("test");
bb.setTestTwo("two");
bb.setCc(Optional.of(cc));
aa.setTestOne("one");
aa.setBb(Optional.of(bb));
Optional<AA> aaOptional = Optional.of(aa);
aaOptional.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).get();

结果:

test

4.10 orElse(T other)

非空返回值value,否则返回默认值

源码:

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

样例:

AA aa = new AA();
BB bb = new BB();
CC cc = new CC();
// cc.setTest("test");
bb.setTestTwo("two");
bb.setCc(Optional.of(cc));
aa.setTestOne("one");
aa.setBb(Optional.of(bb));
Optional<AA> aaOptional = Optional.of(aa);
System.out.println(aaOptional.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElse("空值"));

结果:

空值

4.11 orElseGet(Supplier<? extends T> other)/orElseThrow(Supplier<? extends X> exceptionSupplier)

如果有值则返回值,否则返回Supplier接口生成的值/异常

源码:

public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
ublic class OptionalTest {
    public static void main(String[] args) {
        AA aa = new AA();
        BB bb = new BB();
        CC cc = new CC();
       // cc.setTest("test");
        bb.setTestTwo("two");
        bb.setCc(Optional.of(cc));
        aa.setTestOne("one");
        aa.setBb(Optional.of(bb));
        Optional<AA> aaOptional = Optional.of(aa);
        System.out.println(aaOptional.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElse("空值"));   
 System.out.println(aaOptional.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElseGet(OptionalTest::get));  System.out.println(aaOptional.flatMap(AA::getBb).flatMap(BB::getCc).map(CC::getTest).orElseThrow(RuntimeException::new));
   }

    public static String get() {
        return "调用该方法";
    }
}

返回结果:

空值 调用该方法 Exception in thread "main" java.lang.RuntimeException at java.util.Optional.orElseThrow(Optional.java:290) at com.wcq.jdk.sty.optional.OptionalTest.main(OptionalTest.java:49)

5.Optinal 未实现序列化接口(Serializable)

因此Optinal未实现序列化接口,需要特别注意。Optional主要为null情况处理,因此可以将属性值的获取进行加工,样例如下:

@Data
class AA {
    private String TestOne;
    private BB bb;
    public Optional<BB> getBbOptional() {
        return Optional.ofNullable(bb);
    }
}