39

我正在阅读Effective Java中的序列化一章。

  1. 谁调用 readObject() 和 writeObject()?为什么将这些方法声明为私有?

  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 方法中显式写入它?如果它没有被声明为瞬态,它无论如何都会被写入,对吧?

4

6 回答 6

38

(1) 方法未在任何类或接口中声明。实现Serializable接口并在序列化和反序列化过程中需要特殊处理的类必须实现这些方法,并且序列化器/反序列化器将尝试反映这些方法。

这是Java中相当奇怪的角落之一,API实际上是在javaDoc中定义的......但是如果方法已经在接口中定义,那么它们必须public(我们不能实现接口方法并锁定它通过添加private修饰符)。

为什么是私有的 - javaDoc 没有给出提示。也许它们被指定为私有的,因为除了实现者之外没有其他类打算使用它们。根据定义,它们是私有的

(2) 这个例子简单地展示了特殊处理是如何工作的在这个例子中,size是暂时的,不会被序列化。但是现在我们引入了特殊的处理程序,这个处理程序将值添加size到流中。与具有非瞬态字段的正常方法的区别可能是结果流中元素的顺序(如果重要的话......)。

如果在超类中定义瞬态字段并且子类想要序列化该值,则该示例可能有意义。

于 2011-09-19T07:12:28.343 回答
18

除了不应该被错误的各方使用之外,这是这些方法隐私的另一个原因:

我们不希望这些方法被子类覆盖。相反,每个类都可以有自己的writeObject方法,序列化引擎会一个接一个地调用它们。这仅适用于私有方法(这些方法未被覆盖)。(同样适用于readObject。)

(请注意,这只适用于本身实现 Serializable 的超类。)

这样,子类和超类可以独立发展,并且仍然与旧版本的存储对象保持兼容。

于 2011-09-19T14:43:30.727 回答
11

关于 readObject()/writeObject() 是私有的,这里是交易:如果你的类 Bar 扩展了一些类 Foo;Foo 也实现了 readObject()/writeObject(),Bar 也实现了 readObject()/writeObject()。

现在,当 Bar 对象被序列化或反序列化时,JVM 需要自动为 Foo 和 Bar 调用 readObject()/writeObject()(即,您不需要显式地对这些超类方法进行分类)。但是,如果这些方法不是private,它变成了方法覆盖,JVM不能再调用子类对象上的超类方法。

因此,它们必须是私有的!

于 2012-11-21T13:44:31.733 回答
7

和由readObject类调用。writeObjectObject(Input/Output)Stream

这些方法是(并且必须)声明为私有的(在实现您自己的时),证明/表明这两种方法都没有被实现继承、覆盖或重载。这里的技巧是 JVM 会自动检查是否在相应的方法调用期间声明了任一方法。请注意,JVM 可以随时调用您的类的私有方法,但没有其他对象可以。因此,类的完整性得到维护,序列化协议可以继续正常工作。

而关于瞬态int,它只是控制整个对象序列化的序列化。但是,请注意,从技术上讲,如果所有字段都是瞬态的,则甚至不需要调用。defaultWriteObject()但我认为仍然建议出于灵活性目的调用它,以便稍后您可以在类中引入非瞬态成员,保持兼容性。

于 2011-09-19T06:49:29.020 回答
0

关于瞬态变量,理解为什么要声明瞬态变量并稍后在 writeobject 方法中序列化它们的最佳方法是检查/分析/调试 LinkedList/HashMap/etc 类的 readobject/writeobject 方法。

这通常在您想要以预定义的顺序序列化/反序列化类变量而不依赖于默认行为/顺序时完成。

于 2011-09-21T06:49:31.960 回答
0

假设您有一个引用了套接字的 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 方法的使用。

于 2013-09-20T16:13:56.577 回答