# Java的序列化
- 可序列化类的所有子类都是可序列化的。
- 进行序列化的类必须有无参构造方法。
- 如果一个无法序列化的类却需要序列化,可以使用其子类来序列化。
# 什么时候使用Java序列化?
# 使用ObjectOutputSteram和ObjectInputStream存储对象时
- 可以作为流(存储为文本也行)在网络上传输,或者利用其反序列化进行深拷贝。
- 如果对象未实现Serializable接口,则会抛java.io.NotSerializableException异常
# 入门案例
从下面案例中可知,Bee1的构造方法在反序列化时并未执行
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName = "C:\\Users\\jesse\\Desktop\\bee.object";
Bee1 b1 = new Bee1();
ObjectOutputStream oos
= new ObjectOutputStream(new FileOutputStream(fileName));
oos.writeObject(b1);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
Bee1 nB1 = (Bee1) ois.readObject();
System.out.println(nB1.getName());
}
# 反序列化时如果类路径下没有class文件
会抛java.lang.ClassNotFoundException异常
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName = "C:\\Users\\jesse\\Desktop\\bee.object";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
// 此处会抛ClassNotFoundException异常
ois.readObject();
}
# 深拷贝实现
将对象写入字节流,再从字节流中读出来即可。(Java提供的clone方法只是浅拷贝)
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 将对象写入到字节流中
Bee1 b1 = new Bee1();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(b1);
// 将对象从字节流中读出来
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Bee1 nb1 = (Bee1) ois.readObject();
// 两个对象不是同一个对象了,打印出来的对象地址不一样了
System.out.println("b1="+b1);
System.out.println("nb1="+nb1);
}
# RMI远程调用时
- Java的RMI依赖序列化和反序列化,容易有安全漏洞,必须是内网可信任的机器作为调用双方。
- 客户端只有接口,没有实现类,具体方法在服务端实现。
// 创建RMI接口
public interface NumberGenerator extends Remote {
String generator() throws RemoteException;
}
// 创建RMI接口的实现类
public class NumberGeneratorService implements NumberGenerator {
@Override
public String generator() throws RemoteException {
System.out.println("我执行了一次");
return String.valueOf(Math.random());
}
}
// 服务端代码,将提供的服务注册到端口上
public class Server {
public static void main(String[] args) throws RemoteException {
NumberGenerator numberGenerator = new NumberGeneratorService();
Remote skeleton = UnicastRemoteObject.exportObject(numberGenerator, 0);
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("NumberGenerator", skeleton);
}
}
// 客户端代码,连接到服务端提供的IP端口上,找到指定的服务接口,执行调用即可
public class Client {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
NumberGenerator numberGenerator = (NumberGenerator) registry.lookup("NumberGenerator");
System.out.println(numberGenerator.generator());
}
}
# serialVersionUID的作用
序列化的类默认会生成一个serialVersionUID,通常由我们自己指定,所以经常可见代码第一行有如下:
private static final long serialVersionUID = 1L;
其用于反序列化时判断版本是否一致,不一致则抛出异常。如果不显式指定编译器会自动生成一个值(根据class内容),因此在类被修改后,隐式值会改变,此时反序列化会抛出InvaliClassException。 显式指定的意义在于:大多情况类的前后版本都是需要兼容的,显式指定值让其保持不变,即确认了类是前后兼容的。
# ObjectStreamField
# 参考
Serializable接口的作用性质 (opens new window) Java序列化接口Serializable接口的作用总结 (opens new window) 谈谈实现Serializable接口的作用和必要性 (opens new window)