Что происходит, когда базовый и производный классы имеют переменные с одинаковым именем
Рассмотрим переменные int a
в этих классах:
class Foo {
public int a = 3;
public void addFive() { a += 5; System.out.print("f "); }
}
class Bar extends Foo {
public int a = 8;
public void addFive() { this.a += 5; System.out.print("b " ); }
}
public class test {
public static void main(String [] args){
Foo f = new Bar();
f.addFive();
System.out.println(f.a);
}
}
Я понимаю, что метод addFive()
был переопределен в дочернем классе, а в тесте класса, когда ссылка базового класса, ссылающаяся на дочерний класс, используется для вызова переопределенного метода, вызывается версия дочернего класса addFive
.
Но как насчет переменной public экземпляра a
? Что происходит, когда и базовый класс, и производный класс имеют одну и ту же переменную?
Вывод вышеуказанной программы
b 3
Как это происходит?
Ответы
Ответ 1
Есть на самом деле две различные общедоступные переменные, называемые. a
- У объекта Foo есть переменная
Foo.a
- Объект Bar имеет переменные
Foo.a
и Bar.a
Когда вы запустите это:
Foo f = new Bar();
f.addFive();
System.out.println(f.a);
метод addFive
обновляет переменную Bar.a
, а затем читает переменную Foo.a
Чтобы прочитать переменную Bar.a
, вам нужно сделать это:
System.out.println(((Bar) f).a);
Технический термин для того, что здесь происходит, - "прятаться". Обратитесь к разделу 8.3 JLS и разделу 8.3.3.2 для примера.
Обратите внимание, что скрытие также применяется к static
методам с одинаковой сигнатурой.
Однако методы экземпляра с одинаковой сигнатурой "переопределяются", а не "скрыты", и вы не можете получить доступ к версии метода, которая переопределяется извне. (Внутри класса, который переопределяет метод, переопределенный метод может быть вызван с использованием super
. Однако это единственная ситуация, где это разрешено. Причина, по которой доступ к переопределенным методам обычно запрещен, заключается в том, что он нарушает абстракцию данных.)
Рекомендуемый способ избежать путаницы (случайного) сокрытия - объявить переменные вашего экземпляра как private
и получить к ним доступ через методы getter и setter. Есть много других веских причин использовать геттеры и сеттеры тоже.
Следует также отметить, что: 1) Предоставление открытых переменных (например, a
), как правило, является плохой идеей, поскольку приводит к слабой абстракции, нежелательному связыванию и другим проблемам. 2) Умышленное объявить 2 - ой общественности a
переменной в классе ребенка это действительно ужасная идея.
Ответ 2
Из JLS
8.3.3.2 Пример: Скрытие переменных экземпляра Этот пример аналогичен что в предыдущем разделе, но использует переменные экземпляра, а не статические переменные. Код:
class Point {
int x = 2;
}
class Test extends Point {
double x = 4.7;
void printBoth() {
System.out.println(x + " " + super.x);
}
public static void main(String[] args) {
Test sample = new Test();
sample.printBoth();
System.out.println(sample.x + " " +
((Point)sample).x);
}
}
выводит результат:
4.7 2
4.7 2
потому что объявление x в классе Тест скрывает определение x в class Point, поэтому класс Test не наследует поле x от его суперкласс Point. Следует отметить, однако, что хотя поле x of class Point не наследуется классом Тестирование, тем не менее, выполнено по экземплярам класса Test. В других слова, каждый экземпляр класса Test содержит два поля, один из типов int и один из двух типов. Оба поля несут имя x, но в пределах декларация класса Test, простая имя x всегда относится к полю объявлен в классе Test. Код в методы экземпляра класса Test могут обратитесь к переменной экземпляра x class Point as super.x.
Код, который использует доступ к полю выражение для поля доступа x будет получить доступ к полю с именем x в классе обозначенный типом ссылки выражение. Таким образом, выражение sample.x обращается к двойному значению, переменная экземпляра, объявленная в классе Тест, потому что тип переменной образцом является Тест, но выражение ((Point)).x обращается к int значение, объявленная переменная экземпляра в классе Point, из-за типа.
Ответ 3
В наследовании объект базового класса может ссылаться на экземпляр класса Derived.
Так вот как Foo f = new Bar();
работает нормально.
Теперь, когда вызывается оператор f.addFive();
, он фактически вызывает метод addFive() экземпляра класса Derived, используя ссылочную переменную класса Base. Таким образом, в конечном итоге вызывается метод класса "Бар". Но, как вы видите, метод addFive()
класса "Bar" просто печатает "b", а не значение "a".
Следующий оператор, т.е. System.out.println(f.a)
, тот, который фактически печатает значение a, которое в конечном итоге добавляется к предыдущему выводу, и поэтому вы видите окончательный вывод как "b 3". Здесь используется значение используемого класса Foo.
Надеюсь, что это трюк, исполнение и кодирование понятны, и вы поняли, как вы получили вывод как "b 3".
Ответ 4
Здесь F имеет тип Foo и f variable содержит объект Bar, но java runtime получает fa из класса Foo.Это связано с тем, что имена переменных Java разрешаются с использованием ссылочного типа, а не объекта, к которому он относится.