Java反序列化与对象的创建

Java与单例模式一文中提到了,Java可以通过反序列化来破坏单例,其底层就是利用反射,通过一个代表无参构造方法的Constructor对象,使用其newInstance()方法来创建对象。

但是,在后续的测试代码中发现,其实目标类的无参构造方法并没有执行!所以,对于这个对象的创建过程并不是我一开始想的那样。请看下面的例子:

// 目标类
public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    public Elvis() {
        System.out.println("no parameters constructor invoked!");
    }
}

// 测试
@Test
public void testSerialization() throws Exception {
    Elvis elvis1 = Elvis.INSTANCE;
    FileOutputStream fos = new FileOutputStream("a.txt");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(elvis1);
    oos.flush();
    oos.close();

    Elvis elvis2 = null;
    FileInputStream fis = new FileInputStream("a.txt");
    ObjectInputStream ois = new ObjectInputStream(fis);
    elvis2 = (Elvis) ois.readObject();

    System.out.println("elvis1与elvis2相等吗? ===> " + (elvis1 == elvis2));
}

结果:

no parameters constructor invoked!
elvis1与elvis2相等吗? ===> false

public static final Elvis INSTANCE = new Elvis();是对象第一次实例化,所以第一行的打印no parameters constructor invoked是这个时候执行的。Elvis的构造方法只执行了一次,所以反序列化的时候,Java没有去调用目标类的构造方法来创建对象。

回顾反序列化过程的Java代码,在ObjectInputStream类中的Object readOrdinaryObject(boolean)方法中。如下:

private Object readOrdinaryObject(boolean unshared)
        throws IOException {
    //此处省略部分代码

    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();

    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) {
          throw new InvalidClassException("invalid class descriptor");
    }

    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }

    //此处省略部分代码

    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }

    return obj;
}

重点代码块:

obj = desc.isInstantiable() ? desc.newInstance() : null;

我们知道desc.newInstance()就是创建对象,我读了一下这个方法的Java文档:

创建目标类的一个新的实例对象。如果该类是Externalizable类型的,则调用它自身的无参构造方法(前提是该方法是public)。如果该类是Serializable类型的,则调用该类的第一个非Serializable类型的父类的无参构造方法。

文档已经告诉我们答案了。在我们的测试代码中,Elvis实现了Serializable接口,所以反序列化的时候调用的是它父类的Object的无参构造方法创建新对象的。因此,Elvis的无参构造方法没有执行。

请看改进后的测试代码:

public class Parent {
    public Parent() {
        System.err.println("Parent no-arg constructor invoked!");
    }
}

public class Elvis extends Parent implements Serializable {

    public static final Elvis INSTANCE = new Elvis();

    public Elvis() {
        System.out.println("Elvis no-arg constructor invoked!");
    }
}

我给Elvis加了一个父类Parent,看看结果如何:

Parent no-arg constructor invoked!
Elvis no-arg constructor invoked!
===开始反序列化===
Parent no-arg constructor invoked!
elvis1与elvis2相等吗? ===> false

这下结果是符合我的预期的。父类的无参构造方法被执行了2次。第2次就是desc.newInstance()时候执行的。

通过debug我们也可以发现,我们在newInstance()方法中打个断点。如下图:

在这里插入图片描述
显而易见,代码中的cons指向的是父类的构造方法。
这下我们可以得出结论:对于实现Serializable接口的类,并不要求该类具有一个无参的构造方法, 因为在反序列化的过程中实际上是去其继承树上找到一个没有实现Serializable接口的父类(最终会找到Object),然后构造该类的对象,再逐层往下的去设置各个可以反序列化的属性(也就是没有被transient修饰的非静态属性)。

参考

单例与序列化的那些事儿

Java对象的序列化与反序列化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值