序列化与反序列化

本文详细介绍了Java序列化与反序列化的概念,通过实例展示了如何进行序列化和反序列化操作。讨论了子类实现Serializable接口,但父类未实现时的情况,指出父类需提供空构造器。此外,还分析了当类中存在不可序列化引用对象时,序列化会抛出异常。最后,通过一个例子说明了对象在多次序列化期间,即使属性更新,反序列化结果也不会改变,原因是对象的序列化编码号不会变。
摘要由CSDN通过智能技术生成

1.序列化与反序列化的概念

序列化:将对象写入到IO流中
反序列化:从IO流中恢复对象

Serializable接口是一个标记接口,不用实现任何方法,标记当前类对象是可以序列化的,是给JVM看的。
序列化机制允许将这些实现序列化接口的对象转化为字节序列,这些字节序列可以保证在磁盘上或者网络传输后恢复成原来的对象。序列化就是把对象存储在JVM以外的地方,序列化机制可以让对象脱离程序的运行而独立存在。
序列化在业务代码也许用的不多,但是在框架层面用的是很多的。
先给出序列化的例子。

package com.shi;
import java.io.Serializable;
/**
 * 机动车
 * @author shixiangcheng
 * 2021-12-16
 */
public class Motor implements Serializable{
	private String name;
	public Motor(String name) {
		super();
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Motor{name="+this.name+"}";
	}
}
package com.shi;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
 * 测试类
 * @author shixiangcheng
 * 2021-12-16
 */
public class Test {
	public static void main(String[] args) throws Exception {
		//用于存储序列化的文件,这里的java_下划线仅仅为了说明java序列化对象,没有任何其它含义
		File file=new File("D:/tmp/Motor.java_");
		if(!file.exists()) {
			//先得到文件的上级目录,并创建上级目录
			file.getParentFile().mkdirs();
			//再创建文件
			file.createNewFile();
		}
		
		Motor m=new Motor("你好");
		//创建一个输出流
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
		//输出可序列化对象
		oos.writeObject(m);
		oos.close();
		//反序列化的步骤
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
		//得到反序列化对象
		Object motor=ois.readObject();
		ois.close();
		System.out.println(motor);
	}
}

运行之后,看到磁盘文件因为序列化而多了一个文件。
在这里插入图片描述
控制台中因反序列化输出的对象信息打印如下:
在这里插入图片描述
2.子类实现Serializable接口,父类没有实现,子类可以序列化吗?
去掉父类Motor的implements Serializable,让父类不实现序列化接口,子类Car实现序列化接口

package com.shi;
import java.io.Serializable;
/**
 * 汽车
 * @author shixiangcheng
 * 2021-12-16
 */
public class Car extends Motor implements Serializable{
	private String owner;
	public Car(String owner,String name) {
		super(name);
		this.owner=owner;
	}
}
package com.shi;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
 * 测试类
 * @author shixiangcheng
 * 2021-12-16
 */
public class Test {
	public static void main(String[] args) throws Exception {
		//用于存储序列化的文件,这里的java_下划线仅仅为了说明java序列化对象,没有任何其它含义
		File file=new File("D:/tmp/Motor.java_");
		if(!file.exists()) {
			//先得到文件的上级目录,并创建上级目录
			file.getParentFile().mkdirs();
			//再创建文件
			file.createNewFile();
		}
		Car m=new Car("小车","你好");
		//创建一个输出流
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
		//输出可序列化对象
		oos.writeObject(m);
		oos.close();
		//反序列化的步骤
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
		//得到反序列化对象
		Object motor=ois.readObject();
		ois.close();
		System.out.println(motor);
	}
}

运行结果

Exception in thread "main" java.io.InvalidClassException: com.shi.Car; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)
	at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
	at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
	at java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.io.ObjectInputStream.readObject(Unknown Source)
	at com.shi.Test.main(Test.java:34)

结果显示没有有效地构造器,原来是因为父类没有序列化的时候,Object motor=ois.readObject();需要直接调用父类的无参数构造方法,不经过子类的无参构造方法。
结果却发现打印的不是Car,而是父类Motor,因为子类没有实现toString而调用父类的toString,所以打印了Motor对象,至于父类成员变量id为什么是null,原因如下:
  一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable接口,序列化该子类对象。要想反序列化后输出父类定义的某变量的数值,就需要让父类也实现Serializable接口或者父类有默认的无参的构造函数。
  在父类没有实现Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值,如果在父类无参构造函数中没有对变量赋值,那么父类成员变量值都是默认值,如这里的Long型就是null。
  根据以上特性,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable接口,父类不实现Serializable接口但提供一个空构造方法,则父类的字段数据将不被序列化。
