Java之对象的序列化和反序列化
对象的序列化和反序列化
1)对象序列化,就是将Object对象转换成byte序列,反之叫对象的反序列化。
2)序列化流(ObjectOutputStream),是字节的过滤流—— writeObject()方法
反序列化流(ObjectInputStream)—— readObject()方法
3)序列化接口(Serializable)
对象必须实现序列化接口,才能进行序列化,否则将出现异常。
注:这个接口,没有任何方法,只是一个【标准】
一、最基本的序列化和反序列过程
序列化和反序列都是以Object对象进行操作的,这里通过一个简单的案例来给大家演示一下对象序列化和反序列化的过程。
1、新建一个Student类(测试类)
注意:需要实现序列化接口的类才能进行序列化操作!!
@SuppressWarnings("serial") public class Student implements Serializable{ private String stuno;//id private String stuna;//姓名 private int stuage;//年龄 public String getStuno() { return stuno; } public void setStuno(String stuno) { this.stuno = stuno; } public String getStuna() { return stuna; } public void setStuna(String stuna) { this.stuna = stuna; } public Student() { super(); // TODO Auto-generated constructor stub } public Student(String stuno, String stuna, int stuage) { super(); this.stuno = stuno; this.stuna = stuna; this.stuage = stuage; } @Override public String toString() { return "Student [stuno=" + stuno + ", stuna=" + stuna + ", stuage=" + stuage + "]"; } public int getStuage() { return stuage; } public void setStuage(int stuage) { this.stuage = stuage; } }
2、将Student类的实例序列化成文件
基本操作步骤如下:
1)、指定序列化保存的文件
2)、构造ObjectOutputStream类
3)、构造一个Student类
4)、使用writeObject方法序列化
5)、使用close()方法关闭流
String file="demo/obj.dat"; //对象的序列化 ObjectOutputStream oos=new ObjectOutputStream( new FileOutputStream(file)); //把Student对象保存起来,就是对象的序列化 Student stu=new Student("01","mike",18); //使用writeObject方法序列化 oos.writeObject(stu); oos.close();
运行结果:可以看到demo目录下生成了obj.dat的序列化文件
3、将文件反序列化读出Student类对象
基本操作步骤如下:
1)、指定反序列化的文件
2)、构造ObjectInputStream类
3)、使用readObject方法反序列化
1)、使用close方法关闭流
String file="demo/obj.dat"; ObjectInputStream ois =new ObjectInputStream( new FileInputStream(file)); //使用readObject()方法序列化 Student stu=(Student)ois.readObject();//强制类型转换 System.out.println(stu); ois.close();
运行结果:
注意:在文件反序列化时,readObject方法取出的对象默认都是Object类型,必须强制转换为相应的类型。
二、transient及ArrayList源码分析
在日常编程过程中,我们有时不希望一个类所有的元素都被编译器序列化,这时该怎么办呢?
Java提供了一个transient关键字来修饰我们不希望被jvm自动序列化的元素。下面简单来讲解一下这个关键字。
transient 关键字:被transient修饰的元素,该元素不会进行jvm默认的序列化,但可以自己完成这个元素的序列化。
注意:
1)在以后的网络编程中,如果有某些元素不需要传输,那就可以用transient修饰,来节省流量;对有效元素序列化,提高性能。
2)可以使用writeObject自己完成这个元素的序列化。
ArrayList就是用了此方法进行了优化操作。ArrayList最核心的容器Object[] elementData使用了transient修饰,但是在writeObject自己实现对elementData数组的序列化。只对数组中有效元素进行序列化。readObject与之类似。
--------------自己序列化的方式---------------
在要序列化的类中加入两个方法(这两个方法都是从ArrayList源码中提取出来的,比较特殊的两个方法):
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ s.defaultWriteObject();//把jvm能默认序列化的元素进行序列化操作 s.writeInt(stuage);//自己完成被transient修饰的元素的序列化 } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException,ClassNotFoundException{ s.defaultReadObject();//把jvm能默认反序列化的元素进行反序列化操作 this.stuage=s.readInt();//自己完成stuage的反序列化操作 }
加入这两个方法后,即使被transient修饰的元素也能像刚刚那样进行序列化和反序列化了,jvm会自动使用这两个方法帮助我们完成这动作。
这里又有个问题,为什么还需要手动去完成序列化和反序列化呢,有什么意义呢?
这个问题得再从ArrayList的源码中去分析:
可以看出ArrayList源码中自己序列化的目的:ArrayList底层为数组,自己序列化可以过滤数组中无效的元素,只序列化数组中有效的元素,从而提高性能。
因此,实际编程过程中我们可以根据需要来自己完成序列化以提高性能。
三、序列化中子父类构造函数问题
在类的序列化和反序列化中,如果存在子类和父类的关系时,序列化和反序列化的过程又是怎么样的呢?
这里我写一个测试类来测试子类和父类实现序列化和反序列化时构造函数的实现变化。
public static void main(String[] args) throws IOException { // TODO Auto-generated method stub String file="demo/foo.dat"; ObjectOutputStream oos=new ObjectOutputStream( new FileOutputStream(file)); Foo2 foo2 =new Foo2(); oos.writeObject(foo2); oos.flush(); oos.close(); } } class Foo implements Serializable{ public Foo(){ System.out.println("foo"); } } class Foo1 extends Foo{ public Foo1(){ System.out.println("foo1"); } } class Foo2 extends Foo1{ public Foo2(){ System.out.println("foo2"); } }
运行结果:这是序列化时递归调用了父类的构造函数
接来下看看反序列化时,是否递归调用父类的构造函数。
ObjectInputStream ois=new ObjectInputStream( new FileInputStream(file)); Foo2 foo2=(Foo2)ois.readObject(); ois.close();
运行结果:控制台没有任何输出。
那么这个结果是否证明反序列化过程中父类的构造函数就是始终不调用的呢?
然而不能证明!!
因为再看下面这个不同的测试例子:
class Bar { public Bar(){ System.out.println("bar"); } } class Bar1 extends Bar implements Serializable{ public Bar1(){ System.out.println("bar1"); } } class Bar2 extends Bar1{ public Bar2(){ System.out.println("bar2"); } }
我们用这个例子来测试序列化和反序列化。
序列化结果:
反序列化结果:没实现序列化接口的父类被显示调用构造函数
【反序列化时】,向上递归调用构造函数会从【可序列化的一级父类结束】。即谁实现了可序列化(包括继承实现的),谁的构造函数就不会调用。
总结:
1)父类实现了serializable接口,子类继承就可序列化。
子类在反序列化时,父类实现了序列化接口,则不会递归调用其构造函数。
2)父类未实现serializable接口,子类自行实现可序列化
子类在反序列化时,父类没有实现序列化接口,则会递归调用其构造函数。
本文来自 java入门 栏目,欢迎学习!
以上是Java之对象的序列化和反序列化的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

Spring Boot简化了可靠,可扩展和生产就绪的Java应用的创建,从而彻底改变了Java开发。 它的“惯例惯例”方法(春季生态系统固有的惯例),最小化手动设置
