大家好,我是William。
前几天我在DZone上看到了一篇文章《Optional Anti-Patterns》,该文章的作者列举了数个关于 Java8 的 Optional 类的各种反模式例子,看得我心惊肉跳的,毕竟前几个月才用过这个东西,当时觉得很酷炫,直到最近我接触到 Kotlin 这门语言后,才发现其实空指针的检查应该交给编译器来做,而不是开发者自己去约束自己。反正目前,我觉得 Optional 真的很渣渣(不服来战,我在评论区等着你)。
文章的链接:
https://dzone.com/articles/optional-anti-patterns
下面我将会把文章的中心思想和代码给大家过一遍,顺便中间夹杂个人观点。
第一宗罪:在字段上用 Optional
public class Car {
private List<Wheel> wheels;
private Optional<Engine> engine;
// getter and setter
}
上面的代码问题出现在哪?
实际上,Optional 是不可以参与序列化的,这就很坑了。但是,我又担心 engine 是空的,想用 Optional 来挡一下;又想让 engine 参与序列化,该怎么办呢?
下面的代码给出了优化方案:
public class Car {
private List<Wheel> wheels;
private Engine engine;
// 很稳健
public Optional<Engine> getEngine() {
return Optional.ofNullable(engine);
}
}
第二宗罪:集合中的元素都是 Optional 的
// 内心很崩溃吧
private List<Optional<Wheel>> wheels;
大家千万别像上面这么写集合啊,谁会这么傻在调用 wheels.add() 的时候传入一个 null 值,倘若真的传入了,则直接跑出 NullPointsException 异常。所以上面的写法属于过度设计。
第三宗罪:在方法的形参上用 Optional
我们都知道 Optional 提供了几个静态工厂方法,让开发者方便创建 Optional 对象,看起来貌似很酷炫。
Optional.empty()
Optional.of(T value)
Optional.ofNullable(T value)
但是如果在方法的形参上用 Optional ,画面应该是这样的:
// 声明一个方法
Long calculate(List<Optional<Long>> data) { ... }
// 然后调用它
List<Long> data = Arrays.asList(1L, 2L);
calculate(Optional.of(data));
这里的例子跟第二宗罪的例子有联系,说好的不要在集合中使用 Optional 作为元素的类型。所以正常的画面应该如下:
// 假设方法的形参没有 Optional
List<Long> data = Arrays.asList(1L, 2L);
calculate(data);
我再补充一个稍微典型一点的:
// 声明一个方法
Long max(Optional<Long> a, Optional<Long> b) { ... }
// 妈的智障
max(Optional.of(1L), Optional.of(2L));
第四宗罪:强制序列化 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 have unpredictable results and should be avoided"
— from the Javadoc.
不过,作者也提到一个 Java 在路上的项目叫 Project Valhalla ,将会支持 Optional 序列化,不过要等 Java 版本更新是件很痛苦的事情,再等企业主动更新自己的 JDK 版本又是件很痛苦的事情,现在还有很多企业的项目居然还在用 JDK6,简直不忍直视。
不严谨的结论
目前来说,Optional 很渣渣,还不如 Kotlin 来得直接。