Java序列化总结
序列化就是指将Java对象转换成一系列的字节,反序列化即使将一系列的字节恢复成Java对象。序列话的主要用途在于:
- 持久化对象 - 可以将对象输出的磁盘等介质上
- 跨进程调用 - 不同进程无法相互访问对方的地址空间,可以通过序列化+IPC在不同进程间共享对象
- 通过网络传输对象 - 将对象序列化后通过网络传输,以实现客户端和服务器之间共享对象
Serializable接口的使用
Java提供了Serialzable接口用于序列化,Serilizable接口是一个标记接口,实现这个对象的类无需事先任何方法。
ObjectOutputStream & ObjectIntputStream
ObjectOutputStream 和 ObjectIntputStream 分别用于将对象作为字节流输出以及将字节流转换成对象。下面这例子有两个java程序,一个用于写每一个用于读:
- 测试类
package com.minghui.test;
import java.io.Serializable;
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
}
读程序和写程序分别都需要包办这个类。
- 写程序
public class SerilizationWriteTest {
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
TestClass.sStaticField = "New Static Value";
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream("D:/result"));
output.writeObject(myTest);
output.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
}
}
}
}
}
- 读程序
public class SerilazationReadTest {
public static void main(String[] args) {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(new FileInputStream("D:/result"));
TestClass test = (TestClass) input.readObject();
System.out.println("SerilazationREAD name:" + test.mName);
System.out.println("SerilazationREAD sStaticField:" + TestClass.sStaticField);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
读程序输出结果为:
SerilazationREAD name:New Name
SerilazationREAD sStaticField:static field
需要注意的一点是,静态变量是不参与序列化的,在上面的例子中写程序对静态变量的复制并不会影响读程序中静态变量的值。
类的成员的序列化
如果类中的某些成员无法被序列化会出现什么情况呢?这里为TestClass添加一个未继承Serilizable的成员:
OtherClass mOtherField = new OtherClass();
public class OtherClass {
public int x = 0;
public int y = 0;
OtherClass() {
x = 2;
y = 2;
}
}
修改写程序:
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
myTest.mOtherField.x = 1;
myTest.mOtherField.y = 1;
TestClass.sStaticField = "New Static Value";
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream("D:/result"));
output.writeObject(myTest);
output.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
}
}
}
}
输出结果如下:
java.io.NotSerializableException: com.minghui.test.OtherClass
serialVersionUID
在上面的例子中,修改读程序TestClass中的serialVersionUID:
public static final long serialVersionUID = 2L;
结果如下:
java.io.InvalidClassException: com.minghui.test.TestClass; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.minghui.test.SerilazationReadTest.main(SerilazationReadTest.java:15)
在反序列化的过程中,虚拟机会对比恢复字节流中的serialVersionUID和本地类中的serialVersionUID,仅当相同时才认为两者是相同的类。
子类的序列化
如果一个类A继承自类B,A实现了Serializable接口,B未实现。那么子类中的成员可以被序列化,父类中的成员无法被序列化,反序列化后父类成员的初始值规则如下:
- 如果父类有默认构造函数,则初始值为默认构造函数中的赋值
- 如果父类没有默认构造函数,则初始值为默认值
修改TestClass使其继承与TestParent:
class TestClass extends TestParent implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
}
public class TestParent {
public String mParentField = "Uinitialized";
}
修改写程序,增加为mParentField赋值的语句:
public class SerilizationWriteTest {
public static void main(String[] args) {
TestClass myTest = new TestClass();
myTest.mName = "New Name";
myTest.mParentField = "Runtime Changed";
TestClass.sStaticField = "New Static Value";
//... 同之前的例子
}
修改都程序的中的log打印:
public class SerilazationReadTest {
public static void main(String[] args) {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(new FileInputStream("D:/result"));
TestClass test = (TestClass) input.readObject();
System.out.println("SerilazationREAD name:" + test.mName);
System.out.println("SerilazationREAD parent field:" + test.mParentField);
System.out.println("SerilazationREAD sStaticField:" + TestClass.sStaticField);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果如下:
SerilazationREAD name:New Name
SerilazationREAD parent field:Uinitialized
SerilazationREAD sStaticField:static field
为TestParent增加默认构造函数,输出如下:
SerilazationREAD name:New Name
SerilazationREAD parent field:Constructor Initialized
SerilazationREAD sStaticField:static field
序列化过程的控制
序列化允许将对象变成字节流传输,有时为了安全等方面的考虑,并不需要对所有字段进行序列化,有时需要在序列化的过程中对部分字段做特殊处理(例如加密/解密),JDK提供了一系列方法用于控制序列化的过程。
transient关键字
当成员修饰符为为transient时,JDK不会序列化该成员,TestClass中的mName增加transient修饰符
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public transient String mName = "TestClass";
public TestClass() {
mName = "constructor init";
}
}
输出如下:
SerilazationREAD name:null
SerilazationREAD sStaticField:static field
结合前面一节,可以总结出,避免成员变量被序列化的方法有两种:
- 将不希望被序列化的变量放到父类
- 为不希望被序列化的变量添加transient修饰符
WriteObject & ReadObject
实现了Serilaizable接口的类,可以通过添加WriteObject和ReadObject方法来控制序列化的过程,例子如下:
class TestClass implements Serializable {
public static final long serialVersionUID = 1L;
public static String sStaticField = "static field";
public String mName = "TestClass";
private void writeObject(ObjectOutputStream out) {
try {
PutField pt = out.putFields();
System.out.println("Before encypt name :" + mName);
String name = encyptString(mName);
System.out.println("after encypt name :" + name);
pt.put("Name", name);
out.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
GetField gt = in.readFields();
String name = (String) gt.get("mName", "");
System.out.println("Before decypt name :" + name);
mName = decyptString(name);
System.out.println("after decypt name :" + mName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static String encyptString(String original) {
StringBuilder sb = new StringBuilder();
int len = original.length();
for (int i = 0; i < len; i++) {
char c = (char) (original.charAt(i) + 1);
sb.append(c);
}
return sb.toString();
}
private static String decyptString(String original) {
StringBuilder sb = new StringBuilder();
int len = original.length();
for (int i = 0; i < len; i++) {
char c = (char) (original.charAt(i) - 1);
sb.append(c);
}
return sb.toString();
}
}
写程序的输出如下:
Before encypt name :New Name
After encypt name :Ofx!Obnf
读程序的输出如下:
Before decypt name :Ofx!Obnf
after decypt name :New Name
SerilazationREAD name:New Name
SerilazationREAD sStaticField:static field
需要注意的一点是,GetField的get方法以及PutField的put方法,第一个参数都是类中成员变量的名称。
如果将mName添加transient,结果是什么?
Exception in thread "main" java.lang.IllegalArgumentException: no such field mName with type class java.lang.Object
总结
- 实现Serilzable的类可以被序列化/反序列化
- 实现Serilzable的类的成员必须都也实现了Serilzable接口
- 如果父类未实现Serilzable接口,子类实现了Serilzable接口,反序列化时,父类成员的值为默认构造函数中的赋值,或默认值
- transient修饰的关键字不能被序列化
- 可以通过为类添加WriteObject & ReadObject控制序列化/反序列化过程
- 使用WriteObject & ReadObject序列化/反序列化时无法控制transient修饰的成员
- 使用WriteObject & ReadObject序列化/反序列化时,可以绕过条目2种的限制