idea自动生成序列化id_一看就会的序列化和反序列化

1. 概念简述

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。

  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

或者说:
  • 序列化是将 Java 对象转换成与平台无关的二进制流。
  • 反序列化则是将二进制流恢复成原来的 Java 对象。
即:本质都是为了围绕对象的二进制流进行操作,因为二进制流便于保存到磁盘上(持久化)或者在网络上传输。

2. 何时需要

当我们只在本地 JVM 里运行 Java 实例,这个时候是不需要序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘或数据库中时,或者当我们需要与浏览器进行交互时等等,这个时候就需要序列化和反序列化了。 即:只要我们对内存中的对象进行持久化或网络传输,就需要使用序列化和反序列化。 服务器与浏览器交互时使用到了序列化,JSON 格式实际上就是将一个对象转化为字符串, 所以服务器与浏览器交互时的真正数据格式是字符串,而 Java 中字符串是实现了序列化的,见如下 String 的源码:
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    ......
}

String 类实现了 Serializable 接口,并显示指定 serialVersionUID 的值。

然后我们再来看对象持久化到数据库中时的情况,如 Mybatis 数据库映射文件里的 insert 代码:

<insert id="insertUser" parameterType="com.eric.pojo.User">
    INSERT INTO t_user(name, age) VALUES (#{name}, #{age})
insert>

实际上我们并不是将整个对象持久化到数据库中,而是将对象中的属性持久化到数据库中,而这些属性都是实现了 Serializable 接口的基本属性。

3. 如何实现

如果想要序列化某个类的对象,就需要让该类实现 Serializable 接口或者 Externalizable 接口。

如果实现 Serializable 接口,由于该接口只是个 “标记接口”,接口中不含任何方法,序列化是使用 ObjectOutputStream(处理流)中的 writeObject(obj) 方法将 Java 对象输出到输出流中,反序列化是使用 ObjectInputStream 中的 readObject(in) 方法将输入流中的 Java 对象还原出来。在 Java 中实现了 Serializable 接口后,JVM 会在底层帮我们实现序列化和反序列化,如果我们不实现 Serializable 接口,那就需要自己去写一套序列化和反序列化代码。

4. serialVersionUID

在 Java 的序列化机制中,允许给类提供一个 private static final 修饰的 serialVersionUID 类常量(下称 ID),来作为类版本的代号。在实现 Serializable 接口的对象中,如果不显式指定 ID,那么 JVM 在序列化时会根据属性自动生成一个 ID,然后与属性一起序列化,再进行持久化或网络传输。在反序列化时,JVM 会再根据属性自动生成一个新版 ID,然后将这个新版的 ID 与序列化时生成的旧版的 ID 进行比较,如果相同则反序列化成功,否则就会报错。

当然如果我们对对象序列化后,不修改其类中的属性或者方法,那么 ID 不会改变,反序列化也就不会报错,但这在实际开发中是不可能的,我们的类会不断迭代,一旦类被修改了,那旧对象反序列化就会报错。所以在实际开发中,我们都会显示指定一个ID,值是多少无所谓, 只要不变就行。

只要显示指定了 ID,JVM 在序列化和反序列化时仍然也会生成一个ID,但此时它的值是我们显式指定的值(由指定的值赋给生成的 ID),这样在反序列化时新旧版本的 ID 就一致了,也就不会出现版本报错的问题。

5. 综合实例

(1) User类实现Serializable接口,此时在类中不显式指定 serialVersionUID

public class User implements Serializable {
    private String name;
    private Integer age;
    public User(){}
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    /**setter and getter**/
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

(2) 测试序列化

public class SerializableTest {
    // 注意此文件路径一定写到指定的文件
    private static final String FILE_PATH = "C:\\Users\\Eric\\Desktop\\test.txt";
    // 序列化实现方法
    private static void serialize(User user) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(FILE_PATH)));  
        oos.writeObject(user);
        oos.close();
    }
    // 反序列化实现方法
    private static User deserialize() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(FILE_PATH)));
        return (User) ois.readObject();
    }
    // 测试序列化方法
    public static void main(String[] args) throws Exception {
        User user = new User("eric", 18);
        System.out.println("序列化前的结果: " + user);
        serialize(user); // 序列化user
    }
}

执行结果:

序列化前的结果: User{name='eric', age=18}

执行测试代码后,输出文件 test.txt 中已经有了此对象的序列内容。

(4)现在为 User 类新增一个属性 sex,修改后的 User 如下

