Java序列化与反序列化(Serializable&&Externalizable)

概念

序列化:将Java对象转换为字节序列的过程
反序列化:将字节序列转换为Java对象的过程

为什么要这么操作

我们都知道可以通过网络传递图片、视频、文件等等数据信息,而这些信息最终都是以二进制序列传递的。那么Java对象呢?如果两个系统间要通过Java对象进行通信改怎么办,这个时候就要使用到Java的序列化和反序列化了。

而序列化的好处是什么呢?首先序列化可以永久存储对象信息,我们可以将对象序列化后存储到磁盘上,这样可以保存对象数据;其次可以通过这个方式完成跨网络的通信和信息传递

实现

序列化相关的API
  • 对象输出流

    对象输出类 
    public ObjectOutputStream(OutputStream out) throws IOException 
    
    写入对象方法
    public final void writeObject(ObjectOutput out) throws IOException
    
  • 对象输入流

    对象输入类
    public ObjectInputStream(InputStream in) throws IOException  
    
    读取对象方法 
    public final void readObject(ObjectInput in) throws IOException
    
实现序列化的要求

必须实现接口Serializable 或者 Externalizable 才能被序列化,否则会报错

实现方式

假设以对象student为例,假设对该对象进行序列化和反序列化

  • 若只实现了Serializable接口则

    * ObjectOutputStream采用默认序列化方式,对非transient修饰变量进行序列化
    
    * ObjectInputStream 采用默认的反序列化方式,对非transient修饰的变量进行反序列化
    
  • 如果实现了Serializable接口,并且也定义了writeObject(ObjectOutput out) 和 readObject()方法

    *  ObjectOutputStream会调用类中的writeObject方法进行序列化
    
    *  ObjectInputStream同样会调用类中的readObject进行反序列化
    
  • 如果实现了Externalizable接口,必须实现writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in)方法

    * 类必须有一个无参构造器方法(创建类是默认会有一个无参构造方法,但最好还是手动写一个)
    
    * ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。
    
    * ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。
    
    如果实现了Externalizable接口而writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in)方法没有进行任何处理,默认不对对象做任何序列化操作。
    
    步骤
  • 创建一个对象输出流

ObjectOutputStream out = new ObjectOutputStream(new FileInputStream("D:\\test.txt"));


ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
  • 将要序列化的对象写入对象输出流
out.writeObject(“Hello”);

out.writeObject(new Date());
  • 创建一个对象输入流
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
  • 读取对象
String obj1 = (String)in.readObject();

Date obj2 = (Date)in.readObject();

案例

采用Serializable方法实现

以stuendt为例,注意变量address被transient修饰,关于transient的用法下面会详细说明,请先往下看

public class Student implements Serializable {

    private String name;

    private int age;

    private String sex;

    private transient String address;

    /**
     *  Getter  method for property  name.
     *
     *  @return property value of name
     */

    public String getName() {
        return name;
    }

    /**
     *  Setter method for property name
     *
     *  @param name value to be assigned to property name
     */

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

    /**
     *  Getter  method for property  age.
     *
     *  @return property value of age
     */

    public int getAge() {
        return age;
    }

    /**
     *  Setter method for property age
     *
     *  @param age value to be assigned to property age
     */

    public void setAge(int age) {
        this.age = age;
    }

    /**
     *  Getter  method for property  sex.
     *
     *  @return property value of sex
     */

    public String getSex() {
        return sex;
    }

    /**
     *  Setter method for property sex
     *
     *  @param sex value to be assigned to property sex
     */

    public void setSex(String sex) {
        this.sex = sex;
    }

    /**
     *  Getter  method for property  address.
     *
     *  @return property value of address
     */

    public String getAddress() {
        return address;
    }

    /**
     *  Setter method for property address
     *
     *  @param address value to be assigned to property address
     */

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

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", sex='" + sex + '\'' +
            ", address='" + address + '\'' +
            '}';
    }
}    

