【问题标题】:How is it possible to create instance of subclass using superclass constructor?如何使用超类构造函数创建子类的实例?
【发布时间】:2018-09-25 08:46:32
【问题描述】:

显然,Java 序列化机制以某种方式设法使用超类构造函数创建子类的实例。我想知道,这怎么可能?

这是一个test,它证明了这一点:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.MessageFormat;

public class Test {

    public static class A {
        public final int a;

        public A() {
            this.a = 0;
            System.out.println(
                    MessageFormat.format(
                        "new A() constructor is called to create an instance of {0}.",
                    getClass().getName()));
        }

        public A(int a) {
            this.a = a;
            System.out.println(
                    MessageFormat.format(
                        "new A(int) constructor is called to create an instance of {0}.", 
                    getClass().getName()));
        }
    }

    public static class B extends A implements Serializable {
        public final int b;

        public B(int a, int b) {
            super(a);
            this.b = b;
            System.out.println(
                    MessageFormat.format(
                        "new B(int, int) constructor is called to create an instance of {0}.",
                    getClass().getName()));
        }

        @Override
        public String toString() {
            return "B [a=" + a + ", b=" + b + "]";
        }


    }

    public static void main(String[] args) throws Exception {

        B b1 = new B(10,20);

        System.out.println(b1);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try(ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(b1);
        }

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        try (ObjectInputStream ois = new ObjectInputStream(bis)) {
            B b2 = (B)ois.readObject();
            System.out.println(b2);
        }
    }
}

输出:

new A(int) constructor is called to create an instance of Test$B.
new B(int, int) constructor is called to create an instance of Test$B.
B [a=10, b=20]
new A() constructor is called to create an instance of Test$B.
B [a=0, b=20]

(你可以try it out live on Ideone)。

如您所见,在反序列化期间调用A() 构造函数以生成B 的实例。在后台,这是在ObjectStreamClass.newInstance() 中调用的,并且实例是由Constructor.newInstance() 调用创建的。在调试器中,构造函数consTest$A()

在调试器中跳出,创建的对象最终从ObjectInputStream.readObject(...)返回,并毫无问题地转换为B

所以如果我没记错的话,似乎是使用A() 构造函数(通过反射)来创建B 的实例。

我想知道这怎么可能。

【问题讨论】:

  • 使用字节码查看器:new Type() 首先创建指令NEW Type,然后在此实例上使用INVOKESPECIAL ... 调用构造函数。所以构造函数总是期望堆栈上的最终类型的对象。调用 super 不会创建 B 的对象,而是接收它。
  • @CoronA 这听起来合乎逻辑,但我不明白cons.newInstance(); 怎么知道“最终类型”是什么。 consTest$A(),我根本看不出这里涉及到B
  • 我会指向java.reflect.Constructor 中的一个字段:private volatile ConstructorAccessor constructorAccessor。它填充了GeneratedSerializationConstructorAccesor1@... 类型的对象。它的使用可以在Constructor.newInstance中找到
  • @CoronA 越来越近了,但我还没有完整的图片。
  • @CoronA 我认为这个构造函数访问器只是“按字节码生成反射优化”。还不清楚。

标签: java reflection constructor


【解决方案1】:

我怀疑构造函数cons 一定有问题。而且我找到了A的普通构造函数改成Bserializable constructor的位置。

首先我查看了cons 的第一个设置位置。在序列化的情况下这是ObjectStreamClass的构造函数:

if (externalizable) {
   cons = getExternalizableConstructor(cl);
} else {
   cons = getSerializableConstructor(cl); //here
   ...

于是我翻了一遍,发现在ObjectStreamClass.getSerializableConstructor

Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null);
...
cons = reflFactory.newConstructorForSerialization(cl, cons); //this does change
cons.setAccessible(true);
return cons;

cons.newInstance() 上设置调试监视

  • 在标记线之前 => 类型是A
  • 在标记线之后 => 类型为B

也就是说,用于序列化的构造函数不是A的普通构造函数,而是经过修改的用于序列化的构造函数,适配最终类。

【讨论】:

  • 我暂时不接受你的回答,我会给你/奖励你一个赏金并再次接受。非常感谢您的坚持。
  • 也许投反对票的人可以暗示他/她的期望没有得到满足?谢谢@lexicore,为我指出了这个有趣的细节。
【解决方案2】:

反序列化期间的 JVM 不会调用被反序列化的类的构造函数。但是为了创建反序列化类的实例,它需要首先创建它的超类。所以 JVM 调用第一个父级的无参数构造函数,它没有实现Serializable。尽管它不会在此构造函数中创建您的类的实例。如果您的父类是Serializable,则根本没有构造函数调用。

【讨论】:

  • ObjectStreamClass.newInstance() 确实通过反射调用 Test$A() 构造函数。我想知道结果怎么会是B
  • 这不是最终结果。这是关于JVM如何实现继承和序列化的。要创建对象,JVM 应该创建它的所有父对象。可序列化的父母是在没有构造函数调用的情况下创建的。但是 JVM 不能在没有构造函数调用的情况下创建不可序列化的父级。
  • 因此 JVM 使用构造函数创建不可序列化的父对象,然后继续反序列化而不调用构造函数
  • 序列化是JVM的一部分吗?我在 ObjectInputStream 和 co 中看不到任何 JVM 魔法。一些反射用法,但没什么特别的。
  • 所以 JVM 使用构造函数创建不可序列化的父对象,然后在没有构造函数调用的情况下继续反序列化 - 好吧,让我们把它留给 JVM。但是使用Test$A() 构造函数创建的实例是B 的实例是如何工作的呢?因为这就是这里发生的事情。
猜你喜欢
  • 1970-01-01
  • 2017-11-24
  • 1970-01-01
  • 1970-01-01
  • 2020-07-27
  • 2019-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多