本文已收录 【修炼内功】跃迁之路
Java8的Optional为解决'空'
的问题带来了不少新思路,查看Optional
源码,实现很是简单,逻辑也并不复杂。Stuart Marks在其一次演讲中花了约1个小时的时间来说述如何正确的使用Optional (Optional - The Mother of All Bikesheds by Stuart Marks),也有人调侃道1 hour for Optional, you gotta be kidding me.
使用Optional不难,但用好Optional并不容易html
Stuart Marks在演讲中提到了Optional的基本做用java
Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result", and where using null for that is overwhelmingly likely to cause errors.
在以往的编程模型中,对于“没有内容”,大多数状况须要使用null来表示,而null值老是被人忽略处理(判断),从而在使用过程当中极易引发NPE异常编程
Optional
的出现并非为了替代null
,而是用来表示一个不可变的容器,它能够包含一个非null的T
引用,也能够什么都不包含(不包含不等于null),非空的包含被称做persent
,而空则被称做absent
segmentfault
本质上讲Optional相似于异常检查,它迫使API用户去关注/处理Optional中是否包含内容,从而避免由于忽略null值检查而致使的一些潜在隐患api
假设有一个函数用来根据ID查询学生信息public Student search(Long id)
,如今有一个需求,须要根据ID查询学生姓名oracle
public String searchName(Long id) { Student student = search(id); return student.getName(); }
注意,search函数是可能返回null的,在这种状况下searchName颇有可能会抛出NPE异常app
public String searchName(Long id) { Student student = search(id); return Objects.nonNull(student) ? student.getName() : "UNKNOWN"; }
除非特别明确函数的返回值不可能为null,不然必定要作null值检查,虽然这样写并无增长太大的编码负担,但人总归是懒惰的,忽略检查的状况也老是会出现less
若是咱们改造search函数返回Optional,public Optional<Student> search(Long id)
,再来重写searchName函数ide
public String searchName(Long id) { Optional<Student> student = search(id); return student.getName(); }
这样的代码是编译不过的,它会强制让你去检查search返回的值是否有内容函数
public String searchName(Long id) { Optional<Student> student = search(id); return student.map(Student::getName).orElse("UNKNOWN"); }
Optional的使用能够参考其 API文档,如下内容假设您已了解如何使用Optional
可是否就应该消灭null,所有使用Optional来替代,回答固然是NO,null自有它的用武之地,Optional也并非全能的
kotlin等语言,使用?.
符号来解决java中if...else…
或... ? ... : ...
的啰嗦写法,如上问题可使用student?.name : null
,其语义为"当studen不为null时取其name属性值,不然取null值",kotlin的语法只是简化了编程方式,让编程变得更"爽",但并无解决"人们容易忽略null值检查"的状况
Stuart Marks从5个不一样的角度详细讲述了如何使用Optional,这里不一一叙述,有兴趣的能够直接跳到视频去看,下面将从Stuart Marks提到的7个Optional使用规范,来说述如何正确使用/不要滥用Optional,最后重点解释一下【为何Optional不能序列化】
Optional也是一个引用类型(reference type),其自己也能够赋值为null,若是咱们在使用Optional的时候还要多作一层null检查,就违背了Optional的设计初衷,因此在任什么时候候都不要将Optional类型的变量或返回值赋值为null,咱们但愿的是在遇到Optional的时候不须要关心其是否为null,只须要判断其是否有值便可
public String searchName(Long id) { Optional<Student> student = search(id); if (Objects.isNull(student)) { // Optional可能为null,这严重违背了Optional的设计初衷 return null; } return student.map(Student::getName).orElse("UNKNOWN"); }
若是Optional是absent(不包含任何值)的时候使用Optional.get(),会抛出NoSuchElementException
异常,该接口的设计者也认可其设计的不合理,以后的某个jdk版本中可能会将其标记为@Deprecated
,但尚未计划将其移除
public String searchName(Long id) { Optional<Student> student = search(id); // 直接使用get(),可能会抛NoSuchElementException异常 return student.get().getName(); }
若是确实须要在Optional无内容的时候抛出异常,也请不要使用Optional.get()方法,而要使用Optional.getOrThrow()方法,主动指定须要抛出的异常,虽然该方法并未在jdk8中设计,但已经有计划在接下来的jdk版本中加入
若是必定要使用Optional.get(),请必定要配合isPresent(),先判断Optional中是否有值
public String searchName(Long id) { Optional<Student> student = search(id); // 若是必定要使用Optional.get(),请必定要配合isPresent() return student.isPresent() ? student.get().getName() : "UNKNOWN"; }
链式语法可让代码的处理流程看起来更加清晰,可是为了链式而去使用Optional的话,在某些状况下并不会显得有多优雅
好比,原本可使用三目运算
String process(String s) { return Objects.nonNull(s) ? s : "DEFAULT"; }
若是非要硬生生地使用链式的话
String process(String s) { return Optional.ofNullable(s).orElse("DEFAULT"); }
好比,原本可使用if判断值的有效性
BigDecimal first = getFirstValue(); BigDecimal second = getSecondeValue(); if (Objects.nonNull(first) && Objects.nonNull(second)) { return first.add(second.get()); } else { return Objects.isNull(first) ? second : first; }
若是非要使用链式
Optional<BigDecimal> first = getFirstValue(); Optional<BigDecimal> second = getSecondeValue(); return Stream.of(first, second) .filter(Optional::isPresent) .map(Optional::get) .reduce(BigDecimal::add);
或者
Optional<BigDecimal> first = getFirstValue(); Optional<BigDecimal> second = getSecondeValue(); return first.map(b -> second.map(b::add).orElse(b)) .map(Optional::of) .orElse(second);
从可读性及可维护性上来说并无提高,反而会带来一丝阅读困难,而且上文说过,Optional自己为引用类型,建立的Optional会进入堆内存,若是大量的不合理的使用Optional,也会在必定程度上影响JVM的堆内存及内存回收
在使用Optional的时候,必定要保证Optional的简洁性,即Optional运算过程当中所包含的类型既是最终须要的类型值,不要出现Optional嵌套的状况
Optional<BigDecimal> first = getFirstValue(); Optional<BigDecimal> second = getSecondeValue(); if (!first.isPresent && ! sencond.isPresent()) { return Optional.empty(); } else { return Optional.of(first.orElse(ZERO).add(second.orElse(ZERO))); }
这样的写法,会对代码的阅读带来很大的困扰
尽可能避免将Optional用于类属性、方法参数及集合元素中,由于以上三种状况,彻底可使用null值来代替Optional,没有必要必须使用Optional,另外Optional自己为引用类型,大量使用Optional会出现相似(这样描述不彻底准确)封箱、拆箱的操做,在必定程度上会影响JVM的堆内存及内存回收
首先须要解释,什么是identity-sensitive
,能够参考 object identity and equality in java
identity-sensitive operations包含如下三种操做
==
Optional的JavaDoc中有这样一段描述
This is a value-based class; use of identity-sensitive operations (including reference equality(==), identity hash code, or synchronization) on instances of Optional may hava unpredictable results and should be avoided.
总结下来,就是要避免使用Optional的 ==
equals
hashCode
方法
在继续以前,须要再解释一下什么是 value type
value type - Project Valhalla
- an "object" that has no notion of identity
- "code like a class, works like an int"
- we eventually want to convert Optional into a value type
vale type,首先像一个类(至少从编码角度来说),可是却没有类中identity的概念,运行的时候却又和基本类型很类似
简单来讲就是编码的时候像类,运行的时候像基本类型
显然,Optional目前还不是value type
,而是reference type
,咱们查看Optional类的equals
及hashCode
方法,并无发现有什么特别之处,可是有计划在接下来的某个jdk版本中将Optional定义为value type
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Optional)) { return false; } Optional<?> other = (Optional<?>) obj; return Objects.equals(value, other.value); } @Override public int hashCode() { return Objects.hashCode(value); }
在jdk8中使用Optional的identity-sensitive operations
其实并无太大问题,但很难保证,在从此的后一个jdk版本中将Optional定义为value type
时不会出问题,因此为了兼容jdk升级程序逻辑的正确性,请避免使用Optional的identity-sensitive operations
这也引出了Optional为何不能序列化
首先,须要了解jdk中序列化的一些背景
If Optional were serializable today, it would be serialized as an Object
Consequencds of Optional being serializable
identity
有了以上两个序列化的前提条件,咱们再来看Optional,上面已将说过,虽然目前Optional是reference type
的,但其被标记为value based class
,有计划在从此的某一个JDK版本中将其实现为value type
若是Optional能够序列化,那如今就有两个矛盾点
value type
,而必须是reference type
value type
加入identity-sensitive operations
,这对于目前全部已发行的JDK版本都是相冲突的因此,虽然如今Optional是reference type
,但有计划将其实现为value type
,考虑到JDK序列化的向前及向后兼容性,从一开始就将Optional定为不可序列化,应该是最合适的方案了
若是真的有在类属性上使用Optional的需求怎么办?这里有两个替代方案/讨论能够参考