Java中的序列化机制详细解读

 更新时间:2023年11月23日 08:58:37   作者:我会努力变强的  
这篇文章主要介绍了Java中的序列化机制详细解读,序列化:将对象的状态信息转换为可以存储或传输的数据形式(比如二进制)的过程,反序列化:与序列化相对,把序列化转换成的可以存储或传输的数据形式转化为对象的状态信息的过程,需要的朋友可以参考下

序列化与反序列化

  • 序列化:将对象的状态信息转换为可以存储或传输的数据形式(比如二进制)的过程。
  • 反序列化:与序列化相对,把序列化转换成的可以存储或传输的数据形式转化为对象的状态信息的过程。

java序列化与反序列化

  • 序列化:把对象转换为二进制流。
  • 反序列化:把二进制流数据转化为对象。

使用场景

  1. 永久保存数据。把序列化后的数据保存到磁盘等存储设备中。使用时可以反序列化。比如某些对象不想随着JVM关闭而消失,可以序列化到磁盘中。
  2. 用于网络传输。把对象序列化为二进制数据,传输到远程计算机。
  3. java序列化还可以实现对象深拷贝。

使用方式

java实现序列化与反序列化需要ObjectInputStrem(反序列化)和ObjectOutputStream(序列化)。并且序列化对象需要实现Serializable接口。

代码:

//实现Serializable接口
public class Person implements Serializable{
    private static final long serialVersionUID = 2063917965595163411L;
    private String name;
    private Integer age;
    private String Sex;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getSex() {
        return Sex;
    }
    public void setSex(String sex) {
        Sex = sex;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", Sex='" + Sex + '\'' +
                '}';
    }
}
public class JavaSerializableDemo {
    //初始化一个Person对象
    public static Person initPerson(){
        Person person = new Person();
        person.setAge(18);
        person.setName("Mike");
        person.setSex("man");
        return person;
    }
//序列化
    public static Person serialize(){
        Person person = initPerson();
	//创建一个文件输出流,把序列化的数据写到文件中。
	//创建一个ObjectOutputStream 用于序列化对象,将序列化的数据写到fileOutputStream 流中。
        try (FileOutputStream fileOutputStream = new FileOutputStream("person");
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);){
		//序列化对象
            objectOutputStream.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return person;
    }
//反序列
    public static Person deSerialize(){
        Person person = null;
        //创建一个文件输入流,用于读取序列化文件。
        //创建一个objectInputStream ,用于反序列化对象,通过读取fileInputStream 流数据进行反序列化
        try (FileInputStream fileInputStream = new FileInputStream("person");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);){
			//反序列化
            person = (Person) objectInputStream.readObject();
           return person;
        }catch (IOException e){
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return person;
    }
    public static void main(String[] args) {
        Person person = serialize();
        Person person1 = deSerialize();
        System.out.println("person==>" + person);
        System.out.println("person1==>" +person1);
        //判断两个对象是否是用一个对象
        System.out.println("person == person1 ==> " + (person == person1));
        //分别修改属性,看看会不会影响另外一个对象
        person.setAge(20);
        person1.setName("john");
        System.out.println("person==>" + person);
        System.out.println("person1==>" +person1);
    }
}

结果:

在这里插入图片描述

第一行和第二行对象的属性值一样说明了序列化会保存对象的状态信息。 第三行返回false,说明java的序列化机制实现的是对象的copy,而不是对象引用的copy。 第四行和第五行,修改对象属性不会影响另外一个对象,也说明了java的序列化机制实现的是对象的copy,而不是对象引用的copy。

如果被序列化的对象的类没有实现Serializable接口,就会抛出异常。

在这里插入图片描述

serialVersionUID

serialVersionUID,是序列化版本控制UID,其目的是序列化对象版本控制,有关各版本反序列化时是否兼容。如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。

如果没有显示指定该版本号,编译器会在编译类文件时自动帮我们根据类的信息创建一个版本号。如果没有指定版本号的情况下修改类文件信息,会导致编译器生成的版本id不一样,在反序列化时就会抛出异常。

步骤:

不显示指定版本id。进行序列化操作。修改Person类,比如新增一个fatherName属性。然后用之前序列化的文件进行反序列化为修改后的Person类。

结果:

在这里插入图片描述

由于Person文件进过了修改,编译器生成的版本id就会变化。然后就会导致序列化时的版本id与反序列化时的版本id对不上而抛出异常。

指定版本id后,就算修改文件,也不会导致反序列化异常。

静态变量的序列化

静态变量不会被序列化,也就是序列化时会忽略静态变量。因为静态变量是类级别的属性,而java序列化机制保存的是对象的状态信息。

代码: person新增一个live的静态属性。

在这里插入图片描述

 public static void main(String[] args) {
        //先序列化
        Person person = serialize();
        //修改静态变量的值
        person.live = "宇宙";
        //反序列化
        Person person1 = deSerialize();


        System.out.println(person1.live);

    }

结论: 首先把对象序列化,如果会把静态变量的值序列化的话,那么反序列化出来的对象的静态变量值就应该是序列化前的值,也就是地球村。

如果会忽略静态变量的话,那么反序列化出来的对象的静态变量值就应该是序列化后修改的值,也就是宇宙。

在这里插入图片描述

transient

transient关键字用于修饰成员变量,被修饰的成员变量不会被java的序列化机制序列化。所以反序列化时得到的对象的该属性会使用默认值,对象引用默认null,int 默认为0等,或者如果该类里面初始化了,就使用初始化的值。

Person加上一个被transient修饰的属性aliasName

在这里插入图片描述

修改测试代码和初始化代码,初始化aliasName,序列化和反序列化代码不变:

//初始化一个Person对象
    public static Person initPerson(){
        Person person = new Person();
        person.setAge(18);
        person.setName("Mike");
        person.setSex("man");
        person.setAliasName("Handsome Mike");
        return person;
    }

 public static void main(String[] args) {
        //先序列化
        Person person = serialize();
        //反序列化

        Person person1 = deSerialize();

        //输出被transient修饰的属性aliasName

        System.out.println(person.getAliasName());

        System.out.println(person1.getAliasName());

    }

结果:

在这里插入图片描述

反序列化的对象Person的aliasName属性为null。表名在序列化时,该属性被忽略了。

继承关系的序列化

序列化某个类,如果该类有父类且父类没有实现Serializable接口,则不会序列化父类的属性,反序列化时父类的属性会使用默认值或者创建对象时就初始化的值。

//person类继承一个父类。
//实现Serializable接口
public class Person extends SupperPerson implements Serializable{


    private String name;

    private Integer age;

    private String Sex;

    private transient String aliasName;

    public static  String live = "地球村";



    public String getAliasName() {
        return aliasName;
    }

    public void setAliasName(String aliasName) {
        this.aliasName = aliasName;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return Sex;
    }

    public void setSex(String sex) {
        Sex = sex;
    }

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

//父类
public class SupperPerson{

    private String aaa;

    private String bbb;


    public String getAaa() {
        return aaa;
    }

    public void setAaa(String aaa) {
        this.aaa = aaa;
    }

    public String getBbb() {
        return bbb;
    }

    public void setBbb(String bbb) {
        this.bbb = bbb;
    }
}

//初始化:
//初始化一个Person对象
    public static Person initPerson(){
        Person person = new Person();
        person.setAge(18);
        person.setName("Mike");
        person.setSex("man");
        person.setAliasName("Handsome Mike");
        //初始化父类属性
        person.setAaa("aaaaa");
        person.setBbb("bbbbb");
        return person;
    }

//测试
public static void main(String[] args) {
        //先序列化
        Person person = serialize();
        //反序列化

        Person person1 = deSerialize();

        //输出被transient修饰的属性aliasName

        System.out.println(person.getAaa());

        System.out.println(person.getBbb());


        System.out.println(person1.getAaa());

        System.out.println(person1.getBbb());

    }

结果:

在这里插入图片描述

父类的属性aaa和bbb没有被序列化保存下来。

如果父类也实现了Serializable接口,就会序列化父类的属性。

在这里插入图片描述

其他代码一样。 结果:

在这里插入图片描述

反序列化后父类的属性不再是null。

通常序列化实现深度克隆

克隆一个对象就是保存原对象的状态信息新建一个对象,实现对象的拷贝,新对象与原对象在堆上是两个不同的对象。

public class Student implements Serializable,Cloneable{
    private String name;

    private Integer age;

    private String Sex;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return Sex;
    }

    public void setSex(String sex) {
        Sex = sex;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //实现父类的clone方法。调用序列化和反序列化。
        return deSerialize(serialize(this));
    }

    /**
     * 序列化返回序列化的字节数组
     * @param student
     * @return
     */
    private  byte[] serialize(Student student){

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);){


            objectOutputStream.writeObject(student);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过序列化字节数组反序列化成Student对象。
     * @param studentByteArray
     * @return
     */
    private  Student deSerialize(byte[] studentByteArray){
        try (ByteArrayInputStream bis = new ByteArrayInputStream(studentByteArray);
             ObjectInputStream objectInputStream = new ObjectInputStream(bis);){

            Student student = (Student) objectInputStream.readObject();

            return student;

        }catch (IOException e){
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}


//测试
 public static void main(String[] args) {
        Student student = new Student();
        student.setAge(22);
        student.setName("Mike");
        student.setSex("man");

        try {
            Student studentCopy = (Student) student.clone();
            System.out.println(student);
            System.out.println(studentCopy);
            System.out.println(student == studentCopy);
            System.out.println(student.getName() == studentCopy.getName());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

结果:

在这里插入图片描述

第一行和第二行原对象与克隆对象的属性一致,表示克隆成功。

第三行比较的是原对象与克隆对象是否是同一个对象,比较对象的地址,返回false,表名是两个不同的对象。

第四行判断的是对象的属性是否也进行了克隆,比较两属性对象的地址,返回false,表示也不是同一个对象,达到了深克隆。

总结

  1. 在java中,只要一个类实现了java.io.Serializable接口,那么他就可以被序列化。
  2. 通过ObjectInputStream和ObjectOutputStream对对象进行序列化和反序列化。
  3. 对象能否被反序列化,不仅取决于对象代码是否一直不变,还取决于UID。
  4. 序列化不保存静态属性。
  5. 要想父类的属性也进行序列化,父类也要实现java.io.Serializable接口。
  6. transient关键字控制属性是否会被序列化,如果没有被序列化的属性反序列化后,会被设置成初始值。
  7. 通过序列化机制可以实现对象深拷贝。

到此这篇关于Java中的序列化机制详细解读的文章就介绍到这了,更多相关Java序列化机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java字符串相关类StringBuffer的用法详解

    Java字符串相关类StringBuffer的用法详解

    java.lang包下的StringBuffer类,代表着可变的字符序列,可以用来对字符串内容进行增删改操作。本文将通过示例详细说说它的用法,感兴趣的可以跟随小编一起学习一下
    2022-10-10
  • SpringSecurity注销设置的方法

    SpringSecurity注销设置的方法

    这篇文章主要为大家详细介绍了SpringSecurity注销设置的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Java如何处理数据成为树状结构

    Java如何处理数据成为树状结构

    这篇文章主要介绍了Java如何处理数据成为树状结构问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • java @Value(

    java @Value(

    这篇文章主要介绍了java @Value("${}")获取不到配置文件中值的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java 深入浅出讲解泛型与包装类

    Java 深入浅出讲解泛型与包装类

    泛型是在Java SE 1.5引入的的新特性,本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法,本篇我们一起来学习泛型以及包装类
    2022-04-04
  • 监控Spring Boot 项目运行情况操作方法

    监控Spring Boot 项目运行情况操作方法

    在实际开发中,经常会遇到想要获取到服务器应用的运行情况的场景,在微服务架构下对于每个应用运行情况的监控是保证系统高可用的关键,本文给大家介绍如何实现在Spring Boot的jar包中对系统的运行情况进行监控操作,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • 详解java基于MyBatis使用示例

    详解java基于MyBatis使用示例

    这篇文章主要介绍了详解java基于MyBatis使用示例,对学习MyBatis有一定的帮助,有需要的可以了解一下。
    2016-11-11
  • JAVA反射机制实例教程

    JAVA反射机制实例教程

    这篇文章主要介绍了JAVA反射机制,包括了Java反射机制的各种应用技巧,非常具有实用价值,需要的朋友可以参考下
    2014-09-09
  • HashMap的get()方法的NullPointerException问题

    HashMap的get()方法的NullPointerException问题

    这篇文章主要介绍了HashMap的get()方法的NullPointerException问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Mybatis Plus整合PageHelper分页的实现示例

    Mybatis Plus整合PageHelper分页的实现示例

    这篇文章主要介绍了Mybatis Plus整合PageHelper分页的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09

最新评论