Issue with constructors of nested class

前端 未结 2 1500
挽巷
挽巷 2020-12-14 10:03

This question is about interesting behavior of Java: it produces additional (not default) constructor for nested classes in some situations.

相关标签:
2条回答
  • 2020-12-14 10:46

    The third constructor is a synthetic constructor generated by the compiler, in order to allow access to the private constructor from the outer class. This is because inner classes (and their enclosing classes' access to their private members) only exist for the Java language and not the JVM, so the compiler has to bridge the gap behind the scenes.

    Reflection will tell you if a member is synthetic:

    for (Constructor c : aClass.getDeclaredConstructors()) {
        System.out.println(c + " " + c.isSynthetic());
    }
    

    This prints:

    a.TestNested$A(a.TestNested) false
    private a.TestNested$A(a.TestNested,int) false
    a.TestNested$A(a.TestNested,int,a.TestNested$1) true
    

    See this post for further discussion: Eclipse warning about synthetic accessor for private static nested classes in Java?

    EDIT: interestingly, the eclipse compiler does it differently than javac. When using eclipse, it adds an argument of the type of the inner class itself:

    a.TestNested$A(a.TestNested) false
    private a.TestNested$A(a.TestNested,int) false
    a.TestNested$A(a.TestNested,int,a.TestNested$A) true
    

    I tried to trip it up by exposing that constructor ahead of time:

    class A {    
        A() {
        }   
    
        private A(int a) {
        }
    
        A(int a, A another) { }
    }
    

    It dealt with this by simply adding another argument to the synthetic constructor:

    a.TestNested$A(a.TestNested) false
    private a.TestNested$A(a.TestNested,int) false
    a.TestNested$A(a.TestNested,int,a.TestNested$A) false
    a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true
    
    0 讨论(0)
  • 2020-12-14 11:04

    First of all, thank you for this interesting question. I was so intrigued that I could not resist taking a look at the bytecode. This is the bytecode of TestNested:

    Compiled from "TestNested.java"
      public class a.TestNested {
        public a.TestNested();
          Code:
             0: aload_0       
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return        
    
        public static void main(java.lang.String[]);
          Code:
             0: ldc_w         #2                  // class a/TestNested$A
             3: astore_1      
             4: aload_1       
             5: invokevirtual #3                  // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
             8: astore_2      
             9: aload_2       
            10: arraylength   
            11: istore_3      
            12: iconst_0      
            13: istore        4
            15: iload         4
            17: iload_3       
            18: if_icmpge     41
            21: aload_2       
            22: iload         4
            24: aaload        
            25: astore        5
            27: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            30: aload         5
            32: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
            35: iinc          4, 1
            38: goto          15
            41: new           #2                  // class a/TestNested$A
            44: dup           
            45: new           #6                  // class a/TestNested
            48: dup           
            49: invokespecial #7                  // Method "<init>":()V
            52: dup           
            53: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
            56: pop           
            57: bipush        123
            59: aconst_null   
            60: invokespecial #9                  // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
            63: astore_2      
            64: return        
      }
    

    As you can see, the constructor a.TestNested$A(a.TestNested,int,a.TestNested$1) is invoked from your main method. Furthermore, null is passed as the value of the a.TestNested$1 parameter.

    So let's take a look at the mysterious anonymous class a.TestNested$1:

    Compiled from "TestNested.java"
    class a.TestNested$1 {
    }
    

    Strange - I would have expected this class to actually do something. To understand it, let's take a look at the constructors in a.TestNested$A: class a.TestNested$A { final a.TestNested this$0;

      a.TestNested$A(a.TestNested);
        Code:
           0: aload_0       
           1: aload_1       
           2: putfield      #2                  // Field this$0:La/TestNested;
           5: aload_0       
           6: invokespecial #3                  // Method java/lang/Object."<init>":()V
           9: return        
    
      private a.TestNested$A(a.TestNested, int);
        Code:
           0: aload_0       
           1: aload_1       
           2: putfield      #2                  // Field this$0:La/TestNested;
           5: aload_0       
           6: invokespecial #3                  // Method java/lang/Object."<init>":()V
           9: return        
    
      a.TestNested$A(a.TestNested, int, a.TestNested$1);
        Code:
           0: aload_0       
           1: aload_1       
           2: iload_2       
           3: invokespecial #1                  // Method "<init>":(La/TestNested;I)V
           6: return        
    }
    

    Looking at the package-visible constructor a.TestNested$A(a.TestNested, int, a.TestNested$1), we can see that the third argument is ignored.

    Now we can explain the constructor and the anonymous inner class. The additional constructor is required in order to circumvent the visibility restriction on the private constructor. This additional constructor simply delegates to the private constructor. However, it cannot have the exact same signature as the private constructor. Because of this, the anonymous inner class is added to provide a unique signature without colliding with other possible overloaded constructors, such as a constructor with signature (int,int) or (int,Object). Since this anonymous inner class is only needed to create a unique signature, it does not need to be instantiated and does not need to have content.

    0 讨论(0)
提交回复
热议问题