2

(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.

Community
  • 1
  • 1
snickers10m
  • 1,709
  • 12
  • 28
  • @snickers10m does `B` use non-static attributes of `A` ? Or private ones (static or not) ? – Dici Oct 09 '15 at 00:51
  • @Dici B doesn't use anything from A - there's no code at all in B. However C, which is a type of B, uses the non-static, public instance variable of A, `i`. – snickers10m Oct 09 '15 at 00:53
  • Not sure I agree with the duplicate, but anyway. A simple fix is to define a `getOuterInstance()` on `B` which just returns `A.this` – Dici Oct 09 '15 at 00:54
  • @snickers10m ok, so I think this is bad design. It's like you're trying to break encapsulation by exposing internal functionalities to external classes – Dici Oct 09 '15 at 00:56
  • @Dici That works, but it's kind of a hack. I'm hoping for an actual solution that doesn't add complicated structure. I also disagree with the duplicate, but I'm slowly trudging through the duplicate question to see what I can learn. – snickers10m Oct 09 '15 at 00:56
  • @jarrod I'm re-opening because I think it's a little more complicated than that. If you think I'm wrong, and can explain why, I'll close again. – Sotirios Delimanolis Oct 09 '15 at 00:59
  • @SotiriosDelimanolis Thank you. Frankly the linked "duplicate" didn't help me with my problem at all, it simply redefined what I already know. So thank you. – snickers10m Oct 09 '15 at 01:01
  • @snickers10m would it be possible to present us your high-level problem instead of this implementation detail issue that came up with the design you attempted ? – Dici Oct 09 '15 at 01:26
  • @Dici I don't think it's necessary... After all stack overflow generally wants the most small and independent piece of your program you can generate. – snickers10m Oct 09 '15 at 01:30
  • @snickers10m But SO also does not like AB problems, and I think this is one of them – Dici Oct 09 '15 at 01:32
  • I'll be damned, I've been taken to school. Deleting comments, and my thanks to you. – Hovercraft Full Of Eels Oct 09 '15 at 01:35
  • @HovercraftFullOfEels No worries. I've been taken to school lots today getting up to the point of this question. I'm glad someone else has learned something too. I'll delete my comments as well. – snickers10m Oct 09 '15 at 01:36
  • @HovercraftFullOfEels [`static inner` is a contradiction in terms](http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.3). – user207421 Oct 09 '15 at 01:36
  • @EJP: indeed you're right. – Hovercraft Full Of Eels Oct 09 '15 at 01:37

3 Answers3

3

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);
}
Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • If you look at my edit, I say that I tried moving my code into an instance method in `Main` called `go()`, and changed `static void main` to `new Main().go()`. The error is in the exact same spot. So it doesn't have to do with being defined in a static context. – snickers10m Oct 09 '15 at 01:23
  • @snickers10m If you reread my answer, putting the code in an instance method doesn't change anything in relation to `A` (or `B`), it just makes an instance of `Main` available to `C`. You still need to manually capture the instance of `A`. – Sotirios Delimanolis Oct 09 '15 at 01:24
  • 1
    @snickers10m No need to apologize, this is weird code. Please don't ever use it. – Sotirios Delimanolis Oct 09 '15 at 01:26
  • But why? I think it's quite elegant. Sure it's hard to understand at first but it's simple, compared to storing the enclosing instance in a variable. – snickers10m Oct 09 '15 at 01:26
  • If it's hard to read/understand, it is not elegant to any but one person: the author. – blurfus Oct 09 '15 at 01:28
  • @snickers10m The _why_ to your original code is because Java doesn't support it. The _why_ to the code I propose is because there's almost definitely a better way to model it. – Sotirios Delimanolis Oct 09 '15 at 01:31
  • @ochi But that's not true. I mean if the inventor of recursion only ever used workarounds because whenever he used his "weird and absurd" recursion idea people thought it was hard to read and understand, we never would have gotten that new programmatic concept! – snickers10m Oct 09 '15 at 01:32
  • @SotiriosDelimanolis Fair enough, but then the bolded title of your answer, instead of calling my code "absurd" and "never to be used for production" should instead say "You can't do this, because Java doesn't support this code or any variation of it." Then you can call your own code any names you want at the end. – snickers10m Oct 09 '15 at 01:33
3

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).

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • So basically, in my original code, `.this` could refer to two different places: `Main` because it's a local class of `Main`, and `A` because it's a subclass of an inner class of `Main`, and the Java developers simply said "Only one enclosing instance per class definition"? – snickers10m Oct 09 '15 at 01:42
  • 1
    It's just that `C` is not an inner class of `A`, so there is no notion of enclosing instance. That's what I get from the answer, and that's true – Dici Oct 09 '15 at 01:44
  • 1
    @snickers10m There's not only one enclosing instance per class definition, because inner classes can have inner classes. So you could have D inside C inside B inside A, and any D would have 3 enclosing instances (an A, a B and a C). Add in the fact that any of these classes could have super classes with many enclosing instances, and the whole thing becomes absurd. It's just not sensible for the language to allow you to refer to all these things. – Paul Boddington Oct 09 '15 at 01:46
  • @PaulBoddington Okay. I was just checking my understanding. Thank you for the answer! – snickers10m Oct 09 '15 at 01:51
  • @PaulBoddington So, just to check, what's the rule of thumb for determining the enclosing instance for a class? Is it always the top-level class of the file the class was defined in? Or is it just the next-level-up class? (So in the case of D inside C inside B inside A, would D's enclosing instance be C or A?) – snickers10m Oct 09 '15 at 01:55
  • @PaulBoddington Oh! So essentially `.this` can only work if there is only one enclosing instance. And if there are more, that's where I get the error? – snickers10m Oct 09 '15 at 02:00
  • @snickers10m No, because you can write `A.this`, `B.this` or `C.this`. – Paul Boddington Oct 09 '15 at 02:00
  • @PaulBoddington So `.this` can only work for enclosing instances that appear in the same file? Or rather, you can only write `A.this` if A is defined in the same file? – snickers10m Oct 09 '15 at 02:01
  • Within the same top level class. I think. I'm a bit shaky on all of this, because I try to avoid using inner classes if at all possible because they're so unbelievably confusing. – Paul Boddington Oct 09 '15 at 02:03
  • @PaulBoddington No that makes sense. Thank you for bearing with me. And I accepted yours because it was better, not because he offended me. You both explained that A isn't an enclosing instance of C, but you also explained the programmatic problems with trying to access A from C. – snickers10m Oct 09 '15 at 02:05
  • Ah ok. Glad I could help. – Paul Boddington Oct 09 '15 at 02:07
1

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.

Dici
  • 25,226
  • 7
  • 41
  • 82
  • It's not that I dislike this solution, it's just that it's a workaround rather than a fix. It doesn't address the error about an enclosing instance not being available in scope, it just sidesteps it. I think it's good for you to post this answer - it will be helpful to people. However, I'm not going to make it the accepted answer since it's just a workaround. – snickers10m Oct 09 '15 at 01:09