Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a subclass of an inner class require an enclosing instance?

Consider class OuterClass which has InnerClass

public class OuterClass {
    class InnerClass {

    }
}

And the second class, which is trying to extend InnerClass of OuterClass

public class Clazz extends OuterClass.InnerClass {
    public Clazz(OuterClass outerClass) {
        outerClass.super();
    }
}

So far so good, this code will work, compiler should not give warnings.

But I am trying to understand - why is that necessary to pass to constructor reference of OuterClass? and why is that necessary to call it's super constructor?

I want to understand why is it has to be exact this way?

Also, consider this class (not related to previous ones) in which classes Apartments and Hall are inner classes to Building class which is inner to Solution (innerception).

public class Solution {
    public class Building {
        public class Hall {
            private BigDecimal square;

            public Hall(BigDecimal square) {
                this.square = square;
            }
        }

        public class Apartments {
        }
    }
}

Then there goes top-level class which is trying to extend inner inner class Hall

class BigHall extends Solution.Building.Hall {
    public BigHall(Solution.Building building, BigDecimal square)
    {
        building.super(square);
    }
}

Look at this mess. Last two classes should compile as well, but can you clear it up for me, why BigHall class when extending inner inner class Hall does need to pass to constructor reference to Building object instead of Solution object?

If you can provide quotation from JLS that'd be great!

like image 438
a.anonymous Avatar asked Feb 07 '17 15:02

a.anonymous


People also ask

What is enclosing class in Java?

A nested class is a member of its enclosing class. Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private. Static nested classes do not have access to other members of the enclosing class.

Why inner classes are required?

Inner classes are a security mechanism in Java. We know a class cannot be associated with the access modifier private, but if we have the class as a member of other class, then the inner class can be made private. And this is also used to access the private members of a class.

What is the difference between an inner class and a subclass in Java?

An inner class is a class that is nested or defined within another class. On the other hand, a subclass is a class that is derived from another class.

Can an inner class method have access to the fields of the enclosing class?

An instance of InnerClass can exist only within an instance of OuterClass and has direct access to the methods and fields of its enclosing instance.


1 Answers

Your InnerClass is an inner class.

An inner class is a nested class that is not explicitly or implicitly declared static.

Inner classes may have enclosing instances

An instance i of a direct inner class C [your InnerClass] of a class or interface O [your OuterClass] is associated with an instance of O, known as the immediately enclosing instance of i.

Only inner classes declared in static contexts don't have enclosing instances.

An instance of an inner class I whose declaration occurs in a static context has no lexically enclosing instances.

Your example's inner class is not in a static context and therefore requires an enclosing instance.

The Java Language Specification then states

The constructor of a non-private inner member class implicitly declares, as the first formal parameter, a variable representing the immediately enclosing instance of the class

(It goes into further detail as to why, if you're interested). In other words, your InnerClass constructor really looks like

public InnerClass(OuterClass OuterClass.this){} // this is valid syntax for entirely different reasons

It expects an argument of type OuterClass to use as its enclosing instance. Subclassing this InnerClass type does not change that, especially since any subtype must invoke its supertype's super constructor.

On the subject of constructors, the specification also states

If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments.

So obviously, without the parameter, your code wouldn't work

public class Clazz extends OuterClass.InnerClass {
    public Clazz() {
        // implicit super() invocation
    }
}

This super() constructor invocation wouldn't work. In this case, it's because it's the wrong syntax. But the idea is that the super constructor is expecting an argument for the formal parameter representing the enclosing instance, but you aren't providing one. The correct syntax is the one you already have. Let's define it.

There are multiple kinds of constructor invocations. Yours

public Clazz(OuterClass outerClass) {
    outerClass.super();
}

is a qualified superclass constructor invocation, defined as

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.

The chapter goes one to explain how the expression is evaluated

If the superclass constructor invocation is qualified, then the Primary expression or the ExpressionName immediately preceding ".super", p, is evaluated.
[...]

Otherwise, the result of this evaluation is the immediately enclosing instance of i with respect to S.

In other words, the object referenced by outerClass becomes the required enclosing instance of your Clazz instance.


In simplified terms, ignoring the case of inner classes, your example boils down to something like

public class Foo {}
public class Bar {
    public Bar(Foo foo){}
}
public class SubBar extends Bar {
    public SubBar(Foo foo) {
        super(foo);
    }
}

SubBar has to satisfy the Bar super constructor that expects a Foo instance. This is the same thing that's happening with your Clazz type except its super type's constructor is implicit.

like image 190
Sotirios Delimanolis Avatar answered Oct 02 '22 15:10

Sotirios Delimanolis