正如 fvu 所述,序列化是将类的实例转换为字节数组的过程,反之亦然。并非 Java 中的每个类都可以转换为字节数组(想想 Thread 类,将线程转换为字节数组是没有意义的),这就是您(和我们)需要在那些可以被转换的对象中实现Serializable。
Serializable 只是一个标记接口,它不需要实现任何方法。在大多数情况下,这已经足够了:只需让您的可序列化对象实现接口,然后使用默认行为。
默认的序列化行为在ObjectInputStream 和ObjectOutputStream 类中实现。 FileInputStream 和 FileOutputStream 是不同的类,用于从磁盘上的文件中读取和写入数据。
如果你想将一个对象写入磁盘,你需要使用这样的东西:
MyObject obj = ... // your object instance
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("/path/to/file"));
stream.writeObject(obj);
要回读对象,你需要这个:
ObjectInputStream stream = new ObjectInputStream(new FileInputStream("/path/to/file"));
MyObject obj = (MyObject) stream.readObject();
但有时您希望将对象实例序列化/反序列化到内存中,而不是磁盘中,然后您会使用这样的东西:
MyObject obj = ... // your object instance
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] data = baos.toByteArray();
我将省略从字节数组中读回对象的部分,因为它非常简单,但考虑到您可能使用的巨大组合将是 JVM 提供的所有不同的输入/输出流。
上面的示例使用默认的序列化行为。但是,您可能会遇到要序列化的给定类包含对未实现Serializable 接口的其他类实例的引用的情况,那么我们如何序列化它们呢?我们需要使用 transient 修饰符标记这些引用,以便默认的序列化行为将忽略它们,并且我们需要覆盖提供私有 readObject 和 writeObject 方法的默认行为,这些方法将在尝试序列化/反序列化您的对象时被调用实例。
此外,当序列化发生时,某些类可能会提供替换对象(不同可序列化类的对象实例)。该替换对象将包含原始类的状态,该原始类可以从该替换对象中恢复。此行为是通过在原始类中提供 writeReplace 方法和在替换类中提供 readReplace 来实现的。请注意,原始类和替换类都必须实现Serializable 接口,但只有替换对象中的数据会被序列化。
最后但并非最不重要的是,可以选择完全覆盖实现Externalizable 接口的默认序列化协议。这不是 Serializable 那样的标记接口,您必须实现将对象状态转换为数组的方法,反之亦然。您仍将使用 ObjectInputStream/ObjectOutputStream 对序列化 (externalise??) externalizable 对象实例,但现在将您的类转换为数组的逻辑字节数不再是 JVM 提供的字节数,而是您在实现 Externalizable 类时在类中编写的字节数。
所有这些信息都在 thinksteep 作为评论提供的link 中。