序列化与反序列化要点
序列化通俗点讲就是将对象实例转化为方便存储与传输的形式,传输和存储可以通过网络等多种手段。而反序列化顾名思义就是将序列化之后的对象文件转化为可识别的对象实例。

一般操作过程
transient关键字
反序列化过程父类构造函数的执行问题

一般操作过程
序列化与反序列化主要需要用到两个IO流类: ObjectOutputStreamObjectReadStream要实现序列化则要求序列化的对象类必须直接或间接(父类实现)实现序列化接口。序列化的两个操作流使用方法与其他流类似,下面写一个简单的demo(序列化到本地文件)。
新建一个实现了序列化接口的类: User
package org.devsong.serializationtest.demo01;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 1773737177342889262L;
    private long id;
    private String name;
    private String email;

    public User() {

    }

    public User(long id, String name, String email) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", email=" + email + "]";
    }
}


然后建立一个测试类:
package org.devsong.serializationtest.demo01;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializationTest {

    public static void main(String[] args) throws Exception {
        File aFile = new File("user.dat");

        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(aFile));
        User aUser = new User(9527, "jack", "123456@qq.com");
        oos.writeObject(aUser);
        oos.flush();
        oos.close();

        // 紧接着直接进行反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(aFile));
        User userRec = (User) ois.readObject();
        System.out.println(userRec);
        ois.close();

    }
}


执行结果如下:
User [id=9527, name=jack, email=123456@qq.com]

没什么问题,将User对象实例通过序列化存储到本地的user.dat文件中,然后再从该文件中反序列化得到序列化之前的对象。


transient关键字
有的时候对象的某些成员变量我们用不到,所以不希望该成员变量被序列化,或者我们想用自定义的方式进行序列化,以提高效率,那么这个时候就需要用到transient关键字了。使用该关键字修饰的成员变量不会被JVM默认的序列化方式实行序列化,自然也能够实行默认的反序列化。若要实现修饰的成员变量的序列化,我们需要在对应的类中实现writeObjectreadObject两个方法。

在先前的稍作修改即可对比看到效果,在此之前我们先看一下一个常用的工具类ArrayList的实现,ArrayList能够自行更具实际情况调整存储容量,内部是对数组的封装。很容易想到一个ArrayList实例在序列化时可能遇到的情况,那就是对象存储未达到容量,有空缺,如果空缺的位置也被序列化那就没有实际意义了,所以查看ArrayList的内部实现可以看到其中“elementData”一项是被transient修饰的。如下:
文章正文图片

再看其writeObject和readObject方法,只是对存储有实际对象的数组位置进行了序列化和反序列化操作,避免了无用功。
文章正文图片

文章正文图片

现在回归正题,我们在最初实现的User类中,用transient关键字修饰email, 然后运行测试代码,结果在意料之中,email未被序列化,得到null,如下:
User [id=9527, name=jack, email=null]

下面我们仿照ArrayList内部方法来实现一下User的读/写Object方法,在User类中添加如下代码:

    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
        // 先执行默认序列化
        s.defaultWriteObject();
        // 再自行执行 email 序列化
        s.writeUTF(email);
    }

    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        // 先执行默认反序列化
        s.defaultReadObject();
        // 在自行执行 email 的反序列化
        this.email = s.readUTF();
    }

然后再次运行测试,结果与最初的demo一致。


反序列化中父类构造函数执行问题
如果进行序列化与反序列化的类的父类未实现序列化接口,则反序列化过程中会依次调用未实现序列化接口的父类的构造函数。这里同样写一个简单的demo,如下:
package org.devsong.serializationtest.demo03;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializationTest03 {

    public static void main(String[] args) throws Exception {
        File aFile = new File("temp.dat");

        // Foo2类序列化与反序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(aFile));
        Foo2 foo2 = new Foo2();
        oos.writeObject(foo2);
        oos.flush();
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(aFile));
        System.out.println("foo 反序列化开始");
        Foo2 foo2Rec = (Foo2) ois.readObject();
        System.out.println("foo 反序列化完成
");
        ois.close();

        // Bar2类序列化与反序列化
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream(aFile));
        Bar2 bar2 = new Bar2();
        oos1.writeObject(bar2);
        oos1.flush();
        oos1.close();

        ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream(aFile));
        System.out.println("Bar2反序列化开始");
        Bar2 bar2Rec = (Bar2) ois1.readObject();
        System.out.println("Bar2反序列化结束");
        ois1.close();

    }
}

// 最顶层父类 Foo 实现了Serializable接口
class Foo implements Serializable {
    public Foo() {
        System.out.println("Foo...");
    }
}

class Foo1 extends Foo {
    public Foo1() {
        System.out.println("Foo1...");
    }
}

class Foo2 extends Foo1 {
    public Foo2() {
        System.out.println("Foo2...");
    }
}

// 只有Bar3实现了序列化接口
class Bar {
    public Bar() {
        System.out.println("Bar...");
    }
}

class Bar1 extends Bar {
    public Bar1() {
        System.out.println("Bar1...");
    }
}

class Bar2 extends Bar1 implements Serializable {
    public Bar2() {
        System.out.println("Bar2...");
    }
}

运行测试,执行结果如下:
文章正文图片
执行结果符合预期,Bar与Bar1两个类未实现序列化接口,在对子类Bar2进行反序列化操作时,Bar与Bar1的构造函数均被执行。

若您在浏览文章时发现文章存在问题或者您有不同的见解,欢迎在本站给我留言或者给我邮件,我会及时学习并更正。
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
*转载请注明出处,严禁非法转载。
https://www.devsong.org
QQ留言 邮箱留言
头像
引用:
取消回复
提交
涂鸦
涂鸦
热门