Подтвердить что ты не робот

Различное поведение члена внутреннего класса, если внутренний класс расширяет внешний класс?

Сегодня я наткнулся на какое-то странное внутреннее (нестатическое) поведение класса.

Если у меня есть следующие классы...

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.

Я нахожу это очень странным... Может кто-нибудь объяснить, почему существует эта разница?

4b9b3361

Ответ 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 использует это значение.