【问题标题】:Why default constructor is not called while deserialization process?为什么在反序列化过程中不调用默认构造函数?
【发布时间】:2014-06-07 10:05:28
【问题描述】:
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
TestClass tc = (TestClass)is.readObject();

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

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

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

【问题讨论】:

  • 如果类没有默认(非参数)构造函数怎么办?那么应该调用哪个构造函数呢?反序列化过程应该如何决定构造函数是否很少?
  • 他们就是这样设计的。除非您得到 JDK 作者的回复,否则您在这里得到的只是或多或少不知情的意见。离题。
  • 它是 JAVA 的内部实现,您无需为此烦恼。有兴趣再挖ObjectInputStream#readObject()的源码。

标签: java serialization


【解决方案1】:

值得一读Java Object Serialization Specification: 3 - Object Input Classes,其中readObject方法详细描述并逐步解释。

它是如何工作的?

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

内容恢复正常:

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

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

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

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

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

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

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

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

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


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

示例代码;

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

【讨论】:

  • 好答案,比我的要好,但是您可以尝试更直接地解释在不运行其构造函数的情况下创建对象是如何发生的 - 因为这正是 OP 似乎想要理解的。
  • @BartoszKP 我认为它在defaultReadObject 方法中发生了一些事情。让我更多地发现它。
【解决方案2】:

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


首先,请注意默认构造函数和无参数构造函数之间的区别。默认构造函数是一个没有参数的构造函数,如果你不提供任何其他构造函数。

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

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;没有有效的构造函数 在 java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:147) 在 java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:755) 在 java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751) 在 java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) 在 java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) 在 Ideone.main(Main.java:36)

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

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

【讨论】:

  • default cosntructorno-arg constructor有什么区别?
  • @Braj 我已经添加了解释。
  • 但两者都是no-arg constructor
  • 确实Externalizable接口需要默认构造函数来反序列化,但Serializable不需要(我认为问题是关于)。
  • @Braj 但它们不是一回事,需要的是无参数,而不是默认值。
【解决方案3】:

来自oracle文档

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

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

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

【讨论】:

    猜你喜欢
    • 2019-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-29
    • 2020-10-10
    • 1970-01-01
    • 2018-08-18
    相关资源
    最近更新 更多