0% нашли этот документ полезным (0 голосов)
38 просмотров241 страница

Guskova O OOP V Java

Учебное пособие О. И. Гуськовой посвящено объектно-ориентированному программированию на языке Java и охватывает основные принципы, такие как классы, объекты, наследование и полиморфизм. В документе также рассматриваются дженерики, коллекции и принципы объектно-ориентированного дизайна. Пособие предназначено для студентов и специалистов, желающих углубить свои знания в области программирования на Java.

Загружено:

vladafedorcenko38
Авторское право
© © All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате PDF, TXT или читать онлайн в Scribd
0% нашли этот документ полезным (0 голосов)
38 просмотров241 страница

Guskova O OOP V Java

Учебное пособие О. И. Гуськовой посвящено объектно-ориентированному программированию на языке Java и охватывает основные принципы, такие как классы, объекты, наследование и полиморфизм. В документе также рассматриваются дженерики, коллекции и принципы объектно-ориентированного дизайна. Пособие предназначено для студентов и специалистов, желающих углубить свои знания в области программирования на Java.

Загружено:

vladafedorcenko38
Авторское право
© © All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате PDF, TXT или читать онлайн в Scribd
Вы находитесь на странице: 1/ 241

Министерство образования и науки Российской Федерации

Федеральное государственное бюджетное образовательное учреждение


высшего образования
«Московский педагогический государственный университет»

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

ОБЪЕКТНО ОРИЕНТИРОВАННОЕ
ПРОГРАММИРОВАНИЕ В 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<E>, Iterator<E>
и усовершенствованный цикл for . . . . . . . . . . . . . . . . . . . . . . . . 152

4
СОДЕРЖАНИЕ

7.6. Интерфейс Collection<E> и его подинтерфейсы List<E>,


Set<E>, Queue<E> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
7.7. Интерфейс Map<K,V> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.8. Интерфейс List<E> и его реализации . . . . . . . . . . . . . . . . . . 158
7.9. Упорядочение, сортировка и поиск . . . . . . . . . . . . . . . . . . . 175
7.10. Set<E> – интерфейсы и реализации . . . . . . . . . . . . . . . . . . 180
7.11. Queue<E> – интерфейсы и реализации . . . . . . . . . . . . . . . 190
7.12. Интерфейсы и реализации Map<K,V> . . . . . . . . . . . . . . . . . 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


1. Что такое ООП?
2. Что такое объект?
3. Что такое класс?
4. Назовите основные принципы объектно ориентированного
программирования.
5. Что такое инкапсуляция?
6. Что такое абстракция?
7. Что такое поле/атрибут/переменная-член класса?
8. Как правильно организовать доступ к полям класса?
9. Что такое модификаторы уровня доступа?
10. Что такое конструктор?
11. Чем различаются конструктор по умолчанию, конструктор
без параметров и конструктор с параметрами?
12. Что означает ключевое слово “this” и как его можно исполь-
зовать?
13. Что такое геттеры? Что такое сеттеры?
14. Как применяется метод toString()?

Задания к главе 1
1. Написать программу описания класса Circle на основе при-
мера из раздела 1.7 и написать программу TestCircle, тести-
рующую все public-методы этого класса.
2. Написать программу описания класса Circle из раздела 1.15
c геттерами, сеттерами и методом toString().
3. Изменить программу для класса Time из примера 2 разде-
ла 1.18 таким образом, чтобы выполнялась проверка вход-
ных данных, т.е. в случае значений минут и секунд, выхо-
дящих за диапазон [0,59], и в случае значения часа, не по-
падающего в диапазон [0,23], вывести на экран сообщение
об ошибке.
4. Индивидуальное задание. Описать один из приведенных
ниже классов с использованием геттеров, сеттеров, метода
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”, т.е.,
например, “Иванов, [email protected]”.

Класс 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. КОМПОЗИЦИЯ

Контрольные вопросы к главе 2


1. Что такое ассоциация?
2. Что такое композиция?
3. Что такое агрегация?
4. В чем различие между ассоциацией и композицией?

Задания к главе 2
1. Проверить работу класса «Автор» (Author).
2. Проверить работу классов «Автор» (Author) и «Книга» (Book).
3. Проверить работу классов «Точка» (Point) и «Отрезок» (Line).
4. Для класса Line написать метод public double getGradient(),
определяющий угол наклона отрезка относительно оси X.
5. Для класса Line написать методы public double distance(int
x, int y) и public double distance(Point p), определяющие рас-
стояние от прямой, проходящей через точки начала и конца
отрезка, до заданной точки.
6. Для класса 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. Такое общее поведение включает
в себя, например, реализацию многопоточности и сборку мусора.

Контрольные вопросы к главе 3


1. Что такое наследование?
2. Что такое суперкласс и что такое подкласс?
3. Что означает ключевое слово “extends”?
4. Что такое перегрузка методов? Приведите пример.
5. Что такое переопределение методов? Приведите пример.
6. В чем различие между перегрузкой и переопределением?
7. Что означает ключевое слово “super”?

Задания к главе 3
1. Написать программу реализации класса Cylinder – ци-
линдр – наследник класса Circle – круг из примера 3.1. Ис-
пользовать 3 файла: Circle.java, Cylinder.java, TestCylinder.
java. Проверить работу конструкторов и переопределенных
методов.
2. Вывести на экран площадь поверхности цилиндра (см. п. 1
«Заданий к главе 3»), вычисляемую с помощью переопреде-
ленного метода getArea(). Для описания цилиндра использо-
вать переопределенный метод toString().
3. Написать программу реализации класса 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. Эта проблема может быть разре-
шена посредством использования так называемого абстрактного
класса.

Контрольные вопросы к главе 4


1. Приведите примеры полиморфизма при наследовании.
2. Что такое апкастинг и что такое даункастинг?
3. Объясните применение оператора instanceof.
4. Приведите пример использования полиморфизма для раз-
деления интерфейса и реализации.
5. Обсудите возможность проектирования классов из диаграм-
мы на рис. 4.2 (с. 90) в соответствии с принципом замещения
Лисков (см. гл. 8).
5. Единичное и множественное наследование – разъясните
возможность реализации в Java.

Задания к главе 4
1. Написать программу, реализующую суперкласс Shape (гео-
метрическая фигура) и его подклассы Rectangle (прямо-
угольник) и Triangle (треугольник), в соответствии с диа-
граммой на рис. 4.1 из раздела 4.5.
2. Написать программу, реализующую суперкласс Shape (фи-
гура) и его подклассы Circle (круг) и Rectangle (прямоуголь-
ник), а также Square (квадрат) – подкласс Rectangle в соот-
ветствии с диаграммой на рис. 4.2. Обсудить возможность
реализации такого проекта.
3. Написать программу, реализующую суперкласс 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.

Контрольные вопросы к главе 5


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

Задания к главе 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. Написать интерфейсы GeometricObject – «Геометричес-


кийОбъект», в котором объявляются два абстрактных ме-

108
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<String>
(читается как List of Strings) и ArrayList<String> (читается как
ArrayList of Strings) информирует компилятор, что List and ArrayList
должны удерживать объекты String:
List<String> lst = new ArrayList<String>(); // читается как 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<E>:

Механизм похож на вызов метода. Вспомним, что в определении


метода мы объявляем формальные параметры для передачи дан-
ных в метод. Например,

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


ческие. Например,

Формальные параметры типа в описании класса имеют ту же


цель, что и формальные параметры в описании метода. Класс мо-

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

жет использовать формальные параметры типа для получе-


ния информации о типе при создании объекта данного класса.
Фактические параметры, используемые во время инициализации,
называются фактическими параметрами типа.
Вернемся к классу java.util.List<E>, в котором при фактической
реализации, такой, например, как List<Integer>, все вхождения фор-
мального параметра типа <E> заменяются на фактические парамет-
ры типа <Integer>. Имея эту дополнительную информацию о типе,
компилятор способен провести проверку типов во время компиля-
ции и гарантировать, что во время выполнения не будет ошибки
приведения типов.
Соглашение об именах формальных параметров типа
Рекомендуется использовать одну большую букву для формаль-
ных параметров типа. Например:
• <E> для элемента коллекции;
• <T> для типа;
• <K, V> для ключа и значения;
• <N> для числа
• 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<E> объявляет класс-дженерик с формаль-
ным параметром типа<E>. Во время вызова, например,

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

MyGenericArrayList<String>, конкретный тип <String> или параметр


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

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

Когда класс создается с параметром фактического типа, на-


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

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

С дженериками компилятор способен выполнять проверку ти-


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

6.5. Дженерик-методы

Методы также могут быть определены с дженерик-типами (ана-


логично дженерик-классу). Например,

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

Дженерик-метод может объявить параметры формального типа


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

Однако компилятор проверяет, является ли объект a объек-


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

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

Дженерики дают возможность использовать различный синтак-


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

Синтаксис делает код лучше читаемым и дает возможность


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

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

