Java: нестатические вложенные классы и instance.super()
Мне сложно переносить голову на нестатические вложенные классы в Java. Рассмотрим следующий пример, который печатает "Внутренний", а затем "Ребенок".
class Outer {
class Inner {
Inner() { System.out.println("Inner"); }
}
}
public class Child extends Outer.Inner {
Child(Outer o) {
o.super();
System.out.println("Child");
}
public static void main(String args[]) {
new Child(new Outer());
}
}
Я понимаю, что экземпляры Inner всегда должны быть связаны с Outer экземпляром, и это относится и к Child, так как он расширяет Inner. Мой вопрос в том, что означает синтаксис o.super()
- почему он вызывает конструктор Inner?
Я видел только обычный super(args)
, используемый для вызова конструктора суперкласса и super.method()
для вызова версии суперкласса переопределенного метода, но никогда не являющегося чем-то вроде формы instance.super()
.
Ответы
Ответ 1
Он вызвал "квалифицированный вызов конструктора суперкласса".
Цитирование из здесь:
Явные инструкции вызова конструктора можно разделить на два вида:
-
Альтернативные вызовы конструктора начинаются с ключевого слова this (возможно, предваряемого явными аргументами типа). Они используются для вызова альтернативного конструктора того же класса.
-
Вызовы конструктора суперкласса начинаются либо с ключевого слова super (возможно, с предваряемыми явными аргументами типа), либо с помощью первичного выражения. Они используются для вызова конструктора прямого суперкласса. Вызовы конструктора суперкласса могут быть далее разделены:
-
-
Неквалифицированные вызовы конструктора суперкласса начинаются с ключевого слова super (возможно, предваряется явным аргументом типа).
-
Квалифицированные вызовы конструктора суперкласса начинаются с первичного выражения. Они позволяют конструктору подкласса явно указывать только что созданный объект, немедленно включающий экземпляр в отношении прямого суперкласса (§8.1.3). Это может потребоваться, когда суперкласс является внутренним классом.
Ответ 2
Внутренние классы (нестатические дочерние классы) по существу являются вложенными классами (статические дочерние классы) с неявными ссылками на их родительские объекты. Вот ваш вышеприведенный код, написанный вместо него статическим вложенным классом:
class Outer {
static class Inner {
final Outer outer;
Inner(Outer outer) {
this.outer = outer;
System.out.println("Inner");
}
}
}
public class Child extends Outer.Inner {
Child(Outer o) {
super(o); // o.super();
System.out.println("Child");
}
public static void main(String args[]) {
new Child(new Outer());
}
}
Глядя на это, вы должны понимать, что делает o.super().
Ответ 3
Почему o.super()
в Child
завершает вызов конструктора Outer.Inner
? Это просто: потому что Child extends Outer.Inner
, а вызовы конструктора всегда связаны цепью иерархии.
Здесь небольшое расширение к вашему фрагменту, чтобы проиллюстрировать:
class Outer {
Outer() {
System.out.println("Outer");
}
void outerMethod() { }
class Inner {
Inner() {
System.out.println("OuterInner");
outerMethod();
}
String wealth;
}
}
class OuterChild extends Outer {
OuterChild() {
System.out.println("OuterChild");
}
}
public class OuterInnerChild extends Outer.Inner {
OuterInnerChild(Outer o) {
o.super();
System.out.println("OuterInnerChild");
this.wealth = "ONE MILLION DOLLAR!!!";
}
public static void main(String args[]) {
System.out.println(new OuterInnerChild(new Outer()).wealth);
new OuterChild();
}
}
Отпечатки:
Outer
OuterInner
OuterInnerChild
ONE MILLION DOLLAR!!!
Outer
OuterChild
Некоторые ключевые наблюдения:
- Поскольку
OuterInnerChild extends Outer.Inner
, он наследует wealth
, как и обычную семантику подкласса
- И так же, как и в обычной семантике подкласса, конструктор цепочки
OuterInnerChild
для конструктора Outer.Inner
- Потому что
OuterChild extends Outer
, его цепочки конструктора, даже если явно не вызывается
- Неявно или явно, конструктор связывает иерархию
Но почему компилятор требует, чтобы конструктор OuterInnerChild
принял Outer o
и что o.super()
вызывается?
Теперь это специфично для внутренней семантики класса: это сделано для того, чтобы все экземпляры OuterInnerChild
имели экземпляр Outer
для Outer.Inner
, суперкласс OuterInnerChild
. В противном случае конструктор Outer.Inner
не будет иметь закрытый экземпляр Outer
для вызова outerMethod()
on.
Ответ 4
Понятно, что нестатический внутренний класс "принадлежит" определенному объекту. Это похоже на то, что каждая из них получает свою собственную версию класса, так же как нестатическое поле или метод принадлежит определенному объекту.
Итак, почему у нас есть забавный синтаксис вроде instance.new Inner()
и instance.super()
- для контекстов, где ответ на вопрос "но чей Inner
?" не сразу очевидна. (В нестационарном методе внешнего класса вы можете просто сказать new Inner()
и, как обычно, short для this.new Inner()
.)
Ответ 5
Всегда, чтобы не забывать о базовых принципах, при вызове конструктора подкласса он всегда создает экземпляр родительского класса, независимо от внутренних/внешних классов. В вашем сценарии, поскольку вы расширяете внутренний класс, а ваш внутренний класс является членом родительского класса, который необходимо создать, а затем вызывает фактический внутренний конструктор класса.