JAVA序列化与反序列化
基本概念
java序列化是一种常用的技术,与之对应的是反序列化。java的序列化是指将java对象转化成数据流,并可将之进行持久化到磁盘的技术,通俗的说这是抽象到具体化的一个过程。而反序列化则是相反的过程。那这有什么用呢,你只要相信,存在即合理。根据序列化的概念,我们可以总结序列化的使用场景。
- 需要将对象进行持久化保存时,可将对象序列化进行保存,通常是保存到文件中;
- 在网络传输时,需要将对象进行传输,可将对象转化为数据流进行传输。
这里只列出两种场景,可以使用的场景更多。
使用序列化技术需要实现(implements)Serializable或者Externalizable接口,这两者的区别是,前者会自动序列化所有对象(所谓的自动,其实是自动调用了defaultWriteObject和defaultReadObject方法),而后者需要重写writeExternal方法进行序列化和重写readExternal方法进行反序列化。否则不会自动进行序列化。而后者是前者的扩展,有了Externalizable接口,我们可以自己决定哪些对象可以被序列化,哪些不想被序列化,由自己决定。ps:transient修饰符也可以决定对象序列化,后面讲。
一、实现Serializable接口
1、People 实体类
package lrf.serial.serializable;
import java.io.Serializable;
/**
* 实体类实现了Serializable接口,意味着对象
* 会自动进行序列化,不需要实现任何方法
*/
public class People implements Serializable{
private static final long serialVersionUID = 1969583891010536170L;
private String name;
private int age;
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
2、对序列化的类进行测试
package lrf.serial.serializable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableTest {
public static void main(String[] args) {
try {
// 设置属性值
People people = new People();
people.setName("lrf");
people.setAge(21);
people.setPhone("100001");
// 序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:\\Serializable.txt"));
outputStream.writeObject(people);
System.out.println("序列化成功!!!");
// 反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:\\Serializable.txt"));
People people1 = (People) inputStream.readObject();
System.out.println("name:"+people1.getName());
System.out.println("age:"+people1.getAge());
System.out.println("phone:"+people1.getPhone());
System.out.println("反序列化成功!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面是序列化的结果和反序列化的结果
(1)序列化
虽然是乱码,但这是对象进行序列化的结果,即达到了持久化的目的。
(2)反序列化
序列化成功!!!
name:lrf
age:21
phone:100001
反序列化成功!!!
Process finished with exit code 0
从结果可以看出,反序列化完美的还原了原来的对象,将对象属性值打印出来了。
二、实现Externalizable接口
1、还是实体类People,代码如下
package lrf.serial.externalizable;
import java.io.*;
/**
* 实体类实现了Externalizable接口,需要重写
* writeExternal和readExternal方法,实现自定义
* 的序列化
*/
public class People implements Externalizable{
private static final long serialVersionUID = 1969583891010536170L;
private String name;
private int age;
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
/**
* 这里必须实现这两个方法,否则所有的属性均不能被序列化
* @param out
* @throws IOException
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(age);
// 我们测试不序列化phone属性
// out.writeObject(phone);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject();
this.age = (int) in.readObject();
// this.phone = (String) in.readObject();
}
}
2、对Externalizable序列化的类进行测试
package lrf.serial.externalzable;
import lrf.serial.externalizable.People;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ExternalizableTest {
public static void main(String[] args) {
try {
// 设置属性值
People people = new People();
people.setName("lrf");
people.setAge(21);
people.setPhone("100001");
// 序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:\\Externalizable.txt"));
outputStream.writeObject(people);
System.out.println("序列化成功!!!");
// 反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:\\Externalizable.txt"));
People people1 = (People) inputStream.readObject();
System.out.println("name:"+people1.getName());
System.out.println("age:"+people1.getAge());
System.out.println("phone:"+people1.getPhone());
System.out.println("反序列化成功!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
序列化的结果也是写到文件中了,这里就不展示了,下面展示反序列化的结果:
序列化成功!!!
name:lrf
age:21
phone:null
反序列化成功!!!
Process finished with exit code 0
从结果看出,没有被序列化的phone属性并没有被序列化到文件中,所以反序列化出来过后,是null值。这里可以引申出,写登录账号的代码时,为了安全,可将密码属性设置为不可序列化。
其实,序列化和反序列化可以告一段落了,但是事情还可以更完美一些。
1、transient修饰符
它可以在类实现Serializable情况下,也可实现对象属性不被序列化的功能,但是在实现Externalizable接口的情况下,可以加该修饰符,不会报错,但是不起作用。下面是transient使用的例子。
public class People implements Serializable{
private static final long serialVersionUID = 1969583891010536170L;
// 虽然实现Serializable接口,但是name属性已被transient修饰,是不会被序列化的
transient private String name;
private int age;
private String phone;
2、serialVersionUID ***的生成
serialVersionUID 是序列化类的唯一标识,在不手动加的情况下,编译器会给自动给该类配置一个ID,反序列化时,就能找到该ID对应的类,但是如果在序列化过后,如果改动了该类,则会报错,如下:
java.io.InvalidClassException: lrf.serial.externalizable.People; local class incompatible: stream classdesc serialVersionUID = 1692860238773028575, local class serialVersionUID = -3409095892910349901
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at lrf.serial.serializable.SerializableTest.main(SerializableTest.java:27)
Process finished with exit code 0
为了安全起见,我们应该手动加上一个系列号或者让工具生成一下,这样,就算在序列化过后对类进行了修改,也能进行反序列化,不会出错。我用的是idea2017工具,可以设置工具,如下:
File->Settings,找到Inspections,勾选Serialization issues
然后在实现Serializable或者Externalization接口的类,选中该类,按Alt+Enter键,选中add “serialVersionUID” field 即可自动添加***,如下。