总结:
子类实现Serializable接口,父类没有实现,子类可以序列化!!
这种情况父类一定要提供空构造方法,不要忘了子类的toString方法!

3.类中存在引用对象,这个类对象在什么情况下可以实现序列化?
来一个组合对象,里面引用Motor 对象,此时Motor 对象没有实现Serializable接口,能否序列化呢?

package com.shi;
import java.io.Serializable;
/**
 * 组合
 * @author shixiangcheng
 * 2021-12-16
 */
public class Combo implements Serializable{
	private Long id;
	private Motor motor;
	public Combo(Long id, Motor motor) {
		super();
		this.id = id;
		this.motor = motor;
	}
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Motor getMotor() {
		return motor;
	}
	public void setMotor(Motor motor) {
		this.motor = motor;
	}
	@Override
	public String toString() {
		return "Combo{id="+this.id+",motor="+this.motor+"}";
	}
}
package com.shi;
import java.io.*;
/**
 * 测试类
 * @author shixiangcheng
 * 2021-12-16
 */
public class Test {
	public static void main(String[] args) throws Exception {
		//用于存储序列化的文件,这里的java_下划线仅仅为了说明java序列化对象,没有任何其它含义
		File file=new File("D:/tmp/Motor.java_");
		if(!file.exists()) {
			//先得到文件的上级目录,并创建上级目录
			file.getParentFile().mkdirs();
			//再创建文件
			file.createNewFile();
		}
		Combo m=new Combo(1L,new Motor("你好"));
		//创建一个输出流
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
		//输出可序列化对象
		oos.writeObject(m);
		oos.close();
		//反序列化的步骤
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
		//得到反序列化对象
		Object motor=ois.readObject();
		ois.close();
		System.out.println(motor);
	}
}

运行结果:直接爆出异常,说明Motor类没有序列化

Exception in thread "main" java.io.NotSerializableException: com.shi.Motor
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at com.shi.Test.main(Test.java:28)

总结:
  一个类里面所有的属性必须是可序列化的,这个类才能顺利的序列化。比如,类中存在引用对象,那么这个引用对象必须是可序列化的,这个类才能序列化。

4.同一个对象多次序列化之间有属性更新,前后的序列化有什么区别?
 下面例子中Motor是可序列化的,每次序列化之前都会把Motor的name值修改了,用来观察看看,多次序列化期间,如果对象属性更新,是否会影响序列化,反序列化有什么区别。

package com.shi;
import java.io.*;
/**
 * 测试类
 * @author shixiangcheng
 * 2021-12-16
 */
public class Test {
	public static void main(String[] args) throws Exception {
		//用于存储序列化的文件,这里的java_下划线仅仅为了说明java序列化对象,没有任何其它含义
		File file=new File("D:/tmp/Motor.java_");
		if(!file.exists()) {
			//先得到文件的上级目录,并创建上级目录
			file.getParentFile().mkdirs();
			//再创建文件
			file.createNewFile();
		}
		Motor m=new Motor("A");
		//创建一个输出流
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
		//未序列化,先修改属性
		m.setName("B");
		//输出可序列化对象
		oos.writeObject(m);
		//序列化一次后,再次修改属性
		m.setName("C");
		oos.writeObject(m);
		//序列化两次后,再次修改属性
		m.setName("D");
		oos.writeObject(m);
		oos.close();
		//反序列化的步骤
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
		//得到反序列化对象
		Object m1=ois.readObject();
		Object m2=ois.readObject();
		Object m3=ois.readObject();
		ois.close();
		System.out.println(m1);
		System.out.println(m2);
		System.out.println(m3);
	}
}

运行结果

Motor{name=B}
Motor{name=B}
Motor{name=B}

结果发现反序列化读出的值都是一样的。说明当对象第一次序列化成功后,后续这个对象属性即使有修改,也不会对后面的序列化造成成影响。
这其实是序列化算法的原因,所有要序列化的对象都有一个序列化的编码号,当试图序列化一个对象,会检查这个对象是否已经序列化过,若从未序列化过,才会序列化为字节序列去输出。若已经序列化过,则会输出一个编码符号,不会重复序列化一个对象。如下
在这里插入图片描述
序列化一次后,后续继续序列化并未重复转换为字节序列,而是输出字符q~
总结:
  当第一次序列化之后,不管如何修改这个对象的属性,都不会对后续的序列化产生影响,反序列化的结果都和第一次相同。

欢迎大家积极留言交流学习心得,点赞的人最美丽!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值