java - 内部类不应该实现可序列化

标签 java serialization

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

Inner classes (Item 22) should not implement Serializable. They use compiler-generated synthetic fields to store references to enclosing instances and to store values of local variables from enclosing scopes. How these fields correspond to the class definition is unspecified, as are the names of anonymous and local classes. Therefore, the default serialized form of an inner class is ill- defined.

最佳答案

就像想象一下:

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 beautiful 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 也是可序列化的。输出是什么。我正在热点上运行。

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

所以所有部分都可以写得很好! ...但仍然有一个问题。

注意它之所以有效,是因为我们控制了一些事情。

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

如果您向其他人描述类 B,您会如何称呼该匿名内部类?毕竟是匿名的。您可能会称其为对您有意义的名称,并始终以这种方式引用它。这也是 sdk/runtime 所做的事情。它给它命名的名称不会与类路径中的任何其他类发生冲突。在Hotspot中,我认为它会被命名为A.B$1,因为它是B的第一个匿名内部类(为什么这不是0索引一直困扰着我)。我相信这是热点细节的实现。因此,如果您要采用相同的源代码并使用另一个 sdk 工具集对其进行编译,然后运行代码,并让它反序列化该文件,如果它没有以相同的名称调用该匿名内部类,则可能会出现 ClassNotFoundException 抛出,你会觉得嗯?如何?在哪里?而且追踪起来会很痛苦,因为谁知道该文件是什么时候写入的。

序列化格式有一个规范,这就是这种排序的来源。通常有某种魔数(Magic Number)来指定数据开始,然后是序列化 blob 的类名,前面带有 L,所以我认为在这种情况下,文件将包含类似 >LA.B$1 如果在 Hotspot 上编译并运行。因此,当运行时读取该流时,它真正能做的就是查找 A.B.$1,因为它不知道该文件实际上来自哪个运行时或运行时实例。 (这里记不清了,所以我跳过了很多细节)。

关于java - 内部类不应该实现可序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20672209/

相关文章:

java - Hibernate 事务计数器行为

java - 用 jackson 序列化日期列表

java - 将编码对象写入文件

c# - 成员变量的 XML 序列化为 xmlnode

Java:我可以在不调用其构造函数的情况下反序列化一个对象吗?

java - 为什么会出现 "unreachable code"和 "variable not initialized"编译错误?

java - Hibernate 集合中的并发修改

java - 打印数组中的数字

tomcat Web 应用程序中的 Java RMI UnmarsharledException

java - Java 内存模型 (JSR-133) 是否暗示进入监视器会刷新 CPU 数据缓存?