6.6. Wildcards – подстановочные символы

Рассмотрим следующий код:

Он вызовет ошибку компиляции “incompatible types”, поскольку


ArrayList<String> не является ArrayList<Object>.
Это ошибка – несмотря на нашу интуицию насчет полиморфиз-
ма, поскольку мы часто присваиваем объекту подкласса ссылку
на суперкласс.
Рассмотрим следующие два утверждения:

Строка 2 генерирует ошибку компиляции. Но если бы строка 2


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

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

Подстановочные символы (Wildcards):


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

Пример на подстановочный символ <?>


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

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

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

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

Подстановочный символ для ограничения сверху


<? extends тип>
Подстановочный символ <? extends тип> применяется для огра-
ничения типа, так же как и его подтипа. Например,

List<? extends Number> принимает список типа Number и любой


его подтип, например, List<Integer> или List<Double>.
Очевидно, что <?> может быть интерпретирован как <? extends
Object>, что применимо ко всем классам Java.
Другой пример:

Пример программы на подстановочный символ


<? extends тип>

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

Результаты:
3 5 10
Программа не будет работать для списка List из Integer или Double,
так как класс String не является наследником класса Number.

Подстановочный символ для ограничения снизу <? super тип>


Подстановочный символ <? super тип> применяется для ограни-
чения как типа, так и его супертипа. Другими словами, он указывает
нижнюю границу типа.

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

Пример программы на подстановочный символ <? super тип>

Результаты:
1234

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

6.7. Дженерики, ограничивающие тип

Ограничивающие параметры типа – это дженерик-тип, ко-


торый указывает ограничения на дженерик в форме <T extends
ClassUpperBound>, например, <T extends Number> принимает
Number и его подклассы, такие как Integer and Double.
Пример
Метод add() принимает параметр типа <T extends Number>, ко-
торый принимает Number и его подклассы (такие, как Integer and
Double):

Как компилятор обрабатывает ограничивающие джене-


рики?
Как уже упоминалось, все дженерик-типы заменяются
на тип Object во время компиляции кода. Однако в случае <? extends
Number> дженерик-тип заменяется на тип Number, который служит
ограничением сверху для дженерик-типов.

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

Пример

По умолчанию Object является ограничением сверху для па-


раметра типа. <T extends Comparable<T>> изменяет ограничение
сверху для интерфейса Comparable, который объявляет абстракт-
ный метод compareTo() для сравнения двух объектов.
Компилятор транслирует указанный выше метод в следующие
коды:

При вызове этого метода, например, maximum(55, 66) базовые


целые типа int при помощи автобоксинга преобразуются в объ-
екты Integer, которые затем неявным образом преобразуются

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

с помощью апкастинга в Comparable. Компилятор также явным


образом добавляет операцию даункастинга для типа возвраща-
емого значения:

Мы не должны передавать фактический тип аргумента в дже-


нерик-метод. Компилятор делает вывод о типе аргумента авто-
матически на основе типа фактического параметра, переданного
в метод.

Контрольные вопросы к главе 6


1. Что такое дженерик в Java? Каковы его преимущества?
2. Как работают дженерики?
3. Что такое потеря типа?
4. Если компилятор уничтожает все параметры типов во время
компиляции, почему все же следует применять дженерики?
5. Будет ли следующий класс компилироваться? Если нет,
то почему?

6. В чем различие между List<? extends T> и List <? super T> ?
7. Можно ли передать List<String> в метод, который принимает
List<Object>?
8. В чем различие между List<?> и List<Object> в Java?

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

9. Что такое обобщенные подстановочные символы?


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

Задания к главе 6
1. Напишите дженерик-метод для определения количества
элементов в коллекции, которая имеет определенные
свойства (например, состоит из четных чисел, простых
чисел, палиндромов).
2. Напишите дженерик-метод, чтобы поменять местами два
различных элемента массива.
7. КОЛЛЕКЦИИ

Понятие фреймворка «Коллекции» было рассмотрено в раз-


деле 6.1.
Итак, коллекция – это программный объект, содержащий в себе
набор значений одного или различных типов и позволяющий обра-
щаться к этим значениям.
В разделе 6.1. был приведен пример коллекции ArrayList для вер-
сии Java до JDK1.5 и были сделаны выводы о необходимости прове-
дения программистом точного даункастинга от Object к исходному
классу, а также отсутствии типобезопасности при проведении дан-
ного преобразования с отображением ошибки только во время вы-
полнения.

7.1. ArrayList с дженериками

В JDK 1.5 вводится новый инструмент для разрешения проб-


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

Известно, как передаются аргументы в методах. Для этого надо


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

144
7. КОЛЛЕКЦИИ

Перепишем программу из раздела 6.1 (пример коллекции –


ArrayList) с использованием дженериков:

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

В строках 8 и 14 тип информации о классах коллекции ука-


зан с использованием дженериков, записанных как List<String>,
ArrayList<String> и Iterator<String>. На основе информации о типах
компилятор способен проверить тип аргументов для методов 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<E>, Iterator<E>


и усовершенствованный цикл for

Интерфейс Iterable<E>, который принимает дженерик типа E


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

Все реализации коллекции (например, ArrayList, LinkedList,


Vector) должны реализовывать этот метод, возвращающий объект,
реализующий интерфейс Iterator.
Интерфейс Iterator<E> объявляет три абстрактных метода:

Как видно из приведенного примера, можно использовать цикл


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<E> и его подинтерфейсы List<E>,


Set<E>, Queue<E>

Интерфейс Collection<E>, который принимает в качестве па-


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

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

Возможна ли коллекция из элементов базовых типов?


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

156
7. КОЛЛЕКЦИИ

ванием соответствующих классов-оболочек, таких, например,


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

7.7. Интерфейс Map<K,V>

Интерфейс Map<K,V>, который принимает два дженерика ти-


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

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

Обратите внимание, что Map<K,V> не является подинтерфейсом


Collection<E>, поскольку подразумевает использование пары объ-
ектов для каждого элемента. Подробности рассмотрим далее в раз-
деле 7.12.

7.8. Интерфейс List<E> и его реализации

На практике принято программировать на уровне одного


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

158
7. КОЛЛЕКЦИИ

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

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

Абстрактный суперкласс AbstractList обеспечивает реализа-


цию для многих абстрактных методов, объявленных в интерфей-
сах List, Collection, и Iterable. Однако некоторые методы, например,
как get(int index), остаются абстрактными. Эти методы будут реали-
зованы в конкретных подклассах, таких как ArrayList и Vector.
Классы реализаций ArrayList<E> и Vector<E> для интерфей-
са List<E>
Класс ArrayList<E> является самой лучшей реализацией интер-
фейса List<E> – массива с изменяющимся размером. Реализует все
операции, определенные для структуры данных «список», разреша-
ет любые элементы, включая null. Помимо реализации интерфей-
са List<E>, этот класс предоставляет методы управления размером
массива, который используется для хранения списка. (Этот класс,
грубо говоря, эквивалентен классу Vector, за исключением того,
что является несинхронизированным.) Целостность ArrayList не га-
рантирована при многопоточности. Ответственность обеспечения
синхронизации лежит на программисте.
Каждый объект ArrayList имеет объем, используемый для хра-
нения элементов в виде списка. Этот объем не меньше размера
списка. По мере добавления элементов в ArrayList, объем растет
автоматически.
Конструкторы:
ArrayList() – создает пустой список, с начальным объемом 10.
ArrayList(Collection<? extends E> c) – создает список, содержащий
элементы указанной коллекции в том порядке, в котором они воз-
вращаются итератором коллекции.
ArrayList(int initialCapacity) – создает пустой список с изначаль-
но указанным объемом.

160
7. КОЛЛЕКЦИИ

Методы:
boolean add(E e) – добавляет указанный элемент в конец
списка;
void add(int index, E element) – вставляет указанный элемент
на указанную позицию в списке;
boolean addAll(Collection<? extends E> c) – добавляет все элемен-
ты указанной коллекции в конец данного списка в порядке, опреде-
ленном итератором Iterator данной коллекции;
boolean addAll(int index, Collection<? extends E> c) – добавляет все
элементы указанной коллекции в список, начиная с указанной по-
зиции;
void clear() – удаляет все элементы из списка;
Object clone() – возвращает копию объекта с теми же значениями
полей, что у данного экземпляра ArrayList;
boolean contains(Object o) – возвращает true, если данный список
содержит указанный элемент;
void ensureCapacity(int minCapacity) увеличивает объем данного
экземпляра ArrayList, если это необходимо, чтобы удостовериться,
что он может содержать, по меньшей мере, число элементов, ука-
занное в аргументе minCapacity;
E get(int index) – возвращает элемент, находящийся на указаной
позиции в списке;
int indexOf(Object o) – возвращает индекс первого вхождения
указанного элемента данного списка или -1, если данный список
не содержит указанный элемент;
boolean isEmpty() – возвращает true, если данный список не со-
держит элементов;
Iterator<E> iterator() – возвращает итератор для правильного
прохода по списку;
int lastIndexOf(Object o) – возвращает индекс последнего вхож-
дения указанного элемента данного списка или -1, если список
не содержит данный элемент;
ListIterator<E> listIterator() – возвращает итератор списка (для
прохода в правильной последовательности);

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