运行结果如下:

    public static void main(String[] args) throws Exception {


        Student stu = new Student();

        stu.setName("A");
        stu.setAge(10);
        stu.setSex("man");
        stu.setAddress("CHN");

        //1、序列化:创建对象输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);

        //2、写入序列化对象
        out.writeObject(stu);

        //3、创建对象输入流
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);

        //4、对取对象
        Student obj = (Student)in.readObject();

        System.out.println(obj);


    }
运行结果
Student{name='A', age=10, sex='man', address='null'}

Process finished with exit code 0

可以看到对象对顺利序列化了,但是被transient修饰的变量没有被序列化,这是为什么呢?

transient的用法

在日常开发中经常会遇到这种情况,有些类中存在敏感信息,这些信息想让使用该类对象的人知道,比如用户的身份证信息等,这个时候就可以在不想让对方知道的变量前加上transient关键字,被transient修饰的变量不会被序列化和反序列化。当我们在实际开发中发现某个对象的变量不能被序列化时,就要考虑是不是变量加了transient关键字。

使用场景
  • transient只能修饰变量,不能修饰类和方法
  • 被transient修饰的变量不能持久化,因为该变量无法序列化也就无法存储到磁盘中
  • 对于静态变量,无论有没有被transient修饰,都无法序列化

前两点已经在上面的例子中有所展示,下面我们看下第三点

首先我们增加一个静态变量school

public class Student implements Serializable {

    private String name;

    private int age;

    private String sex;

    private transient String address;

    public static String school = "QHDX";

    /**
     *  Getter  method for property  name.
     *
     *  @return property value of name
     */

    public String getName() {
        return name;
    }

    /**
     *  Setter method for property name
     *
     *  @param name value to be assigned to property name
     */

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

    /**
     *  Getter  method for property  age.
     *
     *  @return property value of age
     */

    public int getAge() {
        return age;
    }

    /**
     *  Setter method for property age
     *
     *  @param age value to be assigned to property age
     */

    public void setAge(int age) {
        this.age = age;
    }

    /**
     *  Getter  method for property  sex.
     *
     *  @return property value of sex
     */

    public String getSex() {
        return sex;
    }

    /**
     *  Setter method for property sex
     *
     *  @param sex value to be assigned to property sex
     */

    public void setSex(String sex) {
        this.sex = sex;
    }

    /**
     *  Getter  method for property  address.
     *
     *  @return property value of address
     */

    public String getAddress() {
        return address;
    }

    /**
     *  Setter method for property address
     *
     *  @param address value to be assigned to property address
     */

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

    /**
     *  Getter  method for property  school.
     *
     *  @return property value of school
     */

    public String getSchool() {
        return school;
    }

    /**
     *  Setter method for property school
     *
     *  @param school value to be assigned to property school
     */

    public void setSchool(String school) {
        Student.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", sex='" + sex + '\'' +
            ", address='" + address + '\'' +
            ", school='" + school + '\'' +
            '}';
    }
}    

运行结果如下:

    public static void main(String[] args) throws Exception {


        Student stu = new Student();

        stu.setName("A");
        stu.setAge(10);
        stu.setSex("man");
        stu.setAddress("CHN");


        //1、序列化:创建对象输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);

        //2、写入序列化对象
        out.writeObject(stu);

        //3、创建对象输入流
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);

        //4、对取对象
        Student obj = (Student)in.readObject();

        System.out.println(obj);


    }

运行结果:
Student{name='A', age=10, sex='man', address='null', school='QHDX'}

Process finished with exit code 0

有人会说school被打印出来,显然是被序列化后。

其实这个地方school并没有被序列化,因为school是静态变量,所以这个地方是从静态数据存储区直接获取的school值,不信我们看下面这段代码,在对象序列化后对school进行修改,我们看下结果

    public static void main(String[] args) throws Exception {


        Student stu = new Student();

        stu.setName("A");
        stu.setAge(10);
        stu.setSex("man");
        stu.setAddress("CHN");


        //1、序列化:创建对象输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);

        //2、写入序列化对象
        out.writeObject(stu);

        stu.setSchool("BJDX");

        //3、创建对象输入流
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);

        //4、对取对象
        Student obj = (Student)in.readObject();

        System.out.println(obj);


    }

运行结果:
Student{name='A', age=10, sex='man', address='null', school='BJDX'}

