前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:Java Optional 为什么设计成不可序列化的?

面试官:Java Optional 为什么设计成不可序列化的?

作者头像
业余草
发布2020-12-31 11:30:01
1.3K1
发布2020-12-31 11:30:01
举报
文章被收录于专栏:业余草业余草

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

推荐:https://www.xttblog.com/?p=5138

面试官:Java Optional 为什么设计成不可序列化的?

Optional 自 Java8 发布以来深受喜爱。很多人认为它是来解决“空”异常问题的,其实它并不能解决空异常,它只是一个容器,这个容器内的对象可能为空,需要使用者自行判断。

Optional 提供的只是一种思想,很多程序员不明其意,代码中存在不少乱用的情况,尤其是中国程序员。以至于,我在面试候选人的时候,问到“Java Optional 为什么设计成不可序列化的?”几乎没有人能回答到点子上。

身边不少的同事也仅仅是停留在使用上,如果稍微问他们几个问题,就会得到“不知道,大家都这么用,我和别人的用法一样”等等类似的答案。更有甚者,把实体类中的所有属性都用上 Optional。

代码语言:javascript
复制
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 方法上保留。这样就可以继续序列化了。

代码语言:javascript
复制
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,而不是属性,集合等位置。

代码语言:javascript
复制
Map<Optional<List<String>>>
List<Optional<Map<String>>>
List<Optional<String >>>
// ...

其次,Optional 作为一个包装类,大量的 Optional 会消耗过多的内存。Optional 在字段中使用可能会浪费内存,并减慢数据结构的遍历速度。

第三,官方也不推荐在序列化、永久存储或通过网络传输中使用 Optional。

第四,在方法的参数中,也不推荐使用 Optional。

代码语言:javascript
复制
public Foo doSomething(String id, Optional<String> barOptional);

// 调用方法
foo("业余草", Optional.of("baz"));
foo("业余草", Optional.empty());

这种情况下,最好的办法是拥有一个重载的方法,该方法接受单个字符串参数并为第二个提供默认值:

代码语言:javascript
复制
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 定为不可序列化,应该是最合适的方案了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你知道的越多,不知道的就越多,业余的像一棵小草!
  • 编辑:业余草
  • 推荐:https://www.xttblog.com/?p=5138
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档