序列化和反序列化
序列化:将对象当前状态转换为字节序列,并通过输出流存储或传输的过程作用:将对象转换成输出流,永久保存或传输
条件:被序列化的对象的类,实现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自动生成,如下图
当设定了版本号,在序列化之后,只要不修改类名和版本号,对类的其他修改,不会使反序列化出现InvalidClassException。
实例二:package serializable.s2;由结果可以看到,被修改和新增的属性值,均为属性的数据类型的默认值。可以理解为删除了faceValue属性,而新增了属性fV和name;反序列化无法获取这些新增属性的值,因为序列化时还没有它们,所以这些属性的值为默认值
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
关键字transient
使用关键字transient修饰的属性,不会被序列化实例三:package serializable.s3;faceValue的值为0,并不是250;这就是因为使用关键字transient修饰的属性,不会被序列化
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
序列化父类
使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