[Java] 序列化之基本认识
一、简介
Java 序列化即将 Java 对象转换为二进制序列的过程,主要用于网络通信、持久化存储。
二、实现方式
Serializable
Java 中实现 Serializable
接口即可实现序列化。
Externalizable
Serializable
的子类,实现类需实现 writeExternal(), readExternal()
方法,和实现 writeObject(), readObject()
类似,可自行修改序列化或反序列化后的对象的信息:
三、主要特性
serialVersionUID
两个类的序列化ID不同,则无法相互序列化与反序列化。
要点:
- 可显示指定
serialVersionUID
: 指定固定值或随机生成 - 未指定时,会默认生成一个
serialVersionUID
: 根据类信息自动生成
序列化ID不一致
SuperClass
类:
import java.io.Serializable;
/** * @author wengliemiao */
public class SuperClass implements Serializable {
private static final long serialVersionUID = 1L;
private String name = "";
private String age = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
SerialVersionTest
测试类:
package com.wlm.jdk.serialVersion;
import java.io.*;
/** 1. @author wengliemiao */
public class SerialVersionTest {
public static void main(String[] args) {
SuperClass superClass = new SuperClass();
superClass.setName("2333");
try {
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test7"));
oo.writeObject(superClass);
oo.close();
} catch (IOException e) {
e.printStackTrace();
}
/* try { ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test7")); SuperClass superClass1 = (SuperClass) oi.readObject(); System.out.println(superClass1.getName()); oi.close(); } catch (Exception e) { e.printStackTrace(); } */
}
}
操作步骤:
SuperClass
类的serialVersionUID
为1L时,执行writeObject()
;- 将
SuperClass
类的serialVersionUID
改为2L,执行readObject()
;
结果为:
未指定序列化ID
类未执行序列化ID时,会自动生成一个,后续如果修改了类信息,则会导致序列化ID不一致,从而导致反序列化失败。
执行步骤:
SuperClass
类信息除不指定serialVersionUID
外,其他信息和前面一致,执行writeObject()
;- 为
SuperClass
类随意增加一个字段,执行readObject()
;
结果为:
静态变量序列化
静态变量无法被序列化,因为序列化保存的是对象的状态,而静态变量是类的状态。
验证步骤:
SuperClass
类增加静态变量b = 10;- 执行序列化操作,并设置 b = 20;
- 执行反序列化操作,并输出 b 的值;
测试 main
方法为:
public static void main(String[] args) {
SuperClass superClass = new SuperClass();
try {
System.out.println("序列化前: " + superClass.b);
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test9"));
oo.writeObject(superClass);
oo.close();
} catch (IOException e) {
e.printStackTrace();
}
superClass.b = 20;
try {
ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test9"));
SuperClass superClass1 = (SuperClass) oi.readObject();
System.out.println("反序列化后: " + superClass1.b);
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
}
结果为:
父类序列化
如果子类实现 Serializable
接口,而父类未实现,则序列化与反序列化时,父类数据不会参与。验证如下:SuperClass
类:
public class SuperClass {
private String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
SubClass
类:
package com.wlm.jdk.serialVersion;
import java.io.Serializable;
/** * @author wengliemiao */
public class SubClass extends SuperClass implements Serializable {
private static final long serialVersionUID = -1681989450035759750L;
private String address = "";
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
测试类如下:
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.setName("wlm");
subClass.setAddress("火车东站");
try {
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test11"));
oo.writeObject(subClass);
oo.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test11"));
SubClass subClass1= (SubClass) oi.readObject();
System.out.println("姓名: " + subClass1.getName());
System.out.println("地址: " + subClass1.getAddress());
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
}
此时输出数据为:
而为父类实现 Serializable
接口后,输出为:
transient 关键字
transient
关键字的作用则是阻止变量的序列化,被反序列化后,transient
变量的值被设为初始值。验证如下, Subclass
类中增加 transient
变量:
private transient String sex;
其他代码与前面一致,输出为:
writeObject(), readObject()
可通过实现 writeObject(), readObject()
方法来进行自定义的序列化和反序列化。如果未实现,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
如 JDK 中 ArrayList
类,elementData[]
字段是 transient
类型,不会参与序列化:
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */
transient Object[] elementData; // non-private to simplify nested class access
ArrayList
通过实现 writeObject(), readObject()
方法,序列化和反序列化 elementData[]
中的数据:
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
参考资料:
Java 序列化的高级认识
深入理解 JAVA 反序列化漏洞
序列化和反序列化
还没有评论,来说两句吧...