snickers10m
snickers10m

Reputation: 1749

Why can't a local class that extends an inner class access the inner class enclosing instance?

(I keep re-reading that question title and thinking about how ridiculous it must look, but I assure you that is the best description of the problem, and I have an actual application where this is the best structure. I swear I'm not crazy.)

Consider the following. Each block is a separate file:

package myPackage;

public class A {

    public int i;

    public A(int i) {
        this.i = i;
    }

    public class B {

    }
}

package myPackage;

import myPackage.A.B;

public class Main {

    public static void main(String[] args) {
        class C extends B {
            public C(A enclosingInstance) {
                enclosingInstance.super();
            }

            public void show() {
                System.out.println(A.this.i);
            }
        }
        A myA = new A(2);
        C myC = new C(myA);
        myC.show();
    }
}

Note that the enclosingInstance business is to solve a problem involving intermediate constructor invocations. See "Why can't outer classes extend inner classes?".

I would expect the output to be "2". But instead, I have a compile error on System.out.println(A.this.i);:

No enclosing instance of the type A is accessible in scope

I think the programmatic concept I'm trying to solve is sound: Create a new type of B inside main to give to A that uses things from A that types of B can access.

So what am I doing wrong, or why isn't this possible in java?

EDIT/UPDATE: Note that the same error appears when the code in main is moved to a non-static method. That is to say, I tried moving everything inside of static void main to a new, non-static method of class Main called go(). Then I changed static void main to the single line new Main().go();. The error is in the same spot. So it doesn't seem to be an issue of class C being defined in a static context.

Upvotes: 2

Views: 357

Answers (3)

Paul Boddington
Paul Boddington

Reputation: 37655

You want A.this to refer to the enclosing instance of the B instance. But why should it? That's not what the syntax means. A.this would mean the enclosing A instance of the C instance, and this does not make sense because C is not an inner class of A.

To make this clearer, here is an example where C is an inner class of A.

public class A {

    public int i;

    public A(int i) {
        this.i = i;
    }

    public class B {
        void foo() {
            System.out.println(A.this.i);
        }
    }

    public class C extends B {
        C(A a) {
            a.super();
        }
        void bar() {
            System.out.println(A.this.i);
        }
    }

    public static void main(String[] args) {
        A a1 = new A(1);
        A a2 = new A(2);
        C c = a1.new C(a2);
        c.foo();
        c.bar();
    }
}

Here C extends B, and both C and B are inner classes of A. Therefore any C has an enclosing A instance, and it also has an enclosing A instance when considered as a B, and these enclosing instances are different (as proved by the fact that foo and bar print different numbers).

So, A.this could not possibly mean what you want it to mean, because it already means something else. I guess the reason why the language designers didn't come up with other syntax to mean the enclosing instance of a super class, is because such syntax would be very complicated, with little pay-off (simple workarounds already exist).

Upvotes: 3

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280112

This is absurd code that you should never write for production.

It is, in part, explained in the documentation for Explicit Constructor Invocations

Qualified superclass constructor invocations begin with a Primary expression or an ExpressionName. They allow a subclass constructor to explicitly specify the newly created object's immediately enclosing instance with respect to the direct superclass (§8.1.3). This may be necessary when the superclass is an inner class.

All this to say that C is a local class (which is an inner class, which is kind of nonsense because if you declare it in a static method there is no enclosing instance) that is a subclass of B but not a nested class of A. As such, there is no enclosing instance. An instance of C does not have an enclosing instance. (Though it would if you declared it in an instance method, but that would be an instance of Main.)

The newly created object's immediately enclosing instance (from JLS) is specified indirectly through a constructor parameter.

You'd have to store it yourself

private A enclosingInstance;
public C(A enclosingInstance) throws CloneNotSupportedException {
    enclosingInstance.super();
    this.enclosingInstance = enclosingInstance;
}

and since A#i is public, you can access it normally

public void show() {
    System.out.println(enclosingInstance.i);
}

Upvotes: 3

Dici
Dici

Reputation: 25980

With the provided information, I would do this :

public class B {
    protected A getOuterInstance() {
        return A.this;
    }
}

and just let C inherit and use this method. I know you dislike this method but this is the simplest answer I can see. With more information, I would probably propose a design which would try not involving any inner class as this is not a normal use case for inner classes.

Upvotes: 1

Related Questions