javaconstructorinner-classesjlsanonymous-inner-class

Implicit constructor in case of anonymous class whose super class is an Inner Class


Consider the following article from the JLS: §15.9.5.1 When the anonymous class extends an inner class - then for the implicit constructor of the anonymous class - following is the rule regarding the body of the implicit constructor:

The constructor body consists of an explicit constructor invocation (§8.8.7.1) of the form o.super(...), where o is the first formal parameter of the constructor, and the actual arguments are the subsequent formal parameters of the constructor, in the order they were declared.

Following is what we understand from this-:

  1. o - is the instance of the class - that just encloses the super class of the anonymous class.
  2. when we do o.super(...) we are actually invoking the super class of the enclosing instance.

Consider the following program:

class A_ {
  public A_(Boolean st){}
  public class B_ {
    public B_(Integer a){}
    public class C_ {
      public C_(String str){}
    }
  }
}

//when creating the anonymous constructor out of C - in a separate class:
public class ThisInAnonymousTesting {
  public static void main(String[] args) {
    A_.B_.C_ member =
        new A_(true)
            .new B_(23)
            .new C_("Hey"){
    };
  }
}

Now when we decompile the anonymous class, we get the following:

/**
 === Anonymous class Declaration
*/
import A_.B_;
import A_.B_.C_;

final class ThisInAnonymousTesting$1 extends C_ {
// - Rule : the 1st argument is the class instance which is the enclosing instance
// of the Super class(C in this case) - ie B
    ThisInAnonymousTesting$1(B_ x0, String str) {
        x0.getClass();

//But the super() invocation here is for the super class - not for the enclosing instance
        super(x0, str);
    }
}

Following are my questions:

  1. Why do we need to do o.super(...) - when we are already passing the initialized instance of o to the anonymous class constructor?
    • ie o gets created only when its super classes would have already been called.
    • The super() call in the constructor is clearly trying to instantiate the class C_ which is fine - as it is the super class for the current anonymous class.
  2. In the decompiled version, what is the need of x0.getClass(); - I mean why does JVM need to do the getClass()?

Not sure if my interpretation of o.super() clause correct?


Solution

  • I think you misunderstood what o.super(...) meant. Statements of the forms:

    ExpressionName . [TypeArguments] super ( [ArgumentList] ) ; 
    Primary . [TypeArguments] super ( [ArgumentList] ) ;
    

    are qualified superclass constructor invocations, and are specified in the explicit constructor invocations section of the JLS.

    It does not invoke the superclass constructor of o. It invokes the enclosing class's superclass constructor, with o as the enclosing instance.

    Here is a simple example:

    class Outer {
        public static final Outer OUTER1 = new Outer(1);
        public static final Outer OUTER2 = new Outer(2);
        public Outer(int x) {
            this.x = x;
        }
    
        private final int x;
    
        class Inner {
            public Inner() {
                System.out.println("In Inner constructor, the enclosing instance's field x is: " + x);
            }
        }
    
        class InnerSubclass extends Inner {
            public InnerSubclass() {
                OUTER1.super();
    
                System.out.println("In InnerSubclass constructor, the enclosing instance's field x is: " + x);
            }
        }
    }
    

    If you do Outer.OUTER2.new InnerSubclass();, the output is:

    In Inner constructor, the enclosing instance's field x is: 1
    In InnerSubclass constructor, the enclosing instance's field x is: 2
    

    OUTER1.super(); there invokes the constructor of Inner (with OUTER1 being the enclosing object), not the constructor of Outer. Note that this is different from just doing super();, as that would be using InnerSubclass's enclosing instance to invoke the superclass constructor, whatever that may be, not necessarily OUTER1.

    So really what the spec is saying, is that the anonymous constructor will call the superclass' constructor, with the enclosing instance being the first parameter of the anonymous constructor. What is the first parameter of the anonymous constructor? This is stated just a few lines before:

    Otherwise, the first formal parameter of the anonymous constructor represents the value of the immediately enclosing instance of i with respect to S

    In your case, new A_(true).new B_(23).

    So the overall effect of this, is something like this:

    final class ThisInAnonymousTesting$1 extends C_ {
    
        ThisInAnonymousTesting$1(B_ o, String str) {
            o.super(str); // recall that this calls C's constructor
        }
    }
    
    // the callsite now becomes like this:
    A_.B_.C_ member = new ThisInAnonymousTesting$1(new A_(true).new B_(23), "Hey");
    

    Note: ThisInAnonymousTesting$1 extends C_ isn't valid Java, but it is allowed in bytecode.

    In the decompiled code, you see the syntax super(x0, str); because there is no such thing as inner classes in bytecode. The enclosing instances of inner classes are all just translated to a private field, and assigned through the first parameter of the constructor. As a consequence, o.super(...) is really just super(o, ...) if you look at the byte code.

    Consider:

    class Outer {
        class Inner {}
    }
    

    Bytecode for Outer$Inner.class is:

    class Outer$Inner {
      final Outer this$0;
    
      Outer$Inner(Outer);
        Code:
           0: aload_0          
           1: aload_1          
           2: putfield      #1 // this.this$0 = Outer parameter (aka enclosing instance)
           5: aload_0          
           6: invokespecial #7 // super()
           9: return
    }