Java 中反序列化过程如何发生?

在我上一篇与“在 Java 中实现Serializable接口的指南”相关的文章中,Bitoo 先生问了一个好问题:“在反序列化时,JVM 如何创建 JVM 没有调用构造器的对象?”。 我曾想在评论中的同一帖子中回复他,但又过一会儿,我想到了这个非常有趣的话题,需要另外撰写详细的文章,并与大家进行讨论。 因此,在这里,我将以对这一主题的有限知识开始讨论,并鼓励大家提出您的想法/疑问,以便使我们所有人都清楚这个主题。 我从这里开始。

我们已经介绍了许多与 Java 中的序列化相关的内容,以及与 Java 中的反序列化相关的一些内容。 我将不再重复同一件事,而是直接进入主要讨论主题,即反序列化如何在 Java 中工作?

反序列化是将先前序列化的对象重建回其原始形式(即对象实例)的过程。 反序列化过程的输入是字节流,它是从网络的另一端获取的,或者我们只是从文件系统/数据库中读取字节流。 立即出现一个问题,在此字节流中写入了什么?

阅读更多信息用于实现Serializable接口的迷你指南

确切地说,字节流(或称序列化数据)具有有关由序列化过程序列化的实例的所有信息。 该信息包括类的元数据,实例字段的类型信息以及实例字段的值。 将对象重新构造回新的对象实例时,也需要相同的信息。 在反序列化对象时,JVM 从字节流中读取其类元数据,该字节流指定对象的类是实现Serializable还是Externalizable接口。

请注意,要无缝进行反序列化,执行反序列化的 JVM 中必须存在要反序列化对象的类的字节码。 否则,将抛出ClassNotFoundException。 这不是很明显吗?

如果实例实现了Serializable接口,则将创建类的实例而无需调用其任何构造器。 真? 那么如果不调用构造器,该如何创建对象呢?

让我们看一个简单的电子程序的字节码:

public class SimpleProgram
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}

Byte code:

public class SimpleProgram extends java.lang.Object{
public SimpleProgram();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String Hello World!
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:	return
}

上面的字节码看起来很真实,不是吗? 在第一行中,我们将把“局部变量表”中的值压入栈。 在这种情况下,我们实际上只是将隐式引用推到“this”,因此它并不是最令人兴奋的说明。 第二条指令是主要内容。 实际上,它调用了最高级类的构造器,在上述情况下,它是Object.java。 一旦调用了最高级类的构造器(即本例中的对象),其余的代码就会执行用代码编写的特定指令。

符合以上概念,即最高级的构造器,我们在反序列化中也有类似的概念。 在反序列化过程中,要求实例的所有父类均应可序列化; 如果层次结构中的任何超类都不可序列化,则它必须具有默认构造器。 现在有道理了。 因此,在反序列化时,将首先搜索超类,直到找到任何不可序列化的类。 如果所有超类都可以序列化,那么 JVM 最终会到达Object类本身,并首先创建Object类的实例。 如果在搜索超类之间,发现任何无法序列化的类,则将使用其默认构造器在内存中分配实例。

如果要在不可序列化的情况下反序列化实例的任何超类,并且也没有默认构造函数,那么JVM会抛出“NotSerializableException”。

另外,在继续进行对象重建之前,JVM 会检查字节流中提到的serialVersionUID是否与该对象类的serialVersionUID相匹配。 如果不匹配,则抛出“InvalidClassException”。

阅读更多内容Java 中的SerialVersionUID以及相关的快速知识

因此,到目前为止,我们使用超类的默认构造器之一将实例保存在内存中。 请注意,此后将不会为任何类调用构造器。 执行超类构造器后,JVM 读取字节流并使用实例的元数据来设置实例的类型信息和其他元信息。

创建空白实例后,JVM 首先设置其静态字段,然后调用默认的readObject()方法(如果未覆盖),否则会调用被覆盖的方法,该方法负责将值从字节流设置为空白实例。

阅读更多信息readObject()writeObject()的示例代码

readObject()方法完成后,反序列化过程完成,您可以使用新的反序列化实例了。

请在评论区域中发表您对该主题的想法/看法/查询。 这些绝对值得欢迎。