# 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)

修改于: 8/11/2022, 3:17:56 PM