JAVA序列化Serializable及Externalizable区别详解
作者:像风一样无影无起 发布时间:2022-04-04 10:49:09
序列化简介
Java 的对象序列化将那些实现 Serializable 接口的对象转换成一个字节序列,并能在之后将这个字节序列完全恢复为原来的对象。
这就意味着 Java 对象在网络上的传输可以不依赖于当前计算机的操作系统,就可以将对象进行传递,这也是Java跨平台的一种体现。
Java 对象的序列化主要支持两种特性:
1、Java的远程方法调用(Remote Method Invocation RMI);
2、对于 JavaBean 来说,序列化也是必须的。
要序列化一个对象,需要创建一个 OutputStream 对象,然后将其封装在 ObjectOutputStream 对象中,再调用 writeObject() 方法就可以完成对象的序列化(也是在这一步进行序列化);反序列化(将一个序列还原为一个对象)就是该过程的反过程:创建一个 InputStream 对象,将其封装在 ObjectInputStream 对象中,使用 readObject() 方法将序列反序列化为对象,当然这是一个Object类型的对象,需要向下转型为我们需要的类型(如果该类型不在本地,会导致反序列化失败,ClassNotFoundException )。
先说结论
序列化有以下方式:
1、实现 Serializable 接口:
2、实现 Externalizable 接口,并重写 writeExternal() readExternal() 方法;
3、(即下文中的 Externalizable 的替代方式进行序列化)如果不想实现Externalizable 接口,又想按照自己的规则进行序列化,可以实现 Serializable 接口,并在该类中添加(添加,不是覆盖、实现)名为 writeExternal() readExternal() 方法,且这两个方法必须为下面这两个准确的方法签名:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException;
一、三种方式完成序列化
1、实现 Serializable 接口序列化
这种方式最为常用且常见,只需要对需要序列化的类实现 Serializable 即可,对于不希望进行序列化的,可以使用 transient 关键词进行修饰(即瞬时变量)。
这种方式序列化的特征:
1、 Serializable 接口仅仅是一个标记接口,不包含任何方法;
2、对于Serializable对象来说,对象完全以它存储的二进制位为基础来构造,(反序列化)不会调用构造器。
2、实现 Externalizable 接口序列化
这种方式可以实现序列化的完全自定义:所有成员变量是否序列化都需要在 writeExternal()、readExternal()
方法中写出;且可以完全自定义序列化方式(在 writerExternal()、readExternal()方法中)。当然,实现 Externalizable 接口必须要重写这两个方法。
这种方式序列化的特征:
1、必须重写 writerExternal()、readExternal()两个方法,并在两个方法中写出所有需要序列化的成员变量;
2、对于 Externalizable对象来说,必须要有无参public构造器,不然会报出 InvalidClassException 异常。
3、 Externalizable 的替代方式进行序列化
让 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject() 方法和 readObject() 方法调用我们编写的这两个方法。
如果想在这种方式中也调用原有默认提供的方式,可以在 writeObject() 中调用: s.defaultWriteObject();,在 readObject() 中调用 s.defaultReadObject();。 这部分代码可以查看 ArrayList 源码。
二、测试代码
1、 Serializable 对象反序列化,不调用任何构造器
Serializable 对象反序列化不调用任何构造器,包括默认构造器,整个对象都是从 InputStream 中取得数据恢复过来的
主测试类 Dogs
public class Dogs {
public static void main(String[] args) throws Exception {
// 创建对象
System.out.println("--- 创建对象 ---");
Dog1 d1 = new Dog1("pidan",4.0);
Dog2 d2 = new Dog2("duanwu","black");
// 序列化
System.out.println("--- 序列化 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/dogs.out"));
oos.writeObject(d1);
oos.writeObject(d2);
System.out.println("--- 反序列化 ---");
// 反序列化 不会调用任何构造器
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/dogs.out"));
Dog1 o1 = (Dog1) ois.readObject();
Dog2 o2 = (Dog2) ois.readObject();
System.out.println("反序列化 o1 : " + o1);
System.out.println("反序列化 o2 : " + o2);
}
}
Serializable 对象 Dog1 Dog2 类
class Dog1 implements Serializable {
private static final long serialVersionUID = -7101743601344663182L;
private String name;
private Double weight;
public Dog1(String name, Double weight) {
System.out.println("Dog1 构造器运行 ---");
this.name = name;
this.weight = weight;
System.out.println("Dog1 : " + this);
}
// 省略get、set、toString方法
}
public class Dog2 implements Serializable {
private static final long serialVersionUID = -5462607652670703938L;
private String name;
private String color;
public Dog2(String name, String color) {
System.out.println("Dog2 构造器运行 ---");
this.name = name;
this.color = color;
System.out.println("Dogs2 : " + this);
}
// 省略get、set、toString方法
}
运行结果:
--- 创建对象 ---
Dog1 构造器运行 ---
Dog1 : Dog1{name='pidan', weight=4.0}
Dog2 构造器运行 ---
Dogs2 : Dog2{name='duanwu', color='black'}
--- 序列化 ---
--- 反序列化 ---
反序列化 o1 : Dog1{name='pidan', weight=4.0}
反序列化 o2 : Dog2{name='duanwu', color='black'}
再最后取出对象时,完全没有调用到其任何构造器。
2、无参构造器对 Externalizable 对象序列化的影响
主测试代码:
public class Persons {
public static void main(String[] args) throws Exception {
// 创建对象
System.out.println("Init Objects");
Person1 p1 = new Person1();
Person2 p2 = new Person2();
// 存储在磁盘上
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/person.out"));
os.writeObject(p1);
os.writeObject(p2);
os.flush();
os.close();
// 取出
ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/person.out"));
System.out.println("取出p1: ");
p1 = (Person1) is.readObject();
p2 = (Person2) is.readObject();
}
}
Externalizable 对象:Perion1 Persion2
public class Person1 implements Externalizable {
public Person1(){
System.out.println("Person1 构造器---");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Person1 writeExternal ---");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Person1 readExternal ---");
}
}
class Person2 implements Externalizable{
// 注意不是public
Person2(){
System.out.println("Person2 构造器 ---");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Person2 writeExternal ---");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Person2 readExternal ---");
}
}
Person2 默认构造器不是 public 的运行结果:
Init Objects
Person1 构造器---
Person2 构造器 ---
Person1 writeExternal ---
Person2 writeExternal ---
取出p1:
Person1 构造器---
Person1 readExternal ---
Exception in thread "main" java.io.InvalidClassException: ...serializableAndexternalizable.Person2; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at ...serializableAndexternalizable.Persons.main(Persons.java:29)
Process finished with exit code 1
将 Person2 构造器改为 public 后:
Init Objects
Person1 构造器---
Person2 构造器 ---
Person1 writeExternal ---
Person2 writeExternal ---
取出p1:
Person1 构造器---
Person1 readExternal ---
Person2 构造器 ---
Person2 readExternal ---
3、使用 Externalizable 对象实现序列化
主测试类 Cats :
public class Cats {
public static void main(String[] args) throws Exception {
// 初始化对象
System.out.println("--- 初始化对象 ---");
Person person = new Person("01", "老王", 30);
Cat2 cat = new Cat2("fugui", person);
// 序列化
System.out.println("--- 序列化对象 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/cats.out"));
oos.writeObject(cat);
System.out.println("--- 反序列化对象 ---");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/cats.out"));
cat = (Cat2) ois.readObject();
System.out.println("--- 反序列化对象后 ---");
System.out.println("cat : " + cat);
}
}
Externalizable 对象: Cat2 ;Serializable 对象:Person :
public class Person implements Serializable {
private static final long serialVersionUID = -822166081906894628L;
private transient String id;
private String name;
private int age;
public Person() {
System.out.println("--- Person 无参构造器 ---");
}
public Person(String id, String name, int age) {
System.out.println("--- Person 无参构造器 ---");
this.id = id;
this.name = name;
this.age = age;
System.out.println("Person : " + this);
}
// 省略get、set、toString方法
}
class Cat2 implements Externalizable {
private static final long serialVersionUID = 1102930161606017855L;
private String name;
private Person minion;
public Cat2() {
System.out.println("Cat2 无参构造器 --->");
}
public Cat2(String name, Person minion) {
System.out.println("Cat2 有参构造器 --->");
this.name = name;
this.minion = minion;
System.out.println("Cat2 : " + this);
}
// 省略get、set、toString方法
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("--- Cat2:writeExternal ---");
// code1
out.writeObject(this.minion);
out.writeObject(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("--- Cat2:readExternal ---");
// code2
this.minion = (Person) in.readObject();
this.name = (String) in.readObject();
}
}
运行结果:
可以注意到Person的成员变量id在使用了 transient 关键词修饰后,就不再序列化该字段了。
--- 初始化对象 ---
--- Person 无参构造器 ---
Person : Person{id='01', name='老王', age=30}
Cat2 有参构造器 --->
Cat2 : Cat2{name='fugui', minion=Person{id='01', name='老王', age=30}}
--- 序列化对象 ---
--- Cat2:writeExternal ---
--- 反序列化对象 ---
Cat2 无参构造器 --->
--- Cat2:readExternal ---
--- 反序列化对象后 ---
cat : Cat2{name='fugui', minion=Person{id='null', name='老王', age=30}}
如果将Cat2类中标注的 code1 与 code2 代码下面的两行代码均注释掉,就不再可以序列化及反序列化了:
注释掉后的运行结果:
--- 初始化对象 ---
--- Person 无参构造器 ---
Person : Person{id='01', name='老王', age=30}
Cat2 有参构造器 --->
Cat2 : Cat2{name='fugui', minion=Person{id='01', name='老王', age=30}}
--- 序列化对象 ---
--- Cat2:writeExternal ---
--- 反序列化对象 ---
Cat2 无参构造器 --->
--- Cat2:readExternal ---
--- 反序列化对象后 ---
cat : Cat2{name='null', minion=null}
4、使用 Externalizable 对象替代方式实现序列化
替代方式就是实现 Serializable 接口,并且添加 writeObject(),readObject() 两个方法注意这两个方法必须有准确的方法特征签名,在这两个方法中编写自定义方式实现序列化和反序列化。
class Mouse implements Serializable {
private static final long serialVersionUID = -3278535893876444138L;
private String name;
private int i;
public Mouse() {
System.out.println("Mouse 无参构造器 ---");
}
public Mouse(String name, int i) {
System.out.println("Mouse 有参构造器 ---");
this.name = name;
this.i = i;
System.out.println("Mouse : " + this);
}
// 方法特征签名必须完全一致
private void writeObject(ObjectOutputStream stream) throws IOException {
// stream.defaultWriteObject();// 可以选择执行默认的writeObject()
System.out.println("--- 这是自定义的writeExternal方法 ---");
stream.writeObject(this.name);
stream.writeInt(this.i);
}
// 方法特征签名必须完全一致
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {
// stream.defaultReadObject(); // 可以选择执行默认的readObject()
System.out.println("--- 这是自定义的readExternal方法 ---");
this.name = (String)stream.readObject();
this.i = stream.readInt();
}
// 省略get、set、toString方法
}
主测试类:
public class Mouses {
public static void main(String[] args) throws Exception {
// 创建对象
System.out.println("--- 创建对象 ---");
Mouse m1 = new Mouse("zhizhi", 2);
// 序列化
System.out.println("--- 序列化 ---");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/mouse.out"));
oos.writeObject(m1);
// 反序列化
System.out.println("--- 反序列化 ---");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/mouse.out"));
// 反序列化结果
System.out.println("--- 反序列化结果 ---");
m1 = (Mouse) ois.readObject();
System.out.println(" zhizhi : " + m1);
}
}
运行结果
--- 创建对象 ---
Mouse 有参构造器 ---
Mouse : Mouse{name='zhizhi', i=2}
--- 序列化 ---
--- 这是自定义的writeExternal方法 ---
--- 反序列化 ---
--- 反序列化结果 ---
--- 这是自定义的readExternal方法 ---
zhizhi : Mouse{name='zhizhi', i=2}
来源:https://www.cnblogs.com/xfyy-2020/p/12879753.html


