【问题标题】:Why are readObject and writeObject private, and why would I write transient variables explicitly?为什么 readObject 和 writeObject 是私有的,为什么我要显式地编写瞬态变量?
【发布时间】:2011-09-19 06:42:58
【问题描述】:

我正在阅读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 方法中显式写入,是否有任何具体原因?如果它没有被声明为瞬态,它无论如何都会被写入,对吧?

【问题讨论】:

  • 如果您有兴趣编写自己的序列化,请查看 Externalizable。这几乎是相同的努力,但具有更大的灵活性。 (后来添加的)
  • 这只是一个示例来演示如何做到这一点。这里没有特定的理由让“大小”是暂时的。

标签: java serialization effective-java


【解决方案1】:

(1) 方法未在任何类或接口中声明。一个类,它实现了Serializable 接口并需要special special handling during the serialization and deserialization process must implement 这些方法,并且序列化器/反序列化器将尝试反映这些方法。

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

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

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

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

【讨论】:

  • 没有关于它的“应该实现那些方法”。没有自定义 readObject()/writeObjects() 的默认序列化在绝大多数情况下都能很好地工作。如果您需要,您可以实施它们。
  • @EJP - 是的,你是对的,更正了这个短语。在某些情况下,这不是应该,而是必须
【解决方案2】:

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

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

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

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

【讨论】:

  • 相反,super.writeObject 对我来说更有意义。
  • @TomášZato 这种方式用于实现 Externalizable 接口(具有公共方法)的类。
【解决方案3】:

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

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

因此它们必须是私有的!

【讨论】:

    【解决方案4】:

    readObjectwriteObjectObject(Input/Output)Stream 类调用。

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

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

    【讨论】:

    • 这些方法不是Object(Input/Output)Stream 的一部分,它们是Serializable 需要特殊处理序列化/反序列化的类所必需的。流类中的readObject()writeObject(Object obj)public final
    • +1;您可以使用反射调用私有方法,这就是 Java 序列化和其他序列化通常做这种事情的方式。对于超类字段,您也需要 defaultWriteObject。也就是说,您可能没有任何非瞬态字段,但超类可能。
    • @Andreas - 是的,它们在 OIS/OOS 中是公开的最终版本,但我的印象是问题是从调用的角度来看的(它们真正调用了哪些类型)。
    • @Saket 根据您的第一段,它们是 ObjectInput/OutputStream 的公共成员,或者根据您的第二段,它们必须是私有的。你不能两全其美。为了避免扩大讨论,我编辑了你的第一段。
    • @PeterLawrey 我记得可序列化类不负责超类。每个可序列化的类只序列化自己的字段。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      假设你有一个引用了 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 方法是私有的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 2017-06-08
      • 2019-04-25
      • 2020-10-22
      相关资源
      最近更新 更多