对象序列化的 两种种方式(实现Serializable接口和实现Externalizable接口)

对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的Java对象。在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中

1、方式一(实现Serializable接口,通过序列化流)

实现Serializable接口,通过ObjectOutputStream和ObjectInputStream将对象序列化和反序列化。
Serializable接口是标记接口,是个空接口,用于标识该类可以被序列化

public class ObjectSerializationDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File("D:\\person.out");
        if(!file.exists()){
            file.mkdirs();
        }
        //序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        Person person = new Person("测试",25,Gender.FEMALE,"ennenen");
        oos.writeObject(person);
        oos.close();
        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Person person1 = (Person) ois.readObject();
        System.out.println(person1);
    }
}

/**
 * Serializable 表示该类可以被序列化
 */
public class Person implements Serializable {
    private String name ;
    private Integer age;
    private Gender gender;
    /**
     * transient 的作用是防止变量被序列化,在反序列化的时候transient 变量的值被赋值为初始值
     * transient 只能修饰变量,不能修饰其他的成分
     */
    private transient String introduce;

    public Person() {
        System.out.println("none-arg constructor");
    }

    public Person(String name, Integer age, Gender gender,String introduce) {
        System.out.println("arg constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.introduce = introduce;
    }

   //省略get 和 set 方法

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

/**
  *Enum类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。
  */
public enum Gender {
    MALE, FEMALE
}

进一步分析:

当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。 当Person对象被保存到person.out文件后,可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的 CLASSPATH 中包含有 Person.class,哪怕在读取Person对象时并没有显示地使用Person类,如上例所示,否则会抛出 ClassNotFoundException。
简单的来说,Java 对象序列化就是把对象写入到输出流中,用来存储或传输;反序列化就是从输入流中读取对象。
序列化一个对象首先要创造某些OutputStream对象(如FileOutputStream、ByteArrayOutputStream等),然后将其封装在一个ObjectOutputStream对象中,在调用writeObject()方法即可序列化一个对象反序列化的过程需要创造InputStream对象(如FileInputstream、ByteArrayInputStream等),然后将其封装在ObjectInputStream中,在调用readObject()即可
注意对象的序列化是基于字节的,不能使用基于字符的流。

对象引用的序列化

当程序序列化一个Teacher对象时,如果该Teacher对象持有一个Person对象的引用,为了在反序列化时可以正常恢复该Teacher对象,程序会顺带将该Person对象也进行序列化,所以Person类也必须是可序列化的,否则Teacher类将不可序列化。

当使用Java序列化机制序列化可变对象时一定要注意只有第一次调用writeObject()方法来输出对象时才会将对象转换成字节序列,并写入到ObjectOutputStream;在后面程序中即使该对象的Field发生了改变,再次调用writeObject()方法输出该对象时,改变后的Field也不会被输出。

Transient 关键字

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。Transient 关键字只能用于修饰Field,不可修饰Java程序中的其他成分。

2、方式二 (实现Externalizable接口,重写writeExternal和readExternal方法)

Externalizable接口继承了Serializable接口,替我们封装了两个方法,一个用于序列化,一个用于反序列化。这种方式是将属性序列化,注意这种方式transient修饰词将失去作用,也就是说被transient修饰的属性,只要你在writeExternal方法中序列化了该属性,照样也会得到序列化。



public class ExternalizableDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person1 person1 = new Person1("测试", 25, Gender.FEMALE, "ennenen");
        //序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(person1);
        //反序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        System.out.println(objectInputStream.readObject());
    }
}

public class Person1 implements Externalizable {
    private String name ;
    private Integer age;
    private Gender gender;
    /**
     * transient 的作用是防止变量被序列化,在反序列化的时候transient 变量的值被赋值为初始值
     * transient 只能修饰变量,不能修饰其他的成分
     * 当类实现Externalizable 接口实现序列化 时,transient 将失去作用
     */
    private transient String introduce;

    public Person1() {
        System.out.println("none-arg constructor");
    }

    public Person1(String name, Integer age, Gender gender,String introduce) {
        System.out.println("arg constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.introduce = introduce;
    }

  
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//        这里需要用变量接收一下
       age = in.readInt();
    }

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

/**
  *Enum类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。
  */
public enum Gender {
    MALE, FEMALE
}

在这里插入图片描述

使用 Externalizable 接口进行序列化时,读取对象会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中,这就是为什么在此次序列化过程中Person类的无参构造器会被调用。
由于这个原因,实现 Externalizable 接口的类必须要提供一个无参构造器,且它的访问权限为public

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值