Java对象的序列化/反序列化原理及源码解析(下)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Java对象的序列化/反序列化原理及源码解析(下)

Other

1. static和transient字段不能被序列化。

序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);这句代码来获取需要被序列化的字段,getSerialFields()方法实际上是调用getDefaultSerialFields()方法的,getDefaultSerialFields()实现如下:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;
    for (int i = 0; i < clFields.length; i++) {
        if ((clFields[i].getModifiers() & mask) == 0) {
            // 如果字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去
            list.add(new ObjectStreamField(clFields[i], false, true));
        }
    }
    int size = list.size();
    return (size == 0) ? NO_FIELDS :
        list.toArray(new ObjectStreamField[size]);
}

从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉。


在进行反序列化的时候会给默认值。

##2. 如何实现自定义序列化和反序列化?

只需要被序列化的对象所属的类定义了void writeObject(ObjectOutputStream oos)和void readObject(ObjectInputStream ois)方法即可,Java序列化和反序列化的时候会调用这两个方法,那么这个功能是怎么实现的呢?


在ObjectClassStream类的构造函数中有下面几行代码:

cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
    new Class<?>[] { ObjectOutputStream.class },
    Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
    new Class<?>[] { ObjectInputStream.class },
    Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
    cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
    new Class<?>[] { ObjectOutputStream.class },
    Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
    new Class<?>[] { ObjectInputStream.class },
    Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
    cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
    new Class<?>[] { ObjectOutputStream.class },
    Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
    new Class<?>[] { ObjectInputStream.class },
    Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
    cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);

getPrivateMethod()方法实现如下:

private static Method getPrivateMethod(Class<?> cl, String name,
                                   Class<?>[] argTypes,
                                   Class<?> returnType)
{
    try {
        Method meth = cl.getDeclaredMethod(name, argTypes);
        meth.setAccessible(true);
        int mods = meth.getModifiers();
        return ((meth.getReturnType() == returnType) &&
                ((mods & Modifier.STATIC) == 0) &&
                ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
    } catch (NoSuchMethodException ex) {
        return null;
    }
}

可以看到在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。


在调用writeSerialData()方法写入序列化数据的时候有

private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) {
            // 其他一些省略代码
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                // 在这里调用用户自定义的方法
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                curContext.setUsed();
                curContext = oldContext;
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
            curPut = oldPut;
        } else {
            defaultWriteFields(obj, slotDesc);
        }
    }
}

首先会调用hasWriteObjectMethod()方法判断有没有自定义的writeObject(),代码如下:

boolean hasWriteObjectMethod() {
    return (writeObjectMethod != null);
}


hasWriteObjectMethod()这个方法仅仅是判断writeObjectMethod是不是等于null,而上面说了,如果用户自定义了void writeObject(ObjectOutputStream oos)这么个方法,则writeObjectMethod不为null,在if()代码块中会调用slotDesc.invokeWriteObject(obj, this);方法,该方法中会调用用户自定义的writeObject()方法。


Java编程思想相关知识点


当程序运行时,有关对象的信息就存储在了内存当中,但是当程序终止时,对象将不再继续存在。我们需要一种储存对象信息的方法,使我们的程序关闭之后他还继续存在,当我们再次打开程序时,可以轻易的还原当时的状态。这就是对象序列化的目的。


java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并且能够在以后将这个字节序列完全恢复为原来的对象,甚至可以通过网络传播。 这意味着序列化机制自动弥补了不同OS之间的差异.


如此,java实现了“轻量级持久性”,为啥是轻量级,因为在java中我们还不能直接通过一个类似public这样的关键字直接使一个对象序列化,并让系统自动维护其他细节问题。因此我们只能在程序中显示地序列化与反序列化


对象序列化的概念加入到语言中是为了支持两种主要特性:


java的远程方法调用(RMI),它使存活于其他计算机上的对象使用起来就像存活于本机上一样。当远程对象发送消息时,需要通过对象序列化来传输参数和返回值。

对于Java Bean来说,对象序列化是必须的。使用一个Bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动的时候进行后期恢复,这种具体工作就是由对象序列化完成的。

使用——对象实现Serializable接口(仅仅是一个标记接口,没有任何方法)。


序列化一个对象:

1. 创建某些OutputStream对象

2. 将其封装在一个ObjectOutputStream对象内

3. 只需调用writeObject()即可将对象序列化

注:也可以为一个String调用writeObject();也可以用与DataOutputStream相同的方法写入所有基本数据类型(它们具有同样的接口)


反序列化

将一个InputStream封装在ObjectInputStream内,然后调用readObject()。最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们


对象序列化不仅保存了对象的“全景图”,而且能够追踪对象内所包含的所有引用,并保存这些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。这种情况有时被称为“对象网”。


例子:

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“worm.out”);

out.writeObject(w);

out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream(“worm.out”);

String s = (String)in.readObject();


