Why can't outer classes extend inner classes?
Asked Answered
A

2

9

Why can't I do this/is there a workaround to accomplish this:

package myPackage;

public class A {
    public class B {

    }
}

package myPackage;  

import myPackage.A.B;

public class C extends B {

}

package myPackage;

public class Main {
    public static void main(String[] args) {
        A myA = new A();
        C myC = myA.new C();
    }
}

The two compilation errors are

  1. On public class C extends B, No enclosing instance of type A is available due to some intermediate constructor invocation

  2. On C myC = myA.new C();, A.C cannot be resolved to a type

Frankly, I think the conceptual idea is sound: I want to make a subclass of B so that when I make a B for A, I have the option of making it have the functionality in B or the functionality in C.

Four workarounds/solutions that I don't want, and why I don't want them:

  1. "Solution: Put C inside of A." I don't want this because what if I can't modify the code for A.java (there are applications that have this restriction)? What if A is part of another API? Then I have to create a new file for C, as I've done here.

  2. "Solution: Put C inside of a class D that extends A." I don't want this because then C is restricted to only being instantiated on instances of type D. I want to make a class that extends B that can be instantiated on all instances of type A (there are applications that need this). Therefore, I need C to not be enclosed by another class, as I've done here.

  3. (Added as a question edit - see JoshuaTaylor's answer for a code sample) "Solution: Make B static." I don't want this because what if functionality in B needs to access its enclosing instance of A (there are applications that need this)? Therefore, I need B to not be static, as I've done here. (2nd question edit: You could make B static and have its constructor take in its enclosing instance, saving it in a protected variable for access in its children, but this is less elegant than the accepted answer by RealSkeptic)

  4. Removed. See edit at bottom.

So, if your answer suggests that I do one of the above, it is not an answer to this question, even though it might be useful for other people.

If your answer is "This is just a flaw of the Java language, you simply can't accomplish that conceptual idea", that is an okay answer, and you should post it. Just a warning though: I will hold off on marking your answer as accepted in case you are wrong. If this is your answer, I would highly appreciate if you have an explanation for why this restriction on the language is in place (as that's the title of this question).

Thank you for any and all help.

EDIT: JoshuaTaylor's answer brings up a valid option: you can extend B anonymously and avoid having to write a constructor as in RealSkeptic's accepted answer. I originally discarded this idea because it does not allow you to access C's enclosing instance of A via "A.this". However, I have since learned that C does not have an enclosing instance of A unless it is specifically defined within the definition of A as a nested class. So please note: none of the solutions below allow you to access the enclosing instance of A that encloses C's ancestor of B via writing "A.this" in a method of C. Classes can only use ".this" to access types which they are specifically nested in. However, if B has functionality that accesses the enclosing instance of A, either an anonymous class via JoshuaTaylor's method or any other class via RealSkeptic's method is required.

Alkalify answered 8/10, 2015 at 21:45 Comment(2)
If B isn't static, then instances of B have a reference to the containing A. If you make B static (public static class B), can you do what you're trying to do?Maniple
@JoshuaTaylor Thanks for mentioning. I don't want that. I'll add that to the list of solutions I don't want and why I don't want that.Alkalify
S
14

Well, it can be done, but you have to remember that each constructor needs to call its super constructor, explicitly or implicitly. That's why you get the "No enclosing instance of type A is available due to some intermediate constructor invocation" error. C's no-args constructor is trying to implicitly call B's no-args constructor, and it can't do that without an A.

So you fix your C to be:

public class C extends B {
    public C(A enclosing) {
        enclosing.super();
    }
}

And then you can create a new C by using:

A myA = new A();
C myC = new C(myA);

Answers to the questions in the comments

  • @Andi Turner asked:

    If you are explicitly passing in an A to the constructor of C, can't C now be static, and have A as a "plain old" member variable in C on which you invoke the required methods?

    It should be noted that C is neither static nor an inner class. It is an individual public class which is extending an inner class B. The implementation of the class B may not be known to the author of C, so it cannot know what methods would be using A, nor does it have access to any private members of A, as C is not a member of A. But B does, and B requires the A instance. An alternative approach would be composition rather than inheritance (where C holds a B instance and delegates operations to it), but if it wants to create that B instance rather than have it passed inside, it will still need an A instance, although it will use enclosing.new B rather than enclosing.super.

  • @rajuGT asked:

    Is C is an individual entity? if so, why does it need A object? and what is the association between myA and myC in this case?

    Yes, C is an individual entity. It wouldn't need A for any of its own methods. But if it tries to call (or inherits and doesn't override) methods from B that involve access to A - then that A is required by the implementation of B. Formally, of course, any instance of B requires a reference to A even if it doesn't actually make use of it. The association between myA and myC are is that myA is the immediate enclosing instance of myC with respect to B. This term is taken from section 8.1.3 of the JLS:

    For every superclass S of C which is itself a direct inner class of a class or interface SO, there is an instance of SO associated with i, known as the immediately enclosing instance of i with respect to S. The immediately enclosing instance of an object with respect to its class' direct superclass, if any, is determined when the superclass constructor is invoked via an explicit constructor invocation statement (§8.8.7.1)

Official reference for this usage

This usage is known as a qualified superclass constructor invocation statement, and is mentioned in the JLS, section 8.8.7.1 - Explicit Constructor Invocations.

Superclass constructor invocations begin with either the keyword super (possibly prefaced with explicit type arguments) or a Primary expression or an ExpressionName. They are used to invoke a constructor of the direct superclass. They are further divided:

  • Unqualified superclass constructor invocations begin with the keyword super (possibly prefaced with explicit type arguments).

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

At the end of that section, you can find examples for explicit constructor invocation statements, including this usage.

Slat answered 8/10, 2015 at 22:8 Comment(11)
Oh my god. It works. It just works. Thank you so much. This is exactly what I was looking for.Alkalify
If you are explicitly passing in an A to the constructor of C, can't C now be static, and have A as a "plain old" member variable in C on which you invoke the required methods?Manifesto
Awesome. I have a question here. Is C is an individual entity? if so, why does it need A object? and what is the association between myA and myC in this case?Olmos
Well this is pretty neat. I like it better than my "solution".Maniple
@AndyTurner Yes, and that is probably what I would do if this solution didn't exist, but this answer is better than that because it makes the most of the already written parts of the java language to fix the problem rather than sidestepping it with extra complication.Alkalify
@AndyTurner Plus, it explains the cause of the problem, which is always a win over a workaround.Alkalify
I just tried this example. I tried enclosing.super(); with nested class and non nested class. I was getting compile time error, saying illegal enclosing instance specification for non nested class and works fine for nested class. This is first time I'm coming across this syntax object.super() where super is not a method of Object class also, even IDE also unable to suggest this. Can you share the link/source where you find it? or any related useful information related to this.?Olmos
It's OK, he said it worked but was taken aback by the fact that it only works for inner classes... @raju - I'll try to find the best official reference tomorrow morning.Slat
@Slat You got me right. @Alkalify I'm not asking for inner class explanation. Actually I attached same doc in my answer to this question. But what I was searching was official doc, calling super method on an object. i.e. enclosing.super(). I tried finding it, but couldnt see one example though. So asked.Olmos
@Olmos I have edited the answer and added more information and references to the JLS. I hope you find it useful. Personally I knew about this usage from the old book The Java Programming Language which, alas, has not been updated after Java 5 but is still an excellent source.Slat
Thanks for your kindness in taking the time out to dig out the references and book suggestion. Ofcourse It will be useful for me and hopefully for others too.Olmos
M
3

You can easily extend nested static classes

Update: You've mentioned that you don't want this first solution, but the phrasing of the question may lead people to it who are willing to have the inner class be static, so I'll leave this in the hopes that it's useful to them. A more proper answer to your exact question is in the second section of this answer.

You can, but the inner class has to be static, because if it's not, then every instance of the inner class has a reference to the enclosing instance of the outer class. A static nested class doesn't have that reference, and you can extend it freely.

public class Outer {
    public static class Inner {

    }
}
public class InnerExtension extends Outer.Inner {

}

But you can also extend nested non-static classes

package test;

public class Outer {
    public class Inner {
        public String getFoo() {
            return "original foo";
        }
    }
}
package test;

public class Extender {
    public static void main(String[] args) {
        // An instance of outer to work with
        Outer outer = new Outer();

        // An instance of Outer.Inner 
        Outer.Inner inner = outer.new Inner();

        // An instance of an anonymous *subclass* of Outer.Inner
        Outer.Inner innerExt = outer.new Inner() {
            @Override
            public String getFoo() {
                return "subclass foo";
            }
        };

        System.out.println("inner's class: "+inner.getClass());
        System.out.println("inner's foo: "+inner.getFoo());
        System.out.println();
        System.out.println("innerExt's class: "+innerExt.getClass());
        System.out.println("innerExt's foo: "+innerExt.getFoo());
    }
}
inner's class: class test.Outer$Inner
inner's foo: original foo

innerExt's class: class test.Extender$1
innerExt's foo: subclass foo
Maniple answered 8/10, 2015 at 21:50 Comment(7)
My apologies for making my question a moving target. I just edited it to say I don't want the class to be static.Alkalify
This is a valid solution to the problem, yes, but not if I need to have an enclosing instance.Alkalify
Good "Update" to your question. If you'd like to do further edits, you could add code examples for my other two "I don't wants" as well to help people who are without those restrictions.Alkalify
Give me a moment. I'm testing your code. I swear I tried this earlier and it didn't work.Alkalify
@Alkalify Actually, I've just realizes that you can create instances of subclasses of non-static nested classes. You'll need an instance of the outer class to do it, but then you can subclass by using new ... () { ... }.Maniple
The problem I have with your solution to extending inner non-static classes using an anonymous extender is that the anonymous class of Inner does not have access to the enclosing instance of Outer. So if I put a method in the anonymous class that tries to access, say, a variable in Outer using Outer.this.variableName, it throws a compilation error: No enclosing instance of the type A is accessible in scope. Do you have any solutions/thoughts to this?Alkalify
@Alkalify I'm not sure whether you can get some workaround with super working or not, but could always have a method in the original inner class that returns the instance of the enclosing class.Maniple

© 2022 - 2024 — McMap. All rights reserved.