1

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?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
theutonium.18
  • 483
  • 2
  • 7
  • I cannot reproduce. I do not get any calls to `getClass` in the byte code. What version of Java are you using, and what decompiler are you using? – Sweeper Jan 15 '22 at 09:48
  • @Sweeper : my question is mainly around the `o.super()` clause mentioned - this syntax is meant to invoke the `super` class of the enclosing instance - my doubt is that why is that needed at all? - when enclosing instance has already been instantiated and passed? -- is this part of specification applicable or if my understanding is incorrect? -- also `super()` is acceptable as current class constructor invocation - but not clear why is it happening for enclosing class? – theutonium.18 Jan 15 '22 at 12:10

1 Answers1

0

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
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • **why at all do we need to invoke the super class constructor for the enclosing instance** - that we are passing to our implicit anonymous class constructor? - the enclosing instance is already a **fully baked object** - all we need to do is explicitly pass it to the anonymous class constructor - so that it can access the members of the enclosing class. – theutonium.18 Jan 15 '22 at 13:28
  • Also - the same fact I had tried to verify - that I cannot see any `o.super(...)` kind of invocation - nor does it theoretically seem to be correct based on the argument above - that we are passing a fully baked object. But the specification says: `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.` – theutonium.18 Jan 15 '22 at 13:31
  • Get this straight: `o.super()` does not call the constructor of `o`! `o.super()` is the same as `super()`, except we explicitly say what the enclosing instance is. Do you understand why it is required to call the superclass constructor in the first line of a constructor? – Sweeper Jan 15 '22 at 13:31
  • It says:- `o` : **First argument to the Constructor** - ie it is the enclosing instance to the anonymous class. `o.super()` : is being called in the anonymous constructor body - this seem odd - as o is supposed to be **fully baked**. – theutonium.18 Jan 15 '22 at 13:34
  • `super(x0, str);` ***is*** the `o.super(str);` invocation that you are looking for. It's just that the decompiler works with bytecode, and inner classes don't exist in bytecode, and your decompiler is not smart enough to recognise that this actually came from an inner class. – Sweeper Jan 15 '22 at 13:36
  • 1
    @theutonium.18 Well, if you find the syntax weird, I cannot help you. Yes `o` is supposed to be fully baked at that point, and `o.super()` is not supposed to initialise `o`. It is supposed to to initialise `this`. – Sweeper Jan 15 '22 at 13:37