内部类不应该实现serializable

时间:2013-12-19 02:07:49

标签: java serialization

我正在阅读Effective Java并且遇到了这一段(目前对我来说有点密集)。有人可以解释为什么内部类不应该更详细地实现Serializable吗?

内部类(第22项)不应实现Serializable。他们使用     编译器生成的合成字段,用于存储对封闭实例和存储的引用     封闭范围的局部变量值。这些字段如何与班级对应     定义未指定,匿名和本地类的名​​称也是如此。因此,     内部类的默认序列化形式是不明确的。

1 个答案:

答案 0 :(得分:1)

所以想象一下:

import java.io.*;

public class A {

     private Object mFoo;

     public A(Object foo) {
       mFoo = foo;
     }

     public Serializable getData() {
        String niceString = "Nice String";
        return new B(niceString);
     }

     public class B implements Serializable {
       private Object mBlob;

       public B (Object blob) {
          mBlob = blob;
       }

       public String toString() {
         return String.format("%s-%s-%s", getClass(), mBlob, mFoo);
       }

     }

     public static void main(String[] args)throws Exception {
       A a = new A("Have a nice Day");
       Serializable s  = a.getData();
       System.out.println("Before:" + s);
        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
        ObjectOutputStream ostream = new ObjectOutputStream(bytesOut);
        ostream.writeObject(s);
        ostream.flush();
        ostream.close();
        ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
        ObjectInputStream istream = new ObjectInputStream(bytesIn);
        System.out.println("After:" + istream.readObject());
     }
}

在这种情况下,存在用于mFoo之类的虚拟结构。可以在toString()中引用它们,因为这个jvm引用了A的实例,其中包含“Have a nice Day”字符串。现在,如果它是静态的,那就是另一回事了。但是没有静态,这可能无法序列化。

Before:class A$B-Nice String-Have a nice Day
Exception in thread "main" java.io.NotSerializableException: A
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
    at A.main(A.java:35)

现在让我们稍微修改一下B

public static class B implements Serializable {

     private Serializable mData2;
     private Object mBlob;

           public B (Object blob) {
              mBlob = blob;
              mData2 = new Serializable() {
                 String data = "Foo";
                 public String toString() {
                  return data;
                 }
              };
           }

           public String toString() {
             return String.format("%s-%s-%s", getClass(), mBlob, mData2);
           }
}

现在没有A的引用,B有一个匿名类,它指向B,因为它在技术上是一个内部类。除了B也是Serializable之外,这也是同样的问题。什么是输出。我在热点上运行。

Before:class A$B-Nice String-Foo
After:class A$B-Nice String-Foo

因此所有部件都可写得很棒! ......但仍有问题。

注意它才有效,因为我们正在控制一些事情。

让我们说,而不是序列化到缓冲区并重新读取它,在同一个应用程序中我们做一些可能更有用的东西。假设我们将B的实例保存到文件中。这基本上是在给定运行时之外序列化匿名内部类。

如果您正在向其他人描述课程B,您会称之为匿名内部课程?这是匿名的。你可能会把它称为对你有意义的东西,并以一贯的方式引用它。这就是sdk /运行时的功能。它命名为不会与类路径中的任何其他类发生冲突的东西。在Hotspot中我认为它将被命名为A.B$1,因为它是B的第一个匿名内部类(为什么这不是0索引总是困扰我)。这是Hotspot详细信息的一个实现,我相信。因此,如果您使用相同的源并使用另一个sdk工具集进行编译,然后运行代码,并将其反序列化该文件,如果它没有将该匿名内部类称为同名,则可能会有{ {1}}扔了,你会像HUH?怎么样?哪里?因为谁知道该文件何时被写入,所以追逐是痛苦的。

序列化格式有一个规范,这是这种类型的源头。通常有一些神奇的数字来指定数据的开头,然后是序列化的blob的类名,前面有一个ClassNotFoundException,所以我认为在这种情况下文件会包含类似L的内容。如果编译并在Hotspot上运行。因此,当它读取该流寻找LA.B$1时,所有运行时都可以真正做到,因为它不知道该文件来自哪个运行时或运行时实例。 (在这里关闭内存,所以我跳过很多细节)。