一篇文章带你深入学习 Java 对象序列化(Serializable/ObjectOutputStream/ObjectInputStream/Externalizable/transient)

一、基本概念与 Serializable 接口

对象序列化就是把一个对象变为二进制的数据流的一种方法
在这里插入图片描述
通过对象序列化可以方便地实现对象的传输或存储

如果一个类的对象想被序列化,则对象所在的类必须实现 java.io.Serializable 接口:

public interface Serializable{}

可以看出该接口并没有定义任何的方法,所以此接口是一个标识接口,表示一个类具备了被序列化的能力。

定义可序列化的类

import java.io.Serializable;

public class Person implements Serializable{//此类的对象可以被序列化
    private String name;
    private int age;
    public Person(String name,int age){//通过构造方法设置属性内容
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "姓名:" + this.name + "; 年龄:" + this.age;
    }
}

此时的 Person 类已经实现了序列化接口,所以此类的对象是可以经过二进制流进行传输的。而如果要完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)

使用对象输出流输出序列化的步骤有时也称为序列化,而使用对象输入流读入对象的过程有时也称为反序列化
在这里插入图片描述
对象序列化和对象反序列化操作时的版本兼容性问题
在这里插入图片描述

二、对象输出流 ObjectOutputStream

一个对象如果要进行输出,则必须使用 ObjectOutputStream 类:

public class ObjectOutputStream extends OutputStream implements
ObjectOutput,ObjectStreamConstants

ObjectOutputStream 类属于 OutputStream 的子类:
在这里插入图片描述
此类的使用形式与PrintStream 非常相似,在实例化时也需要传入一个 OutputStream 的子类对象,然后根据传入的 OutputStream 子类的对象不同,输出的位置也不同:

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class Test{
    public static void main(String[] args) throws Exception{
        File f = new File("D:" + File.separator + "test.txt");
        ObjectOutputStream oos = null;
        OutputStream out = new FileOutputStream(f);//文件输出流
        oos = new ObjectOutputStream(out);//为对象输出流实例化
        oos.writeObject(new Person("Java",30));//保存对象到文件
        oos.close();
    }
}

在这里插入图片描述
可以看到,保存的内容全是二进制数据,本身不可修改,因为会破坏其保存格式

一个对象被序列化后,只有属性被序列化

因为每个对象都具备相同的方法,但是每个对象的属性不一定相同,也就是说,对象保存的只有属性信息,那么在序列化操作时也同样是这个道理,只有属性被序列化

三、对象输入流 ObjectInputStream

使用 ObjectInputStream 可以直接把被序列化好的对象反序列化:

public class ObjectInputStream extends InputStream implements
ObjectInput,ObjectStreamConstants

ObjectInputStream 类也是 InputStream 的子类,需要接收 InputStream 类的实例才可以实例化。
在这里插入图片描述
从文件中将 Person 对象反序列化(读取)

import java.io.*;

public class Root{
    public static void main(String[] args) throws Exception{
        File f = new File("D:" + File.separator + "test.txt");
        ObjectInputStream ois = null;
        InputStream input = new FileInputStream(f);//文件输入流
        ois = new ObjectInputStream(input);//为对象输出流实例化
        Object obj = ois.readObject();//读取对象
        ois.close();//关闭输出
        System.out.println(obj);
    }
}

从结果可以看出,实现了 Serializable 接口类,对象中的所有属性都可被序列化,如果用户想根据自己的需要选择被序列化的属性,则可以使用另外一种序列化接口 Externalizable 接口。

不需要在所有的类中都实现 Serializable 接口
在这里插入图片描述

四、Externalizable 接口

Serializable 接口声明的类的对象都将被序列化,如果用户希望自己指定序列化的内容,则可以让一个类实现 Externalizable 接口

Externalizable 接口是 Serializable 接口的子接口,定义了两个方法:
在这里插入图片描述
可知两个接口分别继承 DataOutputStream 和 DataInput ,这样在这两个方法中就可以像 DataOutputStream 和 DataInputStream 那样直接输出和读取各种类型的数据。