ListIterator<E> listIterator(int index) – возвращает итератор


списка (для прохода в правильной последовательности), начиная
с указанной позиции в списке;
E remove(int index) – удаляет элемент из указанной позиции в списке;
boolean remove(Object o) – удаляет первый встретившийся ука-
занный элемент из списка, если он там имеется;
boolean removeAll(Collection<?> c) – удаляет из списка все элемен-
ты, которые содержатся в указанной коллекции;
protected void removeRange(int fromIndex, int toIndex) – удаляет
из списка все элементы, чей индекс находится от fromIndex, вклю-
чительно, до toIndex, не включая его;
boolean retainAll(Collection<?> c) – оставляет в данном списке
только те элементы, которые содержатся в указанной коллекции;
E set(int index, E element) – заменяет элемент на указанной по-
зиции в списке на указанный элемент;
int size() – возвращает количество элементов данного списка;
List<E> subList(int fromIndex, int toIndex) – возвращает представ-
ление данного списка в виде подсписка, начиная с элемента с ин-
дексом fromIndex, включительно, до элемента с индексом toIndex,
не включая его;
Object[] toArray() – возвращает массив всех элементов данного
списка в правильной последовательности (т.е. от первого до по-
следнего элемента);
<T> 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<T>. Следует
иметь в виду, что метод предполагает представление в виде списка,
а не преобразование в список, поэтому следует внести изменения
в записи для представления в виде массива.

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

Класс вектор Vector<E>


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

166
7. КОЛЛЕКЦИИ

Vector(int initialCapacity, int capacityIncrement) – создает пу-


стой вектор с указанными начальным объемом и инкрементом
объема.
Класс вектор является синхронизированной реализацией ин-
терфейса List и содержит дополнительные наследуемые методы,
такие, например, как:
void addElement(E obj) – добавляет указанный компонент в ко-
нец вектора, увеличивая его размер на 1;
boolean removeElement(Object obj) – удаляет первый, т.е. имею-
щий минимальный индекс, встретившийся аргумент из вектора;
void setElementAt(E obj, int index) – вставляет элемент, представ-
ляющий собой укуазанный объект, на определенное индексом ме-
сто в векторе;
public E elementAt(int index) – возвращает элемент с указан-
ным индексом; этот метод идентичен методу get(int), являюще-
муся частью интерфейса List;
public E firstElement() – возвращает первый элемент (с индек-
сом 0) данного вектора;
public E lastElement() – возвращает последний элемент век-
тора;
public void insertElementAt(E obj,int index) – вставляет указан-
ный объект как элемент данного вектора на место, определенное
индексом. Каждый элемент данного вектора с индексом, большим
или равным указанному индексу, получает индекс, больший преды-
дущего на 1.
Индекс должен иметь значение, больше или равное 0 и меньше
или равное текущему размеру вектора.
Если индекс равен текущему размеру вектора, то новый элемент
добавляется к вектору.
Этот метод идентичен по функциональности методу add(int, E)
(который является частью интерфейса List). Обратите внимание,
что метод add имеет обратный порядок параметров.
Нет смысла использовать эти унаследованные методы иначе чем
для того, чтобы поддержать обратную совместимость.

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

Пример класса Vector<E>


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

168
7. КОЛЛЕКЦИИ

Результаты работы программы:


Начальный размер: 0
Начальный объем: 3

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

Объем после добавления четырех элементов: 5


Текущий объем: 5
Текущий объем: 7
Текущий объем: 9
Первый элемент: 1
Последний элемент: 12
Vector содержит 3.
Элементы вектора:
1 2 3 4 5.45 6.08 7 9.4 10 11 12
В класс Vector добавлена поддержка итераторов. Вместо исполь-
зования доступа к серии элементов одновременно, можно исполь-
зовать цикл для обращения к объектам. Для приведенного примера
это может быть реализовано следующим образом:

Класс ArrayList не синхронизирован. Целостность ArrayList не га-


рантирована при многопоточности. Ответственность обеспечения
синхронизации лежит на программисте. С другой стороны, Vector
внутренне синхронизирован.
Совет по эффективности. Синхронизация предполагает до-
полнительные издержки. Следовательно, если синхронизация
не является целью, надо использовать класс ArrayList вместо класса
Vector для большей эффективности.
Сравнение ArrayList и Vector
Как ArrayList, так и Vector реализуют интерфейс List и поддержи-
вают порядок элементов в порядке их вставки.
Различия между классами ArrayList и Vector приведены в табли-
це 7.1.

170
7. КОЛЛЕКЦИИ

Таблица 7.1
Различия между классами ArrayList и Vector

ArrayList Vector
1. ArrayList не синхронизирован. Vector синхронизирован.

2. ArrayList увеличивает на 50% те- Vector увеличивает на 100%, т.е.


кущий размер массива, если число вдвое, размер массива, если число
элементов превышает его объем. его элементов превышает объем
массива.
3. ArrayList is не является классом- Vector является классом-наследни-
наследником. ком.
4. ArrayList является быстрым, по- Vector является медленным, так
тому что он не синхронизирован. как он синхронизирован, т.е.
при многопоточности он будет
удерживать другие потоки до тех
пор, пока не освободит от блоки-
ровки объект.
5. ArrayList использует интерфейс Vector использует интерфейс
Iterator для прохода по элементам. Enumeration для перемещения
по элементам. Но может также ис-
пользовать и интерфейс Iterator.

Stack<E> – реализация класса для интерфейса List<E>


Stack<E> – класс, определенный для структуры данных «стек»,
организованной по принципу LIFO (last-in-first-out – последним
вошел – первым вышел). Класс Stack является наследником класса
Vector, который является синхронизированным массивом с изме-
няющимся размером. Методы класса Stack<E>:
Stack() – конструктор – создает пустой стек;
E void push(E element) – помещает указанный элемент на верши-
ну стека;
E pop() – удаляет и возвращает элемент с вершины стека;
E peek() – возвращает элемент с вершины стека без его удаления;
boolean empty() – проверяет, является ли стек пустым;
int search(Object obj) – возвращает расстояние от указанного
объекта до вершины стека (от 1 для вершины стека) или –1, если
элемент не найден.

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

Пример – Stack<E>

Результаты:
Пустой стек: []
Пустой стек: true

172
7. КОЛЛЕКЦИИ

Непустой стек: [1001, 1002, 1003, 1004]


Непустой стек: Операция pop – извлечения элемента из стека:
1004
Непустой стек: Непустой стек : после операции pop – извлечения
элемента из стека : [1001, 1002, 1003]
Непустой стек: операция поиска элемента : 2
Пустой ли стек: false

LinkedList<E> – реализация класса для интерфейса List<E>


LinkedList<E> является реализацией двусвязного списка для ин-
терфейса List<E>, который работает эффективно как для вставки
элементов, так и для удаления, используя, как издержки, более
сложную структуру.
LinkedList<E> также реализует интерфейсы Queue<E> и Deque<E>
и может работать с обоих концов очереди. Он может работать с оче-
редью как по принципу FIFO, так и по принципу LIFO.
LinkedList<E> – пример

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

Результаты:
Элементы списка ll: [A, A2, F, B, D, E, C, Z]
Элементы списка ll после удаления элементов: [A, A2, D, E, C, Z]
Список ll после удаления первого и последнего элементов: [A2,
D, E, C]
Список ll после изменения: [A2, D, E изменился, C]

174
7. КОЛЛЕКЦИИ

7.9. Упорядочение, сортировка и поиск

Понятие «упорядочение» используется в двух ситуациях:


1. Для сортировки коллекции или массива с использованием
методов Collections.sort() или Arrays.sort(), упорядочиваю-
щих по заданной спецификации.
2. Некоторые коллекции, в частности SortedSet (TreeSet)
и SortMap (TreeMap), являются упорядоченными. Это озна-
чает, что объекты хранятся в указанном порядке.
Есть два пути указать способ упорядочения объектов:
1. Заставить объекты реализовать интерфейс java.lang.
Comparable и переопределить метод compareTo() для указа-
ния порядка сравнения двух объектов.
2. Создать специальный объект java.util.Comparator с методом
compare() для указания порядка сравнения двух объектов.
Интерфейс java.lang.Comparable<T>
Интерфейс java.lang.Comparable<T> указывает, как два объек-
та должны сравниваться в смысле упорядочения. Этот интерфейс
определяет один абстрактный метод:

Этот способ ссылается на естественный порядок сравнения,


