Различное поведение члена внутреннего класса, если внутренний класс расширяет внешний класс?
Сегодня я наткнулся на какое-то странное внутреннее (нестатическое) поведение класса.
Если у меня есть следующие классы...
class B {
String val = "old";
void run(){
val = "new";
System.out.println(val); // outputs: new
new InnerB().printVal(); // outputs: new
}
private class InnerB {
void printVal(){ System.out.println(val); }
}
}
new B().run();
... все кажется ясным. Экземпляр InnerB принадлежит экземпляру B, поэтому, если он должен выводить val, он печатает уже замененное значение "new".
НО, если внутренний класс расширяет внешний класс, это не работает.
class B {
String val = "old";
void run(){
val = "new";
System.out.println(val); // outputs: new
new InnerB().printVal(); // outputs: new
new InheritedB().printVal(); // outputs: old new
}
private class InnerB {
void printVal(){ System.out.println(val); }
}
private class InheritedB extends B{
void printVal(){ System.out.println(val + " "+ B.this.val); }
}
}
new B().run(); // outputs: new new old!
Если я посмотрю на конструкторы, я также вижу, что новый экземпляр B будет создан, если создан экземпляр InheritedB.
Я нахожу это очень странным... Может кто-нибудь объяснить, почему существует эта разница?
Ответы
Ответ 1
Эта строка:
new InheritedB().printVal();
создает новый экземпляр InheritedB
, содержащий экземпляр которого является существующим экземпляром B
(где val is "new"
). Но на данный момент есть две переменные val
:
- В существующем экземпляре
B
- В случае
InheritedB
, который имеет отдельное поле val
Значение второй переменной "old"
, потому что это фактически значение по умолчанию для поля.
Это утверждение в InheritedB
:
System.out.println(val + " "+ B.this.val);
выводит значение val
, унаследованное от B
, за которым следует значение val
в "содержащем экземпляре".
Возможно, было бы проще подумать, что он реорганизован на:
public class B
{
String val = "old";
}
public class InheritedB extends B {
B other;
public InheritedB(B other)
{
this.other = other;
}
void printVal() {
System.out.println(val + " "+ other.val);
}
}
Затем вы в основном работаете:
B original = new B();
original.val = "new":
InheritedB inherited = new InheritedB(original);
inherited.printVal();
Надеюсь, вы сможете точно следить за тем, что там происходит. Компилятор грубо выполняет ваш исходный код в этот код.
Ответ 2
val
в InheritedB
относится к val
из его базового класса (super.val
), так как эта часть this
.
Если вы не наследуете внешний класс, val
ссылается на область действия внешнего класса (B.this.scope
). Однако, поскольку вы наследуете, this
ближе по объему и, следовательно, скрывает внешнюю область.
Поскольку вы никогда не вызывали run()
на внутреннем this
, this.val
все еще old
.
Если я посмотрю на конструкторы, я также вижу, что новый экземпляр B будет создан, если создан экземпляр InheritedB.
Да; создание производного класса всегда будет создавать экземпляр базового класса. Нет способа наследовать от существующего экземпляра.
Ответ 3
Поскольку InheritedB extends B
, создание экземпляра InheritedB предоставляет атрибут val
, который по умолчанию является "старым" для любого класса new класса B или подкласса.
Здесь InheritedB
печатает свой собственный val
атрибут, а не экземпляр экземпляра B.
Ответ 4
В случае InheritedB
существуют две переменные, называемые val
, одна из B
и одна из InheritedB
. Применение правил видимости дает наблюдаемый результат.
Ответ 5
Разница заключается в том, что класс InnerB
не имеет члена val
. где класс InheritedB
расширяет класс B и имеет свою собственную копию члена val
.
void run(){
val = "new"; //<--- modifies B val not InheritedB val
System.out.println(val); // outputs: new
new InnerB().printVal(); // outputs: new
new InheritedB().printVal(); // outputs: old new
}
В приведенном выше кодовом блоке InnerB printVal получает доступ к элементу container val
, значение которого уже было изменено в методе run
для значения new.
Но копия val в объекте InheritedB по-прежнему является " старым" значением, а не изменена, а функция printVal использует это значение.