Почему внешние классы не могут расширять внутренние классы?
Почему я не могу сделать это / есть обходной путь для достижения этой цели:
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();
}
}
Две ошибки компиляции
На
public class C extends B
,No enclosing instance of type A is available due to some intermediate constructor invocation
На
C myC = myA.new C();
,A.C cannot be resolved to a type
Честно говоря, я думаю, что концептуальная идея обоснована: я хочу сделать подкласс B, чтобы, когда я делаю B для A, у меня есть возможность сделать так, чтобы он имел функциональность в B или функциональность в C.
Четыре обходных пути / решения, которые мне не нужны, и почему я не хочу их:
"Решение: поместите С внутрь А." Я не хочу этого, потому что что, если я не могу изменить код для A.java (есть приложения, которые имеют это ограничение)? Что если A является частью другого API? Затем я должен создать новый файл для C, как я сделал здесь.
"Решение: поместите C в класс D, который расширяет A." Я не хочу этого, потому что тогда C ограничивается только созданием экземпляра на экземплярах типа D. Я хочу создать класс, расширяющий B, который может быть создан на всех экземплярах типа A (есть приложения, которым это необходимо). Поэтому мне нужно, чтобы C не был заключен в другой класс, как я это сделал здесь.
(Добавлено как вопрос редактирования - см. Ответ Джошуа Тейлора для примера кода) "Решение: Сделайте B статичным". Я не хочу этого, потому что что, если функциональность в B нуждается в доступе к включающему его экземпляру A (есть приложения, которым это нужно)? Поэтому мне нужно, чтобы B не был статичным, как я это сделал здесь. (2-е редактирование вопроса: вы можете сделать B статическим и получить его конструктор в своем включающем экземпляре, сохранив его в защищенной переменной для доступа к его дочерним элементам, но это менее элегантно, чем принятый ответ RealSkeptic)
Удалены. См редактировать внизу.
Итак, если ваш ответ предполагает, что я делаю одно из вышеперечисленного, это не ответ на этот вопрос, даже если он может быть полезен для других людей.
Если ваш ответ "Это просто недостаток языка Java, вы просто не сможете реализовать эту концептуальную идею", это хороший ответ, и вы должны опубликовать его. Только предупреждение: я не буду отмечать ваш ответ как принятый, если вы ошибаетесь. Если это ваш ответ, я был бы очень признателен, если бы у вас было объяснение, почему действует это ограничение в отношении языка (так называется заголовок этого вопроса).
Спасибо за любую помощь.
РЕДАКТИРОВАТЬ: ответ JoshuaTaylor вызывает действительный вариант: вы можете расширить B анонимно и избежать необходимости писать конструктор, как в принятом RealSkeptic ответ. Первоначально я отказался от этой идеи, потому что она не позволяет вам получить доступ к включенному в C экземпляру A через "A.this". Однако с тех пор я узнал, что в C нет включающего экземпляра A, если только он не определен в определении A как вложенный класс. Поэтому обратите внимание: ни одно из приведенных ниже решений не позволяет вам получить доступ к включающему экземпляру A, который включает в себя предка C для B, посредством записи "A.this" в методе C. Классы могут использовать ".this" только для доступа к типам, которые они специально вложены в. Однако, если B имеет функциональность, которая обращается к включающему экземпляру A, требуется либо анонимный класс с помощью метода JoshuaTaylor, либо любой другой класс с помощью метода RealSkeptic.
2 ответа
Что ж, это можно сделать, но вы должны помнить, что каждый конструктор должен вызывать свой супер-конструктор, явно или неявно. Вот почему вы получаете сообщение "Нет доступного экземпляра типа A из-за некоторого промежуточного вызова конструктора". C
конструктор без аргументов пытается неявно вызвать B
конструктор без аргументов, и он не может сделать это без A
,
Итак, вы исправите C
быть:
public class C extends B {
public C(A enclosing) {
enclosing.super();
}
}
И тогда вы можете создать новый C
используя:
A myA = new A();
C myC = new C(myA);
Ответы на вопросы в комментариях
@Анди Тернер спросила:
Если вы явно передаете A в конструктор C, разве C не может быть статическим и иметь A в качестве "простой старой" переменной-члена в C, для которой вы вызываете требуемые методы?
Следует отметить, что C не является ни статическим, ни внутренним классом. Это отдельный открытый класс, который расширяет внутренний класс B. Реализация класса B может быть неизвестна автору C, поэтому он не может знать, какие методы будут использовать A, и не имеет доступа к каким-либо закрытым членам. A, так как C не является членом A. Но B делает, а B требует экземпляра A. Альтернативным подходом будет составление, а не наследование (где C содержит экземпляр B и делегирует ему операции), но если он хочет создать этот экземпляр B, а не передавать его внутрь, ему все равно потребуется экземпляр A, хотя он будет использование
enclosing.new B
скорее, чемenclosing.super
,@rajuGT спросил:
Является ли C отдельным лицом? если так, зачем ему нужен объект? и какова связь между myA и myC в этом случае?
Да, C является отдельным лицом. Ему не понадобится ни один из его собственных методов. Но если он пытается вызвать (или наследует и не переопределяет) методы из B, которые включают в себя доступ к A - тогда это A требуется для реализации B. Формально, конечно, любой экземпляр B требует ссылки на A даже если он на самом деле не использует его. Ассоциация между
myA
а такжеmyC
это то, чтоmyA
является непосредственным вмещающим экземпляромmyC
в отношенииB
, Этот термин взят из раздела 8.1.3 JLS:За каждый суперкласс
S
изC
который сам является прямым внутренним классом класса или интерфейсаSO
есть случайSO
связаны сi
, известный как немедленно включающий экземплярi
в отношенииS
, Непосредственно включающий экземпляр объекта относительно прямого суперкласса его класса, если таковой имеется, определяется, когда конструктор суперкласса вызывается с помощью явного оператора вызова конструктора (§8.8.7.1)
Официальная ссылка для этого использования
Это использование называется квалифицированным оператором вызова конструктора суперкласса и упоминается в JLS, раздел 8.8.7.1 - Явные вызовы конструктора.
Вызовы конструктора суперкласса начинаются с любого ключевого слова
super
(возможно, с предваряющими явными аргументами типа) или Первичное выражение или ExpressionName. Они используются для вызова конструктора прямого суперкласса. Они далее разделены:
Неквалифицированные вызовы конструктора суперкласса начинаются с ключевого слова
super
(возможно, с предваряющими явными аргументами типа).Квалифицированные вызовы конструктора суперкласса начинаются с первичного выражения или ExpressionName. Они позволяют конструктору подкласса явным образом указывать экземпляр объекта, который немедленно создается, относительно прямого суперкласса ( §8.1.3). Это может быть необходимо, когда суперкласс является внутренним классом.
В конце этого раздела вы можете найти примеры явных операторов вызова конструктора, включая это использование.
Вы можете легко расширять вложенные статические классы
Обновление: вы упомянули, что вам не нужно это первое решение, но формулировка вопроса может привести к тому, что люди, желающие, чтобы внутренний класс был статичным, поэтому я оставлю это в надежде, что это полезно им. Более правильный ответ на ваш точный вопрос находится во втором разделе этого ответа.
Вы можете, но внутренний класс должен быть статическим, потому что если это не так, то каждый экземпляр внутреннего класса имеет ссылку на включающий экземпляр внешнего класса. Статический вложенный класс не имеет этой ссылки, и вы можете свободно расширять его.
public class Outer {
public static class Inner {
}
}
public class InnerExtension extends Outer.Inner {
}
Но вы также можете расширить вложенные нестатические классы
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