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