Объектно-ориентированное программирование в Java : учебное пособие [Ольга Ивановна Гуськова] (pdf) читать онлайн

-  Объектно-ориентированное программирование в Java : учебное пособие  23.48 Мб, 241с. скачать: (pdf) - (pdf+fbd)  читать: (полностью) - (постранично) - Ольга Ивановна Гуськова

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение
высшего образования
«Московский педагогический государственный университет»

О. И. Гуськова

ОБЪЕКТНО ОРИЕНТИРОВАННОЕ
ПРОГРАММИРОВАНИЕ В JAVA
Учебное пособие

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

МПГУ
Москва • 2018

УДК 004.424(075.8)
ББК 32.973-018я73
Г968

Рецензенты:
О. В. Муравьева, кандидат физико-математических наук,
доцент, зам. зав. кафедрой теоретической информатики
и дискретной математики математического факультета МПГУ
В. П. Моисеев, доцент, кандидат технических наук,
доцент кафедры информатики и прикладной математики
Института математики, информатики и естественных наук МГПУ

Г968

Гуськова, Ольга Ивановна.
Объектно ориентированное программирование в Java :
учебное пособие / О. И. Гуськова. – Москва : МПГУ, 2018. – 240 с.
ISBN 978-5-4263-0648-6
Учебное пособие посвящено объектно ориентированному программированию на языке Java. Рассматриваются основные принципы объектно ориентированного программирования, средства работы со структурами данных – коллекции
и дженерики, принципы объектно ориентированного дизайна.

УДК 004.424(075.8)
ББК 32.973-018я73
ISBN 978-5-4263-0648-6

© МПГУ, 2018
© Гуськова О. И., текст, 2018

СОДЕРЖАНИЕ
ВВЕДЕНИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ПРОГРАММИРОВАНИЯ
1.1. Введение в объектно ориентированное
программирование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2. Краткая история развития объектно ориентированного
программирования. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3. Основные принципы объектно ориентированного
программирования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4. Класс и объект . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5. Определение класса в Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6. Создание экземпляров класса . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.7. Оператор «Точка» . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.8. Переменные-члены и методы-члены класса . . . . . . . . . . . . 17
1.9. Пример объектно ориентированного
программирования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.10. Конструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.11. Модификаторы управления доступом
и области видимости . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.12. Сокрытие информации и инкапсуляция . . . . . . . . . . . . . . . 26
1.13. Геттеры и сеттеры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.14. Ключевое слово “this” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.15. Метод toString() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.16. Константы (final) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.17. Резюме по изменению класса Circle . . . . . . . . . . . . . . . . . . . 32
1.18. Примеры классов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2. КОМПОЗИЦИЯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.1. Пример классов «Автор» и «Книга» . . . . . . . . . . . . . . . . . . . . 48
2.2. Пример классов «Точка» и «Отрезок» . . . . . . . . . . . . . . . . . . 55
2.3. Пример классов «Точка» и «Круг» . . . . . . . . . . . . . . . . . . . . . . 63
3. НАСЛЕДОВАНИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.1. Области видимости . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3.2. Переопределение методов и сокрытие полей . . . . . . . . . . . 74
3.3. Аннотация @Override . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.4. Ключевое слово “super” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

3.5. Дополнение о конструкторах . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.6. Конструктор без параметров по умолчанию . . . . . . . . . . . . 78
3.7. Одиночное наследование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.8. Общий корневой класс java.lang.Object . . . . . . . . . . . . . . . . . 79
4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ
И ИНТЕРФЕЙСЫ
4.1. Подстановка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.2. Апкастинг и даункастинг . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.3. Оператор “instanceof” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.4. Резюме по полиморфизму . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.5. Пример полиморфизма . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ
5.1. Абстрактный метод . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.2. Абстрактный класс . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.3. Интерфейс . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.4. Реализация множественных интерфейсов . . . . . . . . . . . . . . 99
5.5. Интерфейс и абстрактный суперкласс . . . . . . . . . . . . . . . . . 102
5.6. Динамическое (позднее) связывание. . . . . . . . . . . . . . . . . . 102
5.7. Инкапсуляция, связывание и связность. . . . . . . . . . . . . . . . 103
6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ
ВО ФРЕЙМВОРК «КОЛЛЕКЦИИ»
6.1. Введение во фреймворк «Коллекции» . . . . . . . . . . . . . . . . . 112
6.2. Коллекции и небезопасность типов . . . . . . . . . . . . . . . . . . . 118
6.3. Введение в дженерики . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.4. Дженерик-классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.5. Дженерик-методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6.6. Wildcards – подстановочные символы . . . . . . . . . . . . . . . . . 134
6.7. Дженерики, ограничивающие тип . . . . . . . . . . . . . . . . . . . . 140
7. КОЛЛЕКЦИИ
7.1. ArrayList с дженериками . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
7.2. Обратная совместимость . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.3. Автобоксинг и автоанбоксинг –
автоупаковка и автораспаковка . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.4. Иерархия интерфейсов во фреймворке «Коллекции» . . . . 151
7.5. Интерфейсы Iterable, Iterator
и усовершенствованный цикл for . . . . . . . . . . . . . . . . . . . . . . . . 152
4

СОДЕРЖАНИЕ

7.6. Интерфейс Collection и его подинтерфейсы List,
Set, Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
7.7. Интерфейс Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.8. Интерфейс List и его реализации . . . . . . . . . . . . . . . . . . 158
7.9. Упорядочение, сортировка и поиск . . . . . . . . . . . . . . . . . . . 175
7.10. Set – интерфейсы и реализации . . . . . . . . . . . . . . . . . . 180
7.11. Queue – интерфейсы и реализации . . . . . . . . . . . . . . . 190
7.12. Интерфейсы и реализации Map . . . . . . . . . . . . . . . . . 195
7.13. Алгоритмы фреймворка «Коллекции» . . . . . . . . . . . . . . . . 199
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ДИЗАЙНА (ООД) КЛАССОВ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
8.1. SRP – Single responsibility Principle – принцип
единственной ответственности . . . . . . . . . . . . . . . . . . . . . . . . . . 213
8.2. OCP – Open Close Principle –
принцип открытости/закрытости . . . . . . . . . . . . . . . . . . . . . . . . 216
8.3. LSP – Liskov’s Substitution Principle – принцип замещения
Барбары Лисков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
8.4. ISP – Interface Segregation principle – принцип
разделения интерфейса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
8.5. DIP – Dependency Inversion principle –
принцип инверсии зависимостей . . . . . . . . . . . . . . . . . . . . . . . . 228
8.6. Другие принципы ООП и ООД. . . . . . . . . . . . . . . . . . . . . . . . 233
ЗАКЛЮЧЕНИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
БИБЛИОГРАФИЯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

ВВЕДЕНИЕ
Учебное пособие предназначено для магистрантов, обучающихся по программе «Профильное и углубленное обучение информатике», дисциплина – «Языки и методы программирования», и может
быть также интересно студентам бакалавриата и всем интересующимся объектно ориентированным программированием и его реализацией на языке Java.
Целями освоения дисциплины «Языки и методы программирования» является формирование систематизированных знаний в области
объектно ориентированного программирования на языке Java, приобретение навыков разработки программного кода с использованием
современных кросс-платформенных инструментальных средств.
Для изучения данного учебного пособия необходимо знакомство с основными понятиями языка Java, такими, как переменные,
типы данных, массивы, методы и т.д. Для изучения глав 6 и 7 желательно, хотя и необязательно, понимание работы со структурами
данных и знакомство с обработкой исключений.
Учебное пособие состоит из 8 глав. В первой главе рассматриваются основные понятия и принципы объектно ориентированного
программирования. Во второй главе рассматриваются отношения
между классами, при этом особое внимание уделено композиции.
Третья глава посвящена наследованию. В четвертой главе изучаются средства реализации в Java принципа полиморфизма, использование абстрактных классов и интерфейсов рассматривается в пятой
главе. Шестая и седьмая главы предназначены для изучения работы
с дженериками и коллекциями. В восьмой главе обсуждаются принципы объектно ориентированного проектирования SOLID.
Каждая глава содержит примеры, иллюстрирующие изучаемые
понятия. Кроме того, главы содержат контрольные вопросы и задания для самостоятельной работы.
Учебное пособие основано на материалах для преподавания дисциплин «Языки и методы программирования» и «Практикум по решению задач алгоритмизации и программирования» магистрантам МПГУ.

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ПРОГРАММИРОВАНИЯ

1.1. Введение в объектно ориентированное
программирование
Мы живем в мире объектов. Стол, автомобиль, ручка – это объекты. Наряду с физическими существуют также абстрактные объекты,
представителями которых, например, являются числа или геометрические фигуры.
Языки структурного программирования не подходят для абстракций высокого уровня при решении задач реальной жизни. Например,
программы на C, использующие только такие конструкции, как условный оператор, циклы, массивы, функции, являются низкоуровневыми и их трудно применять для абстрагирования с целью построения моделей реального мира или создания игр.
Кроме того, программы, написанные на языках структурного программирования, состоят из функций. Для функций имеется
лишь незначительная возможность их повторного использования.
Трудно копировать функции из одной программы в другую и повторно использовать в другой программе, так как функции, скорее
всего, будут ссылаться на другие функции или глобальные переменные. Другими словами, функции недостаточно инкапсулированы,
поэтому их трудно использовать как повторно используемый программный модуль.
Исследования департамента обороны США 1970-х годов показали, что 80% бюджета уходило на поддержку программного обеспечения и только 20% – на его разработку. При этом программные модули, как правило, невозможно было повторно использовать в других
программах. В то же время компоненты аппаратного обеспечения
можно использовать в других устройствах. Поэтому было предложено разрабатывать программное обеспечение таким образом,
чтобы оно обладало свойствами объекта аппаратного обеспечения.
7

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Для преодоления недостатков структурного программирования
были разработаны языки, поддерживающие парадигму объектно
ориентированного программирования.
Объектно ориентированное программирование (ООП) – методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию
наследования.
Объект – это сущность, обладающая определенным поведением
и способом представления.
Класс – это шаблон, или прототип, по которому создаются объекты. Класс моделирует состояние и поведение объектов реального мира.
Например, автомобиль является экземпляром класса автомобилей. Однако если имеется несколько конкретных автомобилей,
то они не являются классом, потому что класс – это абстракция.
Класс содержит статические свойства (их также называют полями, атрибутами, характеристиками, переменными-членами класса)
и динамическое поведение, общие для всех объектов, в закрытом «запечатанном ящике» и определяет открытый интерфейс для использования таких «ящиков». Поскольку классы хорошо инкапсулированы, то их легко использовать повторно. Таким образом, объектно
ориентированное программирование в классе объединяет данные
и инструкции для обработки данных.
Объект в ООП – это экземпляр некоторого класса. Все экземпляры класса имеют одинаковые свойства, описанные в определении
класса.
Например, можно создать класс «Студент» и определить три экземпляра данного класса: Ivanov, Petrov, Sidorov.

8

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

