概述:Optional 最早是 Google 公司 Guava 中的概念,代表的是可选值。Optional 类从 Java8 版本开始退出奢华套餐,次要为了解决程序中的 NPE 问题,从而使得更少的显式判空,避免代码净化,另一方面,也使得畛域模型中所暗藏的常识,得以显式体现在代码中。Optional 类位于 java.util 包下,对链式编程格调有肯定的反对。实际上,Optional 更像是一个容器,其中寄存的成员变量是一个 T 类型的 value,可值可 Null,应用的是 Wrapper 模式,对 value 操作进行了包装与设计。本文将从 Optional 所解决的问题开始,逐层解剖,由浅入深,文中会呈现 Optioanl 办法之间的比照,实际,误用状况剖析,优缺点等。与大家一起,对这项 Java8 中的新个性,进行了解和深刻。
1、解决的问题
臭名远扬的空指针异样,是每个程序员都会遇到的一种常见异样,任何拜访对象的办法与属性的调用,都可能会呈现 NullPointException,如果要确保不触发异样,咱们通常须要进行对象的判空操作。
举个栗子,有一个人(Shopper)进超市购物,可能会用购物车(Trolley)也可能会用其它形式,购物车里可能会有一袋栗子(Chestnut),也可能没有。三者定义的代码如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public class Shopper {
private Trolley trolley;
public Trolley getTrolley(){return trolley;}
}
public class Trolley {
private Chestnut chestnut;
public Chestnut getChestnut(){return chestnut;}
}
public class Chestnut {
private String name;
public String getName(){return name;}
}
这时想要取得购物车中栗子的名称,像上面这么写,就可能会见到咱们的“老朋友”(NPE)
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String result(Shopper shopper){return shopper.getTrolley().getChestnut().getName();
}
为了能避免出现空指针异样,通常的写法会逐层判空(多层嵌套法),如下
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String result(Shopper shopper) {if (shopper != null) {Trolley trolley = shopper.getTrolley();
if (trolley != null) {Chestnut chestnut = trolley.getChestnut();
if (chestnut != null) {return chestnut.getName();
}
}
}
return "获取失败辽";
}
多层嵌套的办法在对象级联关系比拟深的时候会看的目迷五色的,尤其是那一层一层的括号😒;另外出错的起因也因为不足对应信息而被含糊(例如 trolley 为空时也只返回了最初的获取失败。当然也能够在每一层减少 return,相应的代码有会变得很简短),所以此时咱们也能够用遇空则返回的卫语句进行改写。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String result(Shopper shopper) {if (shopper == null) {return "购物者不存在";}
Trolley trolley = shopper.getTrolley();
if (trolley == null) {return "购物车不存在";}
Chestnut chestnut = trolley.getChestnut();
if (chestnut == null) {return "栗子不存在";}
return chestnut.getName();}
为了取一个名字进行了三次显示判空操作,这样的代码当然没有问题,然而优良的工程师们总是心愿能取得更优雅简洁的代码。Optional 就提供了一些办法,实现了这样的冀望。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String result(Shopper shopper){return Optional.ofNullable(shopper)
.map(Shopper::getTrolley)
.map(Trolley::getChestnut)
.map(Chestnut::getName)
.orElse("获取失败辽");
}
2、罕用办法
1)取得 Optional 对象
Optional 类中有两个构造方法:带参和不带参的。带参的将传入的参数赋值 value,从而构建 Optional 对象;不带参的用 null 初始化 value 构建对象。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
private Optional() {}
private Optional(T value) {}
然而两者都是公有办法,而实际上 Optional 的对象都是通过 动态工厂模式 的形式构建,次要有以下三个函数
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public static <T> Optional<T> of(T value) {}
public static <T> Optional<T> ofNullable(T value) {}
public static <T> Optional<T> empty() {}
创立一个肯定不为空的 Optional 对象,因为如果传入的参数为空会抛出 NPE
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Chestnut chestnut = new Chestnut();
Optional<Chestnut> opChest = Optional.of(chestnut);
创立一个空的 Optional 对象
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.empty();
创立一个可空的 Optional 对象
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Chestnut chestnut = null;
Optional<Chestnut> opChest = Optional.ofNullable(chestnut);
2)失常应用
失常应用的办法能够被大抵分为三种类型,判断类 、 操作类 和取值类
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
// 判断类
public boolean isPresent() {}
// 操作类
public void ifPresent(Consumer<? super T> consumer) {}
// 取值类
public T get() {}
public T orElse(T other) {}
public T orElseGet(Supplier<? extends T> other) {}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {}
isPresent()办法像一个安全阀,管制着容器中的 value 值是空还是有值,用法与本来的 null != obj 的用法类似。当 obj 有值返回 true,为空返回 false(即 value 值存在为真)。但个别实现判断空或不为空的逻辑,应用 Optional 其余的办法解决会更为常见。如下代码将会打印出没有栗子的悲惨事实。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.empty();
if (!opChest.isPresent()){System.out.println("容器里没有栗子");
}
ifPresent()办法是一个操作类的办法,他的参数是一段指标类型为 Consumer 的函数,当 value 不为空时,主动执行 consumer 中的 accept()办法(传入时实现),为空则不执行任何操作。比方上面这段代码,咱们传入了一段输入 value 的 lamda 表达式,打印出了“迁西板栗”。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("迁西板栗"));
opChest.ifPresent(c -> System.out.println(c.getName()));
get()办法源码如下,能够看出,get 的作用是间接返回容器中的 value。但如此粗犷的办法,应用前如果不判空,在 value 为空时,便会毫不留情地抛出一个异样。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public T get() {if (value == null) {throw new NoSuchElementException("No value present");
}
return value;
}
三个 orElse 办法与 get 类似,也都属于取值的操作。与 get 不同之处在于 orElse 办法不必额定的判空语句,撰写逻辑时比拟欢快。三个 orElse 的相同之处是当 value 不为空时都会返回 value。当为空时,则另有各自的操作:orElse()办法会返回传入的 other 实例(也能够为 Supplier 类型的函数);orElseGet()办法会主动执行 Supplier 类型实例的 get()办法;orElseThrow()办法会抛出一个自定的异样。更具体的差异会在前面的 办法比照 中形容。
如上面这段代码,展现了在没有栗子的时候,如何吐出“信阳板栗”、“镇安板栗”,以及抛出“抹油栗子”的正告。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.empty();
System.out.println(opChest.orElse(new Chestnut("信阳板栗")));
System.out.println(opChest.orElseGet(() -> new Chestnut("镇安板栗")));
try {opChest.orElseThrow(() -> new RuntimeException("抹油栗子呀"));
}catch (RuntimeException e){System.out.println(e.getMessage());
}
3)进阶应用
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public Optional<T> filter(Predicate<? super T> predicate) {}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {}
filter()办法承受谓词为 Predicate 类型的函数作为参数,如果 value 值不为空则主动执行 predicate 的 test()办法(传入时实现),来判断是否满足条件,满足则会返回本身 Optional,不满足会返回空 Optional;如果 value 值为空,则会返回本身 Optional(其实跟空 Optional 也差不多)。如下代码,第二句中筛选条件“邵店板栗”与 opChest 中的板栗名不符,没有通过过滤。而第三句的筛选条件与 opChest 统一,所以最初打印进去的是“宽城板栗”。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("宽城板栗"));
opChest.filter(c -> "邵店板栗".equals(c.getName())).ifPresent(System.out::println);
opChest.filter(c -> "宽城板栗".equals(c.getName())).ifPresent(System.out::println);
map()和 flatmap() 办法传入的都是一个 Function 类型的函数,map 在这里翻译为“映射”,当 value 值不为空时进行一些解决,返回的值是通过 mapper 的 apply()办法解决后的 Optional 类型的值,两个办法的后果统一,处理过程中会有差异。如下代码,从 opChest 中获取了板栗名后,从新 new 了一个板栗取名“邢台板栗”,并打印进去,两者输入统一,解决模式上有差别,这个在前面的 办法比照 中会再次说到。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("邢台板栗"));
System.out.println(opChest.map(c -> new Chestnut(c.getName())));
System.out.println(opChest.flatMap(c -> Optional.ofNullable(new Chestnut(c.getName()))));
4)1.9 新增
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {}
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {}
public Stream<T> stream() {}
JDK1.9 中减少了三个办法:ifPresentOrElse()、or()和 stream() 办法。
1.8 时,ifPresent()仅提供了 if(obj != null)的办法,并未提供 if(obj != null)else{}的操作,所以在 1.9 中减少了一个 ifPresentElse() 办法,提供了这方面的反对。该办法接管两个参数 Consumer 和 Runnable 类型的函数,当 value 不为空,调用 action 的 accept()办法,这点与 ifPresent()统一,当 value 为空时,会调用 emptyAction 的 run()办法,执行 else 语义的逻辑。如上面代码,会打印出“木有栗子”的提醒。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.empty();
opChest.ifPresentElse(c -> System.out.println(c.getName()),c -> System.out.println("木有栗子呀"));
or()办法是作为 orElse()和 orElseGet()办法的改良而呈现的,应用办法统一,但后两个办法在执行实现后返回的并非包装值。如果须要执行一些逻辑并返回 Optional 时,能够应用 or()办法。该办法传入 Supplier 接口的实例,当 value 有值时间接返回本身 Optional,当为空时,主动执行 suuplier 的 get()办法,并包装成 Optional 返回,其源码中包装的语句如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<T> r = (Optional<T>) supplier.get();
return Objects.requireNonNull(r);
stream()办法则不必多说,是一个提供给流式编程应用的办法,性能上是一个 适配器,将 Optional 转换成 Stream:没有值返回一个空的 stream,或者蕴含一个 Optional 的 stream。其源码如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
if (!isPresent()) {return Stream.empty();
} else {return Stream.of(value);
}
3、办法比照和总结
Optional 封装的办法较多,抉择一个适合的办法的前提是要理解各自实用的场景和异同
1)创立办法的比照
因为结构器为公有办法,创建对象只能通过动态工厂的形式创立。of()、ofNullable()和 empty()办法是三个静态方法。先上源码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
// 工厂办法
public static <T> Optional<T> of(T value) {return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {return value == null ? empty() : of(value);
}
public static<T> Optional<T> empty() {@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// 构造方法
private Optional() {this.value = null;}
private Optional(T value) {this.value = Objects.requireNonNull(value);
}
// 动态常量
private static final Optional<?> EMPTY = new Optional<>()
of()办法通过调用带参结构,new 出一个 Optional 对象,失常形参带值是不会有问题的,然而当形参为空时,设置 value 前的 Objects.requireNonNull()非空校验,就会抛出一个异样,代码如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public static <T> T requireNonNull(T obj) {if (obj == null)
throw new NullPointerException();
return obj;
}
requireNonNull()办法是 java.util 包下 Objects 类的一个办法,作用是查看传入的参数是否为空,为空会抛出一个 NPE,在 Optional 类中用到的中央还有很多。所以只有确信结构 Optional 所传入的参数不为空时才可应用 of()办法。
与 of()绝对的还有一个 ofNullable() 办法,该办法容许承受 null 值结构 Optional,当形参为 null 时,调用 empty()办法,而 empty() 办法返回的是一个编译期就确定的常量 EMPTY,EMPTY 取值是无参结构器创建对象,最终失去的是一个 value 为空的 Optional 对象。
2)应用办法的比照
2.2)中说到,失常应用的办法中有属于取值类的办法,orElse()、orElseGet()和 orElseThrow(),这三个办法在非空时均返回 value,然而为空时的解决各不相同。先上源码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public T orElse(T other) {return value != null ? value : other;}
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();
}
orElse()和 orElseGet()办法最直观的差别是形参的不同,看上面一段代码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
// 测试语句
Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("桐柏板栗"));
//Optional<Chestnut> opChest = Optional.empty();
opChest.orElse(print("orELse"));
opChest.orElseGet(()->print("orElseGet"));
// 调用办法
private static Chestnut print(String method){System.out.println("燕山板栗最好吃 ----"+method);
return new Chestnut("燕山板栗");
}
第一次,new 出一个“桐柏板栗”的 Optional,别离调用 orElse()和 orElseGet()办法,后果呈现了两行的“燕山板栗最好吃”的输入,因为两个办法在 value 不为 null 时都会执行形参中的办法;
第二次,通过 empty()办法取得一个空栗子的 Optional,再调用 orElse()和 orElseGet()办法,后果竟然还呈现了一行“燕山板栗最好吃”的输入。
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
第一次输入:
燕山板栗最好吃 —-orELse
燕山板栗最好吃 —-orElseGet
第二次输入:
燕山板栗最好吃 —-orELse
其起因是 orElseGet() 的参数是 Supplier 指标类型的函数,简略来说,Suppiler 接口相似 Spring 的懒加载,申明之后并不会占用内存,只有执行了 get()办法之后,才会调用构造方法创立出对象,而 orElse() 是快加载,即便没有调用,也会理论的运行。
这个个性在一些简略的办法上差距不大,然而当办法是一些执行密集型的调用时,比方近程调用,计算类或者查问类办法时,会损耗肯定的性能。
orElseThrow()办法与 orElseGet()办法的参数都是函数类型的,这意味着这两种办法都是懒加载,但针对于必须要应用异样管制流程的场景,orElseThrow()会更加适合,因为能够管制异样类型,使得相比 NPE 会有更丰盛的语义。
3)其余办法的比照
a、map 与 filterMap
先上源码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
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));
}
}
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));
}
}
map()与 filterMap() 的相同点是,都承受一个 Function 类型的函数,并且返回值都是 Optional 类型的数据。然而从源码中咱们也能看出:
首先,map()在返回时,应用了 ofNullable()函数对返回值包了一层,这个函数在 2.1)曾经说过,是一个 Optional 的工厂函数,作用是将一个数据包装成 Optional;而 filterMap()返回时只是做了非空校验,在利用 mapper.apply 时就曾经是一个 Optional 类型的对象。
其次 ,从签名中也能够看出,map() 的 Function 的输入值是 ”?extends U”,这意味着在 mapper.apply()解决实现后,只有吐出一个 U 类型或者 U 类型的子类即可;而 filterMap()的 Functional 的输入值是“Optional<U>”,则在 mapper.apply()解决实现之后,返回的必须是一个 Optional 类型的数据。
b、ifPresent 与 ifPresentOrElse
ifPresentOrElse()办法是作为 ifPresent() 的改良办法呈现的。先看源码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
从源码中能够看出,ifPresentOrElse()参数减少了一个 Runnable 类型的函数 emptyAction,在 value != null 时,都激活了 action.accept()办法。只是当 value == null 时,ifPresentOrElse()办法还会调用 emptyAction.run()办法。所以总的来说,jdk1.9 退出 ifPresentOrElse()办法,是作为 ifPreset 在 if-else 畛域的补充呈现的。
c、or 与 orElse
同样作为改良的 or()办法也是为了解决 orElse 系列办法的“小毛病”呈现的,先看源码:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
Objects.requireNonNull(supplier);
if (isPresent()) {
return this;
} else {
@SuppressWarnings(“unchecked”)
Optional<T> r = (Optional<T>) supplier.get();
return Objects.requireNonNull(r);
}
}
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
or()办法在签名模式上更靠近 orElseGet(),即形参都是 Supplier 类型的函数,然而与其不同的是,or() 办法在形参中,指定了 Supplier 返回的类型必须为 Optional 类型,且 value 的类型必须为 T 或者 T 的子类。orElse 系列的办法,更像是一种生产的办法,从一个 Optional 的实例中“取出“value 的值进入下一步操作,而 or()办法则像是 建造者模式,对 value 有肯定的操作之后,从新吐出的还是 Optional 类型的数据,所以应用时能够串联在一起,后一个 or 解决前一个 or 吐出的 Optional。
4)“危险”办法的比照
这里指的“危险”指的是会抛出异样,毕竟引进 Optional 类的目标就是去除对 NPE 的判断,如果此时再抛出一个 NPE 或者其余的异样,没有解决好就会为程序引入不小的麻烦。所以对 Optional 中可能抛出异样的办法做一个总结。
首先 ,最直观的会抛出异样的办法就是 of() 办法,因为 of 办法会调用带参结构创立实例,而带参结构中有对 value 非空的查看,如果空会抛出 NPE 异样;
其次 ,get() 办法也是一个“危险”的办法,因为当不判空间接应用 get 取值时,会触发 get 中 NoSuchElementException 异样;
再次 ,orElseThrow() 办法也会抛出异样,然而这种异样属于 人为指定 的异样,是为了使得异常情况的语义更加丰盛,而人为设置的,是一种可控的异样;
最初 ,在一些办法中,设置了参数非空查看(Objects.requireNonNull()),这种查看会抛出NPE 异样,除去曾经提到的带参结构器,还有 filter、map、flatMap、or 这四个办法,如果传入的接口实例是 Null 值就会随时引爆 NPE。
4、误用模式与 Best practice
1)误用模式
a、初始化为 null
第一种误用模式是给 Optional 类型的变量初始化的时候.Optional 类型变量是默认不为空的,所以在取办法执行的时候才能够胡作非为 ” 点 ” 进来,如果在初始化的时候呈现:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> chest = null;
并且不及时为 chest 赋值,则还是容易呈现 NPE,正确的初始化形式应该是:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> chest = Optional.empty();
b、简略判空
第二种比拟常见的误用模式应该是应用 isPresent()做简略判空。本来的代码如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String getName(Chestnut chestnut){
if(chestnut == null){return "栗子不存在";}else return chestnut.name();
}
代码中,通过查看 chestnut == null 来解决为空时的状况,简略应用 isPresent()办法判空的代码如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public String getName(Chestnut chestnut){
Optional<Chestnut> opChest = Optional.ofNullable(chestnut);
if(!opChest.isPresent()){return "栗子不存在";}else return opChest.getname();
}
酱婶儿并没有太大差异,所以在应用 Optional 时,首先应防止应用 **Optional.isPresent()**
来查看实例是否存在,因为这种形式和 **null!= obj**
没有区别也没什么意义。
c、简略 get
第三种比拟常见的误用模式是应用 Optional.get()形式来获取 Optional 中 value 的值,get()办法中对 value==null 的状况有抛出异样,所以应该在做完非空校验之后再从 get 取值,或者非常确定 value 肯定不为空,否则会呈现 NoSuchElementException 的异样。绝对的,如果不是很确信,则应用 orElse(),orElseGet(),orElseThrow()取得你的后果会更加适合。
d、作为属性字段和办法参数
第四种误用模式在初学 Optional 的时候容易碰到,当指定某个类中的属性,或者办法的参数为 Optional 的时候,idea 会给出如下提醒:
Reports any uses of java.util.Optional<T>, java.util.OptionalDouble, java.util.OptionalInt, java.util.OptionalLong or com.google.common.base.Optional as the type for a field or parameter. Optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”. Using a field with type java.util.Optional is also problematic if the class needs to be Serializable, which java.util.Optional is not.
粗心是不倡议如此应用 Optional。第一,不倡议应用 Optional 作为字段或参数,其设计是为库办法 返回类型 提供一种无限的机制,而这种机制能够清晰的示意“没有后果”的语义;第二,Optional 没有实现 Serilazable,是不可被序列化的。
这种误用办法比拟显著,复现和防止也比较简单。但笔者还想就这两个倡议的起因做进一步的探索,所以查阅了一些材料,大体的起因如下:
第一个起因,为什么不适宜做属性字段和办法参数?直白的说,就是麻烦。为了引入 Optional,却须要退出多段样板代码,比方判空操作。使得在不适合的地位应用 Optional 不仅没有给咱们带来便当,反而束缚了写代码的逻辑。
写以下域模型代码
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public class Chestnut {
private String firstName;
private Optional<String> midName = Optional.empty();
private String lastName;
public void setMidName(Optional<String> midName) {
this.midName = midName;
}
public String getFullName() {
String fullName = firstName;
if(midName != null) {if(midName.isPresent()){fullName = fullName.concat("." + midName.get());
}
return fullName.concat("." + lastName);
}
}
}
可见在 setter 办法中没有对形参做相应的校验,那么则须要在应用的 getFullName()办法中,减少对属性 midName 的判空操作,因为齐全可能通过 setter 办法使得属性为 null。如果把判空移到 setter 办法中,也并没有缩小判空,使得平白挤进了一段样板代码。另外在传入办法时,也须要对本来的 value 包装一层后再传入,使得代码显得累赘了,如下:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
chest.setMidName(Optional.empty());
chest.setMidName(Optional.of(“ 阿栗 ”));
在属性不为 Optional 的时候,如果给属性赋值,须要应用“生产”操作,比方 orElse(),取出值再赋给属性,相比间接传入 String 类型的值作为字段和形参能够缩小这些步骤,后者反而更加适合。
第二个起因,为什么没有实现序列化?相干能够参见 Java Lamda 的专家组探讨。
JDK 在序列化上比拟非凡,须要同时兼顾向前和向后兼容,比方在 JDK7 中序列化的对象应该可能在 JDK8 中反序列化,反之亦然。并且,序列化依赖于对象的 identity 放弃唯一性。以后 Optional 是援用类型的,但其被标记为 value-based class(基于值的类),并且有打算在今后的某一个 JDK 版本中实现为 value-based class,可见上图。如果被设计为可序列化,就将呈现两个矛盾点:1)如果 Optional 可序列化,就不能将 Optional 实现为 value-based class,而必须是援用类型,2)否则将 value-based class 退出同一性的敏感操作(蕴含援用的相等性如:==,同一性的_hashcode_或者同步等),然而这个与以后已公布的 JDK 版本都是抵触的。所以综上,思考到将来 JDK 的布局和实现的抵触,一开始就将 Optional 设置为不可序列化的,应该是最合适的计划了。
Value-Based Classes(基于值的类),以下是来自 Java doc 的解释:
Value-based Classes
Some classes, such as java.util.Optional
and java.time.LocalDateTime
, are _value-based_. Instances of a value-based class:
1、are final and immutable (though may contain references to mutable objects);
2、have implementations of equals
, hashCode
, and toString
which are computed solely from the instance’s state and not from its identity or the state of any other object or variable;
3、make no use of identity-sensitive operations such as reference equality (==
) between instances, identity hash code of instances, or synchronization on an instances’s intrinsic lock;
4、are considered equal solely based on equals()
, not based on reference equality (==
);
5、do not have accessible constructors, but are instead instantiated through factory methods which make no committment as to the identity of returned instances;
6、are _freely substitutable_ when equal, meaning that interchanging any two instances x
and y
that are equal according to equals()
in any computation or method invocation should produce no visible change in behavior.
A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.
2)Best practice
实际中经常组合应用以上各种办法,且很多办法常与 Lambda 表达式联合,获取想要的后果,这里列出两个常见的应用形式,值类型转换 和汇合元素过滤。
a、示例一
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Chestnut chestnut = new Chestnut(“ 锥栗板栗 ”);
if(chestnut != null){
String chestName = chestnut.getName();
if(chestName != null){
return chestName.concat("好好吃!");
}
}else{
return null;
}
能够简化成:
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Chestnut chestnut = new Chestnut(“ 锥栗板栗 ”);
return Optional.ofNullable(chestnut)
.map(Chestnut::getName)
.map(name->name.concat("好好吃!"))
.orElse(null);
b、示例二
PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
public static void main(String[] args) {
// 创立一个栗子汇合
List<Chestnut> chestList = new ArrayList<>();
// 创立几个栗子
Chestnut chest1 = new Chestnut(“abc”);
Chestnut chest2 = new Chestnut(“efg”);
Chestnut chest3 = null;
// 将栗子退出汇合
chestList.add(chest1);
chestList.add(chest2);
chestList.add(chest3);
// 创立用于存储栗子名的汇合
List<String> nameList = new ArrayList();
// 循环栗子列表获取栗子信息,值获取不为空且栗子名以‘a’结尾
// 如果不符合条件就设置默认值,最初将符合条件的栗子名退出栗子名汇合
for (Chestnut chest : chestList) {
nameList.add(Optional.ofNullable(chest)
.map(Chestnut::getName)
.filter(value -> value.startsWith("a"))
.orElse("未填写"));
}
// 输入栗子名汇合中的值
System.out.println(“ 通过 Optional 过滤的汇合输入:”);
nameList.stream().forEach(System.out::println);
}
5、总结
本文 首先,从所解决的问题开始,介绍了以后 NPE 解决所遇到的问题;而后 ,分类地介绍了 Optional 类中的办法并给出相应的示例; 接着 ,从源码层面对几个罕用的办法进行了比照; 最初,列举出了几个常见的误用模式和 Best practice,完结了全文。
Optional 类具备:能够显式体现值可能为空的语义和暗藏可能存在空指针的不确定性的 长处 ,然而同时也具备,适用范围不是很广(倡议应用于返回值和 NPE 逻辑解决)以及应用时须要更多考量的 毛病。
然而总体看来,Optional 类是随同 Java8 函数式编程呈现的一项新个性。为解决逻辑的实现提供了更多的抉择,将来期待更多的实际和 best practice 呈现,为 Optional 带来更多出场的机会。
6、参考
[1] https://mp.weixin.qq.com/s/q_WmD3oMvgPhakiPLAq-CQ 起源:wechat
[2] https://www.runoob.com/java/java8-optional-class.html 起源: 菜鸟教程
[3] https://blog.csdn.net/qq_40741855/article/details/103251436 起源:CSDN
[4] https://yanbin.blog/java8-optional-several-common-incorrect-usages/#more-8824 起源:blog
[5] https://www.javaspecialists.eu/archive/Issue238-java.util.Optional—Short-Tutorial-by-Example.html 起源:java specialists
[6] Java 核心技术 卷 II – Java8 的流库 – Optional 类型
[7] [](https://www.zhihu.com/questio…)https://www.zhihu.com/question/444199629/answer/1729637041 起源: 知乎
如有不当之处,望斧正~
作者:历子谦