33
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个类。

外部类.class
OuterClass$1.class
OuterClass
$InnerClass.class
OuterClass$InnerClass2.class OuterClass$InnerClass$InnerInnerClass.class OuterClass
$PrivateInnerClass.class

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

4

5 回答 5

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 的编译器使用另一种技术来解决同样的问题。

于 2010-05-21T15:48:06.633 回答
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。反汇编显示该类的完全限定名称被正确记录,因此不清楚为什么类文件最终会出现在错误的目录中。

于 2010-05-21T15:15:39.087 回答
7

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

于 2010-05-21T15:32:58.790 回答
4

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

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

这不是错误。

编译器正在尝试解决访问问题。由于内部类 Test.Request 是私有的,它的构造函数是私有的。如果您对 javap 使用 -private,则可以看到这一点:

$ javap -private Test\$Request 编译自 "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; 私人测试$请求(测试);测试$请求(测试,测试 $1);}

但是,JVM 将不允许 Coucou (Test$1) 的匿名子类访问此私有构造函数。这是 JVM 和 Java 编程语言在嵌套类方面的根本区别。该语言允许嵌套类访问封闭类的私有成员。

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

$ javap -private Test\$Request 编译自 "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; 测试$请求(测试);}

但是,这很容易导致问题,您可以在不应该访问构造函数时访问该构造函数。为了解决这个问题,发明了当前的解决方案。“真正的”构造函数将保持私有:

private Test$Request(Test);

但是,必须允许其他嵌套类调用此构造函数。所以必须提供一个访问构造函数。但是,此访问构造函数必须不同于“真正的”构造函数。为了解决这个问题,编译器向访问构造函数添加了一个额外的参数。这个额外参数的类型必须是独一无二的,并且不会与用户可能编写的任何内容发生冲突。所以一个明显的解决方案是添加一个匿名类并将其用作第二个参数的类型:

Test$Request(Test, Test$1);

但是,编译器很聪明,可以重用任何存在的匿名类。如果您将示例更改为不包含匿名类,您将看到编译器将创建一个:

公共抽象类测试{私有最终类请求{}私有最终类OtherRequest{请求测试(){返回新请求();} } }

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

公共抽象类测试{私有最终类请求{}}

于 2016-06-09T19:22:22.107 回答
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();
  代码:
   0:新#2;//类 OuterClass$PrivateInnerClass
   3:重复
   4:aload_0
   5:aconst_null
   6:调用特殊#3;//方法 OuterClass$PrivateInnerClass."":
(LOuterClass;L OuterClass $1 ;)V
   9:astore_1
   10:新#4;//类OuterClass$1
   13:重复
   14:加载_0
   15:调用特殊#5;//方法 OuterClass$1."":(LOuterClass;)V
   18:astore_2
   19: 常量空值
   20:返回
于 2010-05-21T15:43:22.137 回答