1.2. Краткая история развития
объектно ориентированного программирования
Первым объектно ориентированным языком программирования считается Симула-67, разработанный в 1967 г., хотя Симула-67
традиционно не считается объектно ориентированным языком в каноническом смысле этого слова. Этот язык в значительной степени
опередил свое время, современники (программисты 60-х годов) оказались не готовы воспринять ценности языка Симула-67, и он не выдержал конкуренции с другими языками программирования.
В 1970 г. Алан Кэй и его исследовательская группа в компании Xerox PARK создали персональный компьютер, названный
Dynabook и первый объектно ориентированный язык программирования Smalltalk для программирования на этом компьютере.
По мнению Алана Кея, которого считают одним из «отцов-основателей» ООП, объектно ориентированный подход заключается
в следующем наборе основных принципов (цитата):
«1. Все является объектом.
2. Вычисления осуществляются путем взаимодействия (обмена
данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некоторое действие. Объекты взаимодействуют, посылая и получая сообщения. Сообщение – это запрос
на выполнение действия, дополненный набором аргументов, которые могут понадобиться при выполнении действия.
3. Каждый объект имеет независимую память, которая состоит
из других объектов.
4. Каждый объект является представителем класса, который
выражает общие свойства объектов (таких, как целые числа
или списки).
5. В классе задается поведение (функциональность) объекта. Тем
самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия.
6. Классы организованы в единую древовидную структуру
с общим корнем, называемую иерархией наследования. Память
9

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

и поведение, связанные с экземплярами определенного класса,
автоматически доступны любому классу, расположенному ниже
в иерархическом дереве.
Таким образом, программа представляет собой набор объектов,
имеющих состояние и поведение. Объекты взаимодействуют посредством сообщений. Естественным образом выстраивается иерархия объектов: программа в целом – это объект, для выполнения своих функций она обращается к входящим в нее объектам, которые,
в свою очередь, выполняют запрошенное путем обращения к другим
объектам программы. Естественно, чтобы избежать бесконечной рекурсии в обращениях, на каком-то этапе объект трансформирует обращенное к нему сообщение в сообщения к стандартным системным
объектам, предоставляемым языком и средой программирования.
Устойчивость и управляемость системы обеспечивается за счет
четкого разделения ответственности объектов (за каждое действие
отвечает определенный объект), однозначного определения интерфейсов межобъектного взаимодействия и полной изолированности
внутренней структуры объекта от внешней среды (инкапсуляции)».
В 1980-х годах Гради Буч создал метод разработки программного обеспечения, опубликованный сначала в статье, а затем
в книге «Объектно ориентированный анализ и проектирование».
Впоследствии он развил свои идеи на методы объектно ориентированного дизайна.
В 1990-х Йордан Коад включил идеи поведения в объектно ориентированные методы.
Значительный вклад в развитие объектно ориентированного
подхода был сделан разработкой техники объектного моделирования (Object-Modelling Techniques (OMT)) Джеймса Румбаха и описанием процесса разработки программного обеспечения OOSE
(Object-Oriented Software Engineering) Ивара Якобсона.
В 1994 году Гради Буч и Джеймс Рамбо разрабатывали новый
язык объектно ориентированного моделирования. За основу языка ими были взяты методы моделирования, разработанные Бучем
(метод Буча) и Рамбо (Object-Modeling Technique – OMT).
10

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Затем к идее создания нового языка моделирования подключились новые участники, и основная роль в организации процесса
разработки UML перешла к консорциуму OMG (Object Management
Group). Группа разработчиков в OMG, в которую также входили Буч,
Рамбо и Якобсон, выпустила спецификации UML версий 0.9 и 0.91
в июне и октябре 1996 года.

1.3. Основные принципы объектно ориентированного
программирования
1. Абстракция в объектно ориентированном программировании – это придание объекту характеристик, которые четко определяют его концептуальные границы, отличая от всех других объектов.
Основная идея состоит в том, чтобы отделить способ использования составных объектов данных от деталей их реализации в виде
более простых объектов.
Абстракция является основой объектно ориентированного программирования и позволяет работать с объектами, не вдаваясь
в особенности их реализации.
Так, для описания класса «Студент» имеет смысл рассматривать
такие характеристики объектов, как фамилия, имя, отчество, номер
зачетной книжки, номер курса, номер группы, оценки. Не имеет
смысла оценивать, например, внешние данные или характер.
2. Инкапсуляция – это принцип, который требует сокрытия
деталей реализации используемого программного компонента
при возможности взаимодействовать с ним посредством предоставляемого интерфейса, а также объединение и защита жизненно
важных для компонента данных. При этом пользователю предоставляется только спецификация (интерфейс) объекта. Пользователь
может взаимодействовать с объектом только через этот интерфейс.
Например, автомобиль является объектом, состоящим из других
объектов, таких, как двигатель, коробка передач, рулевое управление и т.п., имеющих свои собственные подсистемы. Но для человека
11

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

автомобиль – единый объект, которым можно управлять с помощью подсистем, даже не зная внутреннего устройства автомобиля.
3. Наследование – принцип, позволяющий описать новый класс
на основе уже существующего (родительского), при этом свойства
и функциональность родительского класса заимствуются новым
классом. Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса. Например, базовым классом может быть
класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т.д.
4. Полиморфизм – возможность объектов с одинаковой спецификацией иметь различную реализацию. Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество
реализаций». Полиморфизм – один из четырех важнейших механизмов объектно ориентированного программирования (наряду
с абстракцией, инкапсуляцией и наследованием). Полиморфизм
позволяет писать более абстрактные программы и увеличить возможность повторного использования кода. Общие свойства объектов объединяются в такие системы, как интерфейс, класс.
Более подробно принципы ООП будут рассмотрены далее.

1.4. Класс и объект
Итак, класс – это шаблон, или прототип, по которому создаются
объекты.
Будем использовать унифицированный язык моделирования
UML (англ. Unified Modeling Language ) для визуализации класса.
Диаграмма классов является ключевым элементом в объектно
ориентированном моделировании. На диаграмме (см. рис. 1.1)
классы изображаются в рамках, содержащих три компонента:
1. В верхней части написано имя класса. Имя класса выравнивается по центру и пишется полужирным шрифтом. Имена классов
12

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

начинаются с заглавной буквы. Если класс абстрактный – то его имя
пишется полужирным курсивом.
2. В средней части располагаются переменные-члены класса
(поля, атрибуты), которые представляют собой статические свойства класса. Они выравниваются по левому краю и начинаются
с маленькой буквы.
3. Нижняя часть диаграммы содержит методы класса, определяющие его динамическое поведение. Они также выровнены по левому
краю и пишутся с маленькой буквы (см. рис. 1.1):
Имя
статические свойства
динамическое поведение
Рис. 1.1. Диаграмма класса

Приведем примеры изображения классов Student (Студент)
и Circle (Круг) на диаграмме UML (см. рис. 1.2):

Рис.1.2. Пример изображения классов Student и Circle на диаграмме

Объект также представляется диаграммой, состоящей их трех
компонентов. На диаграмме объектов отображаются объекты
с указанием текущих значений их полей и связей между объектами. Имя объекта отображается как имяОбъекта:Имякласса
и подчеркивается.
13

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Следующие диаграммы отображают два объекта класса Student
с именами ivanov и petrov (см. рис. 1.3):

Рис. 1.3. Пример изображения двух экземпляров класса – объектов ivanov и petrov.

1.5. Определение класса в Java
Для определения класса в Java используется ключевое слово
class. Например:

Или:

14

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Синтаксис определения класса следующий:
[модификаторы доступа] class Имя_Класса {
//Тело класса, содержащее описание переменных и методов
…..
}
Что такое модификаторы доступа, такие, как public или private
будет разъяснено ниже.
Соглашение об именах классов. Рекомендуется в качестве имени
класса использовать существительные или фразы с использованием латинского шрифта, состоящие из нескольких существительных,
имеющие смысл в используемом контексте. Все слова, входящие
в имена, должны начинаться с большой буквы. Например: Student,
Circle, SocketFactory, FileInputStream.

1.6. Создание экземпляров класса
Чтобы создать экземпляр класса (объект), надо:
1. Объявить объект данного класса, указав имя класса и идентификатор данного объекта.
2. Создать объект, т.е. выделить память и проинициализировать
объект с помощью оператора “new”.
Допустим, например, что у нас есть класс Circle (круг). Тогда, например, мы можем создать экземпляры членов класса следующим
образом:

15

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Если объект объявлен, но не создан, он имеет особое значение,
которое называется null.

1.7. Оператор «Точка»
Переменные и методы, принадлежащие классу, называются переменными-членами (полями) и методами-членами данного класса.
Чтобы обратиться к переменной-члену класса или методу-члену
класса, надо:
1. Указать идентификатор требуемого объекта.
2. Использовать оператор «точка» (.) и указать член класса – переменную или метод.
Например, допустим, что у нас описан класс Circle с двумя
переменными (radius – радиус и color – цвет) и двумя методами
(getRadius()и getArea()). Пусть мы создали 2 объекта класса Circle
с именами c1, c2. Чтобы вызвать метод getArea(), надо сначала указать имя объекта, например, c2, а затем использовать оператор
«точка»: c2. getArea().
Например:

16

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Вызов getArea() без указания объекта не имеет смысла, так
как радиус неизвестен, поскольку может быть много объектов класса Circle, каждый из которых содержит свой радиус.
Более того, c1.getArea() и c2.getArea(), вероятно, получат разные
результаты.
Таким образом, пусть мы имеем класс AClass с переменной
aVariable и методом aMethod(), а также объект данного класса
anInstance, следует пользоваться обращением anInstance.aVariable
и anInstance.aMethod().

1.8. Переменные-члены и методы-члены класса
Переменные-члены класса (поля) имеют имя (идентификатор)
и тип и содержат значение данного типа.
Соглашение об именах переменных. В качестве имени переменной (поля) рекомендуется использовать существительное
или фразу, составленную из нескольких существительных. Первое
слово пишется маленькими буквами, а последующие должны начинаться с большой буквы, например, fontSize, roomNumber, xMax,
yMin. Обратим внимание, что имя переменной начинается с маленькой буквы, а имя класса – с большой.
Формальный синтаксис для определения переменной в Java:
[модификаторДоступа] тип имяПеременной [= начальноеЗначение];
[модификаторДоступа] тип имяПеременной1 [=начальноеЗначение1]
[, тип имяПеременной2 [=начальноеЗначение 2]] ... ;
Например,

17

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Синтаксис объявления метода в Java:
[модификаторДоступа] типВозвращаемогоЗначения
([списокПараметров]) {
// тело метода (реализация) ......
}
Например,

имяМетода

Соглашение об именах методов. В качестве имени метода рекомендуется использовать глагол или фразу, начинающуюся с глагола.
Первое слово пишется маленькими буквами, а последующие должны начинаться с большой буквы, например, getArea(), setRadius().

1.9. Пример объектно ориентированного
программирования
Рассмотрим класс Сircle – круг (см. рис. 1.4, рис. 1.5).
Определение класса:

Рис. 1.4. Определение класса Сircle

18

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Класс Сircle отображен на диаграмме (см. рис. 1.4). Он содержит
2 переменные: radius типа double и цвет – color – типа String, а также
3 метода: getRadius(), getColor() и getArea().
Объекты класса изображены на диаграмме на рис. 1.5:

Рис. 1.5. Объекты класса Circle

Класс Сircle отображен на диаграмме. Он содержит 2 переменные: radius типа double и цвет – color – типа String, а также 3 метода:
getRadius(), getColor() и getArea().
Три объекта класса Сircle , названные с1, с2, с3, должны быть созданы со значениями сооответствующих членов класса, как показано на диаграмме.
Код для описания класса Circle будет храниться в файле “Circle.
java”:
Класс Circle – файл Circle.java

19

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Скомпилируем «Circle.java» в «Circle.class».
Класс Circle не имеет метода main(), поэтому класс Circle нельзя запустить на выполнение как программу. Описание класса
20

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Circle может быть использовано как строительный блок для других программ.
Тестирующая программа для класса TestCircle – файл
TestCircle.java
Напишем класс TestCircle, который использует класс Circle. Класс
TestCircle имеет класс main() и может быть выполнен.

21

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Скомпилируем TestCircle.java в TestCircle.class.
Выполним TestCircle и изучим результат:
Радиус = 2.0 Цвет – синий Площадь = 12.566370614359172
Радиус = 2.0 Цвет – красный Площадь = 12.566370614359172
Радиус = 1.0 Цвет – красный Площадь = 3.141592653589793

1.10. Конструкторы
Конструктор – это специальный метод, который имеет то же имя
метода, что и класс (т.е. имя конструктора и имя класса совпадают).
В рассмотренном выше классе Circle мы определили три перегружаемых версии конструктора Circle(......). Конструктор используется для создания и инициализации всех переменных-членов класса.
Для создания объекта некоторого класса надо использовать специальный оператор “new” после обращения к одному из конструкторов. Например,

Отличия конструктора от обычного метода:
• имя конструктора всегда совпадает с именем класса и по соглашению об именах начинается с большой буквы;
• у конструктора нет возвращаемого значения, следовательно,
не разрешено использовать предложение return в теле конструктора;
• конструктор может быть вызван только через оператор “new”,
при этом может быть вызван только 1 раз для создаваемого
объекта;
• конструкторы не наследуются (обсудим это ниже).
Конструктор по умолчанию: конструктор без параметров называется конструктором по умолчанию. Такой конструктор инициа22

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

лизирует переменные-члены класса их значениями по умолчанию,
например, Circle() в приведенном примере инициализирует переменные radius и color их значениями по умолчанию.
Напомним, что перегрузка метода означает, что метод с одним
и тем же именем может иметь различные реализации, что достигается различием в списке параметров (их количеством, типом
или порядком).
Конструктор, как и другие методы, может быть перегружаемым.
В рассмотренном классе Circle мы определили 3 перегружаемых
версии конструкторов с одинаковым именем Circle, различающихся списком параметров:

В зависимости от списка фактических параметров будет вызываться соответствующий конструктор. Если список параметров не соответствует ни одному методу, будет выдана ошибка компиляции.

1.11. Модификаторы управления доступом
и области видимости
Область видимости – это область программы, в пределах которой идентификатор некоторой переменной, метода или класса
является связанным с этой переменной, соответственно, методом
или классом. За пределами области видимости тот же самый идентификатор может быть связан с другими переменными, методами,
классами.
Видимость поля означает, что его можно использовать в выражениях, передавать в качестве аргумента в методы, изменять его
значение с помощью присваивания.
Видимость метода означает возможность его вызова.
23

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Пакет – это пространство имен, в котором организовано множество классов и/или интерфейсов.
Уровень доступа определяет, могут ли другие классы использовать определенные поля или вызывать определенный метод.
Класс может быть объявлен с модификатором public, и в этом
случае он виден во всех классах везде.
Если класс, не являющийся внутренним, не имеет модификатора доступа, то, по умолчанию, класс доступен в том пакете, в котором он объявлен.
Если класс является внутренним классом, то, поскольку он является членом внешнего класса, то доступ к нему подчиняется правилам доступа для членов класса.
Для членов класса также можно использовать модификатор public
или не использовать никакого идентификатора (доступ по умолчанию). В этих случаях действуют такие же правила, как для класса.
Для членов класса определены два дополнительных модификатора – private и protected.
Итак, модификаторы доступа используются для управления
видимостью класса или членов класса – полей и методов:
1) public: класс, переменная или метод доступны всем другим
объектам в системе;
2) private: класс, переменная или метод доступны только внутри
класса, в котором они объявлены. Любой другой класс из того же
пакета не будет иметь доступа к этим членам класса. Классы и интерфейсы не могут быть объявлены как private;
3) default (модификатор, по умолчанию): если перед именем
класса, метода или переменной отсутствует модификатор доступа, то применяется доступ по умолчанию – default. В этом случае
члены класса видны только внутри пакета (если класс будет объявлен таким образом, то он тоже будет доступен только внутри
пакета);
4) protected: члены класса (поля и методы) доступны только внутри пакета и в наследниках данного класса в других пакетах.
Таблица на рис. 1.6 иллюстрирует методы доступа к членам класса.
24

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Уровень доступа
Модификатор
доступа

Класс

Пакет

Подкласс,
в том числе
в других
пакетах

Остальная часть
программы,
в том числе
другие пакеты

public

Да

Да

Да

Да

protected

Да

Да

Да

Нет

Без модификатора (доступ
по умолчанию)

Да

Да

Нет

Нет

private

Да

Нет

Нет

Нет

Рис. 1.6. Модификаторы доступа и уровень доступа

UML нотация: В UML нотации члены класса с различными модификаторами отображаются следующими знаками:
public – “+”;
protected – “#”;
private – “-“;
без модификатора – “~”.
Остановимся пока на двух методах управления доступом – public
и private.
Например, в приведенном примере класса Circle переменная
radius объявлена как private. В результате radius доступен внутри
класса Circle, но не доступен внутри класса TestCircle, другими словами, вы не можете использовать “c1.radius” для обращения к радиусу c1 в TestCircle.
Попытайтесь вставить инструкцию “System.out.println(c1.
radius);“ в TestCircle и пронаблюдайте сообщение об ошибке.
Затем измените модификатор доступа к переменной radius
на public и перезапустите программу.
С другой стороны, в классе Circle определен public метод getRadius(), следовательно, он может быть вызван в классе
TestCircle.
25

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