Process finished with exit code 0

结果中的school变成了修改后的值,验证了刚才的说法。

那么问题来了,被transient修改是变量一定不能序列化吗?当然不是,只要实现了Externalizable接口,就可以实现

ublic class Student implements Externalizable {

    private String name;

    private int age;

    private String sex;

    private transient String address;

    public static String school = "QHDX";

    /**
     *  Getter  method for property  name.
     *
     *  @return property value of name
     */

    public String getName() {
        return name;
    }

    /**
     *  Setter method for property name
     *
     *  @param name value to be assigned to property name
     */

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

    /**
     *  Getter  method for property  age.
     *
     *  @return property value of age
     */

    public int getAge() {
        return age;
    }

    /**
     *  Setter method for property age
     *
     *  @param age value to be assigned to property age
     */

    public void setAge(int age) {
        this.age = age;
    }

    /**
     *  Getter  method for property  sex.
     *
     *  @return property value of sex
     */

    public String getSex() {
        return sex;
    }

    /**
     *  Setter method for property sex
     *
     *  @param sex value to be assigned to property sex
     */

    public void setSex(String sex) {
        this.sex = sex;
    }

    /**
     *  Getter  method for property  address.
     *
     *  @return property value of address
     */

    public String getAddress() {
        return address;
    }

    /**
     *  Setter method for property address
     *
     *  @param address value to be assigned to property address
     */

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

    /**
     *  Getter  method for property  school.
     *
     *  @return property value of school
     */

    public String getSchool() {
        return school;
    }

    /**
     *  Setter method for property school
     *
     *  @param school value to be assigned to property school
     */

    public void setSchool(String school) {
        Student.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", sex='" + sex + '\'' +
            ", address='" + address + '\'' +
            ", school='" + school + '\'' +
            '}';
    }


    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

        out.writeObject(name);
        out.writeInt(age);
        out.writeObject(sex);
        out.writeObject(address);

        System.out.println("自定义序列化信息");
    }


    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

        name = (String)in.readObject();
        age = in.readInt();
        sex= (String)in.readObject();
        address = (String)in.readObject();
        System.out.println("自定义反序列化");
    }
}    

特别注意:

  • 如果方法writeExternal和readExternal什么都不做处理,那么对象是不会被序列化的
  • 需要序列化的属性,必须在writeExternal手工处理
  • 反序列化时需要在readExternal中逐个获取属性值
  • 反序列化readExternal中属性的顺序必须和序列化writeExternal中的顺序一致,否则会报错,这点很重要

结果如下

自定义序列化信息
自定义反序列化
Student{name='A', age=10, sex='man', address='CHN', school='QHDX'}

Process finished with exit code 0

可以看到 address变量也被序列化了

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java序列化是指将一个Java对象转换成字节序列的过程,以便可以将其存储到文件、数据库或在网络上传输。只有实现了SerializableExternalizable接口的类的对象才能被序列化,否则会抛出异常。 Java反序列化则是将字节序列重新转换成Java对象的过程。通过反序列化,可以从文件、数据库或网络中读取字节序列,并将其转换回原始的Java对象。 Java序列化反序列化有以下几个好处: 1. 数据持久化:通过序列化,可以将对象永久地保存到硬盘上,通常存放在文件中。这样可以实现数据的长期存储和读取。 2. 远程通信:通过序列化反序列化,可以在网络上传送对象的字节序列,实现远程通信。这在分布式系统和客户端-服务器应用中非常有用。 在Java序列化机制中,版本一致性非常重要。当进行反序列化时,JVM会通过比较传来的字节流中的serialVersionUID和本地相应对象的serialVersionUID来验证版本一致性。如果两者相同,就认为是一致的,可以进行反序列化。否则,就会出现序列化版本不一致的异常。 需要注意的是,如果实现了java.io.Serializable接口的类没有显式地定义一个名为serialVersionUID的变量,Java序列化机制会根据编译的class自动生成一个serialVersionUID用于序列化版本比较。但是只有同一次编译生成的class才会生成相同的serialVersionUID。因此,在进行序列化反序列化时,确保对象的类定义和使用的类定义是一致的非常重要。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值