Объясните, как сглаживание переменных работает в этом 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. Но не следует ли печатать, что будет печатать bx? Что именно здесь происходит?

Ответ 1

Да, класс 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 x, а 6 будет напечатано. Хотя текст метода одинаков, ссылка отличается от области видимости.

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

Ответ 2

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

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

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

 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;
    }
}

Ответ 3

Ну, это из-за статической привязки.

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

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

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

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

Ответ 4

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

Ответ 5

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

b.foo(); 

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

Вы тогда вызывается A версия foo(), который затем печатает

this.x

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


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

class B extends A
{
    int x = 6;

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

}

Теперь, позвонив

b.foo();

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