本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序

腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
面试官: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
最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » 从java.io.NotSerializableException:java.util.Optional异常说Optional 不可序列化