为什么匿名内部类不包含从此代码生成的任何内容?

2022-09-01 08:48:56
package com.test;

public class OuterClass {
    public class InnerClass {
        public class InnerInnerClass {

        }
    }

    public class InnerClass2 {

    }

    //this class should not exist in OuterClass after dummifying
    private class PrivateInnerClass {
        private String getString() {
            return "hello PrivateInnerClass";
        }
    }

    public String getStringFromPrivateInner() {
        return new PrivateInnerClass().getString();
    }
}

当在命令行上运行时,此代码将生成 6 个.class文件:javacSun JVM 1.6.0_20

OuterClass.class
OuterClass$1.class
OuterClass$InnerClass.class
OuterClass$InnerClass2.class
OuterClass$InnerClass$InnerInnerClass.class
OuthereClass$PrivateInnerClass.class

在 eclipse 中通过 JDT 运行时,它只生成 5 个类。

OuterClass.class
OuterClass$1.class
OuterClass$InnerClass.class
OuterClass$InnerClass2.class
OuterClass$InnerClass$InnerInnerClass.class
OuthereClass$PrivateInnerClass.class

反编译时,不包含任何内容。这个额外的类来自哪里,为什么创建它?OuterClass$1.class


答案 1

我使用的是多基因elubricants的较小片段。

请记住,字节码中没有嵌套类的概念;但是,字节码可以识别访问修饰符。编译器试图在这里规避的问题是,方法 instantiate() 需要创建 PrivateInnerClass 的新实例。但是,OuterClass 无权访问 PrivateInnerClass 的构造函数(将作为没有公共构造函数的受包保护的类生成)。OuterClass$PrivateInnerClass

那么编译器可以做什么呢?显而易见的解决方案是更改为具有受包保护的构造函数。这里的问题是,这将允许与类接口的任何其他代码创建一个新的实例,即使它被显式声明为 private!PrivateInnerClassPrivateInnerClass

为了防止这种情况,javac编译器正在做一个小技巧:它不是让 的常规构造函数从其他类中可见,而是将其保留为隐藏状态(实际上它根本没有定义它,但这从外部看是一回事)。相反,它会创建一个新的构造函数,该构造函数接收特殊类型的附加参数。PrivateInnerClassOuterClass$1

现在,如果您看一下 ,它会调用该新构造函数。它实际上作为第二个参数(类型)发送 - 该参数仅用于指定此构造函数是应调用的构造函数。instantiate()nullOuterClass$1

那么,为什么要为第二个参数创建一个新类型呢?为什么不使用,比如说,?它仅用于将其与常规构造函数区分开来,并且无论如何都会传递!答案是,与 OuterClass 私有的一样,法律编译器永远不会允许用户调用特殊的构造函数,因为必需的参数类型之一是隐藏的。ObjectnullOuterClass$1OuterClass$PrivateInnerClassOuterClass$1

我猜JDT的编译器使用另一种技术来解决同样的问题。


答案 2

我没有答案,但我能够确认这一点,并将代码段减少到以下内容:

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}

这会产生OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}

这里是:javap -cOuterClass.class

Compiled from "OuterClass.java"
public class OuterClass extends java.lang.Object{
public OuterClass();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public void instantiate();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
                          //(LOuterClass;LOuterClass$1;)V
   9:   pop
   10:  return

}

对于 :OuterClass$PrivateInnerClass

Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;

OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokespecial   #1; //Method "<init>":(LOuterClass;)V
   5:   return

}

如您所见,合成构造函数采用参数。OuterClass$1

因此,创建默认构造函数以采用类型为 的额外参数,并且该默认参数的值为 。javac$15: aconst_null


我发现如果以下任一情况属实,则不会创建:$1

  • 你做public class PrivateInnerClass
  • 声明一个空构造函数PrivateInnerClass
  • 或者你不叫它new
  • 可能是其他事情(例如 嵌套等)。static

可能相关

  • Bug ID:4295934:编译私有内部类会在错误的目录中创建一个匿名类文件

在名为 test 的目录中创建以下源:

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}

从父目录编译文件javac test/testClass.java

请注意,该文件是在当前目录中创建的。不知道为什么甚至创建此文件,因为也创建了一个。testClass$1.classtest/testClass$Inner.class

评估

该文件用于私有内部类的私有构造函数的“访问构造函数”所需的虚拟类。反汇编显示正确记录了此类的完全限定名称,因此不清楚为什么类文件最终位于错误的目录中。testClass$1.classtestClass$Inner


推荐