и метод compareTo() ссылается на метод естественного сравнения.
Строго рекомендуется, чтобы метод compareTo() был совмести-
мым с equals() и hashCode() (наследуемых из java.lang.Object):
1. Если compareTo() возвращает ноль, то equals() должен воз-
вращать true.
2. Если equals() возвращает true, то hashCode() будет создавать
то же int.
Все восемь классов-оболочек базовых типов (Byte, Short, Integer,
Long, Float, Double, Character и Boolean) реализуют интерфейс
Comparable с методом compareTo(), использующим порядок номеров.

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

Пример – интерфейс Comparable


Классы-утилиты java.util.Arrays и java.util.Collections предостав-
ляют много статических методов для различных алгоритмов, на-
пример для поиска и сортировки.
В данном примере будем использовать методы Arrays.sort()
и Collections.sort() для сортировки массива строк и списка из целых
значений Integer на основе интерфейса по умолчанию Comparable.
По умолчанию Comparable для String сравнивает две строки на ос-
нове их кодов в формате Юникод, т.е. большие буквы (из верхнего
регистра) меньше, чем аналогичные маленькие буквы (из нижнего
регистра).

176
7. КОЛЛЕКЦИИ

Интерфейс java.util.Comparator<T>
Помимо интерфейса Comparable (или естественного сравне-
ния), можно передавать объект Comparator в методы сортиров-
ки (Collections.sort() или Arrays.sort()), чтобы обеспечить точность
контроля при сравнении.
Интерфейс Comparator будет переопределять Comparable, если
это возможно.
Интерфейс java.util.Comparator объявляет метод:

Обратите внимание, что надо создать реализацию Comparator<T>


и вызвать метод compare() для сравнения o1 с o2. (В более ран-
ней версии интерфейса Comparable должен был вызываться метод
compareTo(), который имел только один аргумент, т.е. объект срав-
нивался с заданным объектом).

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

Пример – Comparator
В этом примере вместо используемого по умолчанию интерфей-
са Comparable мы определяем соответствующий класс Comparator
для строк String и целых Integer.

178
7. КОЛЛЕКЦИИ

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

7.10. Set<E> – интерфейсы и реализации

Интерфейс Set<E> (см. рис. 7.4) моделирует математическое мно-


жество, в котором нет одинаковых элементов (например, играль-
ные карты). Оно может содержать единственный null-элемент.

Рис. 7.4. Set<E> – интерфейсы и реализации

Интерфейс Set<E> объявляет следующие абстрактные методы:


вставки, удаления и проверки возвращают значение false, если опе-
рация прошла с ошибкой, вместо генерации исключения.

180
7. КОЛЛЕКЦИИ

Реализации интерфейса Set<E> включают:


• класс HashSet<E> хранит элементы в хэш-таблице (по хэш-
коду); HashSet является самой лучшей реализацией для ин-
терфейса Set;
• LinkedHashSet<E> – элементы хранятся в виде двусвязного
списка, что позволяет организовать упорядоченные итерации
вставки и удаления; элементы хэшируются с использованием
метода hashCode() и организованы в связный список в соот-
ветствии с порядком вставки;
• TreeSet<E> – также поддерживает подинтерфейсы NavigableSet
и SortedSet; хранит элементы в виде структуры «дерево», в ко-
торой элементы отсортированы и управляемы; это эффектив-
но для поиска, удаления и добавления элементов (оценка вре-
мени поиска – O(log(n)).
Пример использования класса HashSet<E>
Напишем класс «Книга» – Book и создадим множество Set из объ-
ектов Book.

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

Надо использовать метод таким образом, чтобы в реализации


Set можно было протестировать равенство и дублирование. В дан-
ном примере id выбран как отличительный признак. Мы переопре-
деляем equals() таким образом, чтобы этот метод возвращал true,
если две книги имеют одинаковые id. Мы также переопределяем
hashCode() для совместимости с equals().

182
7. КОЛЛЕКЦИИ

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

Множество не может содержать одинаковые элементы. Элементы


проверяются на дублирование при помощи переопределенного
equal(). Множество Set может содержать null-значение в качестве
элемента (также не дублирующегося).
Методы addAll() и retainAll() – операции, соответственно, объ-
единения и пересечения множеств.
Обратите внимание, что заполнение элементами является про-
извольным и не соответствует порядку add().
Класс LinkedHashSet<E> – пример использования
В отличие от HashSet, класс LinkedHashSet строит связный
список с использованием хэш-таблицы для увеличения эффек-
тивности операций вставки и удаления элементов (за счет более
сложной структуры). Этот класс поддерживает связный список
элементов в том порядке, в котором они вставлялись, т.е. в поряд-
ке метода add().

184
7. КОЛЛЕКЦИИ

Выведенные результаты ясно показывают, что множество упо-


рядочено в порядке вставки элементов, т.е. в порядке метода add().
Интерфейсы NavigableSet<E> и SortedSet<E>
Элементы в the SortedSet<E> сортируются или в порядке, опреде-
ленном add(), или в естественном порядке Comparable, или по объ-
екту, заданному при помощи Comparator (см. раздел 7.9 для изуче-
ния подробностей применения Comparable и Comparator).
Интерфейс NavigableSet<E> является подинтерфейсом множе-
ства Set и объявляет дополнительные методы навигации (нахожде-
ние ближайшего в некотором смысле элемента):

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

Класс TreeSet<E> – пример

186
7. КОЛЛЕКЦИИ

Класс AddressBookEntry реализует интерфейс Comparable


для правильного использования в TreeSet. В нем переопределяет-
ся метод compareTo() для сравнения с переменной name для случая
нечувствительности к верхнему-нижнему регистру. В классе также
переопределяются методы equals() и hashCode(), чтобы они соот-
ветствовали compareTo().

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

Пример – класс TreeSet с интерфейсом Comparator


Перепишем предыдущую программу с объектами Comparator
вместо Comparable. Для иллюстрации будем использовать
Comparator для упорядочивания объектов класса в убывающем по-
рядке от name.

188
7. КОЛЛЕКЦИИ

Класс PhoneBookEntry не реализует интерфейс Comparator.


Нельзя применить метод add() к объекту PhoneBookEntry в TreeSet(),
как в предыдущем примере. Вместо этого определим класс
Comparator и будем использовать объект класса Comparator для соз-
дания TreeSet.
Comparator упорядочивает объекты класса PhoneBookEntry
в убывающем порядке от name и является нечувствительным к верх-
нему-нижнему регистру.

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

В тестирующей программе мы создали множество TreeSet


с BookComparator. Мы также применили метод descendingSet(), что-
бы получить новое множество Set с элементами, упорядоченными
в обратном порядке.

7.11. Queue<E> – интерфейсы и реализации

Очередь – это коллекция, элементы которой добавляются и уда-


ляются по принципу FIFO (first in first out – первым вошел – первым
вышел).
Дек – это двусвязная очередь (очередь с двумя концами), т.е.
элементы могут добавляться и удаляться с двух концов очереди
(с «головы» и с «хвоста»).
Интерфейсы и реализации Queue<E> изображены на рис. 7. 5.
Кроме базовых методов Collection<E>, для очереди Queue<E>
предоставляются дополнительные методы включения, извлечения
и контроля. Каждый из этих методов существует в двух формах –
в одном случае генерируется исключение, если во время выполне-
ния метода возникла ошибка, во втором случае возвращается спе-
циальное значение (или null, или false – в зависимости от метода).

190
7. КОЛЛЕКЦИИ

Рис. 7.5. Интерфейсы и реализации Queue<E>

В последнем случае метод вставки создан специально для исполь-


зования в ограниченных по возможностям реализациях Queue.

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

192
7. КОЛЛЕКЦИИ

Дек может быть использован как очередь, организованная


по принципу FIFO (с возможностью использования методов add(e),
remove(), element(), offer(e), poll(), peek()), или как структура данных,
организованная по принципу LIFO (last-in-first-out – последним
вошел, первым вышел) (с возможностью использования методов
push(e), pop(), peek()).
Реализации интерфейсов Queue<E> и Deque<E> включают:
• PriorityQueue<E> – очередь, в которой элементы находятся
в заданном порядке, а не в соответствии с принципом FIFO;
• ArrayDeque<E> – очередь или дек, организованные как дина-
мические массивы, сходные с ArrayList<E>;
• LinkedList<E> – класс LinkedList<E> также реализует интерфей-
сы Queue<E> и Deque<E> в дополнение к интерфейсу List<E>,
обеспечивая очередь или дек как структуру данных «двусвяз-
ный список».
Базовые методы интерфейса Queue<E> включают добавление
элемента, извлечение и удаление первого элемента при переходе
к следующему или извлечение первого элемента без удаления.
Очередь Queue<E> – пример

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

194
7. КОЛЛЕКЦИИ

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


надо объекту Queue присвоить значение экземпляра LinkedList.
Результаты:
Успешно ли добавлена Среда? true
Удаляем «голову» очереди: Понедельник
1) Изъяли Понедельник из очереди – теперь новая «голова»:
Среда
2) Изъяли Вторник из очереди – теперь новая «голова»:
Содержит ли очередь объект ‘Воскресенье’? true
Содержит ли очередь объект ‘Понедельник’? false

