参考
-
理解Java对象序列化
主要是参考了该文章的例子, 推荐阅读!
Serializable接口
如果一个类要支持序列化, 必须得实现Serializable
接口. 该接口仅仅是一个标记, 没有声明任何方法.
serialVersionUID
属性:
* The serialization runtime associates with each serializable class a version
* number, called a serialVersionUID, which is used during deserialization to
* verify that the sender and receiver of a serialized object have loaded
* classes for that object that are compatible with respect to serialization.
* If the receiver has loaded a class for the object that has a different
* serialVersionUID than that of the corresponding sender's class, then
* deserialization will result in an {@link InvalidClassException}. A
* serializable class can declare its own serialVersionUID explicitly by
* declaring a field named <code>"serialVersionUID"</code> that must be static,
* final, and of type <code>long</code>:
*
* <PRE>
* ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
* </PRE>
这个属性可以有任何的访问修饰符, 不过要是static final long
, 这是一个版本标记, 因为可能这个类可能存在新旧版本, 所以使用该标记来标明. 如果收发方的这个类版本不一致, 在反序列化 (deserialization
) 的时候就会抛出InvalidClassException
异常.
没有实现Serializable接口的例子
内部类NonSerializable
没有实现Serializable
接口, Item
类实现了该接口, 但是有一个NonSerializable
类的成员.
可以看到两个test函数都报错了, 提示serializable.detail.WithoutSerializable$NonSerializable
这个类无法序列化.
遇到这种情况可以用一些方法解决, 比如说在这个字段前面加上transient
关键字, 然后在类中实现writeObject
和readObject
方法来手动序列化这个对象, 后面会有例子.
package serializable.detail;
public class WithoutSerializable implements Serializable {
public static class NonSerializable{
}
public static class Item implements Serializable{
NonSerializable nonSerializable = new NonSerializable();
}
public static void testItem() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/tmp/test.obj"));
WithoutSerializable withoutSerializable = new WithoutSerializable();
Item item = new Item();
// Exception in thread "main" java.io.NotSerializableException: serializable.detail.WithoutSerializable$NonSerializable
objectOutputStream.writeObject(item);
objectOutputStream.close();
}
public static void testWithoutSerializable() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/tmp/test.obj"));
NonSerializable nonSerializable = new NonSerializable();
// Exception in thread "main" java.io.NotSerializableException: serializable.detail.WithoutSerializable$NonSerializable
objectOutputStream.writeObject(nonSerializable);
objectOutputStream.close();
}
public static void main(String[] args) throws IOException {
testWithoutSerializable();
// testItem();
}
}
结合transient, writeObject, readObject的例子
transient
:
如果一个字段被标注了transient
, 那么默认序列化的时候不会序列化该字段.
关于writeObject
以及readObject
的注释:
* Classes that require special handling during the serialization and
* deserialization process must implement special methods with these exact
* signatures:
*
* <PRE>
* private void writeObject(java.io.ObjectOutputStream out)
* throws IOException
* private void readObject(java.io.ObjectInputStream in)
* throws IOException, ClassNotFoundException;
* </PRE>
*
* <p>The writeObject method is responsible for writing the state of the
* object for its particular class so that the corresponding
* readObject method can restore it. The default mechanism for saving
* the Object's fields can be invoked by calling
* out.defaultWriteObject. The method does not need to concern
* itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* <p>The readObject method is responsible for reading from the stream and
* restoring the classes fields. It may call in.defaultReadObject to invoke
* the default mechanism for restoring the object's non-static and
* non-transient fields. The defaultReadObject method uses information in
* the stream to assign the fields of the object saved in the stream with the
* correspondingly named fields in the current object. This handles the case
* when the class has evolved to add new fields. The method does not need to
* concern itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
writeObject
方法用于控制序列化的具体过程, readObject
用于控制反序列化的具体过程.
注意反序列化的时候, 该类的构造函数是没有被调用的.
用于序列化的类:
注意password
和readFromFile
字段是被标记了transient
的.
package serializable.detail;
import java.io.IOException;
import java.io.Serializable;
public class People implements Serializable {
// 用于辨别这个类的版本, 因为可能会有新旧版本
public static final long serialVersionUID = 0L;
private String name;
private int age;
// 使用transient标记, 说明该字段不会被序列化
private transient String password;
private transient boolean readFromFile;
// 无参构造器
public People(){
System.out.println("People()");
}
public People(String name, int age){
System.out.println("People(String name, int age)");
this.name = name;
this.age = age;
}
// 不是接口方法, 序列化过程会通过反射来调用
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
// 先调用defaultWriteObject()方法来序列化其他字段
out.defaultWriteObject();
out.writeBoolean(true);
}
// 不是接口方法, 序列化过程会通过反射来调用
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 先调用`defaultReadObject`来反序列化其他字段
in.defaultReadObject();
readFromFile = in.readBoolean();
}
@Override
public String toString() {
return String.format("[%s, %d, %s, read from file: %s]", name, age, password, readFromFile);
}
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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
测试代码:
public static final String filename = "/tmp/test.obj";
public static void testPeopleSerialize() throws IOException, ClassNotFoundException {
People people = new People("xiaofu", 22);
people.setPassword("12345");
System.out.println("before serializing: ");
System.out.println(people);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filename));
objectOutputStream.writeObject(people);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
// 可能会抛出ClassNotFoundException, 说明需要在classpath中有这个class
People readPeople = (People) objectInputStream.readObject();
System.out.println("after serializing:");
System.out.println(readPeople);
objectInputStream.close();
}
输出:
People(String name, int age)
before serializing:
[xiaofu, 22, 12345, read from file: false]
after serializing:
[xiaofu, 22, null, read from file: true]
可以看到password
并没有被序列化, 而readFromFile
是由writeObject
方法序列化的.
Externalizable接口
Externalizable
接口是继承于Serializable
接口的. 它定义了两个方法分别用于控制序列化
和反序列化
过程.
- void writeExternal(ObjectOutput out) throws IOException;
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
反序列化的时候, 会调用类的无参构造函数.
实例:
package serializable.detail;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
// Externalizable 继承了 Serializable
// 同时如果一个类同时implements了Externalizable和Serializable
// 那么Externalizable会被实际使用
// 而且Externalizable在反序列化过程中会调用默认构造函数来生成对象
// 而Serializable不会
public class ExternalSerialize implements Externalizable {
private int id;
private String name;
public ExternalSerialize(){
System.out.println("in default constructor");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readInt();
name = (String) in.readObject();
}
@Override
public String toString() {
return String.format("[%d, %s]", id, name);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试:
public static void testExternalSerialize() throws IOException, ClassNotFoundException {
ExternalSerialize externalSerialize = new ExternalSerialize();
externalSerialize.setId(1);
externalSerialize.setName("test");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filename));
objectOutputStream.writeObject(externalSerialize);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
// 可能会抛出ClassNotFoundException, 说明需要在classpath中有这个class
ExternalSerialize externalSerialize1 = (ExternalSerialize) objectInputStream.readObject();
objectInputStream.close();
System.out.println(externalSerialize1);
}
输出:
in default constructor
in default constructor
[1, test]
单例的序列化
如果一个类是按照单例来实现的, 那么反序列化后会得到该类的第二个实例, 这可能不是我们想要的结果, 这个时候可以借助readResolve
方法来解决.
实例:
package serializable.detail;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
// 这里提到一个不需要同步的单例创建方法
// https://stackoverflow.com/questions/16106260/thread-safe-singleton-class
public class Singleton implements Serializable{
public static final long serialVersionUID = 1L;
private static Singleton INSTANCE;
private Singleton(){
}
public static Singleton getInstance(){
if (INSTANCE != null)
return INSTANCE;
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
System.out.println("in readObject");
// in.defaultReadObject();
}
// 会替换掉从流中生成的对象
Object readResolve() throws ObjectStreamException {
System.out.println("in readResolve");
return Singleton.getInstance();
}
}
测试:
public static void testSingleton() throws IOException, ClassNotFoundException {
Singleton singleton = Singleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filename));
objectOutputStream.writeObject(singleton);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
// 可能会抛出ClassNotFoundException, 说明需要在classpath中有这个class
Singleton singleton1 = (Singleton) objectInputStream.readObject();
objectInputStream.close();
System.out.println(singleton == singleton1);
}
输出:
in readObject
in readResolve
true
可以看出反序列化的时候依然调用了readObject
方法, 但是最终返回的对象是readResolve
方法中返回的对象.