智渔课堂官方免费教程四十六 :Java基础之序列化和反序列化

时间:2023-01-19 19:24:13

序列化和反序列化

序列化:将对象当前状态转换为字节序列,并通过输出流存储或传输的过程
作用:将对象转换成输出流,永久保存或传输
条件:被序列化的对象的类,实现Serializable接口或Externalizable接口

实例一:package serializable.s1;
import java.io.Serializable;
/**
* 用于创建被序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class Son implements Serializable {
// 年龄
public int age;
// 颜值
public int faceValue;

// 构造方法
public Son(int age, int faceValue) {
this.age = age;
this.faceValue = faceValue;
}

// 重写父类(Object类)的toString方法
@Override
public String toString() {
return "age = " + age + "\nfaceValue = " + faceValue;
}
}
package serializable.s1;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;/** * 序列化对象的类 * @author 学霸联盟 - 赵灿 */public class SerializableObject {	public static void main(String[] args) {		//创建一个对象son		Son son = new Son(18);		//声明文件输出流的引用		FileOutputStream fos = null;		//声明对象输出流的引用		ObjectOutputStream oos = null;		try {			//创建文件输出流的对象,其中文件的后缀名可以自定义			fos = new FileOutputStream("E:\\son.txt");			//创建对象输出流的对象			oos = new ObjectOutputStream(fos);			//将对象写入文件,序列化之后son的age值应为20			oos.writeObject(son);			System.out.println("序列化完成!");		} catch (FileNotFoundException e) {			e.printStackTrace();		} catch (IOException e) {			e.printStackTrace();		} finally {			try {				if (oos != null) {					//关闭对象输出流					oos.close();				}				if (fos != null) {					//关闭文件输出流					fos.close();				}			} catch (IOException e) {				e.printStackTrace();			}		}	}}

反序列化:将序列化产生的字节序列,通过输入流读取,并恢复成对象的过程

package serializable.s1;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 反序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class RecoveryObject {
public static void main(String[] args) {
Son son = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
//创建文件输入流对象,这里的文件路径要和序列化时的一致
fis = new FileInputStream("E:\\son.txt");
ois = new ObjectInputStream(fis);
//从对象输入流中读取对象,并强制转换为Son类型
son = (Son) ois.readObject();
//输出
System.out.println(son.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if(ois != null){
//关闭对象输入流
ois.close();
}
if (fis != null) {
//关闭文件输入流
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输出结果:
age = 18
faceValue = 250

注意:在序列化之后,如果对Son类进行了修改,而没有重新执行序列化操作;此时进行反序列化会出现InvalidClassException(无效的类型异常);
这是因为在序列化时,会给对象加上一个默认的版本号,这个版本号由各属性名,方法名等计算得出;在反序列化时,会验证序列化时产生的版本号,和当前类的版本号是否一致,如果不一致就会产生InvalidClassException


static final long serialVersionUID属性:序列化版本号

作用:用于反序列化时,验证反序列化的对象和当前类是否是同一版本
序列化版本号,可以自己写,也可以使用编译器自动生成的。
自己写这个版本号时要注意,只能写成static final long serialVersionUID这样的,版本号的值可以自定义。
使用Eclipse自动生成,如下图

 智渔课堂官方免费教程四十六 :Java基础之序列化和反序列化

当设定了版本号,在序列化之后,只要不修改类名和版本号,对类的其他修改,不会使反序列化出现InvalidClassException。

实例二:package serializable.s2;
import java.io.Serializable;
/**
* 用于创建被序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class Son implements Serializable {
/**
* 编译器自动生成的序列化版本号
*/
private static final long serialVersionUID = 5430359291175859046L;

public int age;
//将实例一的Son类,增加版本号并执行序列化后,将属性faceValue该为fV在执行反序列化
public int fV;
//增加name属性
String name;

public Son(int age, int faceValue) {
this.age = age;
this.fV = faceValue;
}

@Override
public String toString() {
//增加name的输出
return "age = " + age + "\nfV = " + fV + "\nname = " + name;
}
}
运行结果:
age = 18
fV = 0
name = null
由结果可以看到,被修改和新增的属性值,均为属性的数据类型的默认值。可以理解为删除了faceValue属性,而新增了属性fV和name;反序列化无法获取这些新增属性的值,因为序列化时还没有它们,所以这些属性的值为默认值

关键字transient

使用关键字transient修饰的属性,不会被序列化
实例三:package serializable.s3;
import java.io.Serializable;
/**
* 用于创建被序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class Son implements Serializable {
public int age;
//将实例一Son中的faceValue属性加上关键字transient修饰
public transient int faceValue;
public Son(int age, int faceValue) {
this.age = age;
this.faceValue = faceValue;
}

@Override
public String toString() {
return "age = " + age + "\nfaceValue = " + faceValue;
}
}
运行结果:
age = 18
faceValue = 0
faceValue的值为0,并不是250;这就是因为使用关键字transient修饰的属性,不会被序列化


序列化父类

使Son类继承Father类
实例四:package serializable.s4;
import java.io.Serializable;
/**
* 用于创建被序列化对象的类,继承Father类
* @author 学霸联盟 - 赵灿
*/
public class Son extends Father implements Serializable {
public int age;
public int faceValue;

public Son(int age, int faceValue) {
this.age = age;
this.faceValue = faceValue;
//对从Father类继承的money属性赋值
this.money = 200;
}

@Override
public String toString() {
return "age = " + age + "\nfaceValue = " + faceValue + "\nmoney = "
+ money;
}
}

package serializable.s4;
public class Father{
public int money;
}
运行结果:
age = 18
faceValue = 250
money = 0
package serializable.s4;import java.io.Serializable;//增加Father类对Serializable接口的实现public class Father  implements Serializable {	public int money;}运行结果:age = 18faceValue = 250money = 200

引用类型的属性

Son类中增加一个Friend类型的属性
实例五:package serializable.s5;
import java.io.Serializable;
/**
* 用于创建被序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class Son implements Serializable {
public int age;
public int faceValue;
//增加Friend属性
public Friend friend;

public Son(int age, int faceValue) {
this.age = age;
this.faceValue = faceValue;
//创建Friend对象
friend = new Friend();
friend.name = "张三";
}

@Override
public String toString() {
return "age = " + age + "\nfaceValue = " + faceValue + "\nfriendName = " + friend.name;
}
}

package serializable.s5;
public class Friend{
public String name;
}
执行序列化操作时会出现NotSerializableException
package serializable.s5;import java.io.Serializable;//增加Friend类对Serializable接口的实现public class Friend  implements Serializable{	public String name;}运行结果:age = 18faceValue = 250friendName = 张三
String类,数组,枚举都实现了Serializable接口,所以可以直接使用


自定义序列化

将实例五的Son该为实现Externalizable接口
实例六:package serializable.s6;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* 用于创建被序列化对象的类
* @author 学霸联盟 - 赵灿
*/
public class Son implements Externalizable {
public int age;
public transient int faceValue;
public Friend friend;

//必须包含public的无参构造方法,否则会出现InvalidClassException
public Son() { }

public Son(int age, int faceValue) {
this.age = age;
this.faceValue = faceValue;
//创建Friend对象
friend = new Friend();
friend.name = "张三";
}

/**
* 重写Externalizable接口中定义的方法
* 向输出流中写属性值
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//向输出流中写入属性age和faceValue的值
out.writeInt(age);
out.writeInt(faceValue);
}

/**
* 重写Externalizable接口中定义的方法
* 读取输入流中的属性值,并赋值给相应的属性
* 这里要注意,读属性值的顺序要和写属性值的顺序一致
*/
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
//从输入流中读取age和faceValue的值,并赋值给相应的属性
age = in.readInt();
faceValue = in.readInt();
}

@Override
public String toString() {
return "age = " + age + "\nfaceValue = " + faceValue;
}
}
package serializable.s6;

public class Friend{
public String name;
}
输出结果:
age = 18
faceValue = 250