【问题标题】:Why is an anonymous inner class containing nothing generated from this code?为什么匿名内部类不包含此代码生成的任何内容?
【发布时间】:2011-02-22 09:57:11
【问题描述】:
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();
    }
}

当使用Sun JVM 1.6.0_20 在命令行上运行javac 时,此代码会生成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 不包含任何内容。这个额外的类是从哪里来的,为什么要创建它?

【问题讨论】:

  • 我能够确认这一点。 class OuterClass$1 extends java.lang.Object{ }。很奇怪。
  • 如果您将 PrivateInnerClass 公开,它会消失吗?也许它与“伪造”对类的私有访问有关。
  • @Alex:不,1999 年的错误报告也提到了虚拟类。请参阅我的“答案”。
  • 谢谢大家。我认为我能够根据我发送给你们的各种实况调查任务来解决我的问题。 :)

标签: java inner-classes anonymous-class


【解决方案1】:

我正在使用 polygenelubricants 的较小 sn-p。

请记住,字节码中没有嵌套类的概念;然而,字节码知道访问修饰符。编译器在这里试图规避的问题是 方法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 的编译器使用另一种技术来解决同样的问题。

【讨论】:

  • 和 OP:如果我较小的 sn-p 是一个更容易讨论的主题,请随意将其提升到问题中(因为我的“答案”更像是一个适合问题的事实调查任务无论如何)。
【解决方案2】:

我没有答案,但我可以确认,并将 sn-p 减少到以下:

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 嵌套等)。

可能相关

  • 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。反汇编显示完全限定名称 这个类被正确记录了,所以不清楚为什么类文件结束 在错误的目录中。

【讨论】:

  • 当通过 Eclipse 运行同一个类时,不会创建空类(OuterClass$1.class)。任何想法
【解决方案3】:

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

【讨论】:

  • 伙计,我花了 22 行才给出相同的答案。你摇滚:)
【解决方案4】:

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

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

这不是错误。

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

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

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

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

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

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

private Test$Request(Test);

但是,必须允许其他嵌套类调用它 构造函数。所以必须提供一个访问构造函数。然而,这 访问构造函数必须不同于“真正的”构造函数。到 解决这个问题编译器在访问中增加了一个额外的参数 构造函数。这个额外参数的类型必须是什么 独特的,不会与用户可能拥有的任何东西冲突 书面。所以一个明显的解决方案是添加一个匿名类并使用 那作为第二个参数的类型:

Test$Request(Test, Test$1);

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

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

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

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

【讨论】:

    【解决方案5】:

    还有一个地方 - 如果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;LOuterClass$1;)V
       9:astore_1
       10:新#4; //类 OuterClass$1
       13:重复
       14:加载_0
       15:调用特殊#5; //方法 OuterClass$1."":(LOuterClass;)V
       18:astore_2
       19: 常量空值
       20:返回
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-28
      • 1970-01-01
      • 1970-01-01
      • 2016-05-02
      • 2016-11-26
      • 1970-01-01
      • 1970-01-01
      • 2011-10-21
      相关资源
      最近更新 更多