public class User implements Serializable {
    private String name;
    private Integer age;
    private String sex;
    public User(){}
    public User(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    /**setter and getter**/
    /**更新toString方法**/
}

(5)更改测试代码,测试反序列方法

// 测试反序列化方法
public static void main(String[] args) throws Exception {
   User dUser = deserialize();  // 反序列化
   System.out.println("反序列化后的结果: "+ dUser);
}

执行结果:

Exception in thread "main" java.io.InvalidClassException: exercise1.User; local class incompatible: stream classdesc serialVersionUID = 8499583977626957615, local class serialVersionUID = 5833064335516384202

报错结果意思为:序列化与反序列化产生的 serialVersionUID 不一致.

产生上述错误的原因是我们没有显式的设置 serialVersionUID 的值,而我们在序列化 User 对象之后又对 User 类进行了更改,所以在反序列时 JVM 自动生成的 serialVersionUID 值不一致,导致反序列化错误。

接下来我们在上面 User 类的基础上显式指定一个 serialVersionUID 属性,如下:

private static final long serialVersionUID = 1L;

再重复上述步骤, 测试结果如下:

序列化前的结果: User{name='eric', age=18} 

反序列化后的结果: User{name='eric', age=18, sex='null'}

显示指定 serialVersionUID 后就解决了序列化与反序列化产生的 serialVersionUID 不一致的问题。

还需要注意的是反序列化读取的仅仅是 Java 对象中的数据,而不是包含 Java 类的信息,所以在反序列化时还需要对象所属类的字节码(class)文件,否则会出现 ClassNotFoundException 异常。

6. transient和static

transient 或 static 关键字修饰的属性变量不会被序列化

测试实例如下。

(1)User 类实现 Serializable 接口

public class User implements Serializable {
    // 指定id
    private static final long serialVersionUID = 1L;
    private String name;
    private Integer age;
    // transient修饰变量
    private transient String sex;
    // 静态变量
    private static String signature = "Hello, Eric!";

    /**setter and getter**/
    /**重写toString方法**/
}

注意上述被 transient 和 static 修饰的属性变量。

(2)测试序列化方法

public static void main(String[] args) throws Exception {
    User user = new User();
    user.setName("eric");
    user.setAge(18);
    user.setSex("male");
    System.out.println("序列化前的结果: " + user);
    // 调用序列化方法
    serialize(user);
}

(3) 执行结果

序列化前的结果: User{name='eric', age=18, sex='male', signature='Hello, Eric!'}

(4)修改 User 类

调用序列化方法之后,此时我们把 User 中的静态属性 signature 内容修改为:

private static String signature = "你好,艾瑞克!";

(5)再测试反序列化

public static void main(String[] args) throws Exception {
    // 调用反序列化方法
    User dUser = deserialize();
    System.out.println("反序列化后的结果: "+ dUser);
}

执行结果:

反序列化后的结果: User{name='eric', age=18, sex='null', signature='你好,艾瑞克!'}

由执行结果我们可以看到:sex='null',signature 也发生了更新,这是因为 sex 和 signature 都没有参与序列化,在序列化中是没有这个属性的,而我们更改了 User 类中的 signature,它是静态的,直接输出新的属性值。

static 属性为什么不会被序列化?

因为序列化是针对对象而言的,而 static 属性优先于对象存在,随着类的加载而加载,所以不会被序列化。

同样的,在类中我们显式指定的 serialVersionUID 也是被 static 修饰的,所以它也并没有被序列化,只是在 JVM 序列化对象时,JVM 会自动生成一个 serialVersionUID,此时它会将我们显式指定的 ID 值自动赋给这个生成的 ID,所以就会保证版本号的一致。

7. 总结

  1. 序列化和反序列化是对对象而言,并且围绕的是的对象的二进制流;

  2. 在我们对内存中的对象进行持久化或网络传输时需要使用序列化;

  3. 序列化的对象需要实现 Serializable 标记接口,并推荐指定一个不变的 SerialVersionUID,如:

    private static final long serialVersionUID = 1L;
  4. 使用 ObjectOutputStream 类中的 writeObject(obj) 对对象进行序列化输出;使用 ObjectInputStream 类中的 readObject(obj) 对对象进行反序列化读入。

  5. 对象中被 transientstatic 修饰的成员变量不会参与序列化,如果在反序列化时强行得到这些没有被序列化的值,得到的会是默认值(0 或 null)。

88c55f0176f0e59f0734c130f2e20b4e.gif

6b09b72c195d994f499b97c4bd50a4da.gif

?cf7433565fdb55f4d1009cce701a73b8.gif一文了解拦截器与拦截器链的实现

?cf7433565fdb55f4d1009cce701a73b8.gifJDK动态代理模式

?cf7433565fdb55f4d1009cce701a73b8.gif简单了解一下Java反射技术?

f9e27e34f24f19f641fb85f3fde43e11.png


6ea8deb09a7390738f02a73a5e5c8e17.gif

写留言(99+)

「分享、点赞、在看」0246a7bab4c77fb4e84a6e4568552965.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值