一、序列化与反序列化的概念以及使用场景
1、概念
a)序列化:将对象转换成字节序列的过程;
b)反序列化:将字节序列恢复成对象的过程。
2、使用场景
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
二、结合例子说明
进行序列化的对象,需要实现Serializable接口,否则将无法序列化。序列化采用ObjectOutputStream的writeObject方法实现;反序列化则使用ObjectInputStream的readObject将字节序列还原成对象。
首先创建一个User类,只有简单的几个属性,如下:
import java.io.Serializable; public class User implements Serializable { private String name; private int age; private String address; 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 getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { //重写了toString,方便打印观察 return "age:"+age+",name:"+name+",address:"+address; } }
User对象已经实现了序列化接口。接着进行测试,如下:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Test { /** * 序列化 * @param filePath * 序列化要写入的文件路径 * @throws Exception */ public static void writeObject(String filePath)throws Exception{ User u = new User(); u.setAddress("福建省厦门市"); u.setAge(18); u.setName("漫天的沙"); ObjectOutputStream oos = null; try{ oos = new ObjectOutputStream(new FileOutputStream(filePath)); oos.writeObject(u); oos.flush(); }finally{ if(oos != null){ oos.close(); } } } /** * 反序列化 * @param filePath * 序列化的文件 * @throws Exception */ public static void readObject(String filePath) throws Exception{ ObjectInputStream ois = null; try{ ois = new ObjectInputStream(new FileInputStream(filePath)); User u = (User)ois.readObject(); System.out.println(u); }finally{ if(ois != null){ ois.close(); } } } public static void main(String[] args) throws Exception{ String filePath = "f:/obj.out"; writeObject(filePath); readObject(filePath); } }
执行后打印如下:
证明反序列化成功。
三、扩展
1、serialVersionUID
实现了Serializable接口的对象,如果没有显性的设置serialVersionUID,系统会自动根据方法/属性等计算序列化ID,如果显性进行了设置,则直接使用该值。serialVersionUID有什么用呢?我们可以试着对User对象进行显性设置serialVersionUID=1L,然后再将之前序列化的文件进行反序列化,如下:
/** 为用户对象显性设置序列化id */ private static final long serialVersionUID = 1L;
重新运行程序,报错如下:
Exception in thread "main" java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -5216935088914625952, local class serialVersionUID = 1 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351) at Test.readObject(Test.java:42) at Test.main(Test.java:54)
意思即序列化文件的序列化ID与要反序列化成的对象序列化ID不兼容,因此反序列化失败。所以,如果序列化对象后续可能会增减字段或者修改,为了保证兼容,需要显性的设置序列化ID,避免修改之后原先的序列化不能成功的反序列化产生问题。
2、transient字段使用
如果字段不需要进行序列化,可以在修饰符后添加transient的声明,这样在序列化的时候属性将不参与序列化,例如上面的User对象name不参与序列化,则声明为
/** transient在对象初始化时可以让该字段不参与序列化 */ private transient String name;
重新运行程序,如下:
age:18,name:null,address:福建省厦门市
3、静态变量序列化
正常情况下,静态变量也可以正常的序列化。但是如果中间静态变量进行调整呢,序列化的对象中该静态变量是否会产生变化?为了验证效果,在上面的User新增一个静态变量,同时为了方便观察,重写toString方法,如下:
/**User对象新增的静态变量*/ public static int a = 35555; @Override public String toString() { //重写了toString,方便打印观察 return "age:"+age+",name:"+name+",address:"+address+",a:"+a; }
对main方法调整了,如下:
public static void main(String[] args) throws Exception{ String filePath = "f:/obj.out"; writeObject(filePath); //修改User静态变量a的值 User.a = 10; readObject(filePath); }
重新运行,结果如下:
age:18,name:null,address:福建省厦门市,a:10
说明静态变量的值变了。分析原因,因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
下一篇:java序列化与反序列化进阶(一)