为什么java.util.Optional不可序列化,如何使用此类字段序列化对象


107

Enum类是可序列化的,因此使用枚举序列化对象没有问题。另一种情况是class具有java.util.Optional类的字段。在这种情况下,将引发以下异常:java.io.NotSerializableException:java.util.Optional

如何处理此类,如何序列化它们?是否可以将此类对象发送到远程EJB或通过RMI?

这是示例:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Optional;

import org.junit.Test;

public class SerializationTest {

    static class My implements Serializable {

        private static final long serialVersionUID = 1L;
        Optional<Integer> value = Optional.empty();

        public void setValue(Integer i) {
            this.i = Optional.of(i);
        }

        public Optional<Integer> getValue() {
            return value;
        }
    }

    //java.io.NotSerializableException is thrown

    @Test
    public void serialize() {
        My my = new My();
        byte[] bytes = toBytes(my);
    }

    public static <T extends Serializable> byte[] toBytes(T reportInfo) {
        try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
            try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
                ostream.writeObject(reportInfo);
            }
            return bstream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

如果Optional标记为Serializable,那么get()返回不可序列化的内容会怎样?
WW。

10
@WW。您NotSerializableException,当然会得到一个。
罗恩侯爵

7
@WW。就像收藏。大多数集合类都是可序列化的,但实际上只有集合中包含的每个对象也可以序列化时,集合实例才可以序列化。
Stuart Marks

我个人的看法是:这并不是要提醒人们正确地对对象进行单元测试甚至序列化。我自己遇到了java.io.NotSerializableException(java.util.Optional) ;-(
GhostCat

Answers:


171

此答案是对标题“是否应该可序列化的可选内容”的问题的答复。简短的答案是Java Lambda(JSR-335)专家组考虑并拒绝了它。这一点,和这一个这一个表示为主要设计目标Optional是用来作为当了一回值可能是不存在的函数的返回值。目的是调用方立即检查Optional并提取实际值(如果存在)。如果不存在该值,则调用者可以替换默认值,引发异常或应用某些其他策略。通常,通过将流式方法调用链接到返回Optional值的流管道(或其他方法)的末端来完成此操作。

绝不打算Optional将其用于其他方式,例如用于可选方法参数作为字段存储在object中。通过扩展,可Optional序列化将使其能够持久存储或通过网络传输,这两者都鼓励使用远远超出其原始设计目标。

通常,组织数据要比Optional在字段中存储更好。如果getter(例如问题中的getValue方法)Optional从字段返回实际值,则它将强制每个调用者实施一些用于处理空值的策略。这可能会导致呼叫者之间行为不一致。通常最好让该字段设置的任何代码集在应用时都应用一些策略。

有时人们想放入Optional收藏,例如List<Optional<X>>Map<Key,Optional<Value>>。这通常也是一个坏主意。通常最好OptionalNull-Object值(不是实际的null引用)代替这些用法,或者只是完全从集合中忽略这些条目。


26
Stuart做得好。我必须说,如果推理的话,这是一条非同寻常的链条,而设计一个不打算用作实例成员类型的类是一件很不寻常的事情。尤其是在Javadoc的类合同中没有说明的情况下。也许他们应该设计一个注释而不是一个类。
罗恩侯爵

1
这是一篇关于Sutart回答背后原理的出色博客文章:blog.joda.org/2014/11/optional-in-java-se-8.html
Wesley Hartford

2
好东西,我不需要使用可序列化的字段。否则将是一场灾难
Kurru 2015年

48
有趣的答案是,对我来说,设计选择是完全不能接受和错误的。您说:“通常,比在字段中存储Optional更好的方法来组织数据”,可以肯定,为什么不这样做,但这应该是设计者的选择,而不是语言的选择。这是其中的一种,我非常想念Java中的Scala可选(Scala可选是可序列化的,并且它们遵循Monad的准则)
Guillaume16年

37
“可选的主要设计目标是当可能不存在返回值时,将其用作函数的返回值。” 好吧,看来您不能将它们用于远程EJB中的返回值。太好了
Thilo

15

Serialization通过将持久性序列化形式与您在其上运行的实际运行时实现分离,可以解决许多相关问题。

/** The class you work with in your runtime */
public class My implements Serializable {
    private static final long serialVersionUID = 1L;

    Optional<Integer> value = Optional.empty();

    public void setValue(Integer i) {
        this.value = Optional.ofNullable(i);
    }

    public Optional<Integer> getValue() {
        return value;
    }
    private Object writeReplace() throws ObjectStreamException
    {
        return new MySerialized(this);
    }
}
/** The persistent representation which exists in bytestreams only */
final class MySerialized implements Serializable {
    private final Integer value;

    MySerialized(My my) {
        value=my.getValue().orElse(null);
    }
    private Object readResolve() throws ObjectStreamException {
        My my=new My();
        my.setValue(value);
        return my;
    }
}

该类Optional实现的行为允许在处理可能不存在的值(与的使用相比null)时编写良好的代码。但这并不能给您的数据持久表示带来任何好处。这只会使您的序列化数据更大……

上面的草图可能看起来很复杂,但这是因为它仅演示具有一个属性的模式。您的类具有的属性越多,其简单性就应得到更多的体现。

别忘了,My完全无需更改持久性形式即可完全更改实施的可能性……


2
+1,但是随着字段的增加,将有更多的样板可用于复制它们。
Marko Topolnik

1
@Marko Topolnik:每个属性和方向最多只能一行。但是,通过为提供适当的构造函数class My,通常也可以方便地将其用于其他用途,这readResolve可能是单行实现,从而将每个属性的样板减少到单行。考虑到每个可变属性在类中至少有七行代码,这一点并不多My
Holger 2014年

我写了一篇关于同一主题的文章。本质上,这是该答案的长文本版本:序列化可选
Nicolai

缺点是:您需要对任何使用可选参数的bean / pojo进行此操作。但还是,好主意。
GhostCat


4

这是一个奇怪的遗漏。

您将不得不将该字段标记为,transient并提供您自己的自定义writeObject()方法来编写get()结果本身,以及一种通过从流中读取结果来readObject()还原方法的方法Optional。不要忘记分别致电defaultWriteObject()defaultReadObject()


如果我拥有类的代码,则将纯对象存储在字段中会更方便。在这种情况下,可选类将限制为该类的接口(get方法将返回Optional.ofNullable(field))。但是对于内部表示,不可能使用Optional明确表示该值是可选的。
vanarchi 2014年

我刚刚表明这可能的。如果您出于某种原因另有看法,您的问题到底是什么?
罗恩侯爵

谢谢您的回答,它为我增加了一个选择。在我的评论中,我想展示有关此主题的更多想法,考虑Pro和每种解决方案的对立面。在使用writeObject / readObject方法时,我们在状态表示中具有Optional的明确意图,但是序列化的实现变得更加复杂。如果在计算/流中大量使用字段-使用writeObject / readObject更方便。
vanarchi 2014年

注释应与其出现的答案相关。坦率地说,你的想法很重要。
罗恩侯爵

3

Vavr.io库(以前的Javaslang)也具有Option可序列化的类:

public interface Option<T> extends Value<T>, Serializable { ... }

0

如果要维护更一致的类型列表并避免使用null,则有一个怪异的选择。

您可以使用交集类型存储值。加上lambda,可以实现以下功能:

private final Supplier<Optional<Integer>> suppValue;
....
List<Integer> temp = value
        .map(v -> v.map(Arrays::asList).orElseGet(ArrayList::new))
        .orElse(null);
this.suppValue = (Supplier<Optional<Integer>> & Serializable)() -> temp==null ? Optional.empty() : temp.stream().findFirst();

temp变量分开可以避免关闭value成员的所有者,从而避免序列化过多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.