序列化有哪几种方式?static和transient有什么区别?它们可以被序列化吗?

本文深入解析Java序列化机制,包括序列化与反序列化过程,transient关键字的作用,静态变量的序列化特性,以及实现序列化的不同方式。同时,探讨了serialVersionUID的重要性,序列化注意事项,和序列化接口的选择。

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

又是一个令人懵逼的问题,赶紧来看一下吧

什么是序列化和反序列化

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

我们的对象并不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术。
Java序列化技术正是将对象转变成一串由二进制字节组成的数组,可以通过将二进制数据保存到磁盘或者传输网络,磁盘或者网络接收者可以在对象的属类的模板上来反序列化类的对象,达到对象持久化的目的。

如何进行序列化

类实现序列化接口Serializable
package com.lingluo.basic;
 
import java.io.Serializable;

public class LingluoInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    private static String id;
    private String name;
    private transient String address;

    public static String getId() {
        return id;
    }

    public static void setId(String id) {
        LingluoInfo.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "LingluoInfo{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
序列化/反序列化
package com.lingluo.basic;

import java.io.*;

/**
 * Created by Lingluo on 2020/2/15.
 * ObjectOutputStream代表对象输出流:
 * 它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
 * ObjectInputStream代表对象输入流:
 * 它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
 */
public class LingluoSerializableTest {
    public static void main(String[] args) {
        LingluoInfo lingluoInfo = new LingluoInfo();
        lingluoInfo.setId("007");
        lingluoInfo.setName("灵洛");
        lingluoInfo.setAddress("北京");

        serializeLingluoInfo(lingluoInfo);
        // 在反序列化出来之前,改变静态变量的值
        lingluoInfo.setId("008");
        LingluoInfo person = deserializeLingluoInfo();
        System.out.println(person.toString());
    }

    /**
     * 序列化
     * @param lingluoInfo
     */
    private static void serializeLingluoInfo(LingluoInfo lingluoInfo) {
        // ObjectOutputStream 对象输出流,将 LingluoInfo 对象存储到D盘的 LingluoInfo.txt 文件中,完成对 LingluoInfo 对象的序列化操作
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("d:/lingluoInfo.txt")));
            oos.writeObject(lingluoInfo);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("LingluoInfo 对象序列化成功!");
    }

    /**
     * 反序列化
     */
    private static LingluoInfo deserializeLingluoInfo() {
        ObjectInputStream ois;
        LingluoInfo person = null;

        try {
            ois = new ObjectInputStream(new FileInputStream(new File("d:/lingluoInfo.txt")));
            person = (LingluoInfo) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("LingluoInfo 对象反序列化成功!");
        return person;
    }
}

运行结果:

LingluoInfo 对象序列化成功!
LingluoInfo 对象反序列化成功!
LingluoInfo{id='008', name='灵洛', address='null'}

什么是 transient?

简单来说就是,被 transient 修饰的变量不能被序列化。
LingluoSerializableTest在 address 字段上加了 transient 关键字修饰,反序列化出来之后值为 null,说明了被 transient 修饰的变量不能被序列化。

静态变量能被序列化吗?

id 是静态变量,并在反序列化出来之前改变了静态变量的值,结果可以看出序列化之后的值并非序列化进去时的值。
由以上结果分析可知,静态变量不能被序列化,读取出来的是 id 在 JVM 内存中存储的值。

static和transient的区别

1.如果在一个类中定义了一个使用static修饰的变量,则在序列化的时候,该变量不可以被序列化。但是我们在实验的时候发现,反序列化得到的对象可以调用id,这又是为什么呢?这是因为jvm的原因,由于id是类变量,在创建对象之前,id属性就已经被加载好了,当对象调用id的时候,系统发现没有成员变量id,但是有个LinluoInfo类的类变量id,而且系统发现你的对象,正是LinluoInfo类的实例,那就让你调用吧。而此时我们换一台电脑,或者重启后,再对文件进行反序列化,会发现id的值成了null,这是由于虽然id无法被序列化,但是id这个属性是存在的,既然无法被序列化,就没有值了,那就用int类型的系统默认初始值null。
2.transient修饰的变量跟普通的成员变量在使用上没有任何区别,但是在序列化的时候,一旦系统识别到你的属性有transient修饰,就不可以被序列化,还是同上一点所讲的一样,不能被序列化,并不是这个属性不存在了,而是属性的值无法被保存起来,也就是该属性的值就是默认值,相当于在创建类定义属性的时候不赋值,就是默认值。

