我们在平时的开发过程中,经常发现很多实体类都实现了serializable这个接口,有些又没有实现,好像实现与否都没有太大差别,只知道serializable这个接口是实现序列化的,那么什么是序列化呢?有什么作用呢?这些都不是很清楚。

什么是序列化

        把对象转化成字节序列的过程称为对象的序列化。

        对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。(因为静态static的东西在方法区)。

        在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力。序列化其实可以看成是一种机制,按照一定的格式将 Java 对象的某状态转成介质可接受的形式,以方便存储或传输。其实想想就大致清楚基本流程,序列化时将 Java 对象相关的类信息、属性及属性值等等保存起来,反序列化时再根据这些信息构建出 Java 对象。而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化。

序列化的作用

        1.持久化存储。可以将对象持久化到介质中,就像实现对象直接存储。

        2.对于远程调用,能方便对对象进行编码和解码,就像实现对象直接传输。

        3.提供一种简单又可扩展的对象保存恢复机制。

        4.允许对象自定义外部存储的格式

实现方式

        Java 中进行序列化操作需要实现 Serializable 接口。该接口是一个空接口,起到标识的作用。当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化。

何时需要序列化

        在存储时需要序列化,这是肯定的。大家知道的是序列化是将对象进行流化存储,我们有时候感觉自己在项目中并没有进行序列化操作,也一样是存进去了,那么对象需要经过序列化才能存储的说法,似乎从这儿就给阉割了。事实究竟是怎样的呢?

        首先看我们常用的数据类型类声明:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence

public class Date implements java.io.Serializable, Cloneable, Comparable

        而像其他int、long、boolean类型等,都是基本数据类型,数据库里面有与之对应的数据结构。从上面的类声明来看,我们以为的没有进行序列化,其实是在声明的各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。

        那么,既然实体类的变量都已经帮助我们实现了序列化,为什么我们仍然要显示的让类实现serializable接口呢?

        首先,序列化的目的有两个,第一个是便于存储,第二个是便于传输。我们一般的实体类不需要程序员再次实现序列化的时候,请想两个问题:第一:存储媒体里面,是否是有其相对应的数据结构?第二:这个实体类,是否需要远程传输(或者两个不同系统甚至是分布式模块之间的调用)?

        如果说我们需要将实体类信息写入一个txt文档中,那么不实现序列化接口,就会出错。

        实现序列化前:

//实体类
public class Father {

    public int f;
    
}



//测试类
public class SerializeTest {

    public static void main(String[] args) throws Exception{

        Father father = new Father();
        father.setF(5);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
        oos.writeObject(father);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
        Father f = (Father) ois.readObject();
        System.out.println("Father对象反序列化:" + f.toString());
    }

}

        测试结果:

Exception in thread "main" java.io.NotSerializableException: com.redistext.serialize.Father
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.redistext.serialize.SerializeTest.main(SerializeTest.java:18)

        实现序列化后:

//实体类
public class Father implements Serializable {

    private static final long serialVersionUID = 3569247533577774553L;

    public int f;

}



//测试类
public class SerializeTest {

    public static void main(String[] args) throws Exception{

        Father father = new Father();
        father.setF(5);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
        oos.writeObject(father);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
        Father f = (Father) ois.readObject();
        System.out.println("Father对象反序列化:" + f.toString());
    }

}

        测试结果:

Father对象反序列化:Father(f=5)

遇到的问题

        transient修饰的属性,不会被序列化。

        静态static的属性,不会被序列化。

        实现Serializable接口的时候,一定要给SerialVersionUID赋值。

        当属性是对象的时候,对象也要实现序列化接口。

代码测试

        实体类:

package com.redistext.serialize;

import lombok.Data;

import java.io.Serializable;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/26 20:09
 * @description: V1.0
 */
@Data
public class FlyPig implements Serializable{

//    private static final long serialVersionUID = 532067060492947515L;

    private static String age = "25";

    private String name;

    private String color;

    transient private String car;

//    private String addTip;

}

        测试类:

package com.redistext.serialize;

import java.io.*;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/26 20:08
 * @description: V1.0
 */
public class SerializeTest {

    public static void main(String[] args) throws Exception{
        serializeFlyPig();
        FlyPig flyPig = deserializeFlyPig();
        System.out.println(flyPig.toString());
    }

    private static void serializeFlyPig() throws Exception{
        FlyPig flyPig = new FlyPig();
        flyPig.setCar("audi");
        flyPig.setColor("red");
        flyPig.setName("aaaa");
        //ObjectOutputStream  对象输出流,将flyPig对象存储在D盘的pig.txt文件中,完成对flyPig对象的序列化操作
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
        System.out.println("序列化前flyPig对象信息:" + flyPig.toString());
        oos.writeObject(flyPig);
        System.out.println("FlyPig 对象序列化成功!");
        oos.close();
    }


    private static FlyPig deserializeFlyPig() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
        FlyPig flyPig = (FlyPig) ois.readObject();
        System.out.println("FlyPig 对象反序列化成功!");
        return flyPig;
    }

}

        测试结果:

java将实体类序列化为字节流_java

        根据测试结果可以发现transient关键字修饰的属性无法被序列化。  

        上面的FlgPig实体类中,我们把SerialVersionUID的赋值语句给注释掉了,好像也能实现序列化,那有无SerialVersionUID的赋值语句有什么作用呢?

        我们可以这样测试一下:

       1. 放开FlgPig实体类的addTip字段的赋值语句。

java将实体类序列化为字节流_序列化_02

         2.测试类不进行序列化操作,直接进行反序列化操作。

java将实体类序列化为字节流_java将实体类序列化为字节流_03

         测试结果:

java将实体类序列化为字节流_反序列化_04

Exception in thread "main" java.io.InvalidClassException: com.redistext.serialize.FlyPig; local class incompatible: stream classdesc serialVersionUID = -8106182154492238666, local class serialVersionUID = -1124294899602738656

         发现开始时反序列化的FlgPig实体类的serialVersionUID和加了addTip字段的FlgPig实体类的serialVersionUID不一致,无法进行赋值。

        如果在最开始就加上serialVersionUID值,就不会出现这个情况。因为虽然加了addTip字段,但是serialVersionUID还是同一个,就可以进行反序列化。

        SerializableID号是根据类的特征和类的签名算出来的.为什么ID号那么长,是因为为了避免重复.所以Serializable是给类加上id用的. 用于判断类和对象是否是同一个版本。

        序列化的时候系统会把当前类的serialVersionUID(没有显式赋值系统会默认生成一个) 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID ,看它是否和当前类的serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候就会抛异常,反序列化失败。

serialVersionUID作用

        序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 声明serialVersionUID ,可以很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量,也可能增加了一些新的成员变量,这个时候我们的反序列化依然能够成功,程序依然能够最大程度地恢复数据,相反,如果不指定serialVersionUID ,程序就会挂掉。