猜你喜欢
- 今天安装了jdk1.8、tomcat8、和maven3.5.2,弄好后在myeclipse新建了一个maven项目,项目默认是jdk1.5,
- Java是面向对象的编程语言,在我们开发Java应用的程序员的专业术语里,Java这个单词其实指的是Java开发工具,也就是JDK(Java
- 关于ListBoxListBox是WinForm中的列表控件,它提供了一个项目列表(一组数据项),用户可以选择一个或者多个条目,当列表项目过
- 事件函数的执行顺序先说一下执行顺序吧。 官方给出的脚本中事件函数的执行顺序如下图: 我们可以做一个小实验来测试一下: 在Hierarchy
- 本文实例讲述了Java基于动态规划法实现求最长公共子序列及最长公共子字符串。分享给大家供大家参考,具体如下:动态规划法经常会遇到复杂问题不能
- 本文实例讲述了Android+SQLite数据库实现的生词记事本功能。分享给大家供大家参考,具体如下:主activity命名为Dict:代码
- 目录时间轴是前端UI经常用到的效果,先看下效果图:实现一、借助 Container 中 decoration 属性,设置左侧的 border
- DataBindings属性是很多控件都有的属性,作用有2方面。一方面是用于与数据库的数据进行绑定,进行数据显示。另一方面用于与控件或类的对
- finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常
- Map集合和Collection集合的区别Map集合是有Key和Value的,Collection集合是只有Value。Collection
- 本文实例为大家分享了Qt实现计算器功能的具体代码,供大家参考,具体内容如下该计算器主要通过lineEdit获取和显示数字,通过tablevi
- java 方法签名,我想做java 开发的朋友也知道,方法签名的重要性,是方法重载的一个比较好的解释,尤其是在后续优化方面,这里记录下,有看
- 本文实例为大家分享了C#多线程Thread使用的示例代码,供大家参考,具体内容如下多线程:线程生命周期状态图:C#线程优先级(概率高低):基
- 本文实例讲述了Android编程中EditText限制文字输入的方法。分享给大家供大家参考,具体如下:Android的编辑框控件EditTe
- 基本操作示例VectorApp.javaimport java.util.Vector; import java.lang.*; impor
- 本文实例为大家分享了Unity使用鼠标旋转物体效果的具体代码,供大家参考,具体内容如下了解完基础知识后,然我们来做个小程序练习一下1.在Ma
- 年前无意看到一个用Python写的小桌面程序,可以自动玩扫雷的游戏,觉得挺有意思,决定用C#也做一个。【真实情况
- 1. 增强for概述增强for循环,也叫Foreach循环,用于数组和容器(集合类)的遍历。使用foreach循环遍历数组和集合元素时,无需
- 1、修改全局配置文件(application.yml)server: port: 9001 servlet: &nb
- 对于换肤技术,相信伙伴们都见过一些大型app,到了某些节日或者活动,e.g. 双十一、双十二、春节等等,app的ICON还有内部的页面主题背