dimanche 15 avril 2018

How is it possible to create instance of subclass using superclass constructor?

Apparently Java serialization mechanism somehow manages to create an instance of subclass using superclass constructor. I wonder, how is it possible?

Here's a test which demonstrates this:

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

Output:

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]

As you see, the A() constructor is called during deserialization to produce an instance of B. Under the hood this is invoked in ObjectStreamClass.newInstance() and the instance is created by the Constructor.newInstance() call. In the debugger, the constructor cons is Test$A():

Screenshot from the the debugger showing that <code>cons</code> is <code>Test$A()</code>

Stepping out in the debugger, the created object is finally returned from ObjectInputStream.readObject(...) and it is casted without problems to B.

So if I am not mistaken, it seems that the A() constructor was used (via reflection) to create an instance of B.

I wonder how is this possible.





Aucun commentaire:

Enregistrer un commentaire