有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么我看到枚举常量的字段值会被序列化/反序列化?在哪种情况下,枚举中哪些内容没有序列化?

Serialization Specification(SE8)意味着枚举常量的字段值没有序列化(正如我读到的):

1.12 Serialization of Enum Constants Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form.

但我看到它们确实被序列化/反序列化了

在下面的代码中,枚举常量的public int x = 1234;字段INSTANCE在两种情况下被序列化/反序列化:

  1. 仅在构造函数中设置(在public int x = 1234;之后,setX()方法从未调用),然后成功地反序列化x = 1234
  2. 在构造函数之后,我调用setX(7),但该设置值(x=7)随后被成功反序列化

那么在什么情况下,枚举中不序列化/反序列化那么“形式”这个词是什么意思呢

enum MyEnum1 {

INSTANCE {
    public int x = 1234;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }
};

public abstract int getX();

public abstract void setX(int x);

}

public class AAA {

    public static void main(String[] args) {


        MyEnum1 obj = MyEnum1.INSTANCE; 
        obj.setX(7);
        System.out.println(obj.getX());
        String fileName = "d:\\del.me";
        int bufSz = 8 * 1024;

        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new BufferedOutputStream(new FileOutputStream(fileName), bufSz));
            oos.writeObject(obj);
            oos.flush();
        } catch (IOException e) {   }


        MyEnum1 obj1 = null;
        MyEnum1 obj2 = null;
        try {
            ObjectInputStream ois = new ObjectInputStream(
                    new BufferedInputStream(new FileInputStream(fileName), bufSz));

            ObjectInputStream ois1 = new ObjectInputStream(
                    new BufferedInputStream(new FileInputStream(fileName), bufSz));

            obj1 = (MyEnum1) ois.readObject();
            obj2 = (MyEnum1) ois1.readObject();
        } catch (IOException | ClassNotFoundException e) {  }

        // x is restored, either the one set by setX()
        // or the one just set in inline constructor (initializer) without calling setX() 
        System.out.println(obj1.getX());  // 1234 without setX(7), 7 with setX(7)
        System.out.println(obj2.getX());  // 1234 without setX(7), 7 with setX(7)

    }

}

p.S.This answer对我来说还意味着枚举常量中的字段不会被序列化:

As I see it, it doesn't make sense to mark Enum's field values as transient or make them implement Serializable, since they'll never get serialized, no matter if they're marked as transient or implement Serializable.

但在我看来,enum常量内(如Color { RED { fields }; }或enum内但“在”每个单独常量外(Color { RED { fields }; fields_for_all_constants})的所有(非瞬态)字段(至少是原始字段)都会被序列化


共 (1) 个答案

  1. # 1 楼答案

    您的测试存在缺陷,因为它假定反序列化枚举常量会导致枚举常量的多个实例。来自JLS的§8.9强调我的):

    An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type (§15.9.1).

    In addition to the compile-time error, three further mechanisms ensure that no instances of an enum type exist beyond those defined by its enum constants:

    • The final clone method in Enum ensures that enum constants can never be cloned.

    • Reflective instantiation of enum types is prohibited.

    • Special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.

    这意味着在MyEnum1.INSTANCE上设置x会全局更改值,就像更改“常规”单例的状态一样。当反序列化常量时,会得到已经存在的相同实例,,这意味着它的当前值为^{

    更好的测试是在程序的一次运行中序列化常量,然后在后续运行中反序列化。请尝试以下示例:

    import java.io.*;
    import java.nio.file.*;
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
            var file = Path.of(System.getProperty("user.dir")).resolve("myenum.bin");
            switch (args[0].toLowerCase()) {
                case "save":
                    var instance = MyEnum.INSTANCE;
                    instance.setValue(10);
                    try (var oos = new ObjectOutputStream(Files.newOutputStream(file))) {
                        oos.writeObject(instance);
                    }
                    break;
                case "load":
                    try (var ois = new ObjectInputStream(Files.newInputStream(file))) {
                        System.out.println(((MyEnum) ois.readObject()).getValue());
                    }
                    break;
                default:
                    throw new IllegalArgumentException("expected 'save' or 'load', actual = " + args[0]);
            }
        }
    
        public enum MyEnum {
            INSTANCE {
                private int value = 5;
                @Override public void setValue(int value) { this.value = value; }
                @Override public int getValue() { return value; }
            };
            public abstract void setValue(int value);
            public abstract int getValue();
        }
    }
    

    首先运行java Main save将枚举常量序列化为文件。然后执行java Main load,它将反序列化枚举常量并打印value。尽管在value10时序列化了enum常量,但反序列化实例的value5(初始值)。这强烈表明字段没有与enum常量一起序列化


    As I see my example code is almost exactly like yours, I don't see which difference makes your snippet and mine behave differently...

    代码创建一个单个JVM实例,该实例设置值,序列化枚举,然后反序列化枚举。由于这一切都发生在同一个JVM中,因此反序列化的枚举将是已经存在且x已更改的同一个实例

    我的代码有两种模式:“保存”和“加载”

    • “save”创建一个JVM实例,设置枚举常量的value,然后将枚举序列化为一个文件
    • “load”创建一个JVM实例,并从文件中反序列化枚举常量。它不会修改value

    这两种模式不能在程序的一次调用中发生,这意味着每种模式都使用不同的JVM实例。因为这发生在两个不同的JVM实例上,所以反序列化的enum不是序列化的同一个实例