Наследование. Суперклассы и подклассы. Переопределение методов
В объектном программировании принято использовать имеющиеся классы в качестве “заготовок” для создания новых классов, которые на них похожи, но обладают более сложной структурой и/или отличающимся поведением. Такие “заготовки” называются прародителями (ancestors), а основанные на них новые классы – потомками (descendants) или наследниками. Классы-потомки получают “в пользование” поля и методы, заданные в классах-прародителях, это называется наследованием (inheritance) полей и методов.
В C++ и Java вместо терминов “прародители” и “потомки” чаще используют неудачные названия “суперклассы” (superclasses) и “подклассы” (subclasses). Как уже говорилось, суперклассы должны быть примитивнее подклассов, но приставка “супер” подталкивает программиста к прямо противоположным действиям.
При задании класса-потомка сначала идут модификаторы, затем после ключевого слова class идёт имя декларируемого класса, затем идёт зарезервированное слово extends (“расширяет”), после чего требуется указать имя класса-родителя (непосредственного прародителя). Если не указывается, от какого класса идёт наследование, родителем считается класс Object. Сам класс-потомок называется наследником, или дочерним.
В синтаксисе Java словом extends подчёркивается, что потомок расширяет то, что задано в прародителе – добавляет новые поля, методы, усложняет поведение. (Но всё это делает класс более специализированным, менее общим).
Далее в фигурных скобках идёт реализация класса – описание его полей и методов. При этом поля данных и методы, имеющиеся в прародителе, в потомке описывать не надо – они наследуются. Однако в случае, если реализация прародительского метода нас не устраивает, в классе-потомке его можно реализовать по другому. В этом случае метод необходимо продекларировать и реализовать в классе-потомке. Кроме того, в потомке можно задавать новые поля данных и методы, отсутствующие в прародителях.
Модификаторы, которые можно использовать:
public – модификатор, задающий публичный (общедоступный) уровень видимости. Если он отсутствует, действует пакетный уровень доступа - класс доступен только элементам того же пакета.
abstract – модификатор, указывающий, что класс является абстрактным, то есть у него не бывает экземпляров (объектов). Обязательно объявлять класс абстрактным в случае, если какой-либо метод объявлен как абстрактный.
final – модификатор, указывающий, что класс является окончательным (final) , то есть что у него не может быть потомков.
Таким образом, задание класса-наследника имеет следующий формат:
Модификаторы class ИмяКласса extends ИмяРодителя {
Задание полей;
Задание подпрограмм - методов класса, методов объекта, конструкторов
}
Данный формат относится к классам, не реализующим интерфейсы (interfaces). Работе с интерфейсами будет посвящён отдельный раздел.
Рассмотрим в качестве примера наследование для классов описанной ранее иерархии фигур. Для простоты выберем вариант, в котором Figure - это класс-прародитель иерархии, Dot его потомок, а Circle - потомок Dot (то есть является “жирной точкой”). Напомним, что имена классов принято начинать с заглавной буквы.
Класс Figure опишем как абстрактный – объектов такого типа создавать не предполагается, так как фигура без указания конкретного вида – это, действительно, чистая абстракция. По той же причине методы show (“показать”) и hide (“скрыть”) объявлены как абстрактные. Напомним также, что если в классе хоть один метод является абстрактным, это класс обязан быть объявлен как абстрактный.
public abstract class Figure { //это абстрактный класс
int x=0;
int y=0;
java.awt.Color color;
java.awt.Graphics graphics;
java.awt.Color bgColor;
public abstract void show(); //это абстрактный метод
public abstract void hide(); //это абстрактный метод
public void moveTo(int x, int y){
hide();
this.x= x;
this.y= y;
show();
};
}
Поля x и y задают координаты фигуры, а color – её цвет. Соответствующий тип задан в пакете java.awt. Поле graphics задаёт ссылку на графическую поверхность, по которой будет идти отрисовка фигуры. Соответствующий тип также задан в пакете java.awt. В отличии от полей x, y и color для этого поля при написании класса невозможно задать начальное значение, и оно будет присвоено при создании объекта. То же относится к полю bgColor (от “background color”) – в нём мы будем хранить ссылку на цвет фона графической поверхности. Цветом фона мы будем выводить фигуру в методе hide для того, чтобы она перестала показываться на экране. Это не самый лучший, но зато самый простой способ скрыть фигуру. В дальнейшем при желании реализацию метода можно изменить – это никак не коснётся остальных частей программы. В параграфе, посвящённом конструкторам, в классе FilledCircle мы применим более совершенный способ отрисовки и “скрывания” фигур, основанный на использовании режима рисования XOR (“исключающее или”). Установка этого режима производится методом setXORMode. Такой режим можно использовать для всех наших фигур.
Метод moveTo имеет реализацию несмотря на то, что класс абстрактный, и в этой реализации используются имена абстрактных методов show и hide. Этот вопрос будет подробно обсуждаться в следующем параграфе, посвящённом полиморфизму.
Рассмотрим теперь, как задаётся потомок класса Figure – класс Dot (“Точка”). Для Dot классы Object и Figure будут являться прародителями (суперклассами), причёт Figure будет непосредственным прародителем. Соответственно, для них класс Dot будет являться потомком (подклассом), причём для класса Figure – непосредственным потомком. Класс Dot расширяет (extends) функциональность класса Figure: хотя в нём и не появляется новых полей, зато пишется реализация для методов show и hide, которые в прародительском классе были абстрактными. В классе Figure мы использовали классы пакета java.awt без импорта этого пакета. В классе Dot используется импорт – обычно это удобнее, так как не надо много раз писать длинные имена.
package java_gui_example;
import java.awt.*;
/**
* @author В.В.Монахов
*/
public class Dot extends Figure{
/** Создаёт новый экземпляр типа Dot */
public Dot(Graphics graphics,Color bgColor) {
this.graphics=graphics;
this.bgColor=bgColor;
}
public void show(){
Color oldC=graphics.getColor();
graphics.setColor(Color.BLACK);
graphics.drawLine(x,y,x,y);
graphics.setColor(oldC);
}
public void hide(){
Color oldC=graphics.getColor();
graphics.setColor(bgColor);
graphics.drawLine(x,y,x,y);
graphics.setColor(oldC);
;
}
}
Отметим, что в классе Dot не задаются поля x, y, graphics и метод moveTo – они наследуются из класса Figure. А методы show и hide переопределяются (override) – для них пишется реализация, соответствующая тому, каким именно образом точка появляется и скрывается на экране.
Конструктор Dot(int x, int y, Graphics g) занимается созданием объекта типа Dot и инициализацией его полей. В методах show и hide используются методы объекта graphics. В методе show сначала во временной переменной oldC сохраняется информация о текущем цвете рисования. Затем в качестве текущего цвета устанавливается цёрный цвет (константа java.awt. Color.BLACK). Затем вызывается метод, рисующий точку, в качестве него используется рисование линии с совпадающими началом и концом. После чего восстанавливается первоначальный цвет рисования. Это необходимо для того, чтобы не повлиять на поведение других объектов, пользующихся для каких-либо целей текущим цветом. Такого рода действия являются очень характерными при пользовании разделяемыми (shared) ресурсами. Если вам при работе какого-либо метода требуется изменить состояние разделяемых внешних данных, сначала требуется сохранить информацию о текущем состоянии, а в конце вызова восстановить это состояние.
Термин override (“переопределить”) на русский язык часто переводят как “перекрыть”. Это может вводить в заблуждение, так как имеется ещё одно понятие – перекрытие области видимости (hiding). Такое перекрытие возникает в случае, когда в классе-потомке задаётся поле с тем же именем, что и в прародителе (но, возможно, другого типа). Для методов совпадение имён разрешено, в том числе с именами глобальных и локальных переменных.
Имя метода в сочетании с числом параметров и их типами называется его сигнатурой. А сигнатура метода в сочетании с типом возвращаемого значения называется контрактом метода. В контракт также входят типы возбуждаемых методом исключений, но о соответствующих правилах будет говориться в отдельном параграфе, посвящённом обработке исключительных ситуаций.
Если контракт задаваемого метода совпадает с контрактом прародительского метода, говорят, что метод переопределён. Если у двух методов имена совпадают, но сигнатуры различны – говорят, что производится перегрузка (overloading) методов. Перегрузке методов далее будет посвящён отдельный параграф. Если же в одном классе два метода имеют одинаковые сигнатуры, то даже если их контракты отличаются, компилятор выдаёт сообщение об ошибке.
В классе нашего приложения создадим на экранной форме панель, и будем вести отрисовку по ней. Зададим с помощью редактора свойств белый (или какой-нибудь другой) цвет панели – свойство background. Затем зададим переменную dot, которой назначим объект в обработчике нажатия на кнопку:
Dot dot=new Dot(jPanel1.getGraphics(),jPanel1.getBackground());
После создания объекта-точки с помощью переменной dot можно вызывать методы show и hide:
dot.show();
dot.hide();
Создадим на форме пункты ввода/редактирования текста jTextField1 и jTextField2. В этом случае становится можно вызывать метод moveTo, следующим образом задавая координаты, куда должна перемещаться точка:
int newX=Integer.parseInt(jTextField1.getText());
int newY=Integer.parseInt(jTextField2.getText());
dot.moveTo(newX,newY);
Наш пример оказывается достаточно функциональным для того, чтобы увидеть работу с простейшим объектом.
Рассмотрим теперь класс ScalableFigure (“Масштабируемая фигура”), расширяющий класс Figure. Он очень прост.
package java_gui_example;
public abstract class ScalableFigure extends Figure{
int size;
public void resize(int size) {
hide();
this.size=size;
show();
}
}
!!!Класс ScalableFigure является абстрактным – объектов такого типа создавать не предполагается, так как масштабируемая фигура без указания конкретного вида – это абстракция. По этой же причине в классе не заданы реализации методов show и hide.
Зато появилось поле size (“размер”), и метод resize (“изменить размер”), расширяющий этот класс по сравнению с прародителем. Для того, чтобы изменить размер фигуры, отрисовываемой на экране, надо не только присвоить полю size новое значение, но и правильно перерисовать фигуру. Сначала надо её скрыть, затем изменить значение size, после чего показать на экране – уже нового размера. Следует обратить внимание, что мы пишем данный код на уровне абстракций, для нас не имеет значения, какого типа будет фигура – главное, чтобы она была масштабируемая, то есть являлась экземпляром класса-потомка ScalableFigure. О механизме, позволяющем такому коду правильно работать, будет рассказано далее в параграфе, посвящённом полиморфизму.
Опишем класс Circle (“Окружность”), расширяющий класс ScalableFigure.
package java_gui_example;
import java.awt.*;
public class Circle extends ScalableFigure {
Circle(Graphics g,Color bgColor, int r){ //это конструктор
graphics=g;
this.bgColor=bgColor;
size=r;
}
public void show(){
Color oldC=graphics.getColor();
graphics.setColor(Color.BLACK);
graphics.drawOval(x,y,size,size);
graphics.setColor(oldC);
}
public void hide(){
Color oldC=graphics.getColor();
graphics.setColor(bgColor);
graphics.drawOval(x,y,size,size);
graphics.setColor(oldC);
}
};
В классе Circle не задаётся новых полей – в качестве радиуса окружности используется поле size, унаследованное от класса ScalableFigure. Зато введён конструктор, позволяющий задавать радиус при создании окружности.
Кроме того, написаны новые реализации для методов show и hide, поскольку окружность показывается, скрывается и движется по экрану не так, как точка.
Таким образом, усложнение структуры Circle по сравнением со ScalableFigure в основном связано с появлением реализации у методов, которые до этого были абстрактными. Очевидно, класс Circle является более специализированным по сравнению со ScalableFigure, не говоря уж о Figure.
Поля x, y, color, bgColor, graphics и метод moveTo наследуется в Circle из класса Figure. А из ScalableFigure наследуются поле size и метод resize.
Следует особо подчеркнуть, что наследование относится к классам, а не к объектам. Можно говорить, что один класс является наследником другого. Но категорически нельзя – что один объект является наследником другого объекта. Иногда говорят фразы вроде “объект circle является наследником Figure ”. Это не страшно, если подразумевается, что “объект circle является экземпляром класса-наследника Figure”. Слишком долго произносить правильную фразу. Но следует чётко понимать, что имеется в виду, и злоупотреблять такими оборотами не следует.
Класс Circle является непосредственным (прямым) потомком ScalableFigure , а ScalableFigure – непосредственным (прямым) прародителем класса Circle . То есть для ScalableFigure класс Circle является подклассом, а для Circle класс ScalableFigure является суперклассом. Аналогично, для Figure подклассами являются и ScalableFigure, и Circle. А для Circle суперклассами являются и ScalableFigure, и Figure.
Поскольку в Java все классы— потомки класса Object, то Object является прародителем и для Figure, и для ScalableFigure, и для Circle. Но непосредственным прародителем он будет только для Figure.
- Содержание
- Глава 1. Общие представления о языке Java 6
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans 26
- Глава 3. Примитивные типы данных и операторы для работы с ними 78
- Глава 4. Работа с числами в языке Java 95
- Глава 5. Управляющие конструкции 112
- Глава 6. Начальные сведения об объектном программировании 128
- Глава 7. Важнейшие объектные типы 175
- Введение
- Глава 1. Общие представления о языке Java
- 1.1. Java и другие языки программирования. Системное и прикладное программирование
- 1.2. Виртуальная Java-машина, байт-код, jit-компиляция. Категории программ, написанных на языке Java
- 1.3.Алфавит языка Java. Десятичные и шестнадцатеричные цифры и целые числа. Зарезервированные слова Алфавит языка Java
- Десятичные и шестнадцатеричные цифры и целые числа
- Зарезервированные слова языка Java
- 1.4. Управляющие последовательности. Символы Unicode. Специальные символы Управляющие последовательности
- Простые специальные символы
- Составные специальные символы
- 1.5.Идентификаторы. Переменные и типы. Примитивные и ссылочные типы
- Краткие итоги по главе 1
- Задания
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans
- 2.1.Процедурное и объектно-ориентированное программирование. Инкапсуляция
- 2.2. Работа со ссылочными переменными. Сборка мусора
- 2.3. Проекты NetBeans. Пакеты. Уровни видимости классов. Импорт классов
- 2.4. Базовые пакеты и классы Java
- 2.5. Создание в NetBeans простейшего приложения Java
- 2.6. Компиляция файлов проекта и запуск приложения
- 2.7. Структура проекта NetBeans
- 2.8. Создание в NetBeans приложения Java с графическим интерфейсом
- 2.9. Редактор экранных форм
- 2.10. Внешний вид приложения
- 2.11. Ведение проектов
- 2.11. Редактирование меню экранной формы
- 2.12. Создание нового класса
- 2.13. Документирование исходного кода в Java
- 2.14. Основные компоненты пакетов swing и awt
- 2.15. Технологии Java и .Net
- Краткие итоги по главе 2
- Задания
- Глава 3. Примитивные типы данных и операторы для работы с ними
- 3.1.Булевский (логический) тип
- 3.2.Целые типы, переменные, константы
- 3.3.Основные операторы для работы с целочисленными величинами
- 3.4.Вещественные типы и класс Math
- 3.5.Правила явного и автоматического преобразования типа при работе с числовыми величинами
- 3.6. Оболочечные классы. Упаковка (boxing) и распаковка (unboxing)
- 3.7.Приоритет операторов
- 3.8.Типы-перечисления (enum)
- Краткие итоги по главе 3
- Задания
- Глава 4. Работа с числами в языке Java
- 4.1 Двоичное представление целых чисел Позиционные и непозиционные системы счисления
- Двоичное представление положительных целых чисел
- Двоичное представление отрицательных целых чисел. Дополнительный код
- Проблемы целочисленной машинной арифметики
- Шестнадцатеричное представление целых чисел и перевод из одной системы счисления в другую
- 4.2. Побитовые маски и сдвиги
- 4.3. Двоичное представление вещественных чисел Двоичные дроби
- Мантисса и порядок числа
- Стандарт ieee 754 представления чисел в формате с плавающей точкой*
- Краткие итоги по главе 4
- Задания
- Глава 5. Управляющие конструкции Составной оператор
- Условный оператор if
- Оператор выбора switch
- Условное выражение …?... : …
- Оператор цикла for
- Оператор цикла while – цикл с предусловием
- Оператор цикла do...While – цикл с постусловием
- Операторы прерывания continue, break, return, System.Exit
- Краткие итоги по главе 5
- Задания
- Глава 6. Начальные сведения об объектном программировании
- Наследование и полиморфизм. Uml-диаграммы
- Функции. Модификаторы. Передача примитивных типов в функции
- Локальные и глобальные переменные. Модификаторы доступа и правила видимости. Ссылка this
- Передача ссылочных типов в функции. Проблема изменения ссылки внутри подпрограммы
- Наследование. Суперклассы и подклассы. Переопределение методов
- Наследование и правила видимости. Зарезервированное слово super
- Статическое и динамическое связывание методов. Полиморфизм
- Базовый класс Object
- Конструкторы. Зарезервированные слова super и this. Блоки инициализации
- Удаление неиспользуемых объектов и метод finalize. Проблема деструкторов для сложно устроенных объектов
- Перегрузка методов
- Правила совместимости ссылочных типов как основа использования полиморфного кода. Приведение и проверка типов
- Рефакторинг
- Reverse engineering – построение uml-диаграмм по разработанным классам
- Краткие итоги по главе 6
- Задания
- Глава 7. Важнейшие объектные типы Массивы
- Коллекции, списки, итераторы
- Работа со строками в Java. Строки как объекты. Классы String, StringBuffer и StringBuilder
- Работа с графикой
- Исключительные ситуации Обработка исключительных ситуаций
- Иерархия исключительных ситуаций
- Объявление типа исключительной ситуации и оператор throw
- Объявление метода, который может возбуждать исключительную ситуацию. Зарезервированное слово throws
- Работа с файлами и папками
- Краткие итоги по главе 7
- Задания
- Глава 8. Наследование: проблемы и альтернативы. Интерфейсы. Композиция Проблемы множественного наследования классов. Интерфейсы
- Отличия интерфейсов от классов. Проблемы наследования интерфейсов
- Пример на использование интерфейсов
- Композиция как альтернатива множественному наследованию
- Краткие итоги по главе 8
- Задания
- Глава 9. Дополнительные элементы объектного программирования на языке Java Потоки выполнения (threads) и синхронизация
- Преимущества и проблемы при работе с потоками выполнения
- Синхронизация по ресурсам и событиям
- Класс Thread и интерфейс Runnable. Создание и запуск потока выполнения
- Поля и методы, заданные в классе Thread
- Подключение внешних библиотек dll.“Родные” (native) методы*
- Краткие итоги по главе 9
- Задания
- Глава 10. Введение в сетевое программирование Краткая справка по языку html
- Апплеты
- Сервлеты
- Технология jsp – Java Server Pages
- Краткие итоги по главе 10
- Задания
- Глава 11. Встроенные классы Виды встроенных классов
- Вложенные (nested) классы и интерфейсы
- Внутренние (inner) классы
- Локальные (local) классы
- Анонимные (anonimous) классы и обработчики событий
- Анонимные (anonimous) классы и слушатели событий (listeners)
- Краткие итоги по главе 11
- Задания
- Глава 12. Компонентное программирование Компонентная архитектура JavaBeans
- Мастер создания компонента в NetBeans
- Пример создания компонента в NetBeans – панель с заголовком
- Добавление в компонент новых свойств
- Добавление в компонент новых событий
- Краткие итоги по главе 12
- Задания
- Литература
- Дополнительная литература
- 276 Курс подготовлен при поддержке Sun Microsystems