从java.io.NotSerializableException:java.util.Optional异常说Optional 不可序列化

JAVA herman 448浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:codedq,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:codedq,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

面试官: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 的用法,不是本文重点,感兴趣的可以收藏下图。

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 定为不可序列化,应该是最合适的方案了。

参考资料

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号1:xmtxtt(5000人已满),微信号2:xttblog(5000人已满),微信号3:codedq(超3800)。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » 从java.io.NotSerializableException:java.util.Optional异常说Optional 不可序列化