Java 序列化Serializable

本文深入解析Java序列化机制,涵盖序列化与反序列化的基本概念、JDK序列化API使用方法,探讨序列化版本一致性、静态变量序列化、父类序列化及Transient关键字的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 序列化Serializable

————————————————
版权声明:本文为优快云博主「ratelfu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/weter_drop/article/details/84660173

一 、基本概念

1.1 概念

序列化:将对象的状态信息转换为可以存储或传输的形式的过程

反序列化:将字节序列恢复为对象的过程

1.2 基本用途

  • 把对象的字节序列永久存储在硬盘上,通常是一个文件
  • 在网络上传输对象的字节序列

二、JDK中的序列化API

FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);

FileInputStream fileInputStream = new FileInputStream("person.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
person1 = (Person) objectInputStream.readObject();

Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

UID显式的生成方式:

  • private static final long serialVersionUID = 1L;
  • 根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段

没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。

代码示例:

普通实体类 Person.java

import java.io.Serializable;

public class Person implements Serializable {//序列化必须继承Serializable接口
    private static final long serialVersionUID = 123l;
    private int id;
    private String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

序列化和反序列化 SerialTest.java

import java.io.*;
public class SerialTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = new Person(123, "zhan");
        System.out.println(person.toString());
        FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(person);
        objectOutputStream.flush();
        objectOutputStream.close();

        Person person1;
        FileInputStream fileInputStream = new FileInputStream("person.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        person1 = (Person) objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(person1.toString());
    }
}

三、 四种情形

  1. 假设Person被序列化后,从A传到B,A和B中都有同一个类,但是他们的serialVersionUID不一样。

    报错:

    Exception in thread "main" java.io.InvalidClassException: com.sf.code.serial.Person; local class incompatible: stream classdesc serialVersionUID = 1234567890, local class serialVersionUID = 123456780
    
  2. 假设AB中UID一样,但是A端增加一个字段,然后序列化,B端不变。

    结果:正常执行序列化,反序列化正常,但是A端增加的字段丢失(被B端忽略)

  3. 假设AB中UID一样,如果B端减少一个字段,A端不变。

    结果:序列化、反序列化正常,B端字段少于A端,A端多的字段丢失(被B端忽略)

  4. 假设AB中UID一样,如果B端增加一个字段,A端不变。

    结果:序列化、反序列化正常,B端新增字段被赋予默认值。

四、静态变量序列化

静态变量不会被序列化,因为序列化是保存对象的状态,而静态变量是类的状态。

五、父类的序列化

情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。

解决:

  • 要想将父类对象也序列化,就需要让父类也实现Serializable 接口。

  • 如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

六、Transient关键字

作用: 控制变量的序列化。在变量声明加上该关键字,可以阻止该变量被序列化。在反序列之后,transient变量的值被初始为默认值。int->0, 对象->null

不使用transient使得字段不被序列化的方法:将不要被序列化的字段抽取出来放到弗雷中,子类实现serializable接口,父类不实现。

### Java序列化概念 在Java中,对象的序列化是指把对象转换成字节流的过程,以便于保存到文件或在网络上传输。反序列化则是指将这些字节流转回原始对象的过程。为了使某个类的对象能够被序列化,该类需要实现`java.io.Serializable`接口。 此接口并不提供具体的方法;它只是一个标记接口,表明实现了它的类可以参与序列化进程[^1]。当一个类未实现`Serializable`接口而试图对其进行序列化操作时,将会抛出`NotSerializableException`异常[^2]。 ### Serializable 接口使用方法 为了让一个自定义类支持序列化功能,只需让此类继承`Serializable`接口即可: ```java import java.io.*; class Person implements Serializable { private static final long serialVersionUID = 1L; String name; transient int age; // 不希望age属性被序列化 public Person(String n, int a){ this.name=n; this.age=a; } } ``` 注意,在上述例子中加入了`transient`关键字修饰成员变量`age`,这意味着即使整个类是可序列化的,但是`age`字段不会参与到序列化过程中去。另外还声明了一个名为`serialVersionUID`的静态常量来确保不同版本之间的兼容性。 对于如何实际执行序列化和反序列化过程,则可以通过如下方式完成: #### 序列化对象至文件 ```java public class SerializeDemo { public static void main(String[] args) { Person p = new Person("John", 30); try{ FileOutputStream fileOut = new FileOutputStream("/tmp/person.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(p); out.close(); fileOut.close(); System.out.printf("Serialized data is saved in /tmp/person.ser"); }catch(IOException i){ i.printStackTrace(); } } } ``` #### 从文件读取并恢复对象 ```java public class DeserializeDemo { public static void main(String[] args) { Person p = null; try{ FileInputStream fileIn = new FileInputStream("/tmp/person.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); p = (Person)in.readObject(); in.close(); fileIn.close(); } catch(Exception e){ e.printStackTrace(); return; } System.out.println("Deserialized Person..."); System.out.println("Name: " + p.name); System.out.println("Age: " + p.age); // 这里打印出来的年龄将是默认值0,因为它是瞬态(transient)的 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值