Java 中的序列化 Serializable 和 Externalizable
Java 面试 序列化 About 11,131 wordsSerializable
被static
修饰的字段是不会被序列化的。
被transient
修饰符修饰的字段也是不会被序列化的。
public class Person implements Serializable {
private static final long serialVersionUID = -8483382254070647732L;
private String name;
private Integer age;
private transient String[] hobby;
// set() get() 省略
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hobby=" + Arrays.toString(hobby) +
'}';
}
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
// 调用默认的反序列化函数
objectInputStream.defaultReadObject();
// 手工检查反序列化后年龄
if (age < 0 || age > 150) {
throw new IllegalArgumentException("age 非法: " + age);
}
}
}
序列化到文件
public static void serialize() throws IOException {
Person person = new Person();
person.setName("Alice");
// person.setAge(180);
person.setAge(18);
String[] hobby = {"basketball"};
person.setHobby(hobby);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
objectOutputStream.writeObject(person);
objectOutputStream.close();
System.out.println("serialize complete!");
}
反序列化
public static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("person.txt")));
Person person = (Person) objectInputStream.readObject();
System.out.println(person);
}
NotSerializableException
如果Person
不实现Serializable
接口,则序列化时会抛出NotSerializableException
异常:
Exception in thread "main" java.io.NotSerializableException: Person
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at SerializableDemo.serialize(SerializableDemo.java:26)
at SerializableDemo.main(SerializableDemo.java:10)
ObjectOutputStream
的writeObject0()
方法判断是String
、Array
、Enum
或者实现了Serializable
接口的对象可以序列化,其余的将抛出NotSerializableException
异常。
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {
private final boolean enableOverride;
public ObjectInputStream(InputStream in) throws IOException {
// 省略代码
enableOverride = false;
}
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
// 省略代码
}
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
...
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
...
}
}
serialVersionUID
serialVersionUID
是序列化前后的唯一标识符,如果没有人为显式定义过serialVersionUID
,那编译器会为它自动声明一个。
修改了类的字段(未显示指定serialVersionUID
前提下)或修改了serialVersionUID
后,反序列化会抛出InvalidClassException
异常。
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 3625195237367014680, local class serialVersionUID = -8615747153399946143
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at SerializableDemo.deserialize(SerializableDemo.java:15)
at SerializableDemo.main(SerializableDemo.java:10)
readObject
反序列化时做一个校验等逻辑。反序列化时自动调用实体类中定义的readObject(ObjectInputStream ois)
方法。
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
// 调用默认的反序列化函数
objectInputStream.defaultReadObject();
// 手工检查反序列化后年龄
if (age < 0 || age > 150) {
throw new IllegalArgumentException("age 非法: " + age);
}
}
底层原理:ObjectStreamClass
调用类中的readObject
方法。
public class ObjectStreamClass implements Serializable {
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
...
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
...
}
}
可序列化的单例类有可能并不单例
public class Singleton implements Serializable {
private static final long serialVersionUID = -1576643344804979563L;
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
public static synchronized Singleton getSingleton() {
return SingletonHolder.singleton;
}
}
测试:返回false
,两个对象不相等。
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(
new FileOutputStream( new File("singleton.txt") )
);
// 将单例对象先序列化到文本文件singleton.txt中
objectOutputStream.writeObject( Singleton.getSingleton() );
objectOutputStream.close();
ObjectInputStream objectInputStream =
new ObjectInputStream(
new FileInputStream( new File("singleton.txt") )
);
// 将文本文件singleton.txt中的对象反序列化为singleton1
Singleton singleton1 = (Singleton) objectInputStream.readObject();
objectInputStream.close();
Singleton singleton2 = Singleton.getSingleton();
// 运行结果竟打印 false !
System.out.println( singleton1 == singleton2 );
}
解决方法:在单例类中手写readResolve()函数,直接返回单例对象,来规避。
private Object readResolve() {
return SingletonHolder.singleton;
}
Externalizable
可指定字段序列化与反序列化,必须实现writeExternal()
和readExternal()
方法,transient
不再起作用。
实体类
public class Animal implements Externalizable {
private static final long serialVersionUID = -4504729913144437628L;
// 复写了 writeExternal 和 readExternal 后,transient 不起作用
private transient String name;
private Integer age;
private String[] hobby;
// 省略 get set
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
", hobby=" + Arrays.toString(hobby) +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = (Integer) in.readObject();
}
}
测试类
public class ExternalizableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
serialize();
deserialize();
}
private static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("animal.txt")));
Animal animal = (Animal) objectInputStream.readObject();
System.out.println(animal);
}
private static void serialize() throws IOException {
Animal animal = new Animal();
animal.setName("小兔子");
String[] hobby = {"eat"};
animal.setHobby(hobby);
animal.setAge(3);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("animal.txt")));
objectOutputStream.writeObject(animal);
objectOutputStream.close();
System.out.println("serialize complete!");
}
}
no valid constructor
Externalizable
实现类必须提供无参构造函数,否则会抛出InvalidClassException
异常,并提示no valid constructor
。
相关底层代码:
public class ObjectStreamClass implements Serializable {
private Class<?> cl;
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
}
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
private static Constructor<?> getExternalizableConstructor(Class<?> cl) {
try {
Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
cons.setAccessible(true);
return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
cons : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
}
参考
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