在对一个Serializable对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。

整个对象都是通过InputStream中取得数据恢复而来的。


寻找类:必须保证java虚拟机能够找到相关的.class文件。找不到就会得到一个ClassNotFOundExcption的异常。


序列化的控制——通过实现Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。

1. Externalizable接口继承了Serializable接口,增加了两个方法,writeExternal()和readExternal(),这两个方法会在序列化和反序列化还原的过程中被自动调用。

2. Externalizable对象,在还原的时候所有普通的默认构造器都会被调用(包括在字段定义时的初始化)(只有这样才能使Externalizable对象产生正确的行为),然后调用readExternal().

3. 如果我们从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的存储和恢复功能。

4. 为了正常运行,我们不仅需要在writeExternal()方法中将来自对象的重要信息写入,还必须在readExternal()中恢复数据


防止对象的敏感部分被序列化,两种方式:

1. 将类实现Externalizable,在writeExternal()内部只对所需部分进行显示的序列化

2. 实现Serializable,用transient(瞬时)关键字(只能和Serializable一起使用)逐个字段的关闭序列化,他的意思:不用麻烦你保存或恢复数据——我自己会处理。


Externalizable的替代方法

1. 实现Serializable接口,并添加名为writeObject()和readObject()的方法,这样一旦对象被序列化或者被反序列化还原,就会自动的分别调用writeObject()和readObject()的方法(它们不是接口的一部分,接口的所有东西都是public的)。只要提供这两个方法,就会使用它们而不是默认的序列化机制。

2. 这两个方法必须具有准确的方法特征签名,但是这两个方法并不在这个类中的其他方法中调用,而是在ObjectOutputStream和ObjectInputStream对象的writeObject()和readObject()方法

[图片上传失败…(image-c92672-1517928660769)]

3. 技巧:在你的writeObject()和readObject()内部调用defaultWriteObject()和defaultReadObject来选择执行默认的writeObject()和readObject();如果打算使用默认机制写入对象的非transient部分,那么必须调用defaultwriteObject()和defaultReadObject(),且作为writeObject()和readObject()的第一个操作。


使用“持久性”

1. 只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事;无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),我们都可以被写出。

2. Class是Serializable的,因此只需要直接对Class对象序列化,就可以很容易的保存static字段,任何情况下,这都是一种明智的做法。但是必须自己动手去实现序列化static的值。

使用serializeStaticState()和deserializeStaticState()两个static方法,它们是作为存储和读取过程的一部分被显示的调用的

3. 安全问题:序列化会将private数据保存下来,对于你关心的安全问题,应将其标记为transient。但是这之后,你还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些private变量。


目录
相关文章
|
29天前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
46 15
|
7天前
|
存储 Java
Java中判断一个对象是否是空内容
在 Java 中,不同类型的对象其“空内容”的定义和判断方式各异。对于基本数据类型的包装类,空指对象引用为 null;字符串的空包括 null、长度为 0 或仅含空白字符,可通过 length() 和 trim() 判断;集合类通过 isEmpty() 方法检查是否无元素;数组的空则指引用为 null 或长度为 0。
|
7天前
|
XML JSON Java
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
|
26天前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
25天前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
38 17
|
5月前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
44 10
|
5月前
|
存储 Java
Java的对象和类的相同之处和不同之处
在 Java 中,对象和类是面向对象编程的核心。
80 18
|
5月前
|
Java
Java 对象和类
在Java中,**类**(Class)和**对象**(Object)是面向对象编程的基础。类是创建对象的模板,定义了属性和方法;对象是类的实例,通过`new`关键字创建,具有类定义的属性和行为。例如,`Animal`类定义了`name`和`age`属性及`eat()`、`sleep()`方法;通过`new Animal()`创建的`myAnimal`对象即可调用这些方法。面向对象编程通过类和对象模拟现实世界的实体及其关系,实现问题的结构化解决。
39 4
|
6月前
|
机器学习/深度学习 人工智能 算法
探索人工智能在医疗诊断中的应用与挑战Java编程中的对象和类:基础与实践
【8月更文挑战第27天】随着人工智能(AI)技术的飞速发展,其在医疗领域的应用日益广泛。本文深入探讨了AI技术在医疗诊断中的具体应用案例,包括图像识别、疾病预测和药物研发等方面,并分析了当前面临的主要挑战,如数据隐私、算法偏见和法规限制等。文章旨在为读者提供一个全面的视角,理解AI在改善医疗服务质量方面的潜力及其局限性。
|
5月前
|
Java 程序员
Java编程中的对象和类: 初学者指南
【9月更文挑战第9天】在Java的世界中,对象和类构成了编程的基石。本文将引导你理解这两个概念的本质,并展示如何通过它们来构建你的程序。我们将一起探索类的定义,对象的创建,以及它们如何互动。准备好了吗?让我们开始这段Java的旅程吧!

热门文章

最新文章