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
不包含任何内容。这个额外的课程来自哪里,为什么会创建?
答案 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 -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
}
对于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
嵌套等)。在名为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