1.12. Сокрытие информации и инкапсуляция
Класс инкапсулирует имя, статические атрибуты и динамическое поведение как бы в «ящике». После того как класс определен,
можно запечатать «ящик» и поставить его на «полку» для других
пользователей или повторного использования. Любой пользователь может распечатать «ящик» и использовать его в своих приложениях. Это невозможно в процедурно ориентированных языках
программирования.
Инкапсуляция – это один из основных принципов объектно
ориентированного программирования. Это принцип, который
требует сокрытия деталей реализации используемого программного компонента при возможности взаимодействовать с ним посредством предоставляемого интерфейса, а также объединение
и защита жизненно важных для компонента данных. При этом
пользователю предоставляется только спецификация (интерфейс)
объекта. Пользователь может взаимодействовать с объектом только через этот интерфейс.
Для реализации этого принципа переменные-члены класса обычно скрываются от внешнего мира (т.е. для других
классов) посредством использования модификатора доступа
private, а доступ к переменным-членам осуществляется посредством специальных public методов, как, например, getRadius()
и getColor().
Это требуется в соответствии с принципом сокрытия информации. Так, объекты взаимодействуют друг с другом, используя хорошо организованные интерфейсы. Объектам не разрешено знать
детали реализации других объектов. Детали реализации скрываются, или инкапсулируются, внутри класса. Сокрытие информации способствует повторному использованию класса.
Правило. Никогда не используйте модификатор public для переменной, если для этого нет сто́ящей причины.

26

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

1.13. Геттеры и сеттеры
Для того чтобы разрешить другому классу прочитать значение
private переменной, например, xxx, следует использовать метод,
называемый геттером (от англ. get – получать), обычно имеющий
имя getXxx(). Геттер-метод не должен отображать информацию,
он может обрабатывать данные и ограничивать видимость данных.
Геттеры не изменяют переменную.
Чтобы разрешить другим классам модифицировать переменную,
например, xxx, следует создать метод сеттер (от англ. set – устанавливать) и назвать его setXxx(). Сеттер может провести проверку данных
и преобразовать исходные данные во внутреннее представление.
Например, в нашем классе Circle переменные radius и color объявлены как private. Это означает, что они доступны только внутри класса Circle и не видны в других классах, включая TestCircle класс. У вас
нет доступа к private-переменным radius и color из класса TestCircle
напрямую, т.е., скажем, через c1.radius или c1.color. Класс Circle
предоставляет два метода с доступом public, а именно getRadius()
и getColor(). Класс TestCircle может вызывать эти методы с доступом
public для извлечения значений полей radius и сolor Circle-объекта.
Нельзя изменить radius или сolor объекта Circle после того,
как он сконструирован в классе TestCircle без использования сеттера. Вы
не можете использовать такие предложения, как c1.radius=5.0, для изменения поля radius объекта c1, так как поле radius объявлено как private
в классе Circle и не видно в других классах, включая TestCircle.
Если разработчик класса Circle разрешит изменять поля radius
или color после того, как объект класса Circle уже создан, он должен
предоставить соответствующий сеттер, например:

27

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Имея соответствующую реализацию сокрытия информации,
разработчик класса имеет полный контроль того, что пользователь
класса может и чего не может делать.

1.14. Ключевое слово “this”
Ключевое слово “this” используется для ссылки на данный объект внутри описания класса. В основном ключевое слово “this” используется для того, чтобы избежать двойного толкования.

В приведенном фрагменте кода есть два идентификатора
с именем radius – переменная-член класса (поле) и параметр метода. Это вызывает конфликт имен. Чтобы избежать конфликта
имен, можно было бы назвать параметр метода r вместо radius.
28

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Однако имя radius является более содержательным и близким
по контексту. Java предоставляет ключевое слово “this” для разрешения конфликта имен. Так, “this.radius” отсылает к переменной-члену класса, т.е. к полю, в то время как “radius” предполагает параметр метода.
Рассмотрим в общем виде использование ключевого слова “this”
в конструкторе, сеттере и геттере для private поля с именем xxx
типа T:

29

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Для переменной типа boolean геттер рекомендуется называть
isXxx() вместо getXxx():

Замечания:
• this.имяПеременной – ссылка на поле имяПеременной этого
объекта; this.имяМетода(…) – вызывает метод имяМетода (…)
данного объекта.
• В конструкторе можно использовать this(…) для вызова другого конструктора этого класса.
• Внутри метода можно использовать предложение “return this”
для возврата этого объекта при вызове.

1.15. Метод toString()
Каждый хорошо разработанный Java класс должен иметь publicметод с именем toString(), который возвращает текстовое описание
объекта. Метод toString можно явно или неявно вызвать следующим образом:
30

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

• имяОбъекта.toString();
• через println;
• через конкатенацию строк, т.е. оператор ‘+’.
Выполнение println(имяОбъекта) с объектом в качестве аргумента неявно вызывает метод toString() для этого объекта.
Например, если в наш класс Circle включен метод toString():

В классе TestCircle можно получить краткое текстовое описание
объекта следующим образом:

Метод toString() имеет следующую сигнатуру:
public String toString() { ...... }

31

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

1.16. Константы (final)
Напомним, что константа – это именованная область памяти,
определенная однажды и не имеющая возможности измениться. Константы определяются посредством модификатора final.
Например:

Во время объявления константу надо проинициализировать:

Соглашение об именах: Имя константы – это существительное
или фраза, состоящая из нескольких существительных. Все слова
записываются заглавными буквами и разделяются знаком подчеркивания ‘_’, например, X_REFERENCE, MAX_INTEGER и MIN_VALUE.
Замечания:
1. Константе базового типа нельзя присвоить новое значение.
2. Константному объекту не может быть присвоен новый адрес.
3. Константный класс не может быть подклассом (extended).
4. Константный метод не может быть переопределен.

1.17. Резюме по изменению класса Circle
Окончательная диаграмма для класса Circle имеет вид, представленный на рис. 1.7.

32

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Рис. 1.7. Окончательный результат по проектированию класса Circle.Класс

Circle – файл Circle.java

33

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

34

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

1.18. Примеры классов
Пример 1. Класс Account – «Банковский счет».
Класс с именем Account моделирует банковский счет, диаграмма
класса представлена на рис. 1.8. Класс содержит следующие методы:

Рис. 1.8. Диаграмма класса Account – «Банковский счет»

35

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

• перегружаемые конструкторы;
• геттеры и сеттеры для private переменных-членов класса;
отсутствует сеттер для accountNumber, так как класс спроектирован таким образом, что это поле не может быть изменено;
• public методы credit() и debit(), которые добавляют/вычитают
данное значение amount к/из балансу, соответственно;
• метод toString(), который возвращает «Номер счета:xxx,
Баланс=$xxx.xx» с балансом, округленным до двух знаков после запятой.
Напишем класс Account и программу, тестирующую все public
методы.
Класс Account – файл Account.java

36

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

37

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Тестирующая программа для класса Account – файл
TestAccount.java

38

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Результаты:
Номер счета:1234, Balance=99.99
Номер счета:8888, Balance=0.00
Номер счета:1234, Balance=88.88
Номер счета: 1234
Баланс: 88.88
Номер счета:1234, Balance=98.88
Номер счета:1234, Balance=93.88
Сумма превышает текущий баланс!
Номер счета:1234, Balance=93.88
Пример 2. Класс Time – «Время»
Класс Time моделирует объекты «Время» с указанием часа,
минуты и секунды, как это показано на диаграмме классов (см.
рис. 1.9). Класс Time содержит следующие члены класса:
• 3 private переменных-членов класса: hour, minute и second;
• конструкторы, геттеры и сеттеры;
• метод setTime() для установки часа, минуты и секунды;
• метод toString(), который возвращает значение времени
в виде: «час:минута:секунда» с предшествующим нулем, если
это возможно;
39

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

• метод nextSecond(), который увеличивает значение времени
на 1 секунду. Этот метод возвращает объект “this” для поддержки «каскадных операций», например, t1.nextSecond().
nextSecond(). Обратите внимание, что результат применения
этого метода к 23:59:59 возвратит 00:00:00.

Рис. 1.9. Диаграмма класса Time – «Время»

Напишем класс Time и программу, тестирующую все public методы. В данном случае проверка входных значений не требуется.
Класс Time – файл Time.java

40

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

41

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

42

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

Тестирующая программа – файл TestTime.java

43

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Результаты:
03:02:01
00:00:00
04:05:06
Час: 4
Минута: 5
Секунда: 6
23:59:58
23:59:59
00:00:02

44

1. ОСНОВЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

1.

2.
3.

4.

Контрольные вопросы к главе 1
Что такое ООП?
Что такое объект?
Что такое класс?
Назовите основные принципы объектно ориентированного
программирования.
Что такое инкапсуляция?
Что такое абстракция?
Что такое поле/атрибут/переменная-член класса?
Как правильно организовать доступ к полям класса?
Что такое модификаторы уровня доступа?
Что такоеконструктор?
Чем различаются конструктор по умолчанию, конструктор
без параметров и конструктор с параметрами?
Что означает ключевое слово “this” и как его можно использовать?
Что такое геттеры? Что такое сеттеры?
Как применяется метод toString()?
Задания к главе 1
Написать программу описания класса Circle на основе примера из раздела 1.7 и написать программу TestCircle, тестирующую все public-методы этого класса.
Написать программу описания класса Circle из раздела 1.15
c геттерами, сеттерами и методом toString().
Изменить программу для класса Time из примера 2 раздела 1.18 таким образом, чтобы выполнялась проверка входных данных, т.е. в случае значений минут и секунд, выходящих за диапазон [0,59], и в случае значения часа, не попадающего в диапазон [0,23], вывести на экран сообщение
об ошибке.
Индивидуальное задание. Описать один из приведенных
ниже классов с использованием геттеров, сеттеров, метода
toString() и дополнительно 2 методов:
45

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

а) треугольники;
б) прямоугольники;
в) рациональные числа;
г) квадратные матрицы;
д) студент;
е) книга в магазине (автор, название, цена, наличие на складе);
ж) пациент;
з) автомобиль;
и) квадратное уравнение (корень, экстремум, …);
к) клиент банка (id, ФИО, адрес, номер счета, номер карты);
л) полином.

2. КОМПОЗИЦИЯ
Возможность повторного использования кода принадлежит
к числу важнейших преимуществ Java. При этом изменения не сводятся к копированию и правке кода.
Существуют два способа повторного использования классов –
композиция и наследование.
При композиции объекты уже имеющихся классов создаются внутри нового класса. Механизм построения нового класса из объектов
существующих классов называется композицией. В этом случае используется функциональность готового кода, а не его структура.
Во втором случае новый класс создается как специализация
уже существующего класса. Взяв существующий класс за основу,
к нему добавляется код без изменения существующего класса.
Этот механизм называется наследованием, и большую часть работы в нем совершает компилятор. Наследование является одним
из «краеугольных камней» объектно ориентированного программирования.
Между классами существуют разные типы отношений. Самым
базовым типом отношений является ассоциация. Это означает,
что два класса как-то связаны между собой, и мы пока не знаем точно, в чем эта связь выражена, и собираемся уточнить ее в будущем.
Применительно к созданию классов на основе уже существующих (классов), в широком смысле, используется термин «композиция», т.е. класс создается на основе существующих классов.
В то же время, в более узком смысле, при создании таких классов
используются термины «композиция» и «агрегация».
Ассоциация является общим случаем композиции и агрегации.
Как композиция, так и агрегация обычно выражаются в том,
что класс целого содержит свойства своих составных частей.
Разница между композицией и агрегацией заключается в том,
что в случае композиции целое явно контролирует время жизни
своей составной части (часть не существует без целого), а в случае
агрегации целое хоть и содержит свою составную часть, время их
47

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

жизни не связано (например, составная часть передается через параметры конструктора).
Пример агрегации: Студент входит в Группу любителей физики.
Пример композиции: Машина и Двигатель. Хотя двигатель может быть и без машины, но он вряд ли сможет быть в двух или трех
машинах одновременно, в отличие от студента, который может входить и в другие группы тоже.
UML-нотация: в UML-нотации композиция обозначается
как линия со стрелкой в виде ромбика, указывающей на свои составляющие. Ромбик всегда находится со стороны целого, а простая
линия со стороны составной части; закрашенный ромб означает более сильную связь – композицию, незакрашенный ромб показывает более слабую связь – агрегацию.
Наиболее часто для описания отношений между классами используется ассоциация в форме композиции или наследование
(о наследовании см. гл. 3).

2.1. Пример классов «Автор» и «Книга»
Рассмотрим пример использования классов при композиции.

Рис. 2.1. Диаграмма класса Author

48

2. КОМПОЗИЦИЯ

Класс «Автор» определен на диаграмме (см. рис. 2.1). Он содержит:
• 2 private переменные-члены класса: name – типа String
и email – типа String;
• конструктор для инициализации объекта с двумя параметрами name и email с заданными значениями; в данном случае
отсутствует конструктор по умолчанию, поскольку нет значений по умолчанию для полей name и email.
• public методы геттеры и сеттеры: getName(), getEmail(),
setEmail(). При этом отсутствует сеттер для name, поскольку
объекты создаются таким образом, что их нельзя изменить;
• public метод toString(), который возвращает “name, email”, т.е.,
например, “Иванов, ivanov@kuda.list”.
Класс Author (файл Author.java)

49

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Тестирующая программа для класса «Автор» (TestAuthor.
java)

50

2. КОМПОЗИЦИЯ

Класс «Книга, написанная одним автором» – с использованием анонимного объекта
Опишем класс Book – «Книга» (см. рис. 2.2).
Допустим, что книга написана единственным автором. Класс
Book («Книга»), как показано на диаграмме класса, содержит следующие члены класса:
• четыре private переменных-членов класса: название – name
(String), автор (объект класса Author, только что созданного,
в предположении, что книга написана единственным автором), цена (double) и количество qty(int);
• public геттеры и сеттеры: getName(), getAuthor(), getPrice(),
setPrice(), getQty(), setQty();
• метод toString(), который возвращает Название книги автора
и email; можно использовать метод toString(), который возвращает имя автора с указанием email.

Рис. 2.2. Класс Book – «Книга»

51

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Класс «Книга» ( файл Book.java)

52

2. КОМПОЗИЦИЯ

Тестирующая программа использует анонимный объект.
Анонимный объект – это объект, созданный без присваивания ссылки в качестве значения переменной. То есть объект создан,
а переменной, которая на него ссылается, нет.
Обычно объекты создаются следующим образом:

Анонимные же объекты создаются таким образом:

Анонимный объект можно использовать в программе только
один раз. Нельзя использовать его дважды или более, так как анонимный объект прекращает свое существование немедленно после
выполнения задачи, для которой он предназначен.

53

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Тестирующая программа для класса «Книга» Book (TestBook.java)

54

2. КОМПОЗИЦИЯ

2.2. Пример классов «Точка» и «Отрезок»
Предположим, что мы имеем класс «Точка» с именем Point, определенный в соответствии с диаграммой, представленной ниже (см.
рис. 2.3).

Рис. 2.3. Класс Point