7.12. Интерфейсы и реализации Map<K,V>

Map – карта, это коллекция пар «ключ – значение» (например, имя –


адрес, ISBN – название). Каждый ключ соответствует одному и только
одному значению. Дублирование ключей не разрешено, но дублирова-
ние значений допускается. Карты похожи на массивы, за тем исключе-
нием, что массив использует целый ключ для индексирования доступа
к элементам, в то время как карта использует некоторый произволь-
ный ключ (например, как String или некоторые объекты).
Интерфейсы и реализации Map<K,V> изображены на рис. 7.6.

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

Рис. 7.6. Интерфейсы и реализации Map<K,V>

Интерфейс Map<K,V> объявляет следующие абстрактные методы:

196
7. КОЛЛЕКЦИИ

Реализация интерфейса Map<K,V> включает:


• HashMap<K,V> – реализацию хэш-таблицы для интерфейса
Map<K,V>; это самая лучшая реализация; методы в HashMap
не синхронизированы;
• TreeMap<K,V> – реализация интерфейса SortedMap<K,V>
в виде «дерева»;
• LinkedHashMap<K,V> – хэш-таблица со свойствами связного
списка для улучшения методов вставки и удаления;
• Hashtable<K,V> – модернизированное наследие от реализа-
ций JDK 1.0; реализация синхронизированной хэш-таблицы
интерфейса Map<K,V>, который не допускает null-ключи
или значения для наследуемых методов.
Например,

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

Для карт нет итератора подобно имеющемуся в списке List.


Итератор можно применять, только предварительно получив пред-
ставление ключа или значения.
HashMap – пример

198
7. КОЛЛЕКЦИИ

7.13. Алгоритмы фреймворка «Коллекции»

Фреймворк «Коллекции» имеет два класса утилит – java.util.


Arrays и java.util.Collections, которые содержат часто используемые
алгоритмы, такие как сортировка и поиск на массивах и коллек-
циях. (Обратите внимание, что интерфейс называется Collection,
а класс утилит называется Collections – с “s” в конце слова.)
Класс утилит java.util.Arrays
Класс java.util.Arrays содержит в том числе статические методы
для сортировки массива и поиска в массиве.
Массив – это ссылочный тип в Java. Он может содержать как пе-
ременные базовых типов, так и объекты. В Java определены девять
типов массивов – по одному на каждый из базовых типов (byte,
short, int, long, float, double, char, boolean) и один для Object.
Сортировка – метод Arrays.sort()
Для каждого из базовых типов (за исключением boolean) и Object
имеются два метода сортировки sort().

Два метода также определены для объектов-дженериков, кото-


рые должны быть отсортированы на основе данного интерфейса
Comparator (вместо Comparable).

Предположим, что мы хотим отсортировать массив объ-


ектов типа Integer (т.е. T – Integer). В этом случае мож-
но использовать Comparator<Integer> (который сравнивает

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

при помощи Comparator<Integer>), или Comparator<Number>,


или Comparator<Object>, поскольку Object и Number являются
суперклассами для Integer.
В качестве примера стоит посмотреть примеры предыдущих
разделов об использовании Comparable и Comparator.
Поиск – метод Arrays.binarySearch()
Аналогичным образом имеются по два метода для базовых ти-
пов (кроме boolean) и для Object. Массив должен быть отсортирован
до применения метода binarySearch().

Пример Arrays.binarySearch()
Следующий пример демонстрирует использование метода java.
util.Arrays.binarySearch():

200
7. КОЛЛЕКЦИИ

Результаты:
Отсортированный массив:
Элемент = 5
Элемент = 12
Элемент = 20
Элемент = 30
Элемент = 55

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

Проверка равенства массивов – Arrays.equals()

Копирование массивов – Arrays.copyOf() and Arrays.copyOfRange()

Заполнение массива – Arrays.fill()

202
7. КОЛЛЕКЦИИ

Описание (печать) массива – Arrays.toString()

Преобразование массива в список – Arrays.asList()

Класс утилит java.util.Collections


Аналогично классу java.util.Arrays, класс java.util.Collections
предоставляет статические методы для работы с коллекция-
ми – например, среди прочих, методы сортировки (sort()) и поиска
(binarySearch()).

Обратите внимание, что методы Collections.sort() применимы


только к списку List. Они неприменимы к множеству Set, очереди

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

Queue и карте Map. Тем не менее, множество SortedSet (TreeSet)


и карта SortedMap(TreeMap) сортируются автоматически.
Collections.sort() – пример
Следующий пример демонстрирует применение класса java.util.
Collections.sort()

Результаты:
Начальное значение списка: [Один, Два, Три, Один, Два, Три]
Список после сортировки: [Один, Один, Три, Три, Два, Два]

Бинарный поиск – Collections.binarySearch()


Список List должен быть отсортирован до применения метода
binarySearch().

204
7. КОЛЛЕКЦИИ

Поиск максимального/минимального элемента в данной


коллекции

Имеется также много других методов, таких, например,


как copy(), fill() и т.д.
Синхронизированные Collection, List, Set и Map
Большинство реализаций интерфейса «Коллекции» Collection,
например такие, как ArrayList, HashSet и HashMap, не синхронизи-
рованы для работы с многопоточными приложениями, за исклю-
чением Vector и HashTable, которые модернизированы для соот-
ветствия фреймворку «Коллекции» и синхронизированы. Вместо
использования синхронизированных Vector и HastTable мож-
но создать синхронизированные Collection, List, Set, SortedSet,
Map и SortedMap, используя статические методы Collections.
synchronizedXxx():

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

В соответствии со спецификацией JDK API, «чтобы гарантиро-


вать последовательный доступ, критичным является то, что всякий
доступ к исходному списку осуществляется через возвращенный
список и что пользователь вручную синхронизирует возвращенный
список, проходя по нему». Например,

206
7. КОЛЛЕКЦИИ

Контрольные вопросы к главе 7


1. Что такое коллекция?
2. Можно ли организовать коллекцию из элементов базовых
типов?
3. Что такое автобоксинг и что такое анбоксинг?
4. Назовите интерфейсы и подинтерфейсы коллекции.
5. Что определяет интерфейс Iterable?
6. Какие методы имеет интерфейс Iterator<E>?
7. Как работает усовершенствованный цикл for?
8. В чем различие между циклом и усовершенствованным
циклом?
9. Что такое List<E> , Set<E>, Queue<E>?
10. Как организован интерфейс Map<K,V>?
11. Какие подинтерфейсы имеет интерфейс List<E>?
12. Какие классы реализаций имеет интерфейс List<E>?
13. Как объявить и инициализировать список?
14. Что такое ArrayList и Vector? В чем различие между ними?
15. Как создать ArrayList?
16. Как организовать цикл в ArrayList?
17. Как удалить объекты из коллекции или из ArrayList?
18. В чем различие между ArrayList и LinkedList?
19. Как организован интерфейс Stack<E>?
20. Что напечатается в результате выполнения следующего
фрагмента программы при n=50? Дайте подробное объяс-
нение:

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

21. Каким образом выполняется следующий фрагмент про-


граммы и какой станет в результате очередь queue?

22. Как преобразовать список в массив?


23. В чем различие между массивом и односвязным списком?
24. Что такое стек и как он реализован в Java?
25. Что такое двусвязный список и как он реализован в Java?
26. В чем различие между односвязным и двусвязным списком?
27. Какой метод преобразовывает список в массив?
28. Можно ли представить массив в виде списка?
29. Какие методы используются для сортировки коллекций?
30. Для чего предназначен интерфейс Set<E>?
31. Какие абстрактные методы объявляет интерфейс Set<E>?
32. Какие классы являются реализациями интерфейса Set<E>?
33. Как организовать цикл для Set или HashSet?
34. Для чего предназначен интерфейс Queue<E>?
35. В чем различие между стеком Stack<E> и очередью Queue<E>?
36. Для чего предназначен интерфейс Map<K,V>?
37. Какие абстрактные методы объявляет интерфейс Map<K,V>?
38. Какие реализации имеет Map<K,V>?

Задания к главе 7
1. Распечатать односвязный список.
2. Определить количество элементов списка.
3. Найти среднее значение элементов списка.
4. Записать список в обратном порядке.
5. Записать список в обратном порядке без рекурсии.
6. Удалить повторяющиеся элементы из списка.

208
7. КОЛЛЕКЦИИ

7. Распечатать список в обратном порядке (подсказка: исполь-


зовать Stack).
8. Определить значение k-го элемента списка.
9. Удалить 2-й элемент из списка.
10. Вставить элемент в начало списка.
11. Вставить элемент в конец списка.
12. Определить сумму элементов двух списков.
13. Реализовать список с использованием дженериков.
14. Добавить элемент в середину списка.
15. Найти первый и последний элементы односвязного списка.
16. Найти первый и последний элементы двусвязного списка.
17. Удалить все элементы из ArrayList для его повторного ис-
пользования.
18. Отсортировать ArrayList по убыванию.
19. Найти номер заданного элемента в ArrayList.
20. Удалить заданный элемент из ArrayList.
21. Определить, находится ли заданный элемент в ArrayList.
22. Определить, является ли ArrayList пустым.
23. Написать метод isFull() для стека, созданного из массива
строк.
24. Написать метод, который последовательно читает строки,
а затем выводит их в обратном порядке.
25. Написать метод, который по вводимой строке, состоящей
из круглых, квадратных и фигурных скобок, определя-
ет, правильно ли сбалансированы эти скобки. Например,
для строки [()]{}{[()()]()} программа должна напечатать tru,
а для [(]) – false.
26. С использованием метода peek() определите последний до-
бавленный в стек элемент (без его удаления).
27. Напишите метод size() для стека и для очереди, который
определяет количество элементов в коллекции.
28. Удалить из карты пары «ключ – значение» по заданному ус-
ловию. Например, есть пары «название книги – цена» и надо
удалить все пары, для которых цена больше 300.

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

29. Модифицировать Comparator для сортировки A, a , B, b, C, c ...


(буква из верхнего регистра находится перед буквой из ниж-
него регистра).
30. Убедитесь в том, что объекты класса AddressBookEntry при-
мера на класс TreeSet<E> из раздела 7.10 отсортированы
и хранятся в порядке, соответствующем методу add() интер-
фейса Comparable.
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ДИЗАЙНА (ООД) КЛАССОВ

В объектно ориентированном мире мы видим только объекты.


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

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

Одной из главных причин повреждения программного


обеспечения является введение незапланированных измене-
ний. Каждая часть системы зависит от некоторой другой части,
и поэтому изменения одной части повлияют на другую часть.
Если мы способны справиться с этими зависимостями, то мо-
жем легко управлять системой программного обеспечения и ее
качеством.
Принципы разработки программного обеспечения представ-
ляют собой методические рекомендации, которые позволяют
избежать плохого дизайна. Принципы дизайна ассоциируются
с Робертом Мартином, который собрал их в книге «Принципы, пат-
терны и методики гибкой разработки». В соответствии с выводами
Роберта Мартина имеются три важные характеристики плохого ди-
зайна, которые следует избегать:
• жесткость – подразумевает трудность внесения изменений,
поскольку каждое изменение влияет на другие части системы;
• хрупкость – при внесении изменений непредсказуемые части
системы повреждаются;
• неподвижность – трудно использовать программное обеспе-
чение повторно в другом приложении, так как данное про-
граммное обеспечение не может быть выделено из данного
приложения.
Решение – принципы ООД, шаблоны проектирования и ар-
хитектура программного обеспечения.
Архитектура программного обеспечения говорит нам о том,
как проекты в целом должны быть структурированы.
Шаблоны проектирования позволяют использовать повторно
решения для наиболее часто возникающих проблем.
Принципы ООД рассказывают, как можно, выполняя опреде-
ленные действия, достигнуть желаемого результата. Как это будет
сделано – зависит от нас.
Таким же образом объектно ориентированный дизайн наполнен
многими принципами, которые позволяют нам справляться с зада-
чами дизайна программного обеспечения.

212
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Роберт Мартин (известный в среде профессиональных разра-


ботчиков программного обеспечения как «Дядя Боб») классифи-
цировал принципы дизайна программного обеспечения следую-
щим образом:
1. Принципы проектирования классов, также называемые
SOLID.
2. Принцип связанности пакета.
3. Принцип связи пакетов.
В данном разделе рассмотрим принципы SOLID.
Принципы SOLID
SOLID – это акроним, введенный Робертом Мартином, т.е.:
• Single responsibility – принцип единственной ответст-
венности;
• Open-closed – принцип открытости/закрытости;
• Liskov substitution – принцип замещения Барбары Лисков;
• Interface segregation – принцип разделения интерфейса;
• Dependency inversion – принцип инверсии зависимостей.
Считается, что эти принципы, если применяются вместе, пред-
назначены для повышения вероятности того, что программист соз-
даст систему, которую будет легко поддерживать и расширять в те-
чение долгого времени.
Рассмотрим эти принципы.

8.1. SRP – Single responsibility Principle –


принцип единственной ответственности

Рассмотрим следующий класс Employee – сотрудник :

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

Всякий раз при внесении изменений этот класс будет изменять-


ся, т.е. всякий раз, когда формат отчета будет изменяться, изменит-
ся и класс.
Одно единственное изменение приводит к двойному (или, воз-
можно, больше, чем двойному) тестированию.
Принцип единственной ответственности SRP гласит, что «мо-
дуль программного обеспечения имеет лишь одну единственную
причину для изменения». Здесь:
• модуль программного обеспечения – класс, метод и т.д.
• причина для изменения – ответственность.
Решения, которые не нарушают принцип единственной от-
ветственности SRP
Вопрос в том, как этого достигнуть. Можно создать три разных
класса:
1. Employee – содержит поля класса (данные).
2. EmployeeDB – выполняет операции над базой данных.
3. EmplyeeReport – создает отчет для связанных задач.

214
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Замечание. Этот принцип также применим к методам. Каждый


метод должен иметь единственную ответственность.
Может ли один класс иметь много методов?
Ответ – ДА:
1. Класс должен иметь единственную ответственность.
2. Метод должен иметь единственную ответственность.
3. Класс может иметь более одного метода.
Ответ на этот вопрос зависит от контекста. Здесь ответственность
относится к контексту, о котором мы говорим. Например, класс
EmployeeDB будет ответственным за операции над данными сотруд-
ника для базы данных, в то время как класс EmployeeReport будет от-
ветственным за операции над данными сотрудника для отчета.

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

8.2. OCP – Open Close Principle –


принцип открытости/закрытости

Принцип открытости/закрытости гласит: «Программные моду-


ли должны быть закрыты для модификации, открыты для расши-
рений».
Ниже приведен пример, который нарушает OCP – принцип
открытости/закрытости. Пусть имеется графический редак-
тор, который реализует рисование различных геометричес-
ких фигур. Очевидно, что этот редактор не следует принципу
открытости/закрытости, поскольку класс GraphicEditor должен
модифицироваться при каждом новом классе добавляемой гео-
метрической фигуры. В этом случае имеются несколько недо-
статков:
• для каждой новой добавляемой фигуры тестирующая про-
грамма для GraphicEditor должна быть переделана;
• при добавлении новой геометрической фигуры увеличивает-
ся время разработки, включающее время для добавления, по-
скольку разработчику необходимо понимать логику работы
GraphicEditor;
• добавление новой фигуры может повлиять на существующее
функционирование нежелательным образом, даже если все,
касающееся новой фигуры, работает отлично.
Пусть GraphicEditor – большой класс с большой функционально-
стью, при этом пишется и изменяется многими разработчиками,
в то время как класс для конкретной фигуры может быть написан
только одним разработчиком. В этом случае для значительного
улучшения процесса разработки было бы хорошо разрешить до-
бавление новой фигуры без изменения класса GraphicEditor (см.
рис. 8.1).

216
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Рис. 8.1. Иллюстрация примера плохого проектирования

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

Далее приведен пример (см. рис. 8.2), который поддер-


живает принцип открытости/закрытости. В новом проекте
в GraphicEditor мы используем для рисования объектов аб-
страктный метод draw(), переместив реализацию в конкретные
объекты геометрических фигур. Используя принцип открыто-
сти/закрытости, можно избежать проблем предыдущего дизай-
на, потому что GraphicEditor не изменяется при добавлении но-
вой фигуры:
• не надо дополнительно тестировать никакой фрагмент
кода;
• не надо понимать логику работы исходного кода GraphicEditor;
• поскольку код для рисования перемещен в класс конкретной
фигуры, уменьшается риск повлиять на прежнюю функцио-
нальность при добавлении новой.

218
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Рис. 8.2. Иллюстрация примера правильного проектирования

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

Как и другие принципы ООД, принцип открытости/закрытости –


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

8.3. LSP – Liskov’s Substitution Principle – принцип замеще-


ния Барбары Лисков

При проектировании программного модуля мы создаем неко-


торую иерархию классов. Затем мы расширяем некоторые классы,
создавая классы-наследники.
При этом следует быть уверенными, что новые классы-на-
следники только расширяют функциональность без ее изменения
в старых классах. С другой стороны, новые классы могут создавать
нежелательные эффекты при использовании в существующих про-
граммных модулях.
Принцип замещения Лисков утверждает, что объекты в про-
грамме должны быть заменяемыми на экземпляры их подти-
пов без изменения правильности выполнения программы.
Строгая формулировка Лисков: «Пусть q(x) является свойством,
верным относительно объектов x некоторого типа T. Тогда q(y) так-

220
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

же должно быть верным для объектов y типа S, где S является под-


типом типа T».
Роберт С. Мартин определил этот принцип так: «Функции, кото-
рые используют базовый тип, должны иметь возможность исполь-
зовать подтипы базового типа, не зная об этом».
Пример
Рассмотрим очень известный пример, в котором принцип заме-
щения Лисков нарушается.
В примере используются два класса – суперкласс Rectangle (пря-
моугольник) и подкласс Square (квадрат).

В тестирующей программе можно написать:

Данный код работает нормально, но, в соответствии с принци-


пом замещения Лисков, мы должны иметь возможность заменить
прямоугольник квадратом:

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

Однако квадрат не может иметь разные значения высоты и ши-


рины. Это означает, что мы не можем заменить объект базового
класса объектом класса-наследника. То есть мы нарушаем принцип
замещения Лисков.
Попытаемся сделать ширину и высоту в Rectangle абстрактными
и переопределим их в Square:

222
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Однако мы не можем действовать таким образом, так как опять


нарушаем принцип замещения Лисков, поскольку изменяем пове-
дение полей Width и Height в наследуемом классе (для Rectangle вы-
сота и ширина не могут быть равными).
То есть это не будет замещением.
Решение, которое не будет нарушать принцип замещения
Лисков
Создадим абстрактный класс Shape:

Создадим два независимых друг от друга класса – один для пря-


моугольников – Rectangle, другой – для квадратов – Square таким
образом, чтобы оба класса были наследниками Shape.
Тогда можно записать:

Даже после переопределения в классах-наследниках мы не из-


меняем поведение ширины и высоты, потому что речь идет толь-
ко о геометрической фигуре, не изменяя правило для шири-
ны и высоты. Они могут быть равными, но могут и не быть
равными.

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

8.4. ISP – Interface Segregation principle – принцип


разделения интерфейса

При проектировании приложения следует быть очень осто-


рожными при абстрагировании модулей, содержащих несколько
подмодулей. Считая, что модуль реализуется в классе, мы можем
выполнить абстрагирование системы в интерфейсе. Но если мы мо-
жем захотеть расширить наше приложение, добавив модуль, кото-
рый содержит только некоторые из подмодулей исходной системы,
мы будем вынуждены реализовать полный интерфейс и написать
несколько методов-заглушек (фиктивных).
Принцип разделения интерфейса утверждает, что много интер-
фейсов, специально предназначенных для клиентов, лучше,
чем один интерфейс общего назначения.
То есть клиенты не должны быть вынуждены реализовывать ин-
терфейсы, которые они не используют. Вместо одного «толстого»
интерфейса предпочтительней иметь много маленьких интерфей-
сов, чтобы клиенты маленьких интерфейсов знали только о мето-
дах, которые необходимы им в работе.
Клиенты не должны зависеть от интерфейсов, которые они не ис-
пользуют.
Пример
Рассмотрим пример, в котором нарушен принцип ISP – разделе-
ния интерфейса. Пусть имеется класс Manager, который представ-
ляет сотрудника, управляющего рабочими. В компании работают
два вида рабочих, и они нуждаются в обеденном перерыве, чтобы
принять пищу. Но сейчас в компанию добавили несколько роботов,
которые также работают, но не принимают пищу, поэтому не нуж-
даются в обеденном перерыве. С одной стороны, для класса Robot
(робот) требуется реализовать интерфейс IWorker, потому что робо-
ты работают. С другой стороны, не стоит его реализовывать, потому
что роботы не принимают пищу.
Именно поэтому для данного случая IWorker считается «загряз-
ненным» интерфейсом.

224
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Если мы сохраним существующий дизайн, то новый класс Robot


будет вынужден реализовать метод eat(). Для реализации метода
eat() мы можем написать пустой класс-заглушку, который ничего
не делает (например, устанавливает ежедневный перерыв на обед –
1 сек.), и при этом получить неожиданные последствия в приложе-
нии (например, отчеты, просматриваемые менеджером, будут со-
держать больше обеденных перерывов, чем число людей).
В соответствии с принципом разделения интерфейса гибкий
дизайн не будет создавать «загрязненного» интерфейса. В нашем
случае интерфейс IWorker должен быть разделен на 2 различных
интерфейса.

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

Следующий фрагмент кода поддерживает принцип разделения


интерфейса. Разделим интерфейс IWorker на 2 различных интер-
фейса – новый класс Robot больше не должен реализовывать ме-
тод eat(). Также нам потребуется другая функциональность для ро-
бота, например, перезарядка, для этого создаем другой интерфейс
IRechargeble с методом recharge (перезарядка).

226
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

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

Как и всякий принцип, ISP – принцип разделения интерфейса –


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

8.5. DIP – Dependency Inversion principle –


принцип инверсии зависимостей

При разработке программного обеспечения мы можем считать,


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

228
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

писать сложные классы высокого уровня. Поскольку классы высоко-


го уровня определены в терминах других классов, кажется логич-
ным сделать это. Однако это – не гибкий дизайн. Что случится, если
нам надо будет заменить класс нижнего уровня?
Рассмотрим классический пример программного модуля ко-
пирования, который читает символы с клавиатуры и записывает
их потом на принтер. Класс высокого уровня, содержащий логи-
ку, – это класс копирования Copy. Классы более низкого уровня –
это KeyboardReader (читающий с клавиатуры) и PrinterWriter (запи-
сывающий на принтер).
При плохом дизайне класс высокого уровня сильно зависит
от класса низкого уровня, используя его непосредственно. В таком
случае, если мы хотим внести изменения в дизайн, чтобы перена-
править результат в новый класс FileWriter (записывающий в файл),
мы должны произвести изменения в классе Copy. (Предположим,
что это очень сложный класс, со сложной логикой, который трудно
тестировать.)
Чтобы избежать таких проблем, мы должны применить аб-
страктный слой (уровень) между классами высокого и низкого
уровня. Поскольку модули высокого уровня содержат сложную ло-
гику, они не должны зависеть от модулей низкого уровня, поэто-
му новый абстрактный слой не должен быть создан на базе моду-
лей низкого уровня. Модули низкого уровня должны быть созданы
на базе абстрактного слоя.
В соответствии с этим принципом дизайн структуры классов
следует начать с модулей высокого уровня и затем переходить к мо-
дулям низкого уровня:
Классы высокого уровня --> Абстрактный слой (уровень) -->
Классы низкого уровня.
Принцип инверсии зависимостей формулируется следующим
образом:
• модули верхних уровней не должны зависеть от модулей
нижних уровней; оба типа модулей должны зависеть от аб-
стракций;

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

• абстракции не должны зависеть от деталей; детали должны


зависеть от абстракций.
Пример
Рассмотрим пример, нарушающий принцип инверсии зависи-
мостей. Пусть у нас есть класс Manager (менеджер), который яв-
ляется классом высокого уровня, и класс низкого уровня Worker
(рабочий). К нашему приложению надо добавить новый модуль
для моделирования изменений в структуре компании, определен-
ный через занятость рабочих новой специализации. Мы для этого
создали новый класс SuperWorker.
Предположим, что класс Manager – довольно сложный, содер-
жащий очень сложную логику. И сейчас мы должны изменить его
с учетом класса SuperWorker. Рассмотрим недостатки этого:
• мы должны изменить класс Manager (помним, что это слож-
ный класс, изменения потребуют времени и усилий);
• изменения могут повлиять на функциональность класса
Manager;
• должна быть переделана тестирующая программа.
Решение всех этих проблем может занять много времени
и породить ошибки в прежней функциональности. Ситуация мог-
ла бы быть другой, если бы приложение было разработано, следуя
принципу инверсии зависимостей. Это означает, что класс Manager,
интерфейс IWorker и класс Worker реализуют интерфейс IWorker.
Когда нам надо добавить класс SuperWorker, все, что надо сделать,
это реализовать для него интерфейс IWorker. И не надо делать ни-
каких изменений в уже существующих классах.

230
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Ниже приведен пример, следующий принципу инверсии за-


висимостей. В этом новом дизайне новый абстрактный слой до-
бавляется посредством интерфейса IWorker. Теперь проблемы

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

предыдущего примера разрешены (и не требуется никаких изме-


нений в логику верхнего уровня):
• класс Manager не требует изменений при добавлении
SuperWorker;
• минимизирован риск воздействия на существующую функ-
циональность класса Manager, поскольку мы его не изменяем;
• нет необходимости переделывать тестирующую программу.

232
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

Применение принципа инверсии зависимостей означает,


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

8.6. Другие принципы ООП и ООД

1. Программировать на уровне интерфейса, а не реализа-


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

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

2. Не повторяться. Это означает, что не следует писать повто-


ряющиеся коды, следует использовать принцип абстракции. Если
у вас присутствует один и тот же блок кода более чем в двух местах,
стоит подумать об отдельном методе для него. Если есть констан-
та для многоразового использования, рекомендуется создать гло-
бальную переменную с модификаторами public final. Большим пре-
имуществом использования данного принципа является простота
дальнейшей технической поддержки.
3. Инкапсулировать то, что меняется. Инкапсулируйте код,
который в будущем будет меняться. Преимущество принци-
па – в простоте тестирования и поддержки надлежащим обра-
зом инкапсулированного кода. При написании программ на Java
следуйте правилу создания переменных и методов с модифика-
тором доступа private, расширяя доступ шаг за шагом от private
к protected, но не public.
4. Принцип наименьшего знания, или закон Деметры. Это
набор правил проектирования программного обеспечения, ко-
торый гласит, что модуль не должен знать о деталях внутреннего
устройства объекта, с которым он работает. Если код зависит от де-
талей внутреннего устройства конкретного объекта, то это может
привести к повреждению программного обеспечения при измене-
нии объекта. Этот принцип поддерживает инкапсуляция.
Согласно закону Деметры метод M объекта O может вызывать
только следующие типы методов:
• методы самого объекта O;
• методы объекта, переданные через аргумент;
• метод объекта, который содержится в переменной экземпля-
ра класса;
• любой объект, который создан локально в методе M.
5. Голливудский принцип: «Не звоните нам, мы перезвоним
вам сами». Применительно к программному обеспечению это оз-
начает, что компоненты высокого уровня (например, интерфейсы)
определяют за компоненты низкого уровня (реализации), как и ког-
да им подключаться к системе.

234
8. ПРИНЦИПЫ ОБЪЕКТНО ОРИЕНТИРОВАННОГО ДИЗАЙНА ( ООД ) КЛАССОВ

6. Предпочитать композицию наследованию. Композиция


значительно более гибкая, чем наследование. Композиция позво-
ляет изменить поведение класса во время выполнения и использо-
вать интерфейсы для создания класса с использованием полимор-
физма, что приводит к гибкости.
7. Принцип делегирования. Не делайте все самостоятельно,
поручите работу соответствующему классу.
8. Применять шаблоны проектирования.
9. Стремиться к слабой связности взаимодействующих объ-
ектов. Чем меньше объекты знают друг о друге, тем более гибкой
является система. Одному компоненту нет необходимости знать
о внутреннем устройстве другого.
Резюме
Нельзя избежать изменений. Единственное, что можно сделать,
это разработать программное обеспечение таким образом, чтобы
было возможно управлять этими изменениями.
ЗАКЛЮЧЕНИЕ

Объектно ориентированное программирование (ООП) можно


рассматривать как модель языка программирования, сосредоточен-
ного в бо́льшей степени на объектах и на данных, а не на «действи-
ях» и логике. Исторически программа рассматривалась как логиче-
ская процедура, которая принимает входные данные, обрабатывает
их и создает выходные данные.
Задачей программирования было описание логики работы про-
граммы, а не определение данных.
Объектно ориентированное программирование предполагает,
что главное внимание должно быть уделено объектам, которыми
собираются манипулировать, а не логике, требуемой для этого ма-
нипулирования.
Первым шагом в ООП является идентификация всех объектов
и их отношений между собой. После того как объект определен, де-
лается обобщение на класс объектов, которое определяет вид дан-
ных, которые этот класс содержит, и логические последовательно-
сти для манипулирования данными. Каждая отдельная логическая
последовательность представляет собой метод. Объекты взаимо-
действуют посредством четко определенных интерфейсов.
Концепции и правила, используемые в объектно ориентирован-
ном программировании, предполагают следующие преимущества:
• концепция класса дает возможность определить подклассы
объектов, которые сохраняют полностью или частично ха-
рактеристики суперкласса; это свойство ООП, именуемое
наследованием, требует более тщательного анализа данных,
уменьшает время разработки программного обеспечения
и обеспечивает более точное кодирование;
• поскольку класс хранит только те данные, которые ему не-
обходимы, то при случайном обращении к объекту данно-
го класса связанный с ним код не будет доступен из других
частей программы; этот принцип, известный как сокрытие
данных (инкапсуляция), обеспечивает бо́льшую безопасность

236
ЗАКЛЮЧЕНИЕ

системы и позволяет избежать непреднамеренного поврежде-


ния данных;
• определение класса может использоваться повторно не толь-
ко внутри программы, для которой он был создан, но также
другими объектно ориентированными программами;
• концепция классов позволяет программисту создавать любые
новые типы данных, которые не определены в языке програм-
мирования.
В данном учебном пособии объектно ориентированное про-
граммирование изучалось на основе языка Java, поддерживающего
объектно ориентированный подход.
БИБЛИОГРАФИЯ
1. Шилдт Г. Java. Полное руководство. – М.: Вильямс, 2012.
2. Сеттер Р. В. Изучаем Java на примерах и задачах. – СПб.: Наука
и техника, 2016.
3. Лафоре Р. Структуры данных и алгоритмы JAVA. – СПб.: Питер, 2013.
4. Эккель Б. Философия Java. – СПб.: Питер, 2015.
5. Блинов И. Н., Романчик В. С. Java. Методы программирования. –
Минск: Четыре четверти, 2013.
6. Васильев А. Н. Java. Объектно ориентированное программирова-
ние. – СПб.: Питер, 2012.
7. Вязовик Н. А. Программирование на Java. – М.: Интуит, 2016.
8. Хабибуллин И. Java 7. – СПб.: БХВ-Петербург, 2012.
9. Монахов В. В. Язык программирования Java и среда NetBeans. – СПб.:
БХВ-Петербург, 2011.
10. Блох Д. Java. Эффективное программирование. – М.: Лори, 2014.
11. Седжвик Р., Уэйн К. Алгоритмы на Java. – М.: Вильямс, 2013.
12. Гудрич М. Т., Тамассия Р. Структуры данных и алгоритмы в Java. – М.:
Новое знание, 2013.
13. Блинов И. Н. , Романчик В. С. Практическое руководство по изучению
Java. – М.: УниверсалПресс, 2005.
14. Болл Д., Пател П., Томас М., Хадсон А. Секреты программирования
для Internet на Java. – СПб.: Питер, 2002.
15. Дженерики (Java, обучающая статья). URL: http://www.quizful.net/
post/java-generics-tutorial (дата обращения: 05.11.2017).
16. Обобщения в Java (Java Generics). URL: https://annimon.com/
article/2637 (дата обращения: 05.11.2017).
17. Гетц Б. Теория и практика Java. Эксперименты с generic-методами.
URL: http://www.k-press.ru/cs/2008/3/generic/generic.asp (дата обра-
щения: 05.11.2017).
18. Коллекции в Java. URL: http://www.quizful.net/post/Java-Collections
(дата обращения: 05.11.2017).
19. Коллекции. URL: http://java-course.ru/begin/collections_01/ (дата об-
ращения: 05.11.2017).
20. Autoboxing and Unboxing. URL: https://docs.oracle.com/javase/
tutorial/java/data/autoboxing.html (дата обращения: 05.11.2017).
21. Автоупаковка и автораспаковка. URL: http://www.codenet.ru/
webmast/java/autoboxing.php (дата обращения: 05.11.2017).

238
БИБЛИОГРАФИЯ

22. Маклафлин Б. Улучшенные циклы for/in в Java 5.0. URL: https://www.


ibm.com/developerworks/ru/java/library/j-forin/index.html (дата обра-
щения: 05.11.2017).
23. Итераторы. URL: https://metanit.com/java/tutorial/5.10.php (дата об-
ращения: 05.11.2017).
24. Коллекции Java (Java Collections Framework). URL: https://appliedjava.
wordpress.com/2010/09/23/java-collections-framework/ (дата обраще-
ния: 05.11.2017).
25. Справочник по Java Collections Framework. URL: https://habrahabr.ru/
post/237043/ (дата обращения: 05.11.2017).
26. Десять принципов объектно ориентированного дизайна, ко-
торые должен знать Java-программист. URL: http://info.
javarush.ru/translation/2015/04/05/Десять-принципов-объектно
ориентированного-дизайна-которые-должен-знать-Java-програм-
мист.html (дата обращения: 05.11.2017).
27. Принцип подстановки Барбары Лисков. URL: https://habrahabr.ru/
post/83269/ (дата обращения: 05.11.2017).
Учебное издание

Гуськова Ольга Ивановна

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

Редактор Дубовец В. В.
Оформление обложки Удовенко В. Г.
Компьютерная верстка Ковтун М. А., Дорожкина О. Н.

Управление издательской деятельности


и инновационного проектирования МПГУ
119571, Москва, Вернадского пр-т, д. 88, оф. 446

E-mail: [email protected]

Подписано в печать 15.08.2018.


Формат 60х90/16. Объем 15,0 п. л.
Гарнитура PT Serif, PT Sans.
Тираж 500 экз. Заказ № 825.

Вам также может понравиться