有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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。类文件:

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) 个答案

  1. # 1 楼答案

    我用的是Polygene的小片段

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

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

    为了防止这种情况发生,javac编译器正在做一个小技巧:它没有使PrivateInnerClass的常规构造函数在其他类中可见,而是将其隐藏(实际上它根本没有定义它,但从外部看也是一样)。相反,它创建了一个新的构造函数,该构造函数接收特殊类型OuterClass$1的额外参数

    现在,如果你看一下instantiate(),它会调用新的构造函数。它实际上将null作为第二个参数(类型为OuterClass$1)发送——该参数仅用于指定应调用的构造函数

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

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

  2. # 2 楼答案

    还有一个点——如果用户已经声明了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
    
  3. # 3 楼答案

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

    注释指的是给定链接中可用的源代码

    This is not a bug.

    The compiler is trying to solve an access problem. Since the inner class Test.Request is private its constructor is private. This can be seen if you use -private to javap:

    $ javap -private Test\$Request Compiled from "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; private Test$Request(Test); Test$Request(Test, Test$1); }

    However, the JVM will not allow the anonymous subclass of Coucou (Test$1) access to this private constructor. This is a fundamental difference between the JVM and the Java programming language when it comes to nested classes. The language allows nested classes to access private members of the enclosing class.

    Originally, when nested classes were added to the language, the solution to this problem was to make the constructor package private and would have looked like this:

    $ javap -private Test\$Request Compiled from "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; Test$Request(Test); }

    However, this can easily lead to problems where you can get access to the constructor when you shouldn't. To address this problem, the current solution was invented. The "real" constructor will remain private:

    private Test$Request(Test);
    

    However, other nested classes must be allowed to call this constructor. So an access constructor must be provided. However, this access constructor must be different from the "real" constructor. To solve this problem the compiler adds an extra parameter to the access constructor. The type of this extra parameter must be something unique that doesn't conflict with anything the user might have written. So an obvious solution is to add an anonymous class and use that as the type of the second parameter:

    Test$Request(Test, Test$1);
    

    However, the compiler is clever and reuses any anonymous class if one exists. If you change the example to not include an anonymous class, you will see that the compiler will create one:

    public abstract class Test { private final class Request {} private final class OtherRequest { Request test() { return new Request(); } } }

    If there is no access to the private constructor, the compiler doesn't need to generate any access constructor which explains the behavior of this example:

    public abstract class Test { private final class Request {} }

  4. # 4 楼答案

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

  5. # 5 楼答案

    我没有答案,但我能够确认这一点,并将片段简化为以下内容:

    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:编译私有内部类会在错误的目录中创建匿名类文件

    Create the following source in a directory called test:

    package test;
    public class testClass
    {
        private class Inner
        {
        }
        public testClass()
        {
            Inner in = new Inner();
        }
    }
    

    Compile the file from the parent directory javac test/testClass.java

    Notice that the file testClass$1.class is created in the current directory. Not sure why this file is even created since there is also a test/testClass$Inner.class created as well.

    EVALUATION

    The testClass$1.class file is for a dummy class needed by an "access constructor" for the private constructor of the private inner class testClass$Inner. Dissassembly shows that the fully-qualified name of this class is correctly noted, so it is unclear why the class file ends up in the wrong directory.