55

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Класс Point моделирует двумерную точку (x,y) и содержит следующие члены класса:
• две private переменных-члена класса, которые определяют
координаты точки;
• конструкторы, геттеры и сеттеры;
• метод setXY(), который устанавливает координаты x и y точки,
и метод getXY(), который возвращает x и y как элементы массива из двух элементов;
• метод toString(), который возвращает «(x,y)»;
• 3 версии перегружаемого метода distance():distance(int x,
int y) – возвращает расстояние от заданного объекта до точки,
заданной координатами (x,y); distance(Point another) – возвращает расстояние от данной точки до заданной точки – объекта класса Point, имеющей имя another; distance() – возвращает
расстояние от данного объекта до точки (0,0).
Класс «Точка» – Point (файл Point.java)

56

2. КОМПОЗИЦИЯ

57

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Тестирующая программа для класса Point (TestPoint.java)

58

2. КОМПОЗИЦИЯ

Предположим, надо создать класс «Отрезок» – Line. Можно определить класс Line, используя класс Point посредством композиции:
«отрезок можно определить по двум точкам» (т.е. отрезок «включает в себя две точки» – композиция) или «отрезок имеет (has-a) две
точки (начала и конца)» (см. рис. 2.4).
Композиция выражает соотношение «включает в себя», название которого (“has-a”) обычно используется на английском языке
без перевода.
59

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 2.4. Диаграмма класса Line

Пример 2.2. Класс «Отрезок» (Line), реализованный посредством композиции (файл Line.java)

60

2. КОМПОЗИЦИЯ

61

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

62

2. КОМПОЗИЦИЯ

2.3. Пример классов «Точка» и «Круг»
Допустим, у нас есть уже существующий класс «Точка» (Point),
моделирующий точку и определенный в примере 2.2.
Класс «Круг» (Circle) определен, как показано на диаграмме (см.
рис. 2.5).
Класс Circle содержит:
• две private переменные-члены класса – radius (double) и центр
круга (объект класса Point), который мы создали ранее;
• конструкторы, public геттеры и сеттеры;
• методы getCenterX(), setCenterX(), getCenterY(), setCenterY(),
getCenterXY(), setCenterXY() и т.д.;
• метод toString(), возвращающий текстовое описание данного
(this) объекта в формате «Круг[центр=(x,y),радиус=r]». Следует
использовать метод toString() из класса Point для печати «(x,y)»;
• public double метод getArea() вычисления площади круга;
• public double метод getCircumference() вычисления длины
окружности;
• метод distance(Circle another), который возвращает расстояние от центра данного объекта до центра заданного объекта
класса Circle, имеющего имя another.
63

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 2.5. Диаграмма класса Circle («Круг»)

Класс «Круг» (Circle) – файл Circle.java

64

2. КОМПОЗИЦИЯ

65

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

66

2. КОМПОЗИЦИЯ

Тестирующая программа для класса «Круг» (Circle) –
TestCircle.java

67

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

68

2. КОМПОЗИЦИЯ

1.
2.
3.
4.

1.
2.
3.
4.
5.

6.

Контрольные вопросы к главе 2
Что такое ассоциация?
Что такое композиция?
Что такое агрегация?
В чем различие между ассоциацией и композицией?
Задания к главе 2
Проверить работу класса «Автор» (Author).
Проверить работу классов «Автор» (Author) и «Книга» (Book).
Проверить работу классов «Точка» (Point) и «Отрезок» (Line).
Для класса Line написать метод public double getGradient(),
определяющий угол наклона отрезка относительно оси X.
Для класса Line написать методы public double distance(int
x, int y) и public double distance(Point p), определяющие расстояние от прямой, проходящей через точки начала и конца
отрезка, до заданной точки.
Для класса Line написать метод public boolen intersects(Line
another), который определяет, пересекаются ли данные отрезки.

3. НАСЛЕДОВАНИЕ
Наследование – принцип, позволяющий описать новый класс на основе уже существующего (родительского),
при этом свойства и функциональность родительского класса
заимствуются новым классом. То есть это порождение одного класса от другого, который уже существует. Наследование
происходит с сохранением полей и методов родительского
класса. В процессе наследования можно добавлять новые поля
и методы.
В ООП классы часто организуют иерархически, чтобы избежать дублирования и уменьшить избыточность. Классы внизу
иерархии наследуют все поля (статические атрибуты) и методы (динамическое поведение) из классов, находящихся выше
по иерархии.
Класс, находящийся ниже по иерархии, называется подклассом
(или наследником, потомком). Класс, который находится по иерархии выше, называется суперклассом (или базовым, родительским
классом). Выведя все общие переменные и методы в суперкласс
и оставив только специфические поля и методы в подклассе, избыточность кода может быть значительно уменьшена или устранена,
поскольку эти общие методы и поля не нуждаются в повторении
в подклассах.
Подкласс наследует все переменные и методы из суперкласса, включая своего ближайшего родителя, так же как и всех
остальных предков, за исключением переменных и методов с модификатором private. Важно заметить, что подкласс
не является подмножеством суперкласса. Наоборот, подкласс
является «супермножеством» суперкласса. Это происходит потому, что подкласс наследует все поля и методы суперкласса
и, кроме того, расширяет суперкласс, предоставляя дополнительные поля и методы.
В Java подкласс определяется путем использования ключевого
слова “extends”.
70

3. НАСЛЕДОВАНИЕ

Рис. 3.1. Суперкласс и подкласс

Обозначения UML: В обозначениях UML для наследования используется сплошная линия с незакрашенной стрелкой от подкласса к суперклассу. По соглашению, суперкласс изображается выше
подкласса (рис. 3.1).

3.1. Области видимости
Область видимости – это область программы, в пределах которой идентификатор некоторой переменной, метода или класса
является связанным с этой переменной, соответственно, методом
или классом. За пределами области видимости тот же самый идентификатор может быть связан с другими переменными, методами,
классами.
В этом примере (см. рис. 3.2) класс Cylinder является наследником класса Circle, который мы уже создали в разделе 1.17. Важно
отметить, что мы используем класс Circle повторно. Повторное
использование является одним из наиболее важных свойств ООП.
Класс Cylinder наследует все поля (radius и color) и все методы
(включая getRadius(), getArea()) от суперкласса Circle. В дальнейшем в нем определяется переменная height, два public метода –
getHeight() и getVolume(), а также собственные конструкторы.
Рассмотрим пример.
71

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 3.2. Класс Cylinder – наследник класса Circle

Пример 3.1. Класс Cylinder – наследник класса Circle (файл
Cylinder.java)

72

3. НАСЛЕДОВАНИЕ

Тестирующая программа (файл TestCylinder.java)

73

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Сохраним файлы «Cylinder.java» и «TestCylinder.java» в одном
каталоге или проекте (поскольку мы повторно используем класс
Circle). Откомпилируем и запустим программу. Ожидаемый результат будет:
Радиус = 1.0 Высота = 1.0 Цвет – красный Площадь основания =
3.141592653589793 Объем = 3.141592653589793
Радиус = 5.0 Высота = 2.0 Цвет – красный Площадь основания =
78.53981633974483 Объем = 157.07963267948966
В данном примере для описания полей использован имеющийся
в Java модификатор protected (см. раздел 1.11), благодаря которому
поля и методы с данным модификатором видны в классах-наследниках. Это позволяет получить доступ к переменным-членам класса,
не используя геттеры. Однако в случае использования модификатора
protected данные члены класса будут видны не только в наследниках,
но и во всем пакете, что нарушает принцип инкапсуляции.
Именно по этой причине в приведенном примере в качестве модификатора доступа к полям классов используется private.

3.2. Переопределение методов и сокрытие полей
Подкласс наследует все переменные и методы (кроме переменных и методов с модификатором доступа private) из суперкласса (ближайшего родителя и всех предков). Подкласс может использовать унаследованные поля и методы в соответствии с тем,
74

3. НАСЛЕДОВАНИЕ

как они были определены. В подклассе можно также переопределить унаследованный метод, предоставив его собственную версию,
или скрыть унаследованную переменную, определив переменную
с тем же самым именем.
Например, унаследованный метод getArea() в объекте класса Cylinder вычисляет площадь основания цилиндра. Допустим,
что мы решили переопределить метод getArea() для вычисления
площади поверхности цилиндра в подклассе Cylinder. Рассмотрим
изменения:

Если метод getArea() вызывается из объекта типа Circle, то метод вычисляет площадь круга. Если getArea() вызывается из объекта типа Cylinder, то вычисляется площадь поверхности цилиндра
75

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

путем реализации переопределения. Обратите внимание, что следует использовать метод getRadius() с уровнем доступа public для извлечения значения поля radius из класса Circle, потому что radius
объявлен как private и, таким образом, не доступен для других классов, включая подкласс Cylinder.
Но если переопределить getArea() в классе Cylinder, то getVolume()
(=getArea()*height) больше не работает. Это происходит потому,
что в классе Cylinder будет использован переопределенный метод
getArea(), который не вычисляет площадь основания. Можно решить эту проблему через super.getArea() для использования версии
getArea() из суперкласса.
Обратите внимание, что super.getArea() может быть применен
из определения подкласса, но не из созданного объекта, как, например, c1.super.getArea(), поскольку это нарушает принципы сокрытия информации и инкапсуляции.

3.3. Аннотация @Override
@Override известно как аннотация (введено в JDK 1.5), которая
запрашивает от компилятора проверку, существует ли такой метод
в суперклассе для переопределения. Это хорошо помогает, если сделана ошибка в имени переопределяемого метода. Например, предположим, что мы хотим переопределить метод toString() в подклассе. Если @Override не используется и в имени toString() сделана
ошибка, например, написано TOString(), то это будет рассматриваться как новый метод в подклассе вместо переопределения в суперклассе. Если @Override используется, то компилятор сообщит
об ошибке.
Аннотация @Override не обязательна, но стоит ее иметь.
Аннотации не являются программными конструкциями.
Они не влияют на результат работы программы. Используются
они только на этапе компиляции, после компиляции уничтожаются
и не используются при выполнении.
76

3. НАСЛЕДОВАНИЕ

3.4. Ключевое слово “super”
Повторимся, что внутри определения класса можно использовать ключевое слово “this” для ссылки на данный экземпляр класса.
Похожим образом, ключевое слово “super” отсылает к суперклассу,
который может быть или ближайшим родителем или предком.
Ключевое слово “super” позволяет подклассу получить доступ к полям и методам суперкласса из определения подкласса.
Например, super() и super(список аргументов) может быть использован для вызова конструктора суперкласса. Если подкласс переопределяет метод, унаследованный от суперкласса, например,
getArea(), то можно использовать super.getArea() для вызова версии суперкласса из определения подкласса. Похожим образом, если
подкласс скрывает одну из переменных суперкласса, вы можете использовать super.имяПеременной для ссылки на скрытую переменную в определении подкласса.

3.5. Дополнение о конструкторах
Повторимся, что подкласс наследует все поля и методы суперкласса. Тем не менее подкласс не наследует конструкторов
суперкласса. Каждый класс в Java определяет свои собственные
конструкторы.
В теле конструктора можно использовать super(args), чтобы вызвать конструктор своего ближайшего суперкласса. Обратите внимание, что super(args), если используется, должно быть первым
предложением в конструкторе подкласса. Если super(args) не используется в конструкторе, Java-компилятор автоматически включает инструкцию super() для вызова конструктора без параметров
своего ближайшего суперкласса. Это отражение того факта, что родитель должен быть рожден до того, как может родиться ребенок.
Следует научиться правильно создавать конструкторы суперкласса
до того, как конструировать подкласс.
77

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

3.6. Конструктор без параметров по умолчанию
Если в классе не определен ни один конструктор, Java-компилятор
создает конструктор без параметров, который просто запрашивает вызов super() следующим образом:

Обратите внимание, что:
• Конструктор по умолчанию без аргументов не будет автоматически сгенерирован, если хотя бы один (или более) конструкторов уже были определены. Другими словами, определять конструктор без аргументов при наследовании надо
только в том случае, когда другие конструкторы отсутствуют.
• Если ближайший суперкласс не имеет конструктора по умолчанию (т.е. определены несколько конструкторов, но не определен конструктор без параметров), то будет получена ошибка компиляции при выполнении вызова super(). Обратите
внимание, что Java-компилятор вставляет super() как первое
предложение в конструкторе, если нет super(args).

3.7. Одиночное наследование
Java не поддерживает множественное наследование, т.е. наследование от нескольких классов. Множественное наследование позволяет подклассу иметь более одного суперкласса. Это является
серьезным недостатком в случае, если суперклассы имеют конфликтующие реализации для одного и того же метода. В Java каждый
подкласс может иметь один, и только один, суперкласс, т.е. имеет
78

3. НАСЛЕДОВАНИЕ

место одиночное наследование. С другой стороны, суперкласс может иметь много подклассов.

3.8. Общий корневой класс java.lang.Object
Все классы Java являются наследниками общего корневого класса
Object. Класс Object определяет и реализует общее поведение всех
Java-объектов, выполняемых JRE. Такое общее поведение включает
в себя, например, реализацию многопоточности и сборку мусора.

1.
2.
3.
4.
5.
6.
7.

1.

2.

3.

Контрольные вопросы к главе 3
Что такое наследование?
Что такое суперкласс и что такое подкласс?
Что означает ключевое слово “extends”?
Что такое перегрузка методов? Приведите пример.
Что такое переопределение методов? Приведите пример.
В чем различие между перегрузкой и переопределением?
Что означает ключевое слово “super”?
Задания к главе 3
Написать программу реализации класса Cylinder – цилиндр – наследник класса Circle – круг из примера 3.1. Использовать 3 файла: Circle.java, Cylinder.java, TestCylinder.
java. Проверить работу конструкторов и переопределенных
методов.
Вывести на экран площадь поверхности цилиндра (см. п. 1
«Заданий к главе 3»), вычисляемую с помощью переопределенного метода getArea(). Для описания цилиндра использовать переопределенный метод toString().
Написать программу реализации класса Point3D для трехмерной точки – наследника класса Point для двумерной точки. Программа должна содержать конструкторы, геттеры,
сеттеры, метод toString и два дополнительных метода.

4. ПОЛИМОРФИЗМ,
АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ
Слово «полиморфизм» означает «много форм». Оно произошло от греческого слова «поли» (много) и «морфос» (что означает форму). Например, в химии углерод проявляет полиморфизм,
поскольку он может быть найден в более, чем одной форме: графит и бриллиант. Каждая из форм имеет свои отдельные свойства.
В программировании полиморфизм – это возможность объектов с одинаковой спецификацией иметь различную реализацию.
В Java полиморфизм реализуется посредством перегрузки и переопределения методов.
В соответствии с принципом полиморфизма рекомендуется писать программы на основе общего интерфейса вместо конкретных
реализаций.

