На предыдущем
занятии мы с вами рассмотрели основу механизма наследования. Здесь затронем
некоторые важные нюансы этого процесса и начнем с рассмотрения ключевого слова super.
Ключевое слово super
Часто в базовых
классах имеются несколько конструкторов. Как в этом случае узнать, какой из
конструкторов будет вызван при создании объекта дочернего класса? Например, объявим два класса:
class Properties {
int width, color;
Properties() {
System.out.println("Конструктор Properties()");
}
Properties(int width, int color) {
this.width = width;
this.color = color;
System.out.println("Конструктор Properties(width, color)");
}
}
class Line extends Properties {
double x1, y1;
double x2, y2;
Line() {
System.out.println("Конструктор Line()");
}
}
Здесь в базовом
классе Properties два
конструктора. Если теперь выполнить создание объекта дочернего класса Line:
то в консоли
увидим строчки:
Конструктор
Properties()
Конструктор
Line()
Это говорит о
том, что при создании объекта базового класса, вызывается или конструктор без
аргументов, или конструктор по умолчанию. Причем, конструктор по умолчанию
будет существовать, только если в классе не объявлены никакие другие конструкторы.
То есть, если конструктор Properties() закомментировать и оставить
только второй, то объект дочернего класса Line не будет создан,
возникнет ошибка, т.к. отсутствует конструктор без аргументов. А вот если в
комментарии поставить и второй конструктор, то все заработает благодаря
появлению конструктора по умолчанию.
Но, что если мы
хотим воспользоваться вторым конструктором базового класса с двумя аргументами?
В этом случае, при вызове конструктора дочернего класса нужно явно указать
какой конструктор базового класса следует использовать. Это делается с помощью
ключевого слова super, следующим образом:
class Line extends Properties {
double x1, y1;
double x2, y2;
Line() {
super(0, 0);
System.out.println("Конструктор Line()");
}
}
Здесь в
конструкторе Line первой строчкой
идет вызов конструктора базового класса с двумя аргументами. Обратите внимание,
вызывать конструктор базового класса следует сразу же в первой строчке, т.к.
сначала должен быть создан объект базового класса, а затем уже, объект
дочернего класса.
При запуске
программы увидим две строчки:
Конструктор
Properties(width, color)
Конструктор
Line()
Причем, если у
нас цепочка из нескольких классов, унаследованных друг от друга, то super будет ссылаться
на ближайший базовый класс:
Ключевое слово super в роли ссылки
После создания
объекта базового класса, ключевое слово super можно
использовать как ссылку на этот объект. Например, определим в базовом классе
целочисленное поле id со значением 1:
и точно такое же
поле в дочернем классе, но со значением 2:
Как теперь из
дочернего класса обратиться к id базового класса?
Да, это можно сделать через ссылку super:
class Line extends Properties {
int id = 2;
...
void showId() {
System.out.println("id = "+ id + ", super.id = " + super.id);
}
}
При вызове этого
метода, в консоли увидим:
id = 2, super.id = 1
Также, если
определить метод showId() в базовом классе:
class Properties {
int id = 1;
...
void showId() {
System.out.println("id = "+ id);
}
}
то мы можем к
нему обратиться из дочернего через ссылку super:
class Line extends Properties {
int id = 2;
...
void showId() {
super.showId();
System.out.println("id = "+ id + ", super.id = " + super.id);
}
}
Вот так можно
вызывать переопределенные поля и методы из объекта базового класса, используя
ссылку super.
Оператор instanceof
В
действительности в Java любой класс автоматически наследуется от
предопределенного класса Object. То есть, иерархию наследования
в нашем примере следовало бы изобразить вот так:
И объекты
дочерних классов можно было бы создавать, используя ссылку типа Object:
При этом тип
данных для ссылок g1, g2, g3 будет
автоматически приведен к типу Object, т.к. это базовый суперкласс для
всех этих объектов. Это, так называемое, восходящее преобразование
(по-английски upcasting). Но мы можем
выполнять и нисходящие преобразования: от ссылок на базовые классы к ссылкам на
дочерние классы. Такое обратное преобразование типов автоматически уже не
происходит и мы должны явно указать, какой тип дочернего класса нам нужен.
Например,
так:
И смотрите, что
может произойти. Когда мы пытаемся ссылку g2 привести к
типу Line, то в процессе
выполнения программы возникнет исключение (ошибка), что класс Triangle не может быть
приведен к классу Line. И это логично, т.к. оба класса – самостоятельные
дочерние, унаследованные от Properties. Нельзя один подменить другим. Но,
хорошо, мы в этой простой программе знаем, что g2 – это ссылка
на Triangle. А как быть в
сложных проектах, когда имеется обобщенная ссылка и нам важно знать, какие
дочерние классы включены в объект, на который ссылается g2? Для таких
случаев существует оператор
<ссылка> instanceof <имя класса>
который
возвращает true, если имя дочернего
класса содержится в объекте, по указанной ссылке. Например, прежде чем
выполнять нисходящее преобразование типов, было бы правильно проверить, а
содержится ли объект дочернего класса в объекте, к которому мы хотим привести
тип данных:
Теперь, при
запуске программы у нас никаких ошибок не будет, т.к. проверка g2 instanceof Line будет ложной и
преобразование не выполняется.
Путь кодера
Подвиг 1. Объявите
базовый класс Stationery для
описания канцтоваров с полями: цена, наличие на складе, идентификатор. В этом
классе реализовать два конструктора: без аргументов и с тремя аргументами для
инициализации его полей. Затем, объявить производные от него классы для
описания: ручек, карандашей, тетрадей. В дочерних классах реализовать несколько
конструкторов с вызовом конструктора базового класса без аргументов и с
аргументами. Также в производных классах придумайте разные поля (разные у
разных классов) и пропишите методы в соответствующих классах:
-
displayPen() – для
отображения данных по ручкам;
-
displayPencil() – для
отображения данных по карандашам;
-
displayNotebook() – для
отображения данных по тетрадям.
Создайте объекты
дочерних классов и выведите информацию по ним в консоль.
Подвиг 2. Используя
классы из первого подвига, создайте динамический массив со ссылками Object на экземпляры
дочерних классов. Через эти ссылки вызовите методы displayPen(),displayPencil() и displayNotebook() дочерних
классов (используя нисходящее приведение типов).