Что происходит, когда базовый и производный классы имеют переменные с одинаковым именем

Рассмотрим переменные 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 разрешаются с использованием ссылочного типа, а не объекта, к которому он относится.