Один из основополагающих механизмов концепции повторного использования кода в ООП — это наследование. Оно позволяет получить новые классы из существующих. Новый класс (также называемый подкласс, подтип, производный класс, дочерний класс) может унаследовать члены от старого класса (так называемого суперкласса супертипа, базового класса, родительского класса). Подкласс может добавить новое поведение и свойства и при определенных обстоятельствах изменить наследуемое им поведение.
В Java реализация наследования достигается с помощью расширения классов (т.е. добавления новых полей и методов) и изменения унаследованных членов. Наследование членов достаточно тесно связано с их доступностью. Если член суперкласса доступен по своему простому имени в подклассе (без использования какого-то дополнительного синтаксиса, например такого, как super
), то член рассматривается как унаследованный. Это значит, что закрытые (private
), переопределенные и скрытые члены суперкласса не наследуются. Наследование не следует путать с существованием таких членов в состоянии объекта подкласса (см. Пример 6.1).
Суперкласс задается с помощью ключевого слова extends
в заголовке объявления подкласса. В теле класса производного класса определяются только новые и измененные члены. Остальные его объявления составляются из унаследованных методов. Если оператор extends
не дан в заголовке объявления класса, то класс неявно расширяет класс java.lang.Object
. Такое неявное наследование предполагается в объявлении класса Light
в строке (1) в примере 6.1. Также в примере 6.1 подкласс TubeLight
в строке (2) явно использует оператор extends
и определяет дополнительные члены к уже унаследованным из суперкласса Light
(который, в свою очередь, наследует от класса Object
). Члены суперкласса Light
, которые доступны по своим простым именам в подклассе TubeLight
, наследуются подклассом.
Закрытые (private
) члены суперкласса не наследуются подклассом, и обратиться к ним можно только опосредованно. Закрытое поле indicator
суперкласса Light
не наследуется, но существует в объекте подкласса и косвенно доступно.
С помощью соответствующих модификаторов доступа суперкласс может ограничить доступ к своим членам — например, к каким из них можно обратиться напрямую — и таким образом определить, какие члены могут быть унаследованы его подклассами. Как показано в примере 6.1, подкласс может использовать унаследованные члены, как если бы они были объявлены в теле его класса. Но это не распространяется на члены, объявленные как private
в суперклассе. Члены, которые имеют пакетную видимость в суперклассе, также не наследуются подклассами в других пакетах, так как эти члены только доступны по своему простому имени в подклассах внутри того же самого пакета, где находится суперкласс.
Поскольку конструкторы и блоки инициализации не являются членами класса, они не наследуются подклассом.
class Light { // (1)
// Поля экземпляра
int noOfWatts; // мощность
private boolean indicator; // включено или выключено
protected String location; // положение
// Статические поля
private static int counter; // счетчик объектов типа Light
// Конструктор
Light() {
noOfWatts = 50;
indicator = true;
location = "X";
counter++;
}
public void switchOn() { indicator = true; }
public void switchOff() { indicator = false; }
public boolean isOn() { return indicator; }
private void printLocation() {
System.out.println("Location: " + location);
}
// Статические методы
public static void writeCount() {
System.out.println("Number of lights: " + counter);
}
//...
}
class TubeLight extends Light { // (2) подкласс использует extends.
// Поля экземпляра
private int tubeLength = 54;
private int colorNo = 10;
// Методы экземпляра
public int getTubeLength() { return tubeLength; }
public void printInfo() {
System.out.println("Tube length: " + getTubeLength());
System.out.println("Color number: " + colorNo);
System.out.println("Wattage: " + noOfWatts); // Унаследован.
// System.out.println("Indicator: " + indicator); // Не наследуется.
System.out.println("Indicator: " + isOn()); // Унаследован.
System.out.println("Location: " + location); // Унаследован.
// printLocation(); // Не наследуется.
// System.out.println("Counter: " + counter); // Не наследуется.
writeCount(); // Унаследован.
}
// ...
}
public class Utility { // (3)
public static void main(String[] args) {
new TubeLight().printInfo();
}
}
Вывод программы:
Tube length: 54
Color number: 10
Wattage: 50
Indicator: true
Location: X
Number of lights: 1
Класс в Java может расширять только один другой класс; т.е. он может иметь только одного непосредственного родителя. Такой тип наследования иногда называют одиночным или линейным наследованием реализации. Такой выбор имени уместен, потому что подкласс наследует реализации членов своего суперкласса. Отношение наследования может быть изображено как иерархия наследования (также известная как иерархия классов). Верхние классы в иерархии являются более обобщенными, так как они абстрагируются от поведения класса. Классы внизу иерархии более специализированы, потому что они настраивают унаследованное поведение, используя дополнительные свойства и поведение. На рис. 6.1 показано отношение наследования между классом Light
, который представляет абстракцию, и его более специализированными подклассами. Класс java.lang.Object
всегда находится на вершине любой иерархии наследования Java, так как все классы, за исключением самого класса Object
, наследуют (или прямо или косвенно) от этого класса.
Наследование определяет отношение «есть» (is-a) (также известное как суперкласс — подкласс) между суперклассом и его подклассом. Это означает, что объект подкласса может использоваться независимо от того, используется ли суперкласс. Это часто применяется в качестве лакмусовой бумажки для решения вопроса об использовании наследования. В наследовании имеются тонкости в работе с объектами. Объект класса TubeLight
может использоваться независимо от объекта суперкласса Light
. Объект класса TubeLight
есть также объект (is-a) суперкласса Light
. Отношение наследования транзитивно: если класс B
расширяет класс A
, то класс C
, который расширяет класс B
, будет также наследовать от класса A
через класс B
. Объект класса SpotLightBulb
является также объекте (is-a) iкласса Light
. Отношение «есть» (is-a) не поддерживается между одноранговыми классами: объект класса LightBulb
не является объектом класса TubeLight
.
В то время как наследование определяет отношение «есть» (is-a) между суперклассом и его подклассами, агрегация характеризует отношение «имеет» (has-a) между экземпляром класса и его компонентами (или частями). Экземпляр класса Light
имеет следующие части: поле для хранения значения мощности (noOfWatts
), поле для хранения данных о том, включена лампочка или нет (indicator
), и строковый объект для хранения данных о местоположении (обозначенное полем, хранящим значение ссылки, location
). В Java составной объект не может содержать другие объекты. Он может только содержать ссылки на составляющие его объекты. Это отношение определяет иерархию агрегации, которая воплощает отношение «имеет» (has-a). Объекты-компоненты могут совместно использоваться объектами, и их жизненные циклы могут быть -зависимы от жизненного цикла объекта, их содержащего.
Урок 6. Объектно-ориентированное программирование | Концепции ООП |