transient 真不能被序列化吗?

继续来看示例

类实现序列化接口Externalizable
package com.lingluo.basic;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
 * Created by Lingluo on 2020/2/16.
 */
public class LingluoInfo2 implements Externalizable{
    private static final long serialVersionUID = 1L;
    private transient String id;
    private String name;
    private transient String address;

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(address);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        address = (String) in.readObject();
    }
}

序列化/反序列化
package com.lingluo.basic;

import java.io.*;

/**
 * Created by Lingluo on 2020/2/15.
 * ObjectOutputStream代表对象输出流:
 * 它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
 * ObjectInputStream代表对象输入流:
 * 它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
 */
public class LingluoSerializableTest {
    public static void main(String[] args) {
        LingluoInfo2 lingluoInfo = new LingluoInfo2();
        lingluoInfo.setId("007");
        lingluoInfo.setName("灵洛");
        lingluoInfo.setAddress("北京");

        serializeLingluoInfo(lingluoInfo);
        // 在反序列化出来之前,改变静态变量的值
        lingluoInfo.setId("008");
        LingluoInfo2 person = deserializeLingluoInfo();
        System.out.println(person.toString());
    }

    /**
     * 序列化
     * @param lingluoInfo
     */
    private static void serializeLingluoInfo(LingluoInfo2 lingluoInfo) {
        // ObjectOutputStream 对象输出流,将 LingluoInfo 对象存储到D盘的 LingluoInfo.txt 文件中,完成对 LingluoInfo 对象的序列化操作
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("d:/lingluoInfo2.txt")));
            oos.writeObject(lingluoInfo);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("LingluoInfo 对象序列化成功!");
    }

    /**
     * 反序列化
     */
    private static LingluoInfo2 deserializeLingluoInfo() {
        ObjectInputStream ois;
        LingluoInfo2 person = null;

        try {
            ois = new ObjectInputStream(new FileInputStream(new File("d:/lingluoInfo2.txt")));
            person = (LingluoInfo2) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("LingluoInfo 对象反序列化成功!");
        return person;
    }
}

运行结果:

LingluoInfo 对象序列化成功!
LingluoInfo 对象反序列化成功!
LingluoInfo{id='null', name='null', address='北京'}

address 被 transient 修饰了,为什么还能序列化出来?那是因为 LingluoInfo2 实现了接口 Externalizable,而不是 Serializable。

在 Java 中有两种实现序列化的方式,Serializable 和 Externalizable,可能大部分人只知道 Serializable 而不知道 Externalizable。

这两种序列化方式的区别是:实现了 Serializable 接口是自动序列化的,实现 Externalizable 则需要手动序列化,通过 writeExternal 和 readExternal 方法手动进行,这也是为什么上面的 id, name 为 null 的原因了。

transient 关键字总结

1)transient修饰的变量不能被序列化;

2)transient只作用于实现 Serializable 接口;

3)transient只能用来修饰普通成员变量字段;

4)不管有没有 transient 修饰,静态变量都不能被序列化;

实现序列化为什么必须要 serialVersionUID ?

JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException

具体序列化的过程

序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;

serialVersionUID有两种显示的生成方式

一是默认的1L,比如:private static final long serialVersionUID = 1L;

二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;
注意:显示声明serialVersionUID可以避免对象不一致

序列化注意事项

序列化对象必须实现序列化接口。

序列化对象里面的属性是对象的话也要实现序列化接口。

类的对象序列化后,类的序列化ID不能轻易修改,不然反序列化会失败。

类的对象序列化后,类的属性有增加或者删除不会影响序列化,只是值会丢失。

如果父类序列化了,子类会继承父类的序列化,子类无需添加序列化接口。

如果父类没有序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化。父类需要实现无参构造函数。

用Java序列化的二进制字节数据只能由Java反序列化,不能被其他语言反序列化。如果要进行前后端或者不同语言之间的交互一般需要将对象转变成Json/Xml通用格式的数据,再恢复原来的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值