it-swarm.com.ru

Объясните, как работает скрытие переменных в этом коде Java

Рассмотрим код ниже

class A
{
    int x = 5;
    void foo()
    {
        System.out.println(this.x);
    }
}
class B extends A
{
    int x = 6;
    // some extra stuff
}
class C
{
    public static void main(String args[])
    {
         B b = new B();
         System.out.println(b.x);
         System.out.println(((A)b).x);
         b.foo();
    }
 }  

Выход программы 

6
5
5

Я понимаю первые два, но не могу разобраться с последним. Как b.foo () печатает 5. B класс унаследует метод foo. Но не должен ли он печатать то, что печатал бы b.x? Что именно здесь происходит?

21
Parzival

Да, класс B наследует метод foo. Но переменная x в B скрывает x в A; это не заменит это.

Это вопрос объема. Метод foo в A видит только те переменные, которые находятся в области видимости. Единственная переменная в области видимости - это переменная экземпляра x в A.

Метод foo наследуется, но не переопределяется, в B. Если вы должны были явно переопределить foo с тем же точным кодом:

class B extends A
{
    int x = 6;

    @Override
    void foo()
    {
        System.out.println(this.x);
    }
}

Тогда переменная, которая будет находиться в области видимости, когда на нее ссылается this.x, будет B's x, и будет напечатан 6. Хотя текст метода один и тот же, ссылка отличается из-за области действия.

Кстати, если вы действительно хотите сослаться на A's x в классе B, вы можете использовать super.x.

18
rgettman

Поля не могут быть переопределены в Java и подклассах с теми же именами полей, что и родительский класс, теневые «только» поля родительского класса.
Таким образом, this.x ссылается на x, определенный в текущем классе: A.
Тогда как результат: 5

Чтобы быть более точным: метод foo() наследуется подклассом B, но это не означает, что поведение наследуемого метода изменится в отношении полей экземпляра, на которые ссылаются, поскольку указанные поля не могут быть переопределены: выражение this.x, которое ссылается на поле A.x в метод foo() продолжает ссылаться на A.x

Это то же самое, что и для двух предыдущих утверждений: 

 B b = new B();
 System.out.println(b.x); // refers B.x -> 6
 System.out.println(((A)b).x); // refers A.x -> 5
 b.foo(); // refers under the hood A.x -> 5

Очень хороший ответ rgettman показывает, как вы можете преодолеть скрытие поля в подклассе.
Альтернатива для преодоления сокрытия заключается в создании поля экземпляра private (что рекомендуется) и предоставлении метода, который возвращает значение.
Таким образом, вы получаете выгоду от механизма переопределения, и скрытие полей больше не является проблемой для клиентов классов:

class A
{
    private int x = 5;

    int getX(){
        return x;
    }

    void foo()
    {
        System.out.println(this.getX());
    }
}
class B extends A
{
    private int x = 6;

    int getX(){
        return x;
    }
}
2
davidxxx

Ну, это из-за статического связывания.

1) Статическое связывание в Java происходит во время компиляции, тогда как Dynamic привязка происходит во время выполнения.

2) частные методы, финальные методы и статические методы и переменные использует статическое связывание и связывается компилятором, в то время как виртуальные методы связан во время выполнения на основе объекта времени выполнения.

3) Статическая привязка использует информацию Типа (класса в Java) для привязки в то время как динамическое связывание использует объект для разрешения связывания.

4) Перегруженные методы связываются с использованием статического связывания при переопределении методы связаны с использованием динамического связывания во время выполнения.

2
Michel_T.

В Java методы могут быть переопределены, а переменные - нет. Таким образом, поскольку ваш метод foo не переопределяется в B, он принимает переменную-член из A.

1
Anatolii

Когда вы звоните 

b.foo(); 

Он проверяет, переопределил ли B метод foo(), которого у него нет. Затем он переходит на один уровень вверх к суперклассу A и вызывает этот метод.

Затем вы вызвали A версии foo(), которая затем распечатывает 

this.x

Теперь A не может видеть версию Bx.


Чтобы решить эту проблему, вы должны переопределить метод в B

class B extends A
{
    int x = 6;

    @Override
    void foo()
    {
        System.out.println(this.x);
    }

}

Теперь звоню

b.foo();

позвонит B версии foo(), и вы получите ожидаемый результат.

0
Andreas DM