关键词搜索

源码搜索 ×
×

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

发布2020-02-12浏览504次

详情内容

一、基本概念与 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;
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    二、对象输出流 ObjectOutputStream

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

    public class ObjectOutputStream extends OutputStream implements
    ObjectOutput,ObjectStreamConstants
    
      2

    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();
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

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

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

    三、对象输入流 ObjectInputStream

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

    public class ObjectInputStream extends InputStream implements
    ObjectInput,ObjectStreamConstants
    
      2

    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);
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从结果可以看出,实现了 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);//保存姓名属性
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    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);
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    五、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;
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    六、序列化一组对象

    对象输出时只提供了一个对象的输出操作(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;
        }
    }
    
      2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

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

    相关技术文章

    点击QQ咨询
    开通会员
    返回顶部
    ×
    微信扫码支付
    微信扫码支付
    确定支付下载
    请使用微信描二维码支付
    ×

    提示信息

    ×

    选择支付方式

    • 微信支付
    • 支付宝付款
    确定支付下载