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

And*_*erg 33 java anonymous-class inner-classes

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

OuterClass.class
在OuterClass $ 1.class
在OuterClass $ InnerClass.class
在OuterClass $ InnerClass2.class
在OuterClass $ $将InnerClass InnerInnerClass.class
在OuterClass $ PrivateInnerClass.class

在eclipse中运行JDT时,它只生成5个类.

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

反编译时,OuterClass$1.class什么都不包含.这个额外的课程来自哪里,为什么会创建?

Oak*_*Oak 26

我正在使用polygenelubricants的小片段.

请记住,字节码中没有嵌套类的概念; 但是,字节码知道访问修饰符.编译器试图绕过的问题是 该方法instantiate()需要创建一个新的实例PrivateInnerClass.但是,OuterClass无权访问PrivateInnerClass构造函数(OuterClass$PrivateInnerClass将在没有公共构造函数的情况下生成为受包保护的类).

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

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

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

那么,为什么要为第二个参数创建一个新类型呢?为什么不使用比方说Object?它只用于将它与常规构造函数区分开来并且null无论如何都会被传递!答案是,由于OuterClass$1OuterClass是私有的,因此合法编译器永远不会允许用户调用特殊OuterClass$PrivateInnerClass构造函数,因为OuterClass$1隐藏了一个必需的参数类型.

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


pol*_*nts 12

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

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}
Run Code Online (Sandbox Code Playgroud)

这创造了 OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}
Run Code Online (Sandbox Code Playgroud)

这是javap -c为了OuterClass.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

}
Run Code Online (Sandbox Code Playgroud)

并为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

}
Run Code Online (Sandbox Code Playgroud)

如您所见,合成的构造函数接受一个OuterClass$1参数.

因此,javac创建默认构造函数以获取类型的额外参数$1,并且该默认参数的值为5: aconst_null.


我发现,$1如果满足以下任一条件,则不会创建:

  • 你做 public class PrivateInnerClass
  • 你为...声明了一个无效的构造函数 PrivateInnerClass
  • 或者你不打电话给new
  • 可能是其他的东西(例如static嵌套等).

可能有关系

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

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

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

评估

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


小智 7

根据polygenelubricants的答案,我猜这个神秘的类会阻止任何其他人(即外部OuterClass)实例化a OuterClass$PrivateInnerClass,因为他们无法访问OuterClass$1.