如果一个类要使用 Externalizable 实现序列化时,在此类中必须存在一个无参构造方法,因为在反序列化时会默认调用无参构造实例化对象,如果没有此无参构造,则运行时会出现异常,这与 Serializable 接口不同

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

public class Person implements Externalizable {//此类的对象可以被序列化
    private String name;
    private int age;
    public Person(){}//必须定义无参构造
    public Person(String name,int age){//通过构造方法设置属性内容
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "姓名:" + this.name + "; 年龄:" + this.age;
    }
    //覆写此方法,根据需要读取内容,反序列化时使用
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException{
        this.name = (String)in.readObject();//读取姓名属性
        this.age = in.readInt();
    }
    //覆写此方法,根据需要可以保存属性或具体内容,序列化时使用
    public void writeExternal(ObjectOutput out) throws IOException{
        out.writeObject(this.name);//保存姓名属性
        out.writeInt(this.age);//保存姓名属性
    }
}
import java.io.*;

public class Test{
    public static void main(String[] args) throws Exception{
        ser();//序列化
        dser();//反序列化
    }
    public static void ser() throws Exception{//序列化操作
        File f = new File("D:" + File.separator + "test.txt");
        ObjectOutputStream oos = null;
        OutputStream out = new FileOutputStream(f);//文件输出流
        oos = new ObjectOutputStream(out);//为对象输出流实例化
        oos.writeObject(new Person("Java",30));//保存对象到文件
        oos.close();
    }
    public static void dser() throws Exception{//反序列化操作
        File f = new File("D:" + File.separator + "test.txt");
        ObjectInputStream ois = null;
        InputStream input = new FileInputStream(f);//文件输出流
        ois = new ObjectInputStream(input);//为对象输出流实例化
        Object obj = ois.readObject();//读取对象
        ois.close();//关闭输出
        System.out.println(obj);
    }
}

在这里插入图片描述

五、transient 关键字

Serializable 接口实现的操作实际上是将一个对象中的全部属性进行序列化,当然也可以使用 Externalizable 接口实现部分属性的序列化,但这样的操作比较麻烦。

当使用 Serializable 接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用 transient 关键字进行声明

import java.io.*;

public class Person implements Serializable {//此类的对象可以被序列化
    private transient String name;//此属性将不被序列化
    private int age;//此属性将被序列化
    public Person(String name,int age){//通过构造方法设置属性内容
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "姓名:" + this.name + "; 年龄:" + this.age;
    }
}

六、序列化一组对象

对象输出时只提供了一个对象的输出操作(writeObject(Object obj)),并没有提供多个对象的输出,所以如果现在要同时序列化多个对象,就可以使用对象数组进行操作,因为数组属于引用数据类型,所以可以直接使用 Object 类型进行接收。

序列化一组对象
在这里插入图片描述

import java.io.*;

public class Test{
    public static void main(String[] args) throws Exception{
        Person per[] = {new Person("Java",30),new Person("Python",31)}//定义对象数组
        ser(per);//序列化对象数组
        Object o[] = dser();//读取被序列化的对象数组
        for (int i=0;i<o.length;i++){
            Person p = (Person)o[i];
            System.out.println(p);
        }
    }
    public static void ser(Object obj[]) throws Exception{//序列化操作
        File f = new File("D:" + File.separator + "test.txt");
        ObjectOutputStream oos = null;
        OutputStream out = new FileOutputStream(f);//文件输出流
        oos = new ObjectOutputStream(out);//为对象输出流实例化
        oos.writeObject(obj);//保存对象到文件
        oos.close();
    }
    public static Object[] dser() throws Exception{//反序列化操作
        File f = new File("D:" + File.separator + "test.txt");
        ObjectInputStream ois = null;
        InputStream input = new FileInputStream(f);//文件输出流
        ois = new ObjectInputStream(input);//为对象输出流实例化
        Object obj[] = (Object[]) ois.readObject();//读取对象数组
        ois.close();//关闭输出
        return obj;
    }
}

这里使用对象数组可以保存多个对象,但是数组本身存在长度的限制,为了解决数组中的长度问题,所以使用动态对象数组(类集)完成

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南淮北安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值