Ранее были рассмотрены правила объявления классов с учетом их наследования. В этой лекции было введено понятие переопределенного метода. Однако полиморфизм требует более глубокого изучения. При объявлении одноименных полей или методов с совпадающими сигнатурами происходит перекрытие элементов из родительского и наследующего класса. Рассмотрим, как функционируют классы и объекты в таких ситуациях.
Поля
Начнем с полей, которые могут быть статическими или динамическими. Рассмотрим пример:
class Parent { int а=2;}
class Child extends Parent {inta=3; }
Прежде всего, нужно сказать, что такое объявление корректно. Наследники могут объявлять поля с любыми именами, даже совпадающими с родительскими. Затем, необходимо понять, как два одноименных поля будут сосуществовать. Действительно, объекты класса Child будут содержать сразу две переменных, а поскольку они могут отличаться не только значением, но и типом (ведь это два независимых поля), именно компилятор будет определять, какое из значений использовать. Компилятор может опираться только на тип ссылки, с помощью которой происходитобращение к полю:
Child с = new ChildO; System.out.printIn{c.a); Parent p = с; System.out.println(p.a);
Обе ссылки указывают на один и тот же объект, порожденный от класса Child, но одна из них имеет такой же тип, а другая - Parent. Отсюда следуют и результаты: 3 2
Объявление поля в классе-наследнике "скрыло" родительское поле. Данное объявление так и называется — "скрывающим" (hiding). Это особый случай перекрытия областей видимости, отличный от "затеняющего" (shadowing) и "заслоняющего" (obscuring) объявлений. Тем не менее, родительское поле продолжает существовать. К нему можно обратиться и явно:
class Child extends Parent {
int a=3; // скрывающее объявление
int b=((Parent)this).a; // более громоздкое объявление
int c=super.a; // более простое }
Переменные b и с полнят значение, хранящееся в родительском поле а. Хотя выражение с super более простое, оно не позволит обратиться на два уровня вверх по дереву наследования. А ведь вполне возможно, что в родительском классе это поле также было скрывающим и в родителе родителя хранится еще одно значение. К нему можно обратиться явным приведением, как это делается для Ь.
Рассмотрим следующий пример:
class Parent {int х=0;
public void printXO { System.out.println{x);
} } class Child extends Parent {intx=-1; }
Каков будет результат следующих строк?
newChild().printX();
Значение какого поля будет распечатано? Метод вызывается с помощью ссылки типа Child, но это не сыграет никакой роли. Вызывается метод, определенный в классе Parent, и компилятор, конечно, расценил обращение к полю X в этом методе именно как к полю класса Parent. Поэтому результатом будет 0.
Перейдем к статическим полям. На самом деле, для них проблем и конфликтов, связанных с полиморфизмом, не существует. Рассмотрим пример:
class Parent {
static int a=2; } class Child extends Parent {
static int a=3; } Каков будет результат следующих строк?
Childc = newChild{); System.out. println(c.a); Parent p = c; System.out.println(p.a);
Нужно вспомнить, как компилятор обрабатывает обращения к статистическим полям через ссылочные значения. Неважно, на какой объект называет ссылка. Более того, она может быть даже равна null. Все определяется типом ссылки.