为什么readObject和writeObject是私有的,我为什么要显式地写transient变量?

40

我正在阅读《Effective Java》中有关序列化的章节。

  1. 谁调用了readObject()和writeObject()方法?为什么这些方法被声明为private?

  2. 以下是书中的一段代码:

    // StringList with a reasonable custom serialized form
    public final class StringList implements Serializable {
        private transient int size = 0;
        private transient Entry head = null;
    
        //Other code
    
        private void writeObject(ObjectOutputStream s)
            throws IOException {
            s.defaultWriteObject();
            s.writeInt(size);
            // Write out all elements in the proper order.
            for (Entry e = head; e != null; e = e.next)
               s.writeObject(e.data);
            }
        }
    }
    

    变量size被声明为瞬态变量,然后在writeObject方法中显式写入,这是有特定原因的吗?如果它没有被声明为瞬态变量,它不是仍然会被写入吗?


如果你有兴趣编写自己的序列化程序,请看一下Externalizable。它需要同样的努力,但具有更大的灵活性。(这是后来添加的) - Peter Lawrey
这只是一个示例,用于演示如何操作。没有具体原因要求“大小”是短暂的。 - user207421
6个回答

38
(1) 这些方法没有在任何类或接口中声明。一个实现了Serializable接口并需要在序列化和反序列化过程中特殊处理的类必须实现这些方法,序列化/反序列化器将尝试反射这些方法。
这是Java中相当奇怪的角落之一,API实际上是在javaDoc中定义的...但是如果这些方法已经在接口中定义,那么它们就必须是public(我们不能实现一个接口方法并通过添加private修饰符来锁定它)。 为什么是private- javaDoc没有给出提示。也许它们被定义为私有的,因为除了实现者外,没有其他类打算使用它们。它们是按定义私有的。
(2) 该示例仅显示了特殊处理的工作方式。在此示例中,size是瞬态的,并且不会被序列化。但是现在我们引入特殊处理程序,此处理程序将size的值添加到流中。与非瞬态字段的正常方法相比,结果流中元素的顺序可能有所不同(如果它很重要...)。
如果瞬态字段定义在超类中,并且子类想要序列化该值,则该示例可能有意义。

关于这个问题并没有“必须实现这些方法”的要求。在绝大多数情况下,默认的序列化机制不需要自定义readObject()/writeObjects()方法就可以完美地工作。如果你有需要,当然可以实现它们。 - user207421
@EJP - 是的,你说得对,我已经更正了这个短语。在某些情况下,它不是“应该”而是“必须”。 - Andreas Dolk

18
除了“不应该被错误方使用”之外,这里还有另一个隐私保护的原因:
我们不希望这些方法被子类覆盖。相反,每个类都可以有自己的writeObject方法,并且序列化引擎将按顺序调用它们所有的方法。这只在私有方法中(它们不会被覆盖)才可能实现。(对于readObject也是一样的。)
(请注意,这仅适用于本身实现Serializable的超类。)
这种方式,子类和超类可以独立演进,仍然与旧版本的存储对象兼容。

5
相反,对我来说,super.writeObject 更有意义。 - Tomáš Zato
2
@TomášZato 这种方式用于实现 Externalizable 接口的类(该接口具有公共方法)。 - Paŭlo Ebermann

10
关于readObject()/ writeObject()是私有的问题,情况如下:如果你的类Bar扩展了Foo类; Foo也实现了readObject()/writeObject(),同时Bar也实现了readObject()/writeObject()。
现在,当一个Bar对象被序列化或反序列化时,JVM需要自动调用Foo和Bar的readObject()/writeObject()方法(即无需显式地调用这些超类方法)。但是,如果这些方法不是私有的,它就会成为方法覆盖,JVM将无法在子类对象上调用超类方法。
因此它们必须是私有的!

7
readObjectwriteObject方法是由Object(Input/Output)Stream类调用的。这些方法在实现时必须声明为私有的,以证明它们不会被继承、重写或重载。这里的诀窍在于JVM会自动检查相应的方法调用时是否声明了这两个方法。请注意,JVM可以在任何时候调用您类的私有方法,但其他对象不能。因此,类的完整性得到了保持,并且序列化协议可以正常工作。
至于瞬态的int,它只是控制整个对象序列化的序列化过程。然而,请注意,如果所有字段都是瞬态的,则从技术上讲甚至不需要调用defaultWriteObject()。但我认为仍然建议调用它以提高灵活性,以便稍后可以在类中引入非瞬态成员,从而保持兼容性。

2
这些方法不是 Object(Input/Output)Stream 的一部分,它们是为了需要序列化/反序列化特殊处理的 Serializable 类而必需的。流类中的 readObject()writeObject(Object obj)public final 的。 - Andreas Dolk
1
+1; 你可以使用反射调用私有方法,这也是Java序列化和其他序列化通常完成此类操作的方式。对于超类字段,您需要使用defaultWriteObject。例如,您可能没有任何非瞬态字段,但是超类可能会有。 - Peter Lawrey
@Andreas - 是的,它们在OIS/OOS中是public final的,但我的印象是这个问题是从调用的角度来看的(即它们真正所涉及的类型是什么)。 - Saket
@Saket,根据你的第一段,它们必须是ObjectInput/OutputStream的公共成员,或者根据你的第二段,它们必须是私有的。你不能两者兼得。为了避免进一步讨论,我已编辑了你的第一段。 - user207421
1
@PeterLawrey 我记得 Serializable 类不负责超类。每个 Serializable 类只序列化自己的字段。 - andy

0
假设您有一个引用Socket的类A,如果您想序列化类A的对象,您无法直接进行操作,因为Socket未实现Serializable接口。在这种情况下,您可以编写以下代码。
public class A implements  implements Serializable {

// mark Socket as transient so that A can be serialized

private transient Socket socket;

private void writeObject(ObjectOutputStream out)throws IOException {
    out.defaultWriteObject();

    // take out ip address and port write them to out stream
    InetAddress inetAddress = socket.getInetAddress();
    int port = socket.getPort();
    out.writeObject(inetAddress);
    out.writeObject(port);
}



private void readObject(ObjectInputStream in)
                  throws IOException, ClassNotFoundException{
    in.defaultReadObject();
    // read the ip address and port form the stream and create a frest socket.
    InetAddress inetAddress = (InetAddress) in.readObject();
    int port = in.readInt();
    socket = new Socket(inetAddress, port);
}
}

忽略任何与网络相关的问题,因为目的是展示writeObject / readObject方法的使用。


那并没有回答另一个问题。为什么readObjectwriteObject方法是私有的? - nanofarad

0
关于瞬态变量,最好的理解方式是检查/分析/调试LinkedList / HashMap / etc类的readobject / writeobject方法,以理解为什么要声明瞬态变量并在后续进行序列化。
通常情况下,这是在您想按预定义顺序序列化/反序列化类变量而不依赖于默认行为/顺序时执行的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接