【问题标题】:Is custom enum Serializable too?自定义枚举也是可序列化的吗?
【发布时间】:2013-03-09 09:58:04
【问题描述】:

我知道Enum 是可序列化的。因此,这样做是安全的。 (selectedCountry 是enum Country)

没有客户成员变量的原始枚举

public enum Country {
    Australia,
    Austria,
    UnitedState;
}

片段

@Override
public void onActivityCreated (Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        selectedCountry = (Country)savedInstanceState.getSerializable(SELECTED_COUNTRY_KEY);
    }
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putSerializable(SELECTED_COUNTRY_KEY, selectedCountry);
}

但是,如果我在自定义枚举类中有不可序列化的成员怎么办?例如,

原始枚举客户成员变量

package org.yccheok;

import org.yccheok.R;

/**
 *
 * @author yccheok
 */
public enum Country {
    Australia(R.drawable.flag_au),
    Austria(R.drawable.flag_at),
    UnitedState(R.drawable.flag_us);

    Country(int icon) {
        this.icon = icon;
        nonSerializableClass = new NonSerializableClass(this.toString());
    }

    public int getIcon() {
        return icon;
    }

    public static class NonSerializableClass {
        public NonSerializableClass(String dummy) { this.dummy = dummy; }
        public String dummy;
    }

    private final int icon;

    public NonSerializableClass nonSerializableClass;
}

我测试过。有用。 (我通过打印出序列化前后所有成员变量的值来测试,前后一致)

但是,我不明白它为什么起作用?因为我没有按照Serializable 接口的要求提供正确的readObjectwriteObject

正如 Effective Java 第 75 条:考虑使用自定义序列化表单中所指出的,如果我的枚举中有自定义成员变量,我是否需要提供自己的 readObjectwriteObject

【问题讨论】:

  • “它有效”是什么意思?你做了什么测试?

标签: java android


【解决方案1】:

它起作用的原因是Enum 的序列化过程与其他类的序列化过程不同。来自official documentation

1.12 枚举常量的序列化

枚举常量的序列化方式与普通的可序列化或可外部化对象不同。枚举常量的序列化形式仅由其名称组成;常量的字段值不存在于表单中。为了序列化枚举常量,ObjectOutputStream 写入枚举常量的 name 方法返回的值。为了反序列化一个枚举常量,ObjectInputStream 从流中读取常量名;然后通过调用 java.lang.Enum.valueOf 方法获得反序列化的常量,将常量的枚举类型与接收到的常量名称一起作为参数传递。与其他可序列化或可外部化的对象一样,枚举常量可以作为后续出现在序列化流中的反向引用的目标。

这意味着,您的所有自定义字段不会被序列化。在您的情况下,一切正常,因为您的应用程序进程仍在运行,并且您正在获得传递给 savedInstanceState.putSerializable相同 Enum 实例。

但想象一下,您的应用会因为Android 没有足够的内存而被杀死。下次用户打开应用程序时,您将获得一个 new Enum 实例,并且所有自定义字段都将丢失并由构造函数重新初始化。因此,枚举中的可变字段总是有效的transient

【讨论】:

  • 但是你知道他们如何处理枚举类中的一些客户成员变量,比如nonSerializableClassicon,...?
  • 它们完全没有序列化。
  • 我希望它也不会被序列化和损坏。但是,我的测试显示序列化的工作方式有些...
  • 是的。我认为最有可能是内存中的枚举导致事物“工作”。
  • 在这种情况下,我将使用 Parcelable(使用 enum.name 而不是 enum.ordinal,因为新枚举的成员可能会破坏序数)。感谢您解释为什么序列化似乎“起作用”(实际上它不起作用)。
【解决方案2】:

根据Serializable 文档,根本不需要readObjectwriteObject,因此您的问题可能并不完全正确。

Serializablemarker interface,没有任何方法。

我将您推荐给this answer,它提供了有关序列化实现的更多详细信息(解释了为什么您不需要写入和读取函数)。

而且,正如 Dianne Hackborn 提到的hereParcelable 对于 Android 来说效率更高。

如果你对 Enum 特别感兴趣,请参考below paragraph

1.12 枚举常量的序列化

枚举常量的序列化方式与普通的可序列化或可外部化对象不同。枚举的序列化形式 常量仅由其名称组成;常量的字段值为 不存在于表格中。要序列化枚举常量, ObjectOutputStream 写入枚举常量返回的值 命名方法。要反序列化枚举常量,ObjectInputStream 读取 流中的常量名;然后反序列化的常数是 通过调用 java.lang.Enum.valueOf 方法获得,传递 常量的枚举类型以及接收到的常量名称为 论据。像其他可序列化或可外部化的对象一样,枚举 常量可以作为出现的反向引用的目标 随后在序列化流中。

无法自定义枚举常量序列化的过程:任何类特定的writeObject、readObject、 定义的 readObjectNoData、writeReplace 和 readResolve 方法 枚举类型在序列化和反序列化期间被忽略。 同样,任何 serialPersistentFields 或 serialVersionUID 字段 声明也被忽略——所有枚举类型都有一个固定的 serialVersionUID 为 0L。记录可序列化的字段和数据 枚举类型是不必要的,因为类型没有变化 发送的数据。

所以,我认为Enum 不是测试内部不可序列化类工作的正确选择。

【讨论】:

  • 是的。我发现 Parcelable 效率更高。但是,我想通过直接使用枚举的 Serializable 特性来减少代码(Less code less error)。只是,如果不提供我自己的 readObject 和 writeObject(如 Effective Java #75 中所指出的),我不确定枚举中的自定义成员变量是否会正确运行。
  • 请参考我更新的答案,其中包含序列化规范中的详细信息,该规范对枚举非常具体。
【解决方案3】:

枚举成员的序列化不起作用。 当@vmironov 回答时,nonSerializable 字段永远不会被序列化。这是一个测试:

public enum Country {
Australia;

    public static class NonSerializableClass {
       public NonSerializableClass() {}
       public String dummy;
    }

    public NonSerializableClass nonSerializableClass;
}

将枚举写入序列化流的代码:

public class SerializationTestWrite {
    public static void main(String[] args) throws Exception{
        FileOutputStream f = new FileOutputStream("tmp");
        ObjectOutput s = new ObjectOutputStream(f);

        Country.Australia.nonSerializableClass = new Country.NonSerializableClass();
        Country.Australia.nonSerializableClass.dummy = "abc";

        s.writeObject(Country.Australia);
        s.flush();

        System.out.println(Country.Australia.nonSerializableClass.dummy);
    }
}    

在写入虚拟字段的值是:abc

从序列化流中读取枚举的代码:

public class SerializationTestRead {
    public static void main(String[] args) throws Exception{
        FileInputStream in = new FileInputStream("tmp");
        ObjectInputStream so = new ObjectInputStream(in);
        Country readed = (Country) so.readObject();

        System.out.println(readed.nonSerializableClass);
    }
}

但在阅读时,字段nonSerializableClass的值为:null

【讨论】:

  • 谢谢。我认为很可能我的测试方法是错误的,因为我正在使用相同的过程进行序列化和反序列化。
  • @dcernahoschi,是的,这是预期的结果。但是如果你将SerializationTestRead.main 中的所有代码添加到SerializationTestWrite.main 的末尾并运行它,你会看到nonSerializableClass not null。
猜你喜欢
  • 2015-10-21
  • 2021-05-11
  • 2014-12-12
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多