Как наследование полей экземпляров работает в этом конкретном коде?
class A
{
int a = 2, b = 3;
public void display()
{
int c = a + b;
System.out.println(c);
}
}
class B extends A
{
int a = 5, b = 6;
}
class Tester
{
public static void main(String arr[])
{
A x = new A();
B y = new B();
x.display();
y.display();
}
}
Почему выход выходит как 5,5? И не 5,11?. Как работает метод y.display()
?
Ответы
Ответ 1
почему вывод получается 5,5?
Потому что A.display()
знает только о полях A.a
и A.b
. Это единственные поля, о которых знает любой код в A
. Похоже, вы ожидаете, что объявления в B
будут "переопределять" существующие объявления полей. Они этого не делают. Они объявляют новые поля, которые скрывают существующие поля. Переменные не ведут себя практически так, как это делают методы - концепция переопределения переменной просто не существует. Из JLS раздел 8.3:
Если класс объявляет поле с определенным именем, то объявление этого поля, как говорят, скрывает любые и все доступные объявления полей с тем же именем в суперклассах и суперинтерфейсы класса.
Вы можете получить необходимый эффект, изменив B
так, чтобы его конструктор изменил значения существующих полей, которые он наследует от A
:
class B extends A {
B() {
a = 5;
b = 6;
}
}
Обратите внимание, что это не объявления переменных. Это просто задания. Конечно, в большинстве кодов (ну, в большинстве случаев, я все равно видел) поля в A
были бы частными, поэтому доступ к ним из B
невозможен, но это просто пример с целью объяснения поведения языка.
Ответ 2
В классе A
вы объявляете поля A
и b
. Метод display
использует эти поля. В классе b
вы объявляете НОВЫЕ поля с тем же именем. Вы фактически скрываете старые поля, не "переопределяя" их. Чтобы назначить разные значения для одних и тех же полей, используйте конструктор:
class A {
A(int a, int b) {
this.a = a;
this.b = b;
}
A() {
this(2, 3);
}
int a,b;
public void display() {
int c=a+b;
System.out.println(c);
}
}
class B extends A {
B() {
super(5, 6);
}
}
Ответ 3
При этом:
class B extends A
{
int a = 5, b = 6;
}
вы не переопределяете a
и b
, вы создаете новые переменные с одинаковыми именами. Таким образом, вы получаете четыре переменные (A.a
, A.b
, B.a
, B.b
).
Когда вы вызываете display()
и вычисляете значение c
, будут использоваться A.a
и A.b
, а не B.a
и B.b
Ответ 4
Существует нечто, называемое переменным переопределением. Вот почему вы получаете одинаковый результат в обоих случаях.
Ответ 5
Причина в том, что Java использует концепцию лексической области для разрешения переменных.
В сущности, существует две возможности решения свободных переменных в функции ( "свободный" означает не локальный и не привязанный к параметрам функции):
1) против среды, в которой объявлена функция
2) против среды, в которой функция выполняется (называется)
Java идет первым путем, поэтому свободные переменные в методах разрешаются [статически, во время компиляции] в отношении их лексической области (среды), которая включает в себя:
- параметры метода и локальные переменные метода
- декларации полей в классе, содержащем декларацию метода
- объявления открытого поля в родительском классе
- и т.д., вверх по цепочке наследования
Вы увидите, что это поведение реализовано на большинстве языков программирования, поскольку оно прозрачно для разработчиков и помогает предотвратить ошибки с затенением переменных.
Это противоположно тому, как методы работают в Java:
class A {
public void foo() {
boo();
}
public void boo() {
System.out.println("A");
}
}
class B extends A {
@Override
public void boo() {
System.out.println("B");
}
}
class Main {
public static void main(String[] args) {
B b = new B();
b.foo(); // outputs "B"
}
}
Это называется динамической диспетчером: вызов метода динамически решается во время выполнения против фактического объекта, на который он вызывается.
Ответ 6
Когда вы компилируете свой код, он в значительной степени становится следующим:
class A extends java.lang.Object
{
int a=2,b=3;
public void display()
{
int c=a+b;
System.out.println(c);
}
}
class B extends A
{
int a = 5, b = 6;
public void display()
{
super(); //When you call y.display() then this statement executes.
}
}
class Tester
{
public static void main(String arr[])
{
A x = new A();
B y = new B();
x.display();
y.display();
}
}
И, следовательно, при супервызове вызывается метод class A
.
Теперь перейдите к методу class A
. Здесь int c = a + b;
означает
c = this.a + this.b;
, который равен 2 + 3.
И результат 5.
Ответ 7
Класс B объявляет переменные в области B
, public void display()
является частью класса A
и знает только свои собственные переменные области видимости.
Ответ 8
Это функционал наследования, который дает выход 5,5
.
Ответ 9
Java не имеет ничего похожего на переопределение переменной. Таким образом, при вызове метода display() он обращается к переменным внутри родительского класса "A", а не к переменным внутри подкласса "B".
Это можно объяснить по той же причине, почему вы не можете напечатать переменную, объявленную в подклассе (а не в суперклассе) внутри метода суперкласса. Метод суперкласса просто не имеет доступа к переменным подкласса.
Однако вы сможете распечатать 5,11, если у вас есть методы доступа к полям обоих классов, и вы используете эти методы доступа, чтобы получить значения вместо прямого доступа с использованием имен переменных. (даже если метод display() присутствует только в суперклассе). Это связано с тем, что вызывается переопределенные методы доступа (во втором случае), которые возвращают значения из подкласса.