含义
对象序列化(Serialize)指将一个Java对象写入IO流中,反序列化(Deserialize)是从IO流中恢复该对象.
目标
目标是将对象保存在磁盘中,或允许在网络中传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而实现对象的保存或者传输.并且其他程序获取该二进制流之后,也可以将其反序列化为原来的对象.
需实现接口
Serializable
该接口没有任何方法,只是表明该类可以被序列化
public interface Serializable {
}
Externalizable
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void writeExternal(ObjectOutput out) throws IOException;
ClassNotFoundException;
}
序列化实例
实现序列化和反序列化
创建写入的对象,只有一个字段
class Student implements Serializable {
private String name;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建两个对象
Student studentWrite1 = new Student();
studentWrite1.setName("LiNing");
Student studentWrite2 = new Student();
studentWrite2.setName("zhaosi");
执行序列化
使用的是IO流中的ObjectOutputStream.
注意最后需要把输出流关闭.
这里序列化了两个对象
public static void serialize(Object obj,String file) {
System.out.println("执行序列化......");
ObjectOutputStream outputStream = null;
try {
//创建对象输出流
outputStream = new ObjectOutputStream(new FileOutputStream(file,true));
//将对象写入输出流
outputStream.writeObject(obj);
System.out.println("执行序列化完成......");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
try {
if(outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行反序列化
使用的是IO流中的ObjectInputStream.
注意最后需要把输入流关闭.
public static Object deserialize(String file) {
System.out.println("执行反序列化......");
ObjectInputStream inputStream = null ;
Object obj = null;
try {
FileInputStream fis = new FileInputStream(file);
inputStream = new ObjectInputStream(fis);
obj = inputStream.readObject();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
try {
if(inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
}
测试
public static void main(String[] args) {
//创建对象
Student studentWrite1 = new Student();
studentWrite1.setName("LiNing");
String file = "serialize.txt";
//序列化对象1
SerializeUtils.serialize(studentWrite1, file);
//读取对象
Student studentRead1 = (Student)SerializeUtils.deserialize(file);
System.out.println("学生1:" + studentRead1.getName());
}
输出
执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:xxxx
1.反序列化读取的仅仅是Java对象,而不是Java类.进行反序列化时,必须提提供该对象所属类的class文件,否则将会引发ClassNotFoundExcepyion.
2.如果向文件中使用序列化机制写入多个对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取.
父类序列化
当父类没有实现Serializable时,其能够被序列化么?
1.父类没有无参构造器,父类没有实现Serializable接口
创建一个父类
class Person{
Integer age;
public Person(Integer age) {
super();
this.age = age;
}
}
Student类继承Person类
class Student extends Person implements Serializable {
static final long serialVersionUID = 1L;
private String name;
public Student(Integer age,String name) {
super(age);
this.name = name;
System.out.println("执行构造器");
}
public String getName() {
return name;
}
public String toString() {
return "name = " + name
+ " age = " + age;
}
}
测试
public static void main(String[] args) {
String file = "serialize.txt";
//序列化对象1
SerializeUtils.serialize(new Student(18, "lining"), file);
//读取对象
Student studentRead1 = (Student)SerializeUtils.deserialize(file);
System.out.println("学生1:" + studentRead1.toString());
}
反序列化时将会报错:java.io.InvalidClassException: org.serialize1.Student; no valid constructor
2.父类没有无参构造器,父类实现Serializable接口
class Person implements Serializable{
Integer age;
public Person(Integer age) {
super();
this.age = age;
System.out.println("Person执行有参构造器");
}
}
输出
Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:name = lining age = 18
说明当父类实现Serializable接口,子类反序列化时就不会有问题
3.父类有无参构造器,父类没有实现Serializable接口
class Person {
Integer age;
public Person() {
System.out.println("Person执行无参构造器");
}
public Person(Integer age) {
super();
this.age = age;
System.out.println("Person执行有参构造器");
}
}
输出
Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
Person执行无参构造器
学生1:name = lining age = null
注意到执行反序列化时,调用了父类的无参构造器,并且父类的age属性为null.
总结:
1.父类没有无参构造器,父类没有实现Serializable接口
反序列化时将会报错:java.io.InvalidClassException: org.serialize1.Student; no valid constructor
2.父类有无参构造器,父类实现Serializable接口
父类实现Serializable接口,子类反序列化时就不会有问题
3.父类有无参构造器,父类没有实现Serializable接口
执行反序列化时,调用了父类的无参构造器,并且父类的属性为null
对象引用的序列化
创建一个新类City
class City{
Integer code;
public City(Integer code) {
super();
this.code = code;
}
}
Student 使用City作为其引用属性
class Student extends Person implements Serializable {
static final long serialVersionUID = 1L;
City city = new City(9527);
}
执行反序列化将会报异常:java.io.NotSerializableException: org.serialize1.City
让City实现Serializable,执行结果正常.
class City implements Serializable{
Integer code;
public City(Integer code) {
super();
this.code = code;
}
}
使用transient防止属性被序列化
给name属性加上transient关键字
transient private String name;
执行代码
可以看到name为null,说明其没有被序列化.
Person执行有参构造器
Student执行构造器
执行序列化......
执行序列化完成......
执行反序列化......
学生1:name = null age = 18 city code = 9527
类属性序列化
被stati修饰的类属性,在序列化时并没有被序列化.
1.继承接口Externalizable
2.重写readExternal和writeExternal
3.序列化和反序列化操作方式和上述一样
扩展接口Externalizable实现序列化
class Student1 implements Externalizable {
static final long serialVersionUID = 1L;
private String name;
private int age;
public Student1() {
super();
// TODO Auto-generated constructor stub
}
public Student1(Integer age,String name) {
this.name = name;
this.age = age;
System.out.println("Student执行构造器");
}
public String getName() {
return name;
}
public String toString() {
return "name = " + name
+ " age = " + age;
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String)in.readObject();
age = in.readInt();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
}
从以上可以看出,扩展接口Externalizable实现序列化非常的复杂,如果属性很多,那么写起来就会非常的麻烦.因此不建议使用该方式.
序列化UID
根据前面的描述,当执行反序列化时,环境中必须提供带对象所对应的class文件,否讲将会报异常.但是两个不同的环境,由于项目文件地更改(比如属性或者方法的修改),有可能会导致class文件不一样.
Java序列化提供了一个类型为private static final long的 serialVersionUID属性.该属性用于标识Java序列化的版本,也就是说一个类升级后,只要它的serialVersionUID没有更改,那么序列化机制会将其当作同一个序列化版本.
class Student1 implements Externalizable {
private static final long serialVersionUID = 1L;
}
不显示定义serialVersionUID的问题是,不利于程序在不同的JVM之间的移植,因为不同的JVM计算serialVersionUID并不一定是一样的.
总结
Externalizable和Serializable区别
实现Serializable接口 | 实现Externalizable接口 |
---|---|
系统自动存储必要信息 | 程序员决定存储哪些信息 |
Java内建支持,易于实现,只需实现接口即可,无需其他代码支持 | 仅仅提供两个空方法,实现该接口必须重写这两个方法 |
性能略差 | 性能略高 |
Externalizable编程复杂,不推荐使用.
序列化注意事项
1.对象的类名,属性(包括基本属性,数组,对其他对象的引用(也需要实现Serializable接口))都会被序列化;
2.方法,static属性,transient属性(瞬态属性)都不会被序列化.
3.反序列化必须有序列化对象的class文件.
4.当通过文件,网络来读取序列化对象时,读取的顺序必须和写入的顺序一致.
序列化ID
为了保证程序在不同JVM之间的正常移植或者防止类文件升级导致的反序列化失败,应当对需要被序列化的类添加serialVersionUID属性.
由于static属性,transient属性(瞬态属性)都不会被序列化,因此当对其进行修改时,不影响反序列化结果.类定义可以不更新serialVersionUID值.
非静态非瞬态属性修改:
旧类 | 新类 | 结果 |
---|---|---|
包含同名属性,但是类型不一样 | 反序列化失败,类定义应该更新serialVersionUID值 | |
新类比旧类的属性少 | 反序列化没有问题,旧类多出的属性被忽略,类定义可以不更新serialVersionUID值 | |
新类比旧类的属性多 | 反序列化没有问题,新类多出的属性被设置为null(引用类型)或0(基本类型),类定义可以不更新serialVersionUID值 |