面试官:Java Optional 为什么设计成不可序列化的?
Optional 自 Java8 发布以来深受喜爱。很多人认为它是来解决“空”异常问题的,其实它并不能解决空异常,它只是一个容器,这个容器内的对象可能为空,需要使用者自行判断。
Optional 提供的只是一种思想,很多程序员不明其意,代码中存在不少乱用的情况,尤其是中国程序员。以至于,我在面试候选人的时候,问到“Java Optional 为什么设计成不可序列化的?”几乎没有人能回答到点子上。
身边不少的同事也仅仅是停留在使用上,如果稍微问他们几个问题,就会得到“不知道,大家都这么用,我和别人的用法一样”等等类似的答案。更有甚者,把实体类中的所有属性都用上 Optional。
import java.io.*;
import java.util.Optional;
public class XttblogTest implements Serializable {
private Optional<String> name;
private Integer age;
public Optional<String> getName() {
return name;
}
public void setName(Optional<String> name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public static void main(String[] args) throws Exception {
XttblogTest test = new XttblogTest();
test.setName(Optional.of(""));
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data"));
//序列化时name字段是Optional类型,不支持序列化,报如下异常:
//Exception in thread "main" java.io.NotSerializableException: java.util.Optional
out.writeObject(test);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data"));
XttblogTest obj = (XttblogTest) in.readObject();
Optional<String> name = obj.getName();
Integer age = obj.getAge();
System.out.println(name);
System.out.println(age);
}
}
平时这样使用一点问题也没有,但是当遇到序列化时,就会曝出Exception in thread "main" java.io.NotSerializableException: java.util.Optional
异常。
这样的问题,我在 Code Review 时再三强调,还总是有人愿做“出头鸟”。
如果一定要使用 Optional,或者线上的代码已经被其他人多次调用了,可以把属性上的 Optional 去掉,get 方法上保留。这样就可以继续序列化了。
import java.io.*;
import java.util.Optional;
public class CodedqTest implements Serializable {
private String name;
private Integer age;
public Optional<String> getName(){
return Optional.ofNullable(this.name);
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public static void main(String[] args) throws Exception {
CodedqTest test = new CodedqTest();
test.setName("");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data"));
out.writeObject(test);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data"));
CodedqTest obj = (CodedqTest) in.readObject();
Optional<String> name = obj.getName();
Integer age = obj.getAge();
System.out.println(name);//Optional[]
System.out.println(age);//null
}
}
当然最好的做法是,不在实体类中使用 Optional。
因为,Java 官方根本就不推荐 Optional 用在实体属性上,这也是 Java 设计出 NotSerializableException 异常的原因之一。
Optional 推荐的用法是在函数返回值上。告诉函数调用者,返回的对象存在空异常的可能,需要调用者自行处理。
具体 Optional 的用法,不是本文重点,感兴趣的可以收藏下图。
回到主题,Java 在设计 Optional 之初就把它设计为不可序列化的。具体可以参见 Java Lambda(JSR-335)专家组的讨论http://mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/003274.html
。
我选择了一些内容,供大家参考。
首先,官方推荐的是在函数返回值的位置上使用 Optional,而不是属性,集合等位置。
Map<Optional<List<String>>>
List<Optional<Map<String>>>
List<Optional<String >>>
其次,Optional 作为一个包装类,大量的 Optional 会消耗过多的内存。Optional 在字段中使用可能会浪费内存,并减慢数据结构的遍历速度。
第三,官方也不推荐在序列化、永久存储或通过网络传输中使用 Optional。
第四,在方法的参数中,也不推荐使用 Optional。
public Foo doSomething(String id, Optional<String> barOptional);
// 调用方法
foo("", Optional.of("baz"));
foo("", Optional.empty());
这种情况下,最好的办法是拥有一个重载的方法,该方法接受单个字符串参数并为第二个提供默认值:
foo("", "baz");
foo("");
第五,官方推荐通过在 Stream 流管道(或其他方法)返回 Optional。
Optional 的设计初衷在于,消除“null”而提高可读性,Optional 的最大优点是其“防白痴”。如果你调用了一个返回值为 Optional 的 API,它会迫使您积极考虑不存在的情况,你必须主动的展开 Optional 并解决该情况。
Optional 的出现并不是为了替代 null,而是用来表示一个不可变的容器,它可以包含一个非 null 的 T 引用,也可以什么都不包含(不包含不等于 null),非空的包含被称作 persent,而空则被称作 absent。
本质上讲 Optional 类似于异常检查,它迫使 API 用户去关注/处理 Optional 中是否包含内容,从而避免因为忽略 null 值检查而导致的一些潜在隐患。
最后,在序列化方面。JDK 的序列化比较特殊,需要同时向前及向后兼容,如在 JDK7 中序列化的对象需要能够在 JDK8 中反序列化,同样在 JDK8 中序列化的对象需要能够在 JDK7 中能够反序列化;其次,序列化需要依赖于对象的 identity。
有了以上两个序列化的前提条件,再结合 Optional 目前是 reference type 的,但其被标记为 value based class,其有计划在今后的某一个 JDK 版本中将其实现为 value type。
如果 Optional 设计为序列化的,那现在就有两个矛盾点:
- 如果 Optional 可以序列化,那就没办法将 Optional 实现为 value type,而必须是 reference type
- 或者将 value type 加入 identity-sensitive operations,这对于目前所有已发行的 JDK 版本都是相冲突的
所以,虽然现在 Optional 是 reference type,但有计划将其实现为 value type,考虑到 JDK 序列化的向前及向后兼容性,从一开始就将 Optional 定为不可序列化,应该是最合适的方案了。
参考资料
- Optional Pragmatic Approach
- Nothing is better than the
Optional
type - https://stackoverflow.com/questions/23454952/uses-for-optional/23464794#23464794
- http://mail.openjdk.java.net/pipermail/lambda-libs-spec-experts/2013-May/001814.html
- http://mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/003274.html
: » 从java.io.NotSerializableException:java.util.Optional异常说Optional 不可序列化
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/252265.html