4.1. Подстановка
Подкласс обладает всеми полями и методами своего суперкласса
вследствие наследования. Это означает, что объект подкласса может делать то, что может делать объект суперкласса. В результате
мы можем заменить объектом подкласса объект суперкласса, и все
будет прекрасно работать. Это называется замещением или подстановкой.
Так, в нашем примере классов Circle и Cylinder, Cylinder является подклассом Circle. То есть можно сказать, что Cylinder “is-a” «является» Circle (а в действительности он «является большим, чем»)
Circle. Соотношение подкласс – суперкласс выражает так называемое соотношение “is-a” (является), которое обычно используется
на английском языке без перевода.
С помощью подстановки можно создать объект класса Сylinder
и присвоить его значение Circle (объекту суперкласса):
80

4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Теперь можно вызывать все методы, определенные в классе Circle,
для ссылки на c1 (который все еще представляет объект Cylinder), например, c1.getRadius() и c1.getColor(). Это возможно потому, что объект подкласса обладает всеми свойствами суперкласса.
Однако невозможно вызывать методы, определенные в классе
Cylinder, для ссылки на c1, например, c1.getHeight() и c1.getVolume().
Это происходит потому, что c1 – это ссылка на класс Circle, который
не знает о методах, определенных в классе Cylinder.
с1 – это ссылка на класс Circle, но содержит объект подкласса
Cylinder. Ссылка на c1, однако, сохраняет внутреннюю идентичность. В нашем примере подкласс Cylinder переопределяет методы
getArea() и toString(). c1.getArea() или c1.toString() вызывают переопределенные версии из подкласса Cylinder вместо версий, определенных в Circle. Это происходит потому, что фактически объект c1
содержит внутри объект Cylinder.
Резюме
1. Объекты суперкласса могут быть замещены объектами подкласса.
2. При такой замене мы можем вызывать методы, определенные в суперклассе, и не можем вызывать методы, определенные только в подклассе.
3. Однако, если в подклассе переопределены унаследованные
методы из суперкласса, будут вызваны переопределенные
версии методов подкласса.

4.2. Апкастинг и даункастинг
Замена объекта суперкласса объектом подкласса называется «апкастингом» (англ. upcasting) или приведением к базовому
типу. Название происходит из того факта, что на UML-диаграмме
81

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

подкласс изображается ниже суперкласса. Апкастинг всегда безопасен, так как объект подкласса обладает всеми свойствами суперкласса и может делать все, что может делать суперкласс. Компилятор
проверяет правильность апкастинга и в противном случае выдает
ошибку «несовместимости типов». Например,

Даункастинг (англ. downcasting) возвращает замещенный объект
к определению через подкласс, т.е. даункастинг – это приведение
объекта суперкласса к объекту подкласса. Например,

Даункастинг требует оператора явного приведения типов
в форме префиксного оператора (новый_тип). Даункастинг
не всегда безопасен и вызывает ошибку ClassCastException
во время исполнения, если объект даункастинга не принадлежит правильному подклассу.
Объект подкласса может быть заменен суперклассом, но обратное утверждение неверно.

4.3. Оператор “instanceof”
В Java имеется оператор instanceof типа boolean, который возвращает значение true, если объект является экземпляром данного
класса. Синтаксис оператора следующий:

82

4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Экземпляр подкласса также является экземпляром суперкласса.
Например,

4.4. Резюме по полиморфизму
1.

2.

Объект подкласса выполняет все операции над полями
своего суперкласса. Объект суперкласса может быть заменен объектом подкласса. Другими словами, ссылка
на класс может содержать объект этого класса или объект одного из подклассов – это называется подстановкой
или замещением.
Если значение объекта подкласса присваивается ссылке
на суперкласс, то можно вызывать только методы, определенные в суперклассе. Нельзя вызывать методы, определенные в подклассе.
83

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

3.

Замененное значение сохраняет свою идентичность в переопределенных методах и скрытых переменных. Если подкласс переопределяет методы в суперклассе, то будет выполняться версия подкласса вместо версии суперкласса.
Полиморфизм является мощным средством ООП для разделения интерфейса и реализации. Это средство позволяет программировать интерфейс при проектировании сложных систем.

4.5. Пример полиморфизма
Рассмотрим следующий пример (см. рис. 4.1). Допустим, программа использует различные виды фигур, таких как треугольники, прямоугольники и т.д. Мы должны спроектировать суперкласс
с именем Shape (геометрическая фигура), который определяет public интерфейс (или поведение) всех этих фигур. Например,
мы хотим, чтобы все фигуры имели метод с именем getArea(), который возвращает площадь для заданной фигуры.

Рис. 4.1. Суперкласс Shape и подклассы Rectangle и Triangle

84

4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Класс Shape может быть записан следующим образом:
Класс Shape – файл Shape.java

Обратите внимание, что у нас имеются проблемы в написании метода getArea() в классе Shape, потому что площадь не может быть вычислена до тех пор, пока не известен тип фигуры. Мы будем печатать
сообщение об ошибке во время выполнения. Позднее будет показано,
как разрешить эту проблему. Мы можем наследовать классы Triangle
(Треугольник) и Rectangle (Прямоугольник) от суперкласса Shape.
85

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Класс Rectangle – файл Rectangle.java

Класс Triangle – файл Triangle.java

86

4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Подклассы переопределяют метод getArea(), унаследованный
от суперкласса, и обеспечивают его (getArea()) правильную реализацию.
В нашем приложении можно создать объекты класса Shape
и присвоить им значения объектов из подклассов следующим образом:

87

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Класс TestShape – файл TestShape.java

Красота этого кода в том, что все объекты (ссылки) созданы
от суперкласса, т.е. мы имеем программирование на уровне интерфейса. Вы можете продемонстрировать примеры объектов
различных подклассов, и код будет работать! Вы можете легко расширить вашу программу добавлением других подклассов, таких
как Circle, Square и т.д.
Тем не менее представленное определение класса Shape вызывает проблему в случае, если кто-то опишет объект класса Shape
и вызовет метод getArea() для Shape-объекта, в этом случае программа прервется:

88

4. ПОЛИМОРФИЗМ, АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Это происходит потому, что класс Shape предназначен для создания общего интерфейса для всех подклассов, в которых предполагается предоставить фактическую реализацию. Не рекомендуется
создавать объекты класса Shape. Эта проблема может быть разрешена посредством использования так называемого абстрактного
класса.

1.
2.
3.
4.
5.

5.

1.

2.

3.

Контрольные вопросы к главе 4
Приведите примеры полиморфизма при наследовании.
Что такое апкастинг и что такое даункастинг?
Объясните применение оператора instanceof.
Приведите пример использования полиморфизма для разделения интерфейса и реализации.
Обсудите возможность проектирования классов из диаграммы на рис. 4.2 (с. 90) в соответствии с принципом замещения
Лисков (см. гл. 8).
Единичное и множественное наследование – разъясните
возможность реализации в Java.
Задания к главе 4
Написать программу, реализующую суперкласс Shape (геометрическая фигура) и его подклассы Rectangle (прямоугольник) и Triangle (треугольник), в соответствии с диаграммой на рис. 4.1 из раздела 4.5.
Написать программу, реализующую суперкласс Shape (фигура) и его подклассы Circle (круг) и Rectangle (прямоугольник), а также Square (квадрат) – подкласс Rectangle в соответствии с диаграммой на рис. 4.2. Обсудить возможность
реализации такого проекта.
Написать программу, реализующую суперкласс Shape из п. 2
заданий к главе 4 как абстрактный с абстрактным методом
getArea().

89

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 4.2. Диаграмма для суперкласса Shape и его подклассов Circle (круг)
и Rectangle (прямоугольник), а также Square (квадрат) – подкласса Rectangle

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ
В приведенном примере для геометрической фигуры Shape
мы столкнулись с проблемой применения метода getArea() для объектов класса Shape. Эта проблема может быть разрешена применением абстрактного метода и абстрактного класса.

5.1. Абстрактный метод
Абстрактный метод – это метод, имеющий только сигнатуру (т.е. имя метода, список параметров и тип возвращаемого значения) без реализации (т.е. без тела метода). Чтобы объявить абстрактный метод, используется ключевое слово abstract. Например,
в классе Shape мы можем объявить абстрактный метод getArea()
следующим образом:

Реализация этого метода невозможна в классе Shape, поскольку фактическая фигура еще не известна. (Как вычислить площадь,
если фигура неизвестна?) Реализация этого абстрактного метода будет представлена позже, когда фактическая фигура будет известна. Абстрактные методы не могут быть вызваны, поскольку
они не имеют реализации.

5.2. Абстрактный класс
Класс, содержащий один или более абстрактных методов, называется абстрактным классом. Абстрактный класс должен быть
объявлен с модификатором abstract.
91

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

На диаграмме UML абстрактные классы и абстрактные методы
выделяются курсивом.

Рис. 5.1. Диаграмма – абстрактный класс Shape и подклассы Rectangle и Triangle

Перепишем класс Shape как абстрактный класс, содержащий абстрактный метод getArea() (см. рис. 5.1), следующим образом:
Класс Shape – файл Shape.java

92

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Абстрактный класс неполон в своем определении, поскольку
реализация его абстрактных методов отсутствует. Следовательно,
на его основе нельзя создавать объекты. В противном случае мы будем иметь незавершенный объект с отсутствующим телом метода.
Чтобы использовать абстрактный класс, надо унаследовать подкласс от абстрактного класса. В подклассе-наследнике надо переопределить абстрактные методы и предоставить реализации всех
абстрактных методов. Теперь подкласс-наследник будет завершен,
и от него можно создавать объекты. (Если подкласс не предоставляет реализацию для всех абстрактных методов суперкласса, то подкласс остается абстрактным).
Это свойство абстрактного класса разрешает нашу прежнюю
проблему. Другими словами, можно создать объекты подкласса, такие как Triangle и Rectangle, и провести их апкастинг до Shape (как
в программировании на уровне интерфейсов), но нельзя создать
объект Shape, который не попадет в ловушку, с которой мы столкнулись. Например,

93

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Подведем итоги. Абстрактный класс предоставляет шаблон
для дальнейшего развития. Цель абстрактного класса – предоставить общий интерфейс (или протокол, или договор, или понимание,
или соглашение об именах) для всех своих подклассов. Например,
в абстрактном классе Shape вы можете определить абстрактный метод getArea(). Никакая реализация невозможна, поскольку фактическая фигура неизвестна. Однако, указав сигнатуру абстрактных
методов, все подклассы обязаны использовать сигнатуры этих методов. Подклассы могут предоставлять правильные реализации.
Вместе с применением полиморфизма можно проводить апкастинг объектов подкласса до Shape и программировать на уровне
интерфейса. Разделение на интерфейс и реализацию обеспечивает лучший дизайн программного обеспечения и облегчает его расширение. Например, Shape определяет метод с именем getArea(),
для которого все подклассы должны предоставить правильные реализации – можно запросить getArea() из любого подкласса Shape,
и правильная площадь будет вычислена. Более того, ваше приложение может быть легко расширено для размещения новых фигур
(таких, как Circle или Square) путем наследования большего числа
подклассов.
Рекомендация: Программировать на уровне интерфейса,
а не реализации. (Что означает создание объектов суперкласса,
приведение их к объектам подкласса и вызов методов, определенных только в суперклассе.)
94

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Замечания:
• Абстрактный метод не может быть объявлен как final, поскольку final-метод не может быть переопределен. С другой
стороны, абстрактный метод должен быть переопределен
в наследнике до того, как будет использован.
• Абстрактный метод не может иметь модификатор private (это
приведет к ошибке компиляции). Это потому, что privateметод невидим для подкласса и, таким образом, не может
быть переопределен.

5.3. Интерфейс
Интерфейс в Java – это 100% абстрактный суперкласс, который определяет множество методов, которые его подклассы должны поддерживать. Интерфейс содержит только public
abstract методы (методы с сигнатурой и без реализации) и, возможно, константы (public static final). Для определения интерфейса следует использовать ключевое слово “interface” (вместо
class для обычных классов). Ключевые слова public и abstract
не требуются для абстрактных методов, так как они обязательны по определению.
Интерфейс – это договор о том, что классы могут делать. Он, однако, не указывает, как классу надо это делать.
Соглашение об именах: Используйте причастие (на английском языке), состоящее из одного или нескольких слов. Каждое слово должно начинаться с заглавной буквы, например, Serializable,
Movable, Clonable, Runnable и т.д.
Пример: интерфейс Movable и его реализация
Допустим, наше приложение содержит много объектов, которые могут двигаться. Мы можем определить интерфейс Movable
(см. рис. 5.2), содержащий сигнатуры различных методов
движения.

95

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 5.2. Интерфейс Movable и подкласс MovablePoint

Интерфейс Movable – файл Movable.java

Так же как абстрактный класс, интерфейс не может быть замещен, так как он неполон (тело абстрактных методов отсутствует).
96

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Для того чтобы использовать интерфейс, надо создать наследуемый
подкласс и обеспечить реализацию всех абстрактных методов, объявленных в интерфейсе. После этого подклассы станут завершенными и могут быть замещены.
Подкласс MovablePoint – файл MovablePoint.java
Чтобы унаследовать подклассы из интерфейса, надо использовать
новое ключевое слово “implements” вместо “extends” для наследуемых
подклассов как для обычного, так и для абстрактного классов. Важно
отметить, что подкласс, наследующий интерфейс, должен переопределить все абстрактные методы, определенные в интерфейсе. В противном случае подкласс не может быть откомпилирован. Например:

97

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Другие классы в приложении могут похожим образом реализовать интерфейс Movable и предложить их собственную реализацию абстрактных методов, определенных в интерфейсе
Movable.
Тестирующая программа – файл TestMovable.java
Мы можем также выполнить апкастинг экземпляров подкласса
до интерфейса Movable, в соответствии с принципом полиморфизма, аналогично апкастингу для абстрактного класса.

98

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

5.4. Реализация множественных интерфейсов
Как уже упоминалось, Java поддерживает только единичное наследование. Так, подкласс может быть наследником одного и только
одного класса. Java не поддерживает множественное наследование
во избежание наследования конфликтующих свойств из множественных суперклассов. Множественное наследование, однако,
имеет место в программировании на Java.
Подкласс может реализовывать более одного интерфейса. Это
разрешено в Java, поскольку интерфейс просто определяет абстрактные методы без фактических реализаций и менее явным образом
приводит к наследованию конфликтующих свойств из множественных интерфейсов. Другими словами, Java косвенно поддерживает
множественные наследования посредством реализации множественных интерфейсов. Например:
99

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Формальный синтаксис интерфейса:

Все методы в интерфейсе должны быть public и abstract (по определению). Нельзя использовать другие модификаторы доступа,
такие как private, protected и default, или такие модификаторы,
как static, final.
Все поля могут иметь модификаторы public, static и final (по
определению).
Интерфейс может быть наследником суперинтерфейса.
В обозначениях UML используются сплошная линия, связывающая подкласс с конкретным или абстрактным суперклассом
и пунктирная линия со стрелкой к интерфейсу, как показано
на рис. 5.3. Абстрактные классы и абстрактные методы изображаются курсивом.

100

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Рис. 5.3. Иллюстрация к сравнению обычного суперкласса,
абстрактного класса и интерфейса

Зачем использовать интерфейсы?
Интерфейс – это контракт (или протокол, или договор о взаимопонимании) о том, что классы могут делать. Когда класс реализует определенный интерфейс, он гарантирует реализовать все
абстрактные методы, объявленные в интерфейсе. Интерфейс определяет множество общих поведений. Классы, реализующие интерфейс, соглашаются на эти поведения и предлагают собственную
реализацию этих поведений. Одним из главных применений интерфейса является предложение контракта взаимодействия для двух
объектов. Как известно, класс реализует интерфейс, класс содержит
конкретные реализации методов, объявленных в этом интерфейсе, и гарантируется возможность вызывать эти методы безопасно.
Другими словами, два объекта могут взаимодействовать на основе
контракта, определенного в интерфейсе, вместо специфических реализаций.
Java не поддерживает множественного наследования, как, например, C++. Множественное наследование позволяет наследовать
подкласс более чем от одного суперкласса. Это вызывает проблему
двух суперклассов, имеющих конфликтующие реализации. (Какой
реализации следовать в подклассе?) Однако в Java множественное
101

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

наследование имеет место. Java выполняет это разрешением «реализовать» более одного интерфейса (но наследовать (“extends”)
можно только от единственного суперкласса). Поскольку интерфейсы содержат только абстрактные методы без реализаций, никакого
конфликта не возникает между множественными интерфейсами.
(Интерфейс может содержать константы, но это не рекомендуется;
если подкласс реализует два интерфейса с конфликтующими константами, компилятор выдаст ошибку компиляции.)

5.5. Интерфейс и абстрактный суперкласс
Вопрос о том, как лучше проектировать – с использованием интерфейса или абстрактного суперкласса – не имеет ясного ответа.
Рекомендуется использовать абстрактный суперкласс, если имеется четкая иерархия классов. Абстрактный класс может содержать частичную реализацию (например, переменные и методы).
Интерфейс не может содержать никакой реализации, но просто
определяет поведение.

5.6. Динамическое (позднее) связывание
Часто рассматриваются объекты не типа своего класса,
но их базового типа (суперкласса или интерфейса). Это позволяет писать коды, не зависящие от конкретного типа в реализации.
В примере для Shape мы всегда можем использовать getArea()
и не волноваться по поводу того, являются ли объекты треугольниками или кругами.
Это, однако, создает новую проблему. Во время компиляции
компилятор не может точно знать, какой именно фрагмент кода
связывается с объектом во время выполнения, другими словами,
например, getArea() имеет различные реализации для Rectangle
и Triangle.
102

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

В процедурных языках программирования,например в C,
компилятор генерирует вызов функции по определенному имени, а редактор связей (компоновщик) обращает этот вызов по абсолютному адресу кода, который должен выполниться во время
выполнения программы. Этот механизм называется статическим или ранним связыванием. При вызове метода код, который должен быть выполнен, определяется только во время выполнения.
Для поддержки полиморфизма объектно ориентированный
язык использует иной механизм, называемый динамическим связыванием (или поздним связыванием, или связыванием во время
выполнения). При вызове метода исполняемый код определяется
только во время выполнения. Во время компиляции компилятор
проверяет, существует ли метод, и выполняет проверку типа по аргументам и типу возвращаемого значения, однако не знает, какой
именно фрагмент кода выполнится при выполнении. Когда сообщение посылается объекту, чтобы вызвать метод, объект определяет, какой именно код будет выполняться.
Несмотря на то что динамическое связывание разрешает проблему поддержки полиморфизма, оно вызывает новую проблему.
Компилятор не способен проверить правильность приведения типов. Правильность приведения типов может быть проверена только
во время выполнения (посредством исключения ClassCastException,
генерируемого в случае несоответствия типов).
Разрешить эту проблему позволяют дженерики (см. гл. 6).

5.7. Инкапсуляция, связывание и связность
При объектно ориентированном дизайне желательно проектировать герметично инкапсулированные классы, совершенно
не связанные с другими классами и имеющие высокую связанность
внутри, так как именно такие классы легко поддерживать, также
они применимы для повторного использования.
103

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Инкапсуляция требует содержать данные и методы внутри класса, чтобы пользователи не имели доступа к данным напрямую,
но только посредством методов. Герметичная инкапсуляция может
быть достигнута объявлением всех переменных класса с модификатором private и поддержкой public-методов – геттеров и сеттеров для переменных. Преимуществом является то, что при этом вы
имеете полный контроль над тем, как данные должны быть прочитаны (например, в каком формате) и каким образом данные будут
изменены (например, при проверке).
Сокрытие информации: другим преимуществом герметичной инкапсуляции является сокрытие информации, что означает,
что пользователи не знают (и не нуждаются в этом знании), как данные хранятся внутри класса.
Преимущество герметичной инкапсуляции важнее необходимости вызова дополнительных методов.
Связывание относится к степени, с которой один класс зависит
от знания внутреннего устройства другого класса. Герметичное связывание нежелательно, так как, если один класс изменяет свое внутреннее представление, все другие тесно связанные классы должны
быть переписаны.
Очевидно, избегание связывания часто ассоциируется с герметичной инкапсуляцией. Например, использование хорошо определенного public-метода для доступа к данным вместо прямого доступа к данным.
Связность относится к степени, с которой класс или метод
противостоит разрушению на мелкие части. Желательна высокая
степень связности. Каждый класс должен быть спроектирован таким образом, чтобы моделировать единую сущность со своим
множеством ответственностей и выполнять тесно связанные задачи. А каждый метод должен выполнять единственную задачу.
Классы с низкой связностью трудно поддерживать и повторно
использовать.
Итак, высокая связность ассоциируется с избеганием связывания. Это происходит потому, что класс с высокой связностью
104

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

имеет меньшее (или минимальное) взаимодействие с другими
классами.
Более подробно вопросы проектирования классов рассматриваются в главе 7.

1.
2.
3.
3.
4.
5.

Контрольные вопросы к главе 5
Что такое абстрактный метод?
Что такое абстрактный класс?
Что означает ключевое слово “abstract”?
Что такое интерфейс?
Как применяется ключевое слово “implements”?
Возможно ли применение множественных интерфейсов?

Задания к главе 5
1. Написать программу, реализующую абстрактный суперкласс Shape с абстрактным методом getArea() и его подклассы Rectangle (прямоугольник) и Triangle (треугольник) в соответствии с диаграммой на рис. 5.1 из раздела 5.2.
2. Написать программу, реализующую суперкласс Shape из п. 2
заданий к главе 4 как абстрактный с абстрактным методом
getArea() – см. рис. 5.4.
Два поля класса color(String) и filled(boolean) – protected
поля, они доступны в подклассах и классах одного пакета.
Они обозначены знаком ‘#’. Написать геттеры и сеттеры для всех
полей и toString(). Написать два абстрактных метода getArea()
и getPerimeter() – на диаграмме выделены курсивом.
В подклассах Circle и Rectangle будут переопределяться
и реализовываться абстрактные методы getArea(), getPerimeter()
и toString().
3. Реализовать интерфейс Movable, содержащий абстрактные методы перемещения вверх, вниз, влево, вправо,
для подкласса перемещаемой точки MovablePoint, содержащего реализации указанных методов – см. раздел 5.3.
105

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Рис. 5.4. Диаграмма классов для задания 2 главы 5

106

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

4.

Используя интерфейс Movable из 5.3, написать два класса – MovablePoint и MovableCircle, задав скорость перемещения точки и радиуса окружности соответственно. Наброски кода:

107

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Напишите тестирующую программу и проверьте следующие утверждения:

5.

108

Написать интерфейсы GeometricObject – «ГеометрическийОбъект», в котором объявляются два абстрактных ме-

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

тода: getParameter() и getArea(), как указано в диаграмме
на рис. 5.5, и Resizable – «ИзменяемыйРазмер» (масштабирование) в соответствии с диаграммой (рис. 5.5):

Рис. 5.5. Диаграмма классов для задания 5 главы 5

109

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

6.

Написать реализацию класа Circle из задания 5 с protected
переменной radius для интерфейса GeometricObject:

7. Написать тестирующую программу TestCircle для проверки
методов, определенных в Circle.
8. Класс ResizableCircle определяется как подкласс класса Circle, который также реализует интерфейс Resizable,
как показано на диаграмме классов. В интерфейсе Resizable
объявлен абстрактный метод resize(), который изменяет
размер (radius) в соответствии с заданным процентным
соотношением. Напишите интерфейс Resizable и класс
ResizableCircle:

110

5. АБСТРАКТНЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

9.

Напишите тестирующую программу TestResizableCircle
для проверки методов, определенных в ResizableCircle.

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ
ВО ФРЕЙМВОРК «КОЛЛЕКЦИИ»
Дженерики – это параметризованные типы. С их помощью
можно объявлять классы, интерфейсы и методы, в которых тип данных указан в виде параметра.
Дженерики похожи на шаблоны в C++ (но имеют существенные
отличия). Дженерики поддерживают абстрагирование типов.
Разработчики классов должны проектировать классы с использованием дженериков, в то время как пользователи этих классов
должны указывать конкретный тип при создании объектов или вызове методов.
Так, мы знакомы с передачей аргументов в методах. Аргументы
размещаются в круглых скобках () и передаются в метод. В дженериках вместо передачи аргументов передается тип информации,
указанный внутри угловых скобок .
Первоначально дженерики использовались для абстрагирования типов при работе с коллекциями (см. гл. 7).

6.1. Введение во фреймворк «Коллекции»
В Java имеется возможность использования массива для хранения
элементов одного типа как одного из базовых типов или объектов.
Однако массив не поддерживает динамическое распределение памяти – он имеет фиксированную длину, которая не может быть изменена,
будучи однажды заданной. Массив является простой линейной структурой. Многие приложения могут потребовать более сложных структур данных, таких как связный список, стек, множество или деревья.
В Java динамически распределенные структуры данных, такие
как ArrayList, LinkedList, Vector, Stack, HashSet, HashMap, Hashtable,
поддерживаются единой архитектурой, которая называется
«Фреймворк Коллекции», определяющей общее поведение всех
классов, входящих в коллекции.
112

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Коллекция – это объект, который содержит набор объектов.
Каждый из этих объектов в коллекции называется элементом.
Фреймворк, по определению, – это программное обеспечение, облегчающее разработку и объединение разных компонентов большого программного проекта. Фреймворк – это набор интерфейсов,
который расширяет возможности для проектирования.
В Java фреймворк «Коллекции» предлагает единый интерфейс
для хранения, извлечения и действий с элементами коллекции
независимо от лежащей в основе их фактической реализации.
В Java пакет фреймворка «Коллекции» (java.util) содержит:
1. Набор интерфейсов.
2. Классы реализаций.
3. Алгоритмы (например, сортировки или поиска).
Первоначально структуры данных Java состояли из array, Vector,
и Hashtable, которые были описаны неунифицированным способом. Впоследствии был введен единый фреймворк «Коллекции»,
в соответствии с которым были модифицированы классы (Vector
и Hashtable).
В JDK 1.5 были введены дженерики, которые поддерживают
передачу типов и добавляют такие преимущества, как, например,
автобоксинг (англ. autoboxing – упаковка – обычно используется без перевода) и анбоксинг (англ. unboxing – распаковка – также
обычно используется без перевода), усовершенствование цикла
for. Коллекции модернизированы для поддержки дженериков и используют все предоставляемые ими преимущества.
Для понимания этой главы надо хорошо понимать:
• полиморфизм, в особенности апкастинг и даункастинг;
• интерфейсы, абстрактные методы и их реализации.
Классы и интерфейсы для коллекций содержатся в пакете
java.util.
Пример коллекции – ArrayList
Рассмотрим пример коллекции ArrayList. ArrayList является
структурой данных, похожей на массив, но способной изменять
свой размер.
113

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Напомним, что коллекция – это объект, который удерживает набор элементов. Ниже приведен пример использования ArrayList
для удержания набора объектов типа String:

114

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Возможно, придется откомпилировать программу с ключом
Xlint:unchecked и будут получены предупреждающие комментарии.
Мы обсудим эти предупреждения позже.
Рассмотрим программу
• Cтроки 2–4 – импорт интерфейсов и классов коллекции из пакета java.util package:

Рис. 6.1. Иерархия классов и интерфейсов

Иерархия класса ArrayList показана на рисунке (см. рис. 6.1).
Мы видим, что ArrayList реализует интерфейсы List, Collection
и Iterable. Интерфейсы Collection и Iterable определяют общее поведение всех реализаций коллекции. Интерфейс Collection определяет, как добавить элемент в коллекцию и как удалить элемент
из коллекции. Интерфейс Iterable определяет механизм перебора
или просмотр всех элементов коллекции. Вместо использования
интерфейса Collection напрямую, обычно используется один из его
подинтерфейсов – List (упорядоченный список, поддерживающий
доступ по индексу), Set (отсутствие повторяющихся элементов)
115

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

или Queue (очередь – организация данных по принципу FIFO (First
In First Out)).
• В строке 8 мы создаем объект ArrayList и применением апкастинга приводим его к интерфейсу List. Следует иметь в виду,
что хорошая программа оперирует интерфейсами, а не конкретными реализациями. Коллекции предлагают набор интерфейсов для программирования на уровне интерфейсов,
а не реализации.
• Интерфейс Collection определяет общее поведение коллекции, такое как добавление и удаление элементов. Он объявляет такие абстрактные методы, как:

В версиях до JDK 1.5 метод add(Object) оперирует с java.lang.
Object, который является корневым классом Java. Поскольку все
классы Java являются подклассами Object, любой класс Java может быть приведен с применением апкастинга к Object и добавлен
в коллекцию. Апкастинг, который всегда безопасен в смысле приведения типов, выполняется компилятором.
• Суперинтерфейс Iterable определяет механизм перебора
или просмотра элементов коллекции посредством так называемого объекта Iterator. Интерфейс Iterable содержит только
один абстрактный метод для извлечения объекта Iterator, ассоциируемого с коллекцией.

116

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Для версий до JDK 1.5:
• boolean hasNext() – возвращает true, если в коллекции еще
имеются элементы;
• Object next() – возвращает следующий элемент;
• void remove() – удаляет последний объект, возвращенный итератором.
Метод hasNext() возвращает true, если в коллекции еще имеются элементы, а метод next() возвращает следующий элемент. Метод
remove() удаляет последний элемент коллекции, возвращенный методом next(). Метод remove() следует вызывать только после вызова
метода next().
• Строки 15–20 – извлекается объектом Iterator, ассоциируемым с данным ArrayList, и использует цикл while
для итерации(прохода по всем элементам) всех элементов
коллекции несмотря на их фактическую реализацию.
• В строке 18 метод iter.next() возвращает java.lang.Object. В версиях до JDK 1.5 программист должен был точно выполнить даункастинг к объекту Object исходного класса String до выполнения следующих действий.
Приведенная выше программа работает идеально хорошо, если
мы решим использовать реализации LinkedList, Vector или Stack
(из интерфейса List вместо ArrayList). Мы только будем должны
модифицировать строку 8, чтобы применить List с другой реализацией. Другие оставшиеся коды следует оставить без изменения.
В этом и есть красота программирования на уровне интерфейсов,
а не на уровне фактических реализаций.

Этот пример иллюстрирует унифицированную архитектуру
фреймворка «Коллекции», определенную в интерфейсах Collection
(и его подинтерфейсах List, Set, Queue), Iterable и Iterator. Можно
117

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

программировать на уровне этих интерфейсов вместо программирования на уровне фактических реализаций.
В версиях, предшествующих JDK 1.5, коллекция создавалась
для содержания объекта java.lang.Object. Поскольку Object является корневым классом, все Java-классы – потомки класса Object могут быть приведены с применением апкастинга к Object и удерживаться в коллекции. Однако при извлечении элемента из коллекции
(в форме Object) на программисте лежит ответственность провести
даункастинг от Object обратно к исходному классу до того, как будут
выполняться дальнейшие действия.

6.2. Коллекции и небезопасность типов
Подход к коллекциям до JDK 1.5 имеет следующие недостатки:
1. Апкастинг к java.lang.Object выполняется непосредственно
компилятором. Но программист должен точно провести даункастинг от Object к исходному классу.
2. Компилятор не способен во время компиляции проверить,
выполнен ли даункастинг правильно. Неправильно выполненный даункастинг будет отражен только во время выполнения в виде генерации исключения ClassCastException.
Это известно как динамическое связывание, или позднее
связывание. Например, если вы случайно добавили объект
Integer в приведенный выше список, который предназначен
для удерживания объектов типа String, то ошибка отобразится, только когда вы попытаетесь провести даункастинг
Integer к String, т.е. во время выполнения. Например,

118

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Почему бы не позволить компилятору проводить апкастинг
и даункастинг и проверять ошибки приведения вместо того, чтобы
оставлять эту проверку на время выполнения?

6.3. Введение в дженерики
В JDK 1.5 вводится новый инструмент для разрешения этой
проблемы, который получил название дженерики. Дженерики
позволяют передавать тип информации компилятору в форме
. Таким образом, компилятор может выполнить все необходимые действия по проверке типов во время компиляции,
обеспечивая безопасность по приведению типов во время выполнения.
Например, следующее утверждение с дженериками List
(читается как List of Strings) и ArrayList (читается как
ArrayList of Strings) информирует компилятор, что List and ArrayList
должны удерживать объекты String:
List lst = new ArrayList(); // читается как List of //
Strings, ArrayList of Strings.
Известно, как передаются аргументы в методах. Для этого аргументы помещаются внутри круглых скобок () и таким образом
передаются в метод. В дженериках вместо передачи аргументов
компилятору передается тип информации, заключив его в угловые
скобки .
119

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Проиллюстрируем использование дженериков на примере типобезопасного массива для удержания объектов конкретного типа
(похожих на ArrayList).
Начнем с версии без дженериков:

120

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

MyArrayList не является типобезопасным. Например, если
мы создаем MyArrayList, который предназначен для удержания
объектов типа String, но в него добавляется объект типа Integer,
то компилятор не может обнаружить ошибку. Это происходит потому, что MyArrayList разработан для удержания объектов типа
Object и для любого из классов Java может быть проведен апкастинг
до Object.

121

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Если мы собираемся создать список строк String, но случайно
добавили объект не типа String, то неявным образом для него будет проведен апкастинг до Object. Компилятор не способен определить, действительно ли проведен даункастинг во время компиляции (это известно как позднее или динамическое связывание).
Неправильный даункастинг будет выявлен только во время выполнения в форме исключения ClassCastException, что может быть
слишком поздно. Компилятор не способен обнаружить эту ошибку
во время компиляции. Можем ли мы заставить компилятор обнаруживать эту ошибку и гарантировать типобезопасность во время
выполнения?

6.4. Дженерик-классы
Начиная с версии JDK 1.5 вводятся так называемые дженерики
для разрешения данной проблемы. Дженерики позволяют абстрагировать типы. Можно разработать класс с дженерик-типом и указать
конкретный тип во время инициализации объекта. Компилятор бу122

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

дет в состоянии выполнить необходимую проверку типов во время
компиляции и гарантировать, что никакая ошибка по приведению
типов не будет иметь места во время выполнения. Это известно
как типобезопасность.
Посмотрим на описание интерфейса java.util.List:

Механизм похож на вызов метода. Вспомним, что в определении
метода мы объявляем формальные параметры для передачи данных в метод. Например,

Во время вызова формальные параметры заменяются на фактические. Например,

Формальные параметры типа в описании класса имеют ту же
цель, что и формальные параметры в описании метода. Класс мо123

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

жет использовать формальные параметры типа для получения информации о типе при создании объекта данного класса.
Фактические параметры, используемые во время инициализации,
называются фактическими параметрами типа.
Вернемся к классу java.util.List, в котором при фактической
реализации, такой, например, как List, все вхождения формального параметра типа заменяются на фактические параметры типа . Имея эту дополнительную информацию о типе,
компилятор способен провести проверку типов во время компиляции и гарантировать, что во время выполнения не будет ошибки
приведения типов.
Соглашение об именах формальных параметров типа
Рекомендуется использовать одну большую букву для формальных параметров типа. Например:
• для элемента коллекции;
• для типа;
• для ключа и значения;
• для числа
• S, U, V и т.д. для 2-го, 3-го, 4-го параметров типа.
Пример дженерик-класса
В данном примере описан класс GenericBox, который имеет параметр типа E, который удерживает переменную content типа E.
Конструктор, геттер и сеттер работают с параметром типа E. Метод
toString() отображает фактический тип параметра переменной content.

124

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Следующая тестирующая программа создает объекты класса GenericBox с различными типами переменных (String, Integer
и Double). Обратите внимание, что в JDK 1.5 также введены автобоксинг и анбоксинг для преобразования объектов базового типа
и объектов класса оболочек.

125

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Результаты:
Привет! (class java.lang.String)
123 (class java.lang.Integer)
55.66 (class java.lang.Double)
Потеря типа
Из предыдущего примера видно, что компилятор заменял параметр типа E фактическим типом (таким, как String, Integer) во время инициализации объектов. В этом случае компилятору потребуется создавать новый класс для каждого фактического типа.
В действительности компилятор заменяет все ссылки на параметр типа E на Object, выполняет проверку типов и добавляет
требуемую операцию даункастинга. Например, GenericBox компилируется следующим образом (этот код компилируется без использования дженериков):

126

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Компилятор также добавляет операцию даункастинга в коды тестирующей программы:

Таким образом, одно и то же описание класса используется для всех типов. Самым важным является то, что компиляция
в байт-код с этими добавлениями выполняется без дженериков.
Но как только программа запущена – вся информация о параметрах типов теряется. Этот процесс называется потерей типа.
Вернемся к типобезопасному ArrayList
Итак, вернемся к примеру MyArrayList. С использованием дженериков мы можем переписать программу следующим образом:

127

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Проанализируем программу
MyGenericArrayList объявляет класс-дженерик с формальным параметром типа. Во время вызова, например,
128

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

MyGenericArrayList, конкретный тип или параметр
фактического типа, заменил параметр формального типа .
Неявным остается то, что дженерики реализуются Javaкомпилятором как обратная конвертация, называемая потерей
типа, которая транслирует или перезаписывает использующий
дженерики код в код, не использующий дженерики (чтобы гарантировать обратную совместимость). Эта операция конвертации
удаляет всю информацию о типах дженериков. Например, ArrayList
станет ArrayList. Параметр формального типа, такой
как , заменяется на Object по умолчанию (или на наиболее высокий тип параметра в данной иерархии типов). Когда окончательный код не является корректным с точки зрения компилятора, компилятор добавляет операцию приведения типов.
Следовательно, транслируемый код выглядит следующим
образом:

129

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Когда класс создается с параметром фактического типа, например, MyGenericArrayList, компилятор гарантирует, что
add(E e) работает только с типом String. Он также добавляет правильный оператор даункастинга, чтобы возвращенный тип E соответствовал get(). Например,

130

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

С дженериками компилятор способен выполнять проверку типов во время компиляции и гарантировать типобезопасность
во время выполнения.
В отличие от шаблона в C++, который создает новый тип для каждого конкретного параметризованного типа, в Java дженерик-класс
компилируется только один раз и имеется один-единственный
файл класса, который используется для создания объектов для всех
конкретных типов.

6.5. Дженерик-методы
Методы также могут быть определены с дженерик-типами (аналогично дженерик-классу). Например,
131

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Дженерик-метод может объявить параметры формального типа
(например, , ) с предшествующим указанием возвращаемого типа. Параметры формального типа могут быть затем использованы как средства для удержания возвращаемого типа, параметров методов и локальных переменных в теле дженерик-метода
для правильной проверки типов компилятором.
Аналогично дженерик-классу, при трансляции дженерик-метода формальные типы параметров заменяются с потерей его типа.
Все дженерик-типы заменяются на тип Object по умолчанию (или
на наиболее высокий тип параметра в данной иерархии типов).
Транслируемая версия выглядит следующим образом:

Однако компилятор проверяет, является ли объект a объектом типа E[], а объект lst – объектом типа ArrayList и является
ли объект e объектом типа E во время вызова, чтобы гарантировать
типобезопасность. Например,

132

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Дженерики дают возможность использовать различный синтаксис для указания типа в дженерик-методах. Можно указать фактический тип в угловых скобках , между оператором «точка» (.)
и именем метода. Например,

Синтаксис делает код лучше читаемым и дает возможность
контроля посредством дженерик-типа в ситуациях, когда тип может быть неочевидным.

133

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

6.6. Wildcards – подстановочные символы
Рассмотрим следующий код:

Он вызовет ошибку компиляции “incompatible types”, поскольку
ArrayList не является ArrayList.
Это ошибка – несмотря на нашу интуицию насчет полиморфизма, поскольку мы часто присваиваем объекту подкласса ссылку
на суперкласс.
Рассмотрим следующие два утверждения:

Строка 2 генерирует ошибку компиляции. Но если бы строка 2
прошла успешно и некоторые случайные объекты были добавлены
в objLst, strLst, то они были бы повреждены и больше не содержали
бы только строки String (поскольку objLst и strLst имеют одни и те
же ссылки).
Имея в виду сказанное, предположим, что мы хотим написать
метод printList(List) для печати элементов списка. Если мы определим метод как printList(List lst), то он сможет только принимать аргументы List, но не List или List.
Например,

134

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Подстановочные символы (Wildcards):
Обобщенный подстановочный символ
Для разрешения данной проблемы в дженериках предоставляется обобщенный символ (wildcard) (?), который применим к любому
неизвестному типу. Например, перепишем наш printList() следующим образом, чтобы принимать List любого неизвестного типа.

Пример на подстановочный символ
Если мы хотим, чтобы дженерик-метод работал со всеми типами данных, может быть использован обобщенный подстановочный
символ. Следующая программа объясняет его применение.

135

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Результаты:
6 3 10
-------ABC
136

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

Подстановочный символ для ограничения сверху
может быть интерпретирован как и List в Java?

6. ДЖЕНЕРИКИ И ВВЕДЕНИЕ ВО ФРЕЙМВОРК « КОЛЛЕКЦИИ »

9. Что такое обобщенные подстановочные символы?
10. Что такое подстановочные символы для ограничения
сверху/снизу?

1.

2.

Задания к главе 6
Напишите дженерик-метод для определения количества
элементов в коллекции, которая имеет определенные
свойства (например, состоит из четных чисел, простых
чисел, палиндромов).
Напишите дженерик-метод, чтобы поменять местами два
различных элемента массива.

7. КОЛЛЕКЦИИ
Понятие фреймворка «Коллекции» было рассмотрено в разделе 6.1.
Итак, коллекция – это программный объект, содержащий в себе
набор значений одного или различных типов и позволяющий обращаться к этим значениям.
В разделе 6.1. был приведен пример коллекции ArrayList для версии Java до JDK1.5 и были сделаны выводы о необходимости проведения программистом точного даункастинга от Object к исходному
классу, а также отсутствии типобезопасности при проведении данного преобразования с отображением ошибки только во время выполнения.

7.1. ArrayList с дженериками
В JDK 1.5 вводится новый инструмент для разрешения проблемы типобезопасности, который получил название дженерики.
Дженерики позволяют передавать тип информации компилятору в форме . Таким образом, компилятор может выполнить
все необходимые действия по проверке типов во время компиляции, обеспечив безопасность по приведению типов во время
выполнения.
Например, следующее утверждение с дженериками List
(читается как List of Strings) и ArrayList (читается как ArrayList
of Strings) информирует компилятор, что List and ArrayList должны
удерживать объекты String:

Известно, как передаются аргументы в методах. Для этого надо
поместить аргументы внутри круглых скобок () и передать их в метод. В дженериках вместо передачи аргументов компилятору передается тип информации, заключенный в угловые скобки .
144

7. КОЛЛЕКЦИИ

Перепишем программу из раздела 6.1 (пример коллекции –
ArrayList) с использованием дженериков:

145

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

В строках 8 и 14 тип информации о классах коллекции указан с использованием дженериков, записанных как List,
ArrayList и Iterator. На основе информации о типах
компилятор способен проверить тип аргументов для методов add()
и выдает ошибку компиляции в строке 20, когда мы пытаемся добавить объект Integer. Компилятор может также автоматически включить оператор даункастинга в строку 16 и определить неверный тип
в строке 21 в методах get() по извлечению элементов из коллекции.
Обратите внимание, что в предыдущем примере для версий, предшествующих JDK 1.5, программист должен был точно выполнить
операцию даункастинга.
В JDK 1.5 была также введена новая структура цикла, названная
усовершенствованным циклом for (строки 24–26). Переменная цикла str будет обращаться к каждому элементу lst в теле цикла.

7.2. Обратная совместимость
Если скомпилировать программу версии до JDK 1.5, используя
компилятор версии JDK 1.5 или выше, например,

146

7. КОЛЛЕКЦИИ

Придется использовать опцию (ключ) -Xlint:unchecked.
Компилятор выдаст предупреждающие сообщения о небезопасности операций (т.е. компилятор не способен проверить типы и обеспечить безопасность типов во время выполнения). Можно выполнить программу с этим предупреждением, т.е. откомпилировать
программу с ключом -Xlint:unchecked.

– непроверенный вызов add(E) как к члену с непроверенным типом
java.util.List
.

7.3. Автобоксинг и автоанбоксинг –
автоупаковка и автораспаковка
Коллекция содержит только объекты. Коллекция не может содержать элементы базовых типов (таких, как int или double).
Чтобы поместить элемент базового типа в коллекцию (такую,
например, как ArrayList), следует инкапсулировать элемент базового типа в объект-оболочку, используя соответствующий классоболочку, как показано ниже на рис. 7.1.

Рис. 7.1. Базовые типы данных и классы-оболочки

147

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

В версиях до JDK 1.5 надо было значение базового типа заключить в оболочку объекта и распаковать значение примитивного
типа из объекта-оболочки:

В версиях до JDK 1.5 добавляется код для упаковки и распаковки.
В JDK 1.5 вводятся новые средства, имеющие названия автобоксинг (упаковка) и анбоксинг (распаковка). Например:

Пример: коллекции из базовых типов (версия до JDK 1.5)
Версии до JDK 1.5 не поддерживают дженериков, автобоксинг
и усовершенствованный цикл for. Коды для коллекции могут быть
весьма беспорядочными и, более важно, небезопасными в смысле
передачи типов.

148

7. КОЛЛЕКЦИИ

Пример – автобоксинг и анбоксинг для базовых типов
С дженериками, автобоксингом и усовершенствованным циклом for коды для коллекции становятся более короткими и, более
важно, типобезопасными. Например:

149

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

150

7. КОЛЛЕКЦИИ

7.4. Иерархия интерфейсов во фреймворке «Коллекции»
Иерархия интерфейсов (и обычно используемых классов реализаций) во фреймворке «Коллекции» показана на рис. 7.2.

Рис. 7.2. Иерархия интерфейсов во фреймворке «Коллекции»

151

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

7.5. Интерфейсы Iterable, Iterator
и усовершенствованный цикл for
Интерфейс Iterable, который принимает дженерик типа E
(читается как Iterable элемента типа E), объявляет один абстрактный метод, имеющий название iterator() для извлечения итератора,
т.е. Iterator-объекта, ассоциируемого с коллекцией и реализующего интерфейс Iterator. Объект Iterator может быть затем использован для организации перебора всех элементов соответствующей
коллекции.

Все реализации коллекции (например, ArrayList, LinkedList,
Vector) должны реализовывать этот метод, возвращающий объект,
реализующий интерфейс Iterator.
Интерфейс Iterator объявляет три абстрактных метода:

Как видно из приведенного примера, можно использовать цикл
while для перебора элементов с интерфейсом Iterator следующим
образом:

152

7. КОЛЛЕКЦИИ

Кроме интерфейса Iterator, начиная с JDK 1.5, также вводится
усовершенствованный цикл for, который можно использовать
для перебора элементов коллекции (так же как и массива).
Синтаксис этого оператора следующий (читается как «для каждого элемента коллекции»):

Переменная цикла item берет каждый элемент коллекции
при каждом выполнении тела цикла (см. пример из раздела 7.1).
Возможность модификации объектов в коллекции
Усовершенствованный цикл for предлагает удобный способ
для перебора элементов коллекции. Однако он скрывает интерфейс
Iterator, следовательно, вы НЕ можете удалять (посредством Iterator.
remove()) или заменять элементы.
С другой стороны, поскольку переменная цикла получает клонированную копию как ссылку на объект, усовершенствованный
оператор for может быть применен для модификации «мутирующих» (изменяющихся) элементов (таких, как StringBuilder), используя ссылки на «клонированные» объекты, но он не может
модифицировать «немутируемые» (неизменяемые) элементы
(такие, как String и оболочки базовых классов), поскольку созданы новые ссылки.
153

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Пример использования усовершенствованного цикла for
для коллекции «мутирующих» (изменяющихся) объектов (таких, как StringBuilder)

Пример использования усовершенствованного цикла for
для «немутирующих» (неизменяющихся) объектов, таких
как String

154

7. КОЛЛЕКЦИИ

7.6. Интерфейс Collection и его подинтерфейсы List,
Set, Queue
Интерфейс Collection, который принимает в качестве параметра дженерик типа E (читается как «коллекция элементов
типа E), является корневым интерфейсом фреймворка «Коллекции».
Он определяет общее поведение для всех классов, например, устанавливает, как добавить или удалить элемент посредством следующих абстрактных методов:

155

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Возможна ли коллекция из элементов базовых типов?
Collection не может состоять из элементов базовых типов, таких, например, как int или double. Значения базовых типов должны быть заключены в оболочки объектов (с использо156

7. КОЛЛЕКЦИИ

ванием соответствующих классов-оболочек, таких, например,
как Integer и Double). В JDK 1.5 введены автобоксинг и автоанбоксинг для упрощения процессов автоупаковки и автораспаковки.
См. примеры раздела 7.3 «Автобоксинг и автоанбоксинг...».
На практике обычно программируют на уровне подинтерфейсов
интерфейса Collection: List, Set или Queue, которые имеют следующие спецификации:
• List – список – моделирует массив изменяемого размера,
для которого разрешается доступ по индексу. Список может
содержать повторяющиеся элементы. Часто используемые реализации списка List – это ArrayList, LinkedList, Vector и Stack.
• Set – множество – моделирует математическое множество,
повторяющиеся элементы недопустимы. Часто используемые
реализации подинтерфейса Set – это HashSet и LinkedHashSet.
Подинтерфейс SortedSet моделирует упорядоченное и отсортированное множество элементов, реализованное TreeSet.
• Queue – очередь – моделирует очереди, организованные
по принципу First-in-First-out (FIFO) – «первым вошел – первым вышел». Подинтерфейс Deque моделирует очереди,
с которыми можно работать с двух концов. Реализации включают PriorityQueue, ArrayDeque и LinkedList.
Подробности этих подинтерфейсов и их реализации рассмотрим
далее.

7.7. Интерфейс Map
Интерфейс Map, который принимает два дженерика типов K и V (читается как «Карта ключа типа K и значения типа V»),
используется как коллекция «ключ-значение». Никакие повторяющиеся ключи не разрешены. Часто используемые реализации
включают HashMap, Hashtable и LinkedHashMap. Их подинтерфейс
SortedMap моделирует упорядоченную и отсортированную
карту на основании ключа, реализованного в TreeMap.
157

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Обратите внимание, что Map не является подинтерфейсом
Collection, поскольку подразумевает использование пары объектов для каждого элемента. Подробности рассмотрим далее в разделе 7.12.

7.8. Интерфейс List и его реализации
На практике принято программировать на уровне одного
из подинтерфейсов коллекции – List, Set или Queue, вместо того
чтобы программировать на уровне суперинтерфейса Collection.
Эти подинтерфейсы в дальнейшем совершенствуют поведение
коллекции.
List – список – моделирует одномерный массив с изменяемой размерностью (структуру данных «список»), поддерживается доступ по индексу. Элементы в списке могут удаляться или добавляться по конкретному индексу в соответствии со значением
переменной int index. Список может содержать повторяющиеся
элементы, может также содержать null элементы. В списке можно
осуществлять поиск, проходя последовательно по его элементам
и совершать операции над переменными выбранного диапазона
значений.
Списки являются наиболее часто используемыми структурами
данных.
Интерфейс List объявляет следующие абстрактные методы,
помимо суперинтерфейсов (см. рис. 7.3).
Поскольку список List имеет индекс, то такие операции, как add(),
remove(), set(), могут быть применены к элементу, находящемуся
на указанной позиции.

158

7. КОЛЛЕКЦИИ

Рис. 7.3. Интерфейсы List и его реализации

159

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Абстрактный суперкласс AbstractList обеспечивает реализацию для многих абстрактных методов, объявленных в интерфейсах List, Collection, и Iterable. Однако некоторые методы, например,
как get(int index), остаются абстрактными. Эти методы будут реализованы в конкретных подклассах, таких как ArrayList и Vector.
Классы реализаций ArrayList и Vector для интерфейса List
Класс ArrayList является самой лучшей реализацией интерфейса List – массива с изменяющимся размером. Реализует все
операции, определенные для структуры данных «список», разрешает любые элементы, включая null. Помимо реализации интерфейса List, этот класс предоставляет методы управления размером
массива, который используется для хранения списка. (Этот класс,
грубо говоря, эквивалентен классу Vector, за исключением того,
что является несинхронизированным.) Целостность ArrayList не гарантирована при многопоточности. Ответственность обеспечения
синхронизации лежит на программисте.
Каждый объект ArrayList имеет объем, используемый для хранения элементов в виде списка. Этот объем не меньше размера
списка. По мере добавления элементов в ArrayList, объем растет
автоматически.
Конструкторы:
ArrayList() – создает пустой список, с начальным объемом 10.
ArrayList(Collection c) – удаляет из списка все элементы, которые содержатся в указанной коллекции;
protected void removeRange(int fromIndex, int toIndex) – удаляет
из списка все элементы, чей индекс находится от fromIndex, включительно, до toIndex, не включая его;
boolean retainAll(Collection c) – оставляет в данном списке
только те элементы, которые содержатся в указанной коллекции;
E set(int index, E element) – заменяет элемент на указанной позиции в списке на указанный элемент;
int size() – возвращает количество элементов данного списка;
List subList(int fromIndex, int toIndex) – возвращает представление данного списка в виде подсписка, начиная с элемента с индексом fromIndex, включительно, до элемента с индексом toIndex,
не включая его;
Object[] toArray() – возвращает массив всех элементов данного
списка в правильной последовательности (т.е. от первого до последнего элемента);
T[] toArray(T[] a) – возвращает массив, содержащий все
элементы данного списка в правильной последовательности (от
первого до последнего элемента); тип возвращенного массива
при выполнении – тот же самый, что и у указанного массива;
void trimToSize() – сокращает массив до размера данного экземпляра ArrayList, который и будет текущим размером списка (так
как при удалении элементов размер списка не изменяется).
Методы, унаследованные из класса java.util.AbstractList:
boolean equals(Object o) – сравнивает указанный объект с данным списком (на предмет равенства);
int hashCode() – возвращает хэш-код для данного списка.
162

7. КОЛЛЕКЦИИ

Методы, унаследованные из класса java.util.AbstractCollection
containsAll – возвращает true, если данный список содержит все
элементы указанной коллекции;
toString – возвращает представление коллекции в виде строки
символов. Строковое представление состоит из списка элементов
коллекции в порядке, в котором они возвращаются итератором,
список заключается в квадратные скобки («[]»), соседние элементы
разделяются символами «, » (запятая и пробел). Элементы преобразуются в строку таким же образом, как и в “String.valueOf(Object).
Методы, унаследованные из класса java.lang.Object:
protected void finalize() – генерирует исключение Throwable, этот
метод вызывается, когда Java – сборщик мусора обнаруживает,
что на объект нет ссылок; подкласс переопределяет метод finalize,
чтобы запросить системные ресурсы или выполнить другие операции по очистке памяти;
public final Class getClass() – возвращает класс объекта во время выполнения, например,

public final void notify() – «просыпается» один поток, который ожидает на «мониторе» данный объект; если несколько потоков ожидают
данный объект, то для «просыпания» выбирается один из них;
public final void notifyAll() – пробуждает все потоки;
public final void wait(long timeout) – генерирует исключение
InterruptedException. У метода wait() есть три вариации. Один метод wait() бесконечно ждет другой поток, пока не будет вызван
метод notify() или notifyAll() на объекте. Другие две вариации метода wait() ставят текущий поток в ожидание на определенное
время. По истечении этого времени поток просыпается и продолжает работу.
Методы, унаследованные из интерфейса java.util.List:
boolean containsAll(Collection c) – возвращает true, если данный список содержит все элементы данной коллекции;
163

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

boolean equals(Object o) – сравнивает указанный объект со списком в смысле ихравенства. Возвращает true тогда и только тогда,
когда указанный объект является также списком, оба списка имеют
одинаковый размер и все соответствующие пары элементов в обоих списках равны (Два элемента e1 и e2 считаются равными, если
(e1==null ? e2==null : e1.equals(e2)).)
int hashCode() – возвращает значение хэш-кода для данного списка, хэш-код для списка определяется как результат следующих вычислений:

Преобразование списка в массив – метод toArray()
Суперинтерфейс Collection определяет метод toArray() для создания массива на основе данного списка. Возвращенный массив
возможно модифицировать.

Пример преобразования списка в массив

164

7. КОЛЛЕКЦИИ

Представление массива в виде списка – Arrays.asList()
Класс java.util.Arrays предлагает статический метод Arrays.
asList() для представления массива в виде списка List. Следует
иметь в виду, что метод предполагает представление в виде списка,
а не преобразование в список, поэтому следует внести изменения
в записи для представления в виде массива.

165

О. И. ГУСЬКОВА. ОБЪЕКТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В JAVA

Класс вектор Vector
Класс вектор Vector реализует увеличивающийся или уменьшающийся массив объектов. Как и массив, он содержит элементы,
доступ к которым возможен по индексу. Однако размер вектора
Vector может расти или уменьшаться в зависимости от добавления
или удаления элементов после того, как Vector уже был создан.
Конструкторы:
Vector() – создает пустой вектор таким образом, что внутренний
массив имеет размер 10, а его стандартный инкремент объема равен нулю;
Vector(Collection