Java中的序列化机制详细解读
序列化与反序列化
- 序列化:将对象的状态信息转换为可以存储或传输的数据形式(比如二进制)的过程。
- 反序列化:与序列化相对,把序列化转换成的可以存储或传输的数据形式转化为对象的状态信息的过程。
java序列化与反序列化
- 序列化:把对象转换为二进制流。
- 反序列化:把二进制流数据转化为对象。
使用场景
- 永久保存数据。把序列化后的数据保存到磁盘等存储设备中。使用时可以反序列化。比如某些对象不想随着JVM关闭而消失,可以序列化到磁盘中。
- 用于网络传输。把对象序列化为二进制数据,传输到远程计算机。
- 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,表示也不是同一个对象,达到了深克隆。
总结
- 在java中,只要一个类实现了java.io.Serializable接口,那么他就可以被序列化。
- 通过ObjectInputStream和ObjectOutputStream对对象进行序列化和反序列化。
- 对象能否被反序列化,不仅取决于对象代码是否一直不变,还取决于UID。
- 序列化不保存静态属性。
- 要想父类的属性也进行序列化,父类也要实现java.io.Serializable接口。
- transient关键字控制属性是否会被序列化,如果没有被序列化的属性反序列化后,会被设置成初始值。
- 通过序列化机制可以实现对象深拷贝。
到此这篇关于Java中的序列化机制详细解读的文章就介绍到这了,更多相关Java序列化机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
HashMap的get()方法的NullPointerException问题
这篇文章主要介绍了HashMap的get()方法的NullPointerException问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-09-09Mybatis Plus整合PageHelper分页的实现示例
这篇文章主要介绍了Mybatis Plus整合PageHelper分页的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-09-09
最新评论