一个源文件怎么会生成这么多的.class文件呢?下面通过问答的形式阐述Java在编译内部类的私有构造函数时采用的策略。JDK版本为1.8.0_111

Q: 为什么会生成 Outer$Inner ?

因为InnerOuter的内部类,而在JVM层面没有内部类的概念,每个类都要有与之对应的.class文件。所以在源码中Outer.Inner类在编译之后对应的文件就是Outer$Inner.class

Q: 那为什么要生成 Outer$1 ?

额。。。这个不太清楚额。要不咱们来看看Outer$1.class的文件内容?

Q: 为毛Outer$1 里面啥都没有啊?

不应该啊,怎么连默认构造函数都没有?真是个神奇的存在,你看,我写了一个Empty类,编译之后不是空的啊,至少还有个默认的构造函数!!

Q: 所以Outer$1 这个空类到底是干嘛用的?

我也不知道啊,编译器怎么生成了这么个空空荡荡的类,连构造函数都没有,不能实例化啊,要它有什么用。这个类肯定在Outer或者是Outer$Inner 中被用到了。

长这么漂亮一定是与内部类有关,咱们先来看看Outer$Inner.class中有没有用到它吧。

快看快看,Outer$Inner类有两个构造函数,第二个构造函数用到Outer$1

Q: 等等,为什么这两个构造函数源码里面都没写啊?

废话!源码里面还没有定义Outer$1这个类呢,这些都是编译器加的啊。

这两个构造函数的第一个参数都是Outer类型,由于在Java语言层面,Inner类可以访问Outer类所有的成员,而JVM是没有内部类的概念的,所以在初始化Inner类时,要将Outer类的对象传入,这样在Inner类中就可以访问Outer类中的成员了。final Outer this$0就是用来持有Outer类对象的引用的。

第一个构造函数private Outer$Inner(Outer)对应的就是源码里面的那个无参构造函数。

Q: 那么为什么会出现第二个构造函数呢?

你有没有发现,Outer.Inner的构造函数是private的,这样一来,就不能在Outer中实例化Outer.Inner类了,不能调用它的构造函数啊(JVM遵循与Java相同的访问控制)!但是Java语言又允许这样做,你看,在Outer.foo()函数中不就实例化了么,编译还通过了。所以编译器一定要做些微小的工作,来保证能实例化成功。

为了能在Outer类中成功实例化Outer$Inner,编译器给Outer$Inner加了一个Outer类可以访问的构造函数,在该构造函数内部调用那个无法访问的构造函数,进行初始化工作(第2行,invokespecial #1)。

Q: 为毛这么麻烦啊,直接提升默认构造函数的可见性不就行了?

你啊,还是Naive,提升可见性之后不就与源码中的private语义冲突了么?其它类本来不能实例化Outer$Inner的,结果被你一提升,其它类也能实例化了。

我们来举个例子吧。假设咱们编译时把private提升为package-private,那么相当于在源码里面删掉了private,编译之后就与Outer.Inner2一模一样

这样本来不能被其它类实例化的就可以被实例化了!

看,Outer.Inner2就相当于被提升可见性的Outer.InnerOuter.Inner2被成功实例化,而本来Outer.Inner是不能被实例化的。

Q: 好好好,你说的都对。那就加一个构造函数咯,话说第二个参数Outer$1不是空空如也,不能被实例化么?那怎么传进去呢?

谁说传参一定要传实例化后的对象,可以直接传null啊,来来来,咱们看看Outer.class的内容,看看它是怎么传参的

仔细看看foo函数,第4行aload_0传第一个参数,第5行aconst_null传第二个参数null[jvms-6.5]。所以第二个参数是以null传进去的,并不需要实例化Outer$1对象。

Q: 辛辛苦苦弄一个Outer$1出来,又不实例化,那要它干嘛,直接用Object不行么?反正是传null进去啊,还不用新生成一个类

骚年,你问的很好啊,新生成的Outer$1好像就是用来指定第二个参数的类型,并没有其它的作用。那么为什么不用已有类来作为第二个参数的类型呢,比如Object,String?看看下面的代码

在源码里面给Outer.Inner类加一个构造函数,你看看在Outer$Inner.class里面,编译器为private构造函数生成的构造函数与Inner(Object o)对应的构造函数是不是很相似啊?

你把第二个参数用Object试试,把Outer$1换成java.lang.Object,那不就有两个签名相同的构造函数了吗?那就冲突了啊!用一个新类型作为第二个参数,就是为了避免与已有构造函数冲突啊!

骚年,是不是豁然开朗!

Q: 说得好像很有道理,问你啊,上面的例子中 new Inner(null) 会调用哪个构造函数呢?那两个相似的构造函数都可以用啊?

当然是Inner(Object o)啊,尽管编译器给private Inner(){}加了参数,但是在Java语言层面它还是无参的啊,所以肯定是用有一个参数的构造函数啊!

Q: 所以新生成一个类型Outer$1是为了避免冲突,一般人不会定义这样的类型。但是有人非要弄一个这样的类型作为参数呢?

嘿嘿嘿,聪明的编译器早就想到了,它会拒绝编译!请看下图

Q: 原来如此,那你总结一下吧

编译内部类的私有构造函数时,为了使外部类能实例化内部类,会给内部类添加对应的构造函数(可访问性为default),在对应的私有构造函数参数列表中添加一个类型为Outer$1的参数,并在所添加的构造函数中调用对应的私有构造函数,进行初始化。举个例子:

构造函数Inner()Inner(int i, String s)会被编译器处理,Inner(int i)不会,因为在Outer中没有实例化。

(完)

Reference:

http://stackoverflow.com/questions/2883181/why-is-an-anonymous-inner-class-containing-nothing-generated-from-this-code