Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't outer classes extend inner classes?

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.

like image 432
snickers10m Avatar asked Oct 08 '15 21:10

snickers10m


People also ask

Can outer class extend inner class?

An inner class can be extended by another class outside of it's outer class. If you are extending static inner class (Static nested class), then it is a straight forward implementation.

Why a class Cannot extend itself?

A class cannot extend itself since it IS itself, The definition of subclass is that it extends another class and inherits the state and behaviors from that class. so it is not a subclass. Inner classes are allowed to extend the outer class because those are two different classes.

Can outer class access inner class methods?

A nested class can be public, private, package private, or protected as a member of the outer class. The outer java classes can access inner class private or protected members.

Can outer class access inner class variables?

If the inner class defined as private and protected, can outer class access the members of inner class? Yes. These qualifiers will only affect the visibility of the inner class in classes that derive from the outer class.


2 Answers

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.

like image 72
RealSkeptic Avatar answered Sep 24 '22 11:09

RealSkeptic


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
like image 37
Joshua Taylor Avatar answered Sep 25 '22 11:09

Joshua Taylor