关于java:为什么反序列化过程中不调用默认构造函数?

Why default constructor is not called while deserialization process?

1
2
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
TestClass tc = (TestClass)is.readObject();

反序列化后得到TestClass的对象,但没有调用TestClass的默认构造函数。据我了解
有两种方法可以创建对象,即使用 new 运算符或 TestClass.class.newInstance()。两者都调用默认构造函数。

看起来反序列化过程不是用大约两种方法创建对象,这就是为什么不调用默认构造函数的原因。
问题是反序列化如何创建对象?

另外一点是,如果 TestClass 扩展了 BaseTestClass 并且 BaseTestClass 没有实现序列化,
BaseTestClass 的构造函数被调用,但不是 TestClass。为什么这样 ?我相信这背后会有一些合乎逻辑的原因。
但是我没听懂?


值得一读的Java对象序列化规范:3 - 对象输入类,其中readObject方法详细描述并逐步解释。

怎么运行的?

分配了一个类的实例。实例及其句柄被添加到一组已知对象中。

适当恢复的内容:

  • 对于可序列化对象,运行第一个不可序列化超类型的无参数构造函数。

    • 对于可序列化的类,字段被初始化为适合其类型的默认值。

    • 然后通过调用特定于类的readObject方法来恢复每个类的字段,或者如果没有定义这些方法,则通过调用defaultReadObject方法。

    • 请注意,在反序列化期间,不会为可序列化类执行字段初始值设定项和构造函数。

    • 在正常情况下,写入流的类的版本将与读取流的类相同。在这种情况下,流中对象的所有超类型都将匹配当前加载的类中的超类型。

    • 如果编写流的类的版本与加载的类具有不同的超类型,则 ObjectInputStream 必须更加小心地恢复或初始化不同类的状态。

    • 它必须逐步遍历类,将流中的可用数据与正在恢复的对象的类进行匹配。出现在流中但未出现在对象中的类的数据将被丢弃。

    • 对于出现在对象中但不在流中的类,通过默认序列化将类字段设置为默认值。

  • 对于可外部化的对象,运行类的无参数构造函数,然后调用 readExternal 方法来恢复对象的内容。

  • 理解第一个点的示例代码对于可序列化对象,运行第一个不可序列化超类型的无参数构造函数。

    示例代码;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class TestClass1 {
        public TestClass1() {
            System.out.println("TestClass1");
        }
    }

    class TestClass2 extends TestClass1 implements Serializable {
        public TestClass2() {
            System.out.println("TestClass2");
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Object construction via calling new keyword");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("resources/dummy.dat"));
        out.writeObject(new TestClass2());

        System.out.println("Object construction via readObject method");
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("resources/dummy.dat"));
        TestClass2 tc = (TestClass2) is.readObject();
    }

    输出:

    1
    2
    3
    4
    5
    6
    Object construction via calling new keyword
    TestClass1
    TestClass2

    Object construction via readObject method
    TestClass1


    来自oracle文档

    Reading an object from the ObjectInputStream is analogous to creating a new object. Just as a new object's constructors are invoked in the order from the superclass to the subclass, an object being read from a stream is deserialized from superclass to subclass. The readObject or readObjectNoData method is called instead of the constructor for each Serializable subclass during deserialization.

    所以简而言之,它应该在从超类到子类的层次结构中调用 readObject() 方法。仅当所有超类都实现可序列化接口时才会出现,否则将调用超类的默认构造函数。
    如此可序列化

    Each subclass of a serializable object may define its own readObject method. If a class does not implement the method, the default serialization provided by defaultReadObject will be used. When implemented, the class is only responsible for restoring its own fields, not those of its supertypes or subtypes.


    注意:这与 Externalizable 类有关,而不是 Serializable,正如 Pshemo 在下面的评论中正确指出的那样。 Braj 发布的答案显示了 Serializable.

    的代码示例

    首先,请注意默认构造函数和无参数构造函数之间的区别。如果您不提供任何其他构造函数,则默认构造函数是生成的无参数构造函数。

    ObjectInputStream 要求一个类有一个无参数的构造函数,下面是一个演示它的代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import java.util.*;
    import java.lang.*;
    import java.io.*;

    class Ideone
    {
      static class Test implements Externalizable
      {
        //public Test() {}

        public Test(int x)
        {
        }

        public void writeExternal(ObjectOutput out)
            throws IOException
        {
        }

        public void readExternal(ObjectInput in)
            throws IOException, ClassNotFoundException
        {
        }
    }

    public static void main(String[] args)
        throws java.lang.Exception
      {
        Test t = new Test(0);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(t);
        oos.close();

        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(is);
        t = (Test)ois.readObject();
        ois.close();
      }
    }

    生产:

    Exception in thread"main" java.io.InvalidClassException: Ideone$Test; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:147)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:755)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
    at Ideone.main(Main.java:36)

    Ideone 演示:http://ideone.com/yPpJrb

    当您取消注释无参数构造函数时,它可以正常工作。当您删除提供的单参数构造函数时,它也可以正常工作 - 因为那样会生成默认构造函数。