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