JAVA序列化与反序列化

基本概念

java序列化是一种常用的技术,与之对应的是反序列化。java的序列化是指将java对象转化成数据流,并可将之进行持久化到磁盘的技术,通俗的说这是抽象到具体化的一个过程。而反序列化则是相反的过程。那这有什么用呢,你只要相信,存在即合理。根据序列化的概念,我们可以总结序列化的使用场景。

  1. 需要将对象进行持久化保存时,可将对象序列化进行保存,通常是保存到文件中;
  2. 在网络传输时,需要将对象进行传输,可将对象转化为数据流进行传输。
    这里只列出两种场景,可以使用的场景更多。

使用序列化技术需要实现(implements)Serializable或者Externalizable接口,这两者的区别是,前者会自动序列化所有对象(所谓的自动,其实是自动调用了defaultWriteObject和defaultReadObject方法),而后者需要重写writeExternal方法进行序列化和重写readExternal方法进行反序列化。否则不会自动进行序列化。而后者是前者的扩展,有了Externalizable接口,我们可以自己决定哪些对象可以被序列化,哪些不想被序列化,由自己决定。ps:transient修饰符也可以决定对象序列化,后面讲。

一、实现Serializable接口

1、People 实体类

package lrf.serial.serializable;

import java.io.Serializable;

/**
 * 实体类实现了Serializable接口,意味着对象
 * 会自动进行序列化,不需要实现任何方法
 */
public class People implements Serializable{

    private static final long serialVersionUID = 1969583891010536170L;
    
    private String name;
    private int age;
    private String phone;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

2、对序列化的类进行测试

package lrf.serial.serializable;

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

public class SerializableTest {

    public static void main(String[] args) {

        try {
            // 设置属性值
            People people = new People();
            people.setName("lrf");
            people.setAge(21);
            people.setPhone("100001");
            
            // 序列化
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:\\Serializable.txt"));
            outputStream.writeObject(people);
            System.out.println("序列化成功!!!");

            // 反序列化
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:\\Serializable.txt"));
            People people1 = (People) inputStream.readObject();
            System.out.println("name:"+people1.getName());
            System.out.println("age:"+people1.getAge());
            System.out.println("phone:"+people1.getPhone());
            System.out.println("反序列化成功!!!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

下面是序列化的结果和反序列化的结果
(1)序列化
JAVA序列化与反序列化
虽然是乱码,但这是对象进行序列化的结果,即达到了持久化的目的。

(2)反序列化

序列化成功!!!
name:lrf
age:21
phone:100001
反序列化成功!!!

Process finished with exit code 0

从结果可以看出,反序列化完美的还原了原来的对象,将对象属性值打印出来了。

二、实现Externalizable接口

1、还是实体类People,代码如下

package lrf.serial.externalizable;

import java.io.*;

/**
 * 实体类实现了Externalizable接口,需要重写
 * writeExternal和readExternal方法,实现自定义
 * 的序列化
 */
public class People implements Externalizable{

    private static final long serialVersionUID = 1969583891010536170L;

    private String name;
    private int age;
    private String phone;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    /**
     * 这里必须实现这两个方法,否则所有的属性均不能被序列化
     * @param out
     * @throws IOException
     */
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(age);
        // 我们测试不序列化phone属性
//        out.writeObject(phone);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = (String) in.readObject();
        this.age = (int) in.readObject();
//        this.phone = (String) in.readObject();
    }
}

2、对Externalizable序列化的类进行测试

package lrf.serial.externalzable;

import lrf.serial.externalizable.People;

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

public class ExternalizableTest {

    public static void main(String[] args) {

        try {
            // 设置属性值
            People people = new People();
            people.setName("lrf");
            people.setAge(21);
            people.setPhone("100001");
            // 序列化
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:\\Externalizable.txt"));
            outputStream.writeObject(people);
            System.out.println("序列化成功!!!");

            // 反序列化
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:\\Externalizable.txt"));
            People people1 = (People) inputStream.readObject();
            System.out.println("name:"+people1.getName());
            System.out.println("age:"+people1.getAge());
            System.out.println("phone:"+people1.getPhone());
            System.out.println("反序列化成功!!!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

序列化的结果也是写到文件中了,这里就不展示了,下面展示反序列化的结果:

序列化成功!!!
name:lrf
age:21
phone:null
反序列化成功!!!

Process finished with exit code 0

从结果看出,没有被序列化的phone属性并没有被序列化到文件中,所以反序列化出来过后,是null值。这里可以引申出,写登录账号的代码时,为了安全,可将密码属性设置为不可序列化。

其实,序列化和反序列化可以告一段落了,但是事情还可以更完美一些。

1、transient修饰符
它可以在类实现Serializable情况下,也可实现对象属性不被序列化的功能,但是在实现Externalizable接口的情况下,可以加该修饰符,不会报错,但是不起作用。下面是transient使用的例子。

public class People implements Serializable{

    private static final long serialVersionUID = 1969583891010536170L;
    
    // 虽然实现Serializable接口,但是name属性已被transient修饰,是不会被序列化的
    transient private String name;
    private int age;
    private String phone;

2、serialVersionUID ***的生成
serialVersionUID 是序列化类的唯一标识,在不手动加的情况下,编译器会给自动给该类配置一个ID,反序列化时,就能找到该ID对应的类,但是如果在序列化过后,如果改动了该类,则会报错,如下:

java.io.InvalidClassException: lrf.serial.externalizable.People; local class incompatible: stream classdesc serialVersionUID = 1692860238773028575, local class serialVersionUID = -3409095892910349901
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at lrf.serial.serializable.SerializableTest.main(SerializableTest.java:27)

Process finished with exit code 0

为了安全起见,我们应该手动加上一个系列号或者让工具生成一下,这样,就算在序列化过后对类进行了修改,也能进行反序列化,不会出错。我用的是idea2017工具,可以设置工具,如下:
File->Settings,找到Inspections,勾选Serialization issues
JAVA序列化与反序列化
然后在实现Serializable或者Externalization接口的类,选中该类,按Alt+Enter键,选中add “serialVersionUID” field 即可自动添加***,如下。
JAVA序列化与反序列化