序列化与反序列化
JDK版本为1.8
序列化
序列化:把一个Java对象变成二进制内容,本质上就是一个byte[]
数组。
反序列化:把一个二进制内容(也就是byte[]
数组)变回Java对象。有了反序列化,保存到文件中的byte[]
数组又可以“变回”Java对象,或者从网络上读取byte[]
并把它“变回”Java对象。
一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable
接口,它的定义如下:
public interface Serializable {
}
Serializable
接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。
序列化Java对象
新建一个User
类
public class User implements Serializable {
private Integer id;
private String username;
private String password;
// 省略get、set方法
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
新建测试类
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
serialize();
deserialize();
}
// 反序列化
public static void deserialize( ) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream( new FileInputStream( new File("user.txt")));
User user = (User) objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列化结果为:");
System.out.println(user.toString());
}
// 序列化
public static void serialize() throws IOException {
User user = new User();
user.setId(1);
user.setUsername("lzc");
user.setPassword("123456");
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("user.txt") ) );
objectOutputStream.writeObject(user);
objectOutputStream.close();
System.out.println("序列化成功!");
}
}
Serializable接口作用
如果需要序列化的Java对象没有实现Serializable
接口,在序列化对象的时候会抛出java.io.NotSerializableException
异常。查看源码ObjectOutputStream.writeObject(Object obj)
方法
// java.io.ObjectOutputStream
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
// 会运行到这里,主要看这个方法
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// 省略......
// 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) { // Serializable类型
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
从上面的方法可以看出,只有字符串类型、数组类型、枚举类型、Serializable类型的对象才能够进行序列化操作,否则会抛出NotSerializableException
异常
serialVersionUID
一般实现了Serializable
接口的对象都会定义一个serialVersionUID
字段,该字段可以用编辑器自动生成。
private static final long serialVersionUID = -8838781226739307504L;
serialVersionUID
适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException
。
如果不指定serialVersionUID
字段,编译器会为它自动声明一个。
假设User
类中没有定义serialVersionUID
字段,创建一个User
对象保存到user.txt
文件中。然后在User
类中新增一个字段,此时去读取user.txt
文件进行反序列化,这个时候就会反序列化失败,报InvalidClassException
异常。
如果在User
类中定义了serialVersionUID
字段,并且保证序列化与反序列化时对象的serialVersionUID
一致,反序列化操作才能成功。
特殊字段
- 除了
serialVersionUID
字段,凡是被static
修饰的字段都不会被序列化。序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。 - 被
transient
关键字修饰的字段也不会被序列化,如下所示。
private transient String password;
反序列化破坏单例模式
新建UserSingleton
类
public class UserSingleton implements Serializable {
private static final long serialVersionUID = -1123030495398145165L;
private UserSingleton() {
}
// 使用静态内部类实现线程安全的单例模式
private static class SingletonHolder {
private static final UserSingleton singleton = new UserSingleton();
}
public static UserSingleton getSingleton() {
return SingletonHolder.singleton;
}
}
新建测试类
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("UserSingleton.txt") ) );
objectOutputStream.writeObject(UserSingleton.getSingleton());
objectOutputStream.close();
System.out.println("序列化成功!");
ObjectInputStream objectInputStream1 =
new ObjectInputStream( new FileInputStream( new File("UserSingleton.txt")));
UserSingleton userSingleton1 = (UserSingleton) objectInputStream1.readObject();
objectInputStream1.close();
UserSingleton userSingleton2 = UserSingleton.getSingleton();
System.out.println(userSingleton1 == userSingleton2);
}
}
通过打印结果可以发现,反序列化的对象与原对象并不相等
解决办法:在单例类中编写readResolve()
方法
public class UserSingleton implements Serializable {
private static final long serialVersionUID = -1123030495398145165L;
private UserSingleton() {
}
// 使用静态内部类实现线程安全的单例模式
private static class SingletonHolder {
private static final UserSingleton singleton = new UserSingleton();
}
public static UserSingleton getSingleton() {
return SingletonHolder.singleton;
}
// 当反序列化从流中读取对象时,readResolve()会被调用,用其中返回的对象替代反序列化新建的对象
private Object readResolve() {
return SingletonHolder.singleton;
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接