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

时间:2010-05-21 15:00:37

标签: java inner-classes anonymous-class

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();
    }
}

在使用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不包含任何内容。这个额外的课程来自哪里,为什么会创建?

5 个答案:

答案 0 :(得分:26)

我正在使用polygenelubricants的小片段。

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

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

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

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

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

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

答案 1 :(得分:12)

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

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创建默认构造函数以获取类型为$1的额外参数,并且该默认参数的值为5: 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.class是在当前目录中创建的。   不确定为什么甚至创建了这个文件,因为还创建了test/testClass$Inner.class

     

<强>评价

     

testClass$1.class文件用于“访问”所需的虚拟类   构造函数“用于私有内部类的私有构造函数   testClass$Inner。 Dissassembly显示完全限定名称   这个类是正确记录的,所以不清楚为什么类文件结束   在错误的目录中。

答案 2 :(得分:7)

基于polygenelubricants的答案,我猜这个神秘的类会阻止任何其他人(即OuterClass之外的人)实例化OuterClass$PrivateInnerClass,因为他们无法访问OuterClass$1 1}}。

答案 3 :(得分:3)

搜索后我发现了这个链接。 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717

评论指的是给定链接中可用的源代码。

  

这不是错误。

     

编译器正在尝试解决访问问题。自内心   class Test.Request是私有的,它的构造函数是私有的。这可以   如果你使用-private到javap:

     

$ javap -private Test \ $ Request编译自&#34; Test.java&#34;最后一堂课   Test $ Request扩展java.lang.Object {       最后测试这个$ 0;       private Test $ Request(Test);       测试$ Request(测试,测试$ 1); }

     

但是,JVM不允许使用Coucou的匿名子类   (测试$ 1)访问此私有构造函数。这是一个基础   JVM和Java编程语言之间的区别   来到嵌套类。该语言允许嵌套类访问   封闭类的私人成员。

     

最初,当嵌套类被添加到语言中时,   这个问题的解决方案是使构造函数包私有化   看起来像这样:

     

$ javap -private Test \ $ Request编译自&#34; Test.java&#34;最后一堂课   Test $ Request扩展java.lang.Object {       最后测试这个$ 0;       测试$请求(试验); }

     

但是,这很容易导致您可以访问的问题   你不应该的构造函数。要解决这个问题,   目前的解决方案是发明的。 &#34;真实&#34;构造函数将保留   私人:

private Test$Request(Test);
     

但是,必须允许其他嵌套类调用它   构造函数。因此必须提供访问构造函数。但是,这个   访问构造函数必须与&#34; real&#34;不同。构造函数。至   解决这个问题,编译器为访问添加了一个额外的参数   构造函数。这个额外参数的类型必须是某种东西   与用户可能拥有的任何东西都不会产生冲突的独特之处   书面。所以一个明显的解决方案是添加一个匿名类并使用   作为第二个参数的类型:

Test$Request(Test, Test$1);
     

但是,编译器很聪明并且重用任何匿名类(如果有的话)   存在。如果您将示例更改为不包含匿名类,   你会看到编译器会创建一个:

     

public abstract class Test {       私人最终班级请求{}       private final class OtherRequest {Request test(){return new Request(); }       }}

     

如果无法访问私有构造函数,则编译器不会   需要生成任何解释行为的访问构造函数   这个例子:

     

public abstract class Test {       私人决赛课请求{}}

答案 4 :(得分:0)

还有一个地方 - 如果用户已经宣布OuterClass$1,那么OuterClass$PrivateInnerClass将把它作为构造函数参数:

public class OuterClass { 

    ... 

    public String getStringFromPrivateInner() { 
        PrivateInnerClass c = new PrivateInnerClass();
        Object o = new Object() {};
        return null;
    }
}

-

public java.lang.String getStringFromPrivateInner();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial   #3; //Method OuterClass$PrivateInnerClass."":
(LOuterClass;LOuterClass$1;)V
   9:   astore_1
   10:  new     #4; //class OuterClass$1
   13:  dup
   14:  aload_0
   15:  invokespecial   #5; //Method OuterClass$1."":(LOuterClass;)V
   18:  astore_2
   19:  aconst_null
   20:  areturn