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

时间:2014-06-07 10:05:28

标签: java serialization

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

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

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

另一点是,如果TestClass扩展了BaseTestClass并且BaseTestClass没有实现序列化, 调用BaseTestClass的构造函数但不调用TestClass。为什么这样 ?我相信它背后会有一些合理的理由。 但是我没有得到它?

3 个答案:

答案 0 :(得分:5)

值得一读Java Object Serialization Specification: 3 - Object Input Classes,其中详细介绍了readObject方法以及逐步说明。

它是如何工作的?

分配了类的实例。实例及其句柄将添加到已知对象集中。

内容得到适当恢复:

  1. 对于可序列化对象,运行第一个非可序列化超类型的no-arg构造函数

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

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

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

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

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

    • 必须逐步执行这些类,将流中的可用数据与要还原的对象的类进行匹配。流中出现但未在对象中出现的类的数据将被丢弃。

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

  2. 对于可外部化的对象,运行该类的no-arg构造函数,然后调用readExternal方法来恢复该对象的内容。


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

    示例代码;

    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();
    }
    

    输出:

    Object construction via calling new keyword
    TestClass1
    TestClass2
    
    Object construction via readObject method
    TestClass1
    

答案 1 :(得分:0)

注意:这与Externalizable课程有关,而不是Serializable,正如Pshemo在下面的评论中正确指出的那样。 Answer posted by Braj显示了Serializable的代码示例。


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

ObjectInputStream要求一个类有一个no-arg构造函数,这是一个演示它的代码示例:

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();
  }
}

产地:

  

线程中的异常" main" java.io.InvalidClassException:Ideone $ Test;没有有效的构造函数       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)       在java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)       在Ideone.main(Main.java:36)

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

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

答案 2 :(得分:0)

来自oracle文档

  

从ObjectInputStream中读取对象类似于创建新对象。正如从超类到子类的顺序调用新对象的构造函数一样,从流中读取的对象从超类反序列化为子类。在反序列化期间,将调用readObject或readObjectNoData方法而不是每个Serializable子类的构造函数。

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

  

可序列化对象的每个子类都可以定义自己的readObject方法。如果类未实现该方法,则将使用defaultReadObject提供的默认序列化。实现时,该类仅负责恢复其自己的字段,而不是其超类型或子类型的字段。