КулЛиб - Классная библиотека! Скачать книги бесплатно 

Java from EPAM : учебно-методическое пособие [Игорь Николаевич Блинов] (pdf) читать онлайн

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


 [Настройки текста]  [Cбросить фильтры]
И. Н. Блинов
В. С. Романчик

Java
from EPAM

Учебно-методическое пособие

МИНСК
ИЗДАТЕЛЬСТВО «ЧЕТЫРЕ ЧЕТВЕРТИ»
2020

УДК 004.434
ББК 32.973.26-018.2
Б69

Рецензенты:
кандидат технических наук, доцент В. Д. Левчук,
кандидат технических наук, доцент О. Г. Смолякова
Рекомендовано
Ученым Советом механико-математического факультета Белорусского государственного
университета в качестве пособия для студентов высших учебных заведений,
обучающихся по специальностям
1-31 03 08 «Математика и информационные технологии (по направлениям)»,
1-31 03 01 «Математика (по направлениям)»

Б69

Блинов, И. Н., Романчик, В. С.
Java from EPAM : учеб.-метод. пособие / И. Н. Блинов, В. С. Романчик. — Минск :
Четыре четверти, 2020. — 560 с.
ISBN 978-985-581-391-1.
Пособие предназначено для программистов, начинающих и продолжающих изучение технологий
Java SE. В книге рассматриваются основы языка Java и концепции объектно-ориентированного
и функционального программирования. Также изложены аспекты применения библиотек классов
языка Java, включая файлы, коллекции, Stream API, сетевые и многопоточные приложения, а также
взаимодействие с СУБД и ХМL.
В конце каждой главы даются теоретические вопросы по изученной главе, тестовые вопросы по
материалу главы и задания для выполнения. В приложениях приведены дополнительные материалы
с кратким описанием технологий Log4J2 и TestNG.

УДК 004.434
ББК 32.973.26-018.2

ISBN 978-985-581-391-1

© Блинов И. Н., Романчик В. С., 2020
© Оформление. ОДО «Издательство
“Четыре четверти”», 2020

ОГЛАВЛЕНИЕ
ПРЕДИСЛОВИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Глава 1. ВВЕДЕНИЕ В ООП И ФП . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Глава 2. ТИПЫ ДАННЫХ И ОПЕРАТОРЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Глава 3. КЛАССЫ И МЕТОДЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Глава 4. НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Глава 5. ВНУТРЕННИЕ КЛАССЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Глава 6. ИНТЕРФЕЙСЫ И АННОТАЦИИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Глава 7. ФУНКЦИОНАЛЬНЫЕ ИНТЕРФЕЙСЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Глава 8. СТРОКИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Глава 9. ИСКЛЮЧЕНИЯ И ОШИБКИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Глава 10. ПОТОКИ ВВОДА/ВЫВОДА . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Глава 11. КОЛЛЕКЦИИ И STREAM API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Глава 12. ПОТОКИ ВЫПОЛНЕНИЯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Глава 13. JAVA DATABASE CONNECTIVITY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Глава 14. СЕТЕВЫЕ ПРОГРАММЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
Глава 15. JAVA API FOR XML PROCESSING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
ОТВЕТЫ НА ТЕСТОВЫЕ ВОПРОСЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
Приложение 1. Log4J2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522
Приложение 2. TestNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540

3

СОДЕРЖАНИЕ ГЛАВ
ПРЕДИСЛОВИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Глава 1. ВВЕДЕНИЕ В ООП И ФП . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Базовые понятия ООП . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Базовые понятия ФП . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Базовые понятия Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Простое приложение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Установка JDK и IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Компиляция и запуск приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Основы классов и объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Объектные ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Консольный ввод\вывод . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Base code conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Вопросы к главе 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Задания к главе 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Тестовые задания к главе 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Глава 2. ТИПЫ ДАННЫХ И ОПЕРАТОРЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Базовые типы данных и литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Документирование кода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Операторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Классы-оболочки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Оператор условного перехода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Оператор выбора . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Циклы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Вопросы к главе 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Задания к главе 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Тестовые задания к главе 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Глава 3. КЛАССЫ И МЕТОДЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Переменные класса, экземпляра класса и константы . . . . . . . . . . . . . . . . . . 67
Ограничение доступа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Конструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Проектирование методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Использование параметров метода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

4

ОГЛАВЛЕНИЕ
Использование параметра метода для получения результата . . . . . . . . . . . . 75
Использование возвращаемого значения . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Оболочка Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Статические методы и поля . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Модификатор final и неизменяемость . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Абстрактные методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Модификатор native . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Модификатор synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Логические блоки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Перегрузка методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Параметризованные классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Параметризованные методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Методы с переменным числом параметров . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Перечисления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Immutable и record . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Декомпозиция . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Рекомендации при проектировании классов . . . . . . . . . . . . . . . . . . . . . . . . 109
Вопросы к главе 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Задания к главе 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Тестовые задания к главе 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Глава 4. НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Наследование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Классы и методы final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Использование super и this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Переопределение методов и полиморфизм . . . . . . . . . . . . . . . . . . . . . . . . . 129
Методы подставки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
«Переопределение» статических методов . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Абстракция . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Полиморфизм и расширение функциональности . . . . . . . . . . . . . . . . . . . . 136
Класс Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Клонирование объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
«Сборка мусора» и освобождение ресурсов . . . . . . . . . . . . . . . . . . . . . . . . 145
Пакеты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Статический импорт . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Рекомендации при проектировании иерархии . . . . . . . . . . . . . . . . . . . . . . . 149
Вопросы к главе 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Задания к главе 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Тестовые задания к главе 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Глава 5. ВНУТРЕННИЕ КЛАССЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Внутренние (inner) классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Вложенные (nested) классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Анонимные (anonymous) классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Вопросы к главе 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

5

JAVA FROM EPAM
Задания к главе 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
Тестовые задания к главе 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Глава 6. ИНТЕРФЕЙСЫ И АННОТАЦИИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Интерфейсы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Параметризация интерфейсов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Аннотации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Вопросы к главе 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Задания к главе 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Тестовые задания к главе 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Глава 7. ФУНКЦИОНАЛЬНЫЕ ИНТЕРФЕЙСЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Методы default и static в интерфейсах . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Функциональные интерфейсы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Интерфейс Predicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Интерфейс Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Интерфейс Consumer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Интерфейс Supplier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Интерфейс Comparator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Замыкания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Ссылки на методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Вопросы к главе 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Задания к главе 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Тестовые задания к главе 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Глава 8. СТРОКИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Класс String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
StringBuilder и StringBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Регулярные выражения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Интернационализация приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
Интернационализация чисел . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Интернационализация дат . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
API Date\Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Форматирование информации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Шифрование и кодирование строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
Вопросы к главе 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Задания к главе 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Тестовые задания к главе 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Глава 9. ИСКЛЮЧЕНИЯ И ОШИБКИ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Иерархия исключений и ошибок . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Способы обработки исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Обработка нескольких исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Оператор throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Собственные исключения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Генерация непроверяемых исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276

6

ОГЛАВЛЕНИЕ
Блок finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Наследование и исключения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ошибки статической инициализации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Рекомендации по обработке исключений . . . . . . . . . . . . . . . . . . . . . . . . . . .
Отладочный механизм assertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Вопросы к главе 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Задания к главе 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Тестовые задания к главе 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

277
278
280
281
285
286
287
287

Глава 10. ПОТОКИ ВВОДА/ВЫВОДА . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Байтовые и символьные потоки ввода/вывода . . . . . . . . . . . . . . . . . . . . . . . 290
File, Path и Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Чтение из потока . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
Предопределенные стандартные потоки . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Сериализация объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
Сериализация в XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
Класс Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
Архивация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Вопросы к главе 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Задания к главе 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Тестовые задания к главе 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
Глава 11. КОЛЛЕКЦИИ И STREAM API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Общие определения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
ArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
Итераторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
Stream API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Алгоритмы сведения Collectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Метасимвол в коллекциях . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
Класс LinkedList и интерфейс Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
Интерфейс Deque и класс ArrayDeque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
Множества . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
EnumSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
Карты отображений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Унаследованные коллекции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
Алгоритмы класса Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Вопросы к главе 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
Задания к главе 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
Тестовые задания к главе 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Глава 12. ПОТОКИ ВЫПОЛНЕНИЯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Класс Thread и интерфейс Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Жизненный цикл потока . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Перечисление TimeUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
Интерфейс Callable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373

7

JAVA FROM EPAM
Механизм Fork\Join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Параллелизм . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Timer и поток TimerTask . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Управление приоритетами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Управление потоками . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Потоки-демоны . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Потоки и исключения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Атомарные типы и модификатор volatile . . . . . . . . . . . . . . . . . . . . . . . . . . .
Методы synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Инструкция synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Монитор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Механизм wait\notify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Интерфейс Lock как альтернатива synchronized . . . . . . . . . . . . . . . . . . . . .
Семафор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Барьер . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CountDownLatch или «защелка» . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Обмен блокировками . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Блокирующие очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CopyOnWriteArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Phaser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Вопросы к главе 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Задания к главе 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Тестовые задания к главе 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

375
379
381
382
382
385
386
387
390
392
393
394
397
403
408
411
415
416
418
420
422
423
424
425

Глава 13. Java DataBase Connectivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Драйверы, соединения и запросы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
СУБД MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
Соединение и запрос . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
Добавление и изменение записи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
Метаданные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
Подготовленные запросы и хранимые процедуры . . . . . . . . . . . . . . . . . . . 437
Транзакции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Точки сохранения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Data Access Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
DAO. Уровень метода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
DAO. Уровень слоя . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
Вопросы к главе 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Задания к главе 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
Глава 14. СЕТЕВЫЕ ПРОГРАММЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
Компьютерные сети . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
Локальные сети . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
Распределенные и глобальные сети . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462
Сеть VPN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462

8

ОГЛАВЛЕНИЕ
Адресация в локальных сетях . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Адресация в глобальных сетях . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Доменные имена . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
URL адреса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сетевые протоколы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Модели OSI/ISO и TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Протоколы TCP и UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Протокол передачи гипертекста . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Протоколы RPC, REST, SOAP, SSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сетевые программы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сокетные соединения по TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Многопоточность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Датаграммы и протокол UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Электронная почта . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Простой сервлет . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Вопросы к главе 14 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Задания к главе 14 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

463
464
465
466
467
468
469
470
470
471
474
476
479
482
484
487
487

Глава 15. Java API for XML Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Древовидная и псевдособытийная модели . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Валидация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
Псевдособытийная модель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
SAX-анализаторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493
Древовидная модель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Создание XML-документа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
StAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
Схема XSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
Вопросы к главе 15 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
Задания к главе 15 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
ОТВЕТЫ НА ТЕСТОВЫЕ ВОПРОСЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
Приложение 1. Log4J2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522
Приложение 2. TestNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540
СПИСОК РЕКОМЕНДУЕМОЙ ЛИТЕРАТУРЫ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559

ПРЕДИСЛОВИЕ
Пособие «Java from EPAM» включает переработанную и обновленную версию предыдущих книг авторов «Java 2. Практическое руководство» 2005 года,
«Java. Промышленное программирование» 2007 года и «Java. Методы программирования» 2013 года. Как известно, знания в области ИТ за 5 лет обновляются больше чем на 50%.
Книга создавалась в процессе обучения языку Java и технологиям студентов
механико-математического факультета и факультета прикладной математики
и информатики Белорусского государственного университета, а также слушателей очных и онлайн-тренингов EPAM Systems по ряду направлений технологий Java. При изучении Java в рамках данного пособия знание других языков
необязательно, книгу можно использовать для обучения программированию на
языке Java «с нуля».
Интересы авторов, направленные на обучение, определили структуру этой
книги. Она предназначена как для начинающих изучение Java-технологий, так
и для студентов и программистов, переходящих на Java с другого языка программирования. Авторы считают, что профессионала «под ключ» обучить
нельзя, им становятся только после участия в разработке нескольких серьезных Java-проектов. В то же время данный курс может служить ступенькой
к мастерству. Прошедшие обучение по этому курсу успешно сдают различные
экзамены, получают международные сертификаты и участвуют в командной
разработке промышленных программных проектов.
Книга разделена на две логические части. В первой даны фундаментальные
основы языка Java и концепции объектно-ориентированного программирования.
Во второй изложены наиболее важные аспекты применения языка, в частности
коллекции и базы данных, многопоточность и взаимодействие с XML. В конце
каж­дой главы приводятся вопросы для закрепления теоретических знаний, тестовые вопросы по материалам данной главы и задания для выполнения по рассмот­
ренной теме. Ответы к тестовым вопросам сгруппированы в отдельный блок.
В приложениях предложены дополнительные материалы, относящиеся к использованию в информационных системах, основанных на применении Javaтехнологий, популярных технологий Log4J и TestNG.
Авторы выражают благодарность компании EPAM Systems и ее сотрудникам, принимавшим участие в подготовке материалов этой книги и в ее издании.

Глава 1

ВВЕДЕНИЕ В ООП И ФП
Каждый может написать программу, которую может понять компьютер. Хороший программист пишет
программу, которую может понять человек.
Мартин Фаулер

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

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

11

JAVA FROM EPAM

Базовые понятия ФП
Функциональное программирование (ФП) было добавлено в очередную
версию Java и ориентировано на вычисления и обработку информации. Этот
подход предполагает формирование функции как объекта и передачу этого
объекта в метод для использования или, наоборот, сама функция есть возвращаемое методом значение. Функциональный стиль программирования не заменит другие подходы к созданию кода. Многие действительно последовательные процессы, такие как поведение программных моделей в реальном времени,
игровые и другие программы, организующие взаимодействие компьютера с человеком, не могут быть выражены в функциональном стиле. Функциональное
программирование позволяет по-иному взглянуть на процесс программирования, а некоторые приемы программирования, которые предназначены для написания программ в чисто функциональном стиле, могут с успехом использоваться и в традиционном программировании вместе с ООП.

Базовые понятия Java
Объектно-ориентированный язык Java был разработан в компании Sun
Microsystems в 1995 году для программирования небольших устройств и введения динамики на сайтах в виде апплетов. Немного позже язык Java нашел
широкое применение в интернет-приложениях, добавив на клиентские вебстраницы динамический интерфейс, улучшив вычислительные возможности.
Однако уже буквально через несколько лет после создания язык практически
покинул клиентские страницы и перебрался на серверы. На стороне клиента
его место заняли языки JavaScript и его производные.
При создании язык Java предполагался более простым, чем его синтаксический
предок С++. Сегодня с появлением новых версий возможности языка Java существенно расширились и во многом перекрывают функциональность С++. Java уже не
уступает по сложности предшественникам и называть его простым нельзя.
Отсутствие указателей, как наиболее опасного средства языка С++, нельзя
считать сужением возможностей, а тем более — недостатком, это просто требование безопасности. Возможность работы с произвольными адресами памяти через безтиповые указатели позволяет игнорировать защиту памяти. Вместо
указателей в Java широко используются ссылки. Отсутствие в Java множественного наследования состояния легко заменяется на более понятные конструкции с применением интерфейсов.
Системная библиотека классов языка Java содержит классы и пакеты, реализующие и расширяющие базовые возможности языка, а также сетевые средства,
взаимодействие с базами данных, многопоточность и многое другое. Методы
классов, включенные в эти библиотеки, вызываются JVM (Java Virtual Machine)
во время интерпретации программы.
12

ВВЕДЕНИЕ В ООП И ФП
В Java все объекты программы расположены в динамической памяти — куче
данных (heap) — и доступны по объектным ссылкам, которые хранятся в стеке
(stack). Это решение исключило непосредственный доступ к памяти, но усложнило
работу с элементами массивов и сделало ее менее эффективной по сравнению с программами на C++. В свою очередь, в Java предложен усовершенствованный механизм работы с коллекциями, реализующими основные динамические структуры
данных. Необходимо отметить, что объектная ссылка языка Java содержат информацию о классе объекта, на который она ссылается, так что объектная ссылка — это не
только ссылка на объект, размещенный в динамической памяти, но и дескриптор
(описание) объекта. Наличие дескрипторов позволяет JVM выполнять проверку
совместимости типов на фазе интерпретации кода, генерируя исключение в случае
ошибки. В Java изменена концепция организации динамического распределения памяти: отсутствуют способы программного освобождения динамически выделенной
памяти с помощью деструктора, понятие которого исключено из Java. Вместо этого
реализована система автоматического освобождения памяти «сборщик мусора», выделенной с помощью оператора new. Программист может только рекомендовать системе освободить выделенную динамическую память.
В отличие от C++, Java не поддерживает перегрузку операторов, беззнаковые целые, прямое индексирование памяти и, как следствие, указатели. В Java
существуют конструкторы, но отсутствуют деструкторы, т.к. применяется автоматическая сборка мусора, запрещены оператор goto и слово const, хотя они
являются зарезервированными словами языка.
Кроме ключевых слов в Java существуют три литерала: null, true, false, не
относящиеся к ключевым и зарезервированным словам, а также зарезервированное слово var, значение которого зависит от его позиции в коде. Слово var
используется вместо типа локальной переменной метода, для которой существует инициализатор, определяющий тип: var str = "sun";. Введены также новые ключевые слова record и yield.
Ключевые и зарезервированные слова языка Java:
abstract
assert
boolean
break
byte
case
catch
char
class
const*
сontinue

default
do
double
else
enum
extends
final
finally
float
for
goto*

if
implements
import
instanceof
int
interface
long
native
new
package
private

protected
public
record
return
short
static
strictfp
super
switch
synchronized
this

throw
throws
transient
try
var
void
volatile
while
yield

13

JAVA FROM EPAM

Простое приложение
Изучение любого языка программирования удобно начинать с программы
передачи символьного сообщения на консоль.
// # 1 # простое линейное приложение # HelloTutorial.java
public class HelloTutorial {
public static void main(String[] args) {
System.out.println("tutorial->https://docs.oracle.com/javase/tutorial/");
}
}

Здесь функция main(), с которой начинается выполнение любой программы Java, является методом класса HelloTutorial. Такая простая структура
класса не является хорошей. Пусть в процессе тестирования или внедрения
системы окажется, что фразу необходимо заменить на другую, например,
в конце поставить восклицательный знак. Для этого программисту придется
обыскивать весь код, искать места, где встречается указанная фраза, и заменять ее новой. Во избежание подобных проблем сообщение лучше хранить
в отдельном методе или константе (а еще лучше — в файле) и при необходимости вызывать его. Тогда изменение текста сообщения приведет к локальному изменению одной-единственной строки кода. В следующем примере этот
код будет переписан с использованием двух классов, реализованных на основе простейшего применения объектно-ориентированного программирования:
/* # 2 # простое объектно-ориентированное приложение # FirstProgram.java */
package by.epam.learn.main;
public class FirstProgram {
public static void main(String[] args) {
// declaring and creating an object
TutorialAction action = new TutorialAction();
// calling a method that outputs a string
action.printMessage("tutorial-> https://docs.oracle.com/javase/tutorial/");
}
}

/* # 3 # простой класс # TutorialAction.java */
class TutorialAction {
void printMessage(String msg) { // method definition
// output string
System.out.println(msg);
}
}

14

ВВЕДЕНИЕ В ООП И ФП
Здесь класс FirstProgram используется для того, чтобы определить метод
main(), который вызывается автоматически интерпретатором Java и может называться контроллером этого примитивного приложения. Метод main() получает
в качестве параметра аргументы командной строки String[]args, представляющие
массив строк, и является открытым (public) членом класса. Это означает, что метод main() может быть виден и доступен любому классу. Ключевое слово static
объявляет методы и переменные класса, используемые при работе с классом в целом, а не только с объектом класса. Символы верхнего и нижнего регистров здесь
различаются. Тело метода main() содержит объявление объекта:
TutorialAction action = new TutorialAction();

и вызов его метода:
action.printMessage("tutorial-> https://docs.oracle.com/javase/tutorial/");

Вывод строки tutorial-> https://docs.oracle.com/javase/tutorial/ в примере
осуществляет метод println() (ln — переход к новой строке после вывода) статического поля-объекта out класса System, который подключается к приложению автоматически вместе с пакетом java.lang. Приведенную программу необходимо поместить в файл FirstProgram.java (расширение .java обязательно),
имя которого должно совпадать с именем public-класса.
Перед объявлением класса располагается строка
package by.epam.learn.main;

указывающая на принадлежность класса пакету с именем by.epam.learn.main,
который является на самом деле каталогом на диске. Для приложения, состоящего из двух классов, наличие пакетов не является необходимостью. Однако
даже при отсутствии слова package классы будут отнесены к пакету по умолчанию (unnamed), размещенному в корне проекта. Если же приложение состоит из нескольких сотен классов, то размещение классов по пакетам является
жизненной необходимостью.
Классы из примеров 2 и 3 могут сохраняться как в одном файле, так и в двух
файлах FirstProgram.java и TutorialAction.java. На практике следует хранить
классы в отдельных файлах, что позволяет всем разработчикам проекта быстрее воспринимать концепцию приложения в целом.
/* # 4 # простое объектно-ориентированное приложение # FirstProgram.java */
package by.epam.learn.main;
import by.epam.learn.action.TutorialAction; // import a class from another package
public class FirstProgram {
public static void main(String[] args) {
TutorialAction action = new TutorialAction();
action.printMessage("tutorial-> https://docs.oracle.com/javase/tutorial/");
}
}

15

JAVA FROM EPAM
// # 5 # простой класс # TutorialAction.java
package by.epam.learn.action;
public class TutorialAction {
public void printMessage(String msg) {
System.out.println(msg);
}
}

Установка JDK и IDE
Чтобы начать программировать, необходимо скачать установку Java с официального сайта производителя oracle.com по линку https://www.oracle.com/
java/technologies/javase-downloads.html.
Затем установить на компьютер. При инсталляции рекомендуется указывать для размещения корневой каталог. Если JDK установлена в директории
(для Windows) c:\Java\jdk[version], то каталог, который компилятор Java будет
рассматривать как корневой для иерархии пакетов, можно вручную задавать
с помощью переменной среды окружения в виде: СLASSPATH=.;c:\Java\
jdk[version]\.
Переменной задано еще одно значение «.» для использования текущей директории, например, с:\workspace в качестве рабочей для хранения своих собственных приложений.
Чтобы можно было вызывать сам компилятор и другие исполняемые программы, переменную PATH нужно проинициализировать в виде PATH=
c:\Java\jdk[version]\bin.
Этот путь указывает на месторасположение файлов javac.exe и java.exe.
В различных версиях операционных систем путь к JDK может указываться
различными способами.
JDK (Java Development Kit) — полный набор для разработки и запуска приложений, состоящий из компилятора, утилит, исполнительной системы JRE,
библиотек, документации.
JRE (Java Runtime Environment) — минимальный набор для исполнения
приложений, включающий JVM, но без средств разработки.
JVM (Java Virtual Machine) — базовая часть исполняющей системы Java,
которая интерпретирует байт-код Java, скомпилированный из исходного текста
Java-программы для конкретной операционной системы.
Однако при одновременном использовании нескольких различных версий
компилятора и различных библиотек применение переменных среды окружения начинает мешать эффективной работе, так как при выполнении приложения поиск класса осуществляется независимо от версии. Когда виртуальная
машина обнаруживает класс с подходящим именем, она его и подгружает.
16

ВВЕДЕНИЕ В ООП И ФП
Такая ситуация предрасполагает к ошибкам, порой трудноопределимым.
Поэтому переменные окружения начинающим программистам лучше не определять вовсе.
Для компиляции и запуска приложений можно использовать два способа:
1) Командная строка;
2) IDE (IntelliJ IDEA, Eclipse, NetBeans etc.).
IDE позволяют создавать, компилировать и запускать приложения в значительно более удобной форме, чем с помощью командной строки.
IntelliJ IDEA — интегрированная среда разработки программного обеспечения для Java, разработанная компанией JetBrains.
Загрузить IntelliJ IDEA можно на официальном сайте https://www.jetbrains.com/
idea/. Среда IDEA используется для разработки приложений, поэтому необходимо
установить Java Development Kit (JDK). Студентам и магистрантам предоставляется возможность установить полную лицензионную версию после заполнения
регистрационной формы https://www.jetbrains.com/shop/eform/students.
После установки IntelliJ IDEA программа предложит настроить среду разработки: выбрать тему и установить дополнительные плагины.
Eclipse — сообщество открытого кода, или open source, чьи проекты сфокусированы на создании открытой платформы для разработки, развертывания,
управления приложениями с использованием различных фреймворков, инструментов и сред исполнения. Загрузить продукты Eclipse можно на официальном сайте http://eclipse.org/.
Для установки Eclipse Java IDE необходимо распаковать загруженный архив
в ту директорию, в которой будет храниться IDE. Предварительно также необходимо установить JDK.

Компиляция и запуск приложения
Простейший способ компиляции — вызов строчного компилятора из корневого каталога, в котором находится каталог by, каталог epam и так далее:
javac by/epam/learn/action/TutorialAction.java
javac by/epam/learn/main/FirstProgram.java

При успешной компиляции создаются файлы FirstProgram.class
и TutorialAction.class, имена которых совпадают с именами классов.
Запустить этот байт-код можно с помощью интерпретатора Java:
java by.epam.learn.main.FirstProgram

Здесь к имени приложения FirstProgram.class добавляется путь к пакету от
корня проекта by.epam.learn.main, в котором он расположен.
Обработка аргументов командной строки, передаваемых в метод main(), относится к необходимым программам в самом начале обучения языку. Аргументы
17

JAVA FROM EPAM
представляют последовательность строк, разделенных пробелами, значения
которых присваиваются объектам массива String[] args. Элементу args[0] присваивается значение первой строки после имен компилятора и приложения.
Количество аргументов находится в значении args.length. Поле args.length является константным полем класса, массива, значение которого не может быть
изменено на протяжении всего жизненного цикла объекта массива.
/* # 6 # вывод аргументов командной строки в консоль # PrintArgumentMain.java */
package by.epam.learn.arg;
public class PrintArgumentMain {
public static void main(String[] args){
for (String str : args) {
System.out.printf("Argument--> %s%n", str);
}
}
}

В данном примере используется цикл for для неиндексируемого перебора
всех элементов и метод форматированного вывода printf(). Тот же результат
был бы получен при использовании традиционного вида цикла for
for (int i = 0; i < args.length; i++) {
System.out.println("Argument--> " + args[i]);
}

Запуск этого приложения осуществляется с помощью следующей командной строки вида:
java by.epam.learn.arg.PrintArgumentMain 2020 Oracle "Java SE"

что приведет к выводу на консоль следующей информации:
Argument--> 2020
Argument--> Oracle
Argument--> Java SE
Приложение, запускаемое с аргументами командной строки, может быть
использовано как один из примитивных способов ввода в приложение внешних данных.

Основы классов и объектов
Классы в языке Java объединяют поля класса, методы, конструкторы, логические блоки и внутренние классы. Основные отличия от классов C++: все
функции определяются внутри классов и называются методами; невозможно
создать метод, не являющийся методом класса, или объявить метод вне класса;
спецификаторы доступа public, private, protected воздействуют только на те
18

ВВЕДЕНИЕ В ООП И ФП
объявления полей, методов и классов, перед которыми они стоят, а не на участок от одного до другого спецификатора, как в С++; элементы по умолчанию
не устанавливаются в private, а доступны для классов из данного пакета.
Объявление класса в общем виде следующее:
[specificators] class ClassName [extends SuperClass] [implements list_interfaces] {
/* implementation */
}

Спецификатор доступа к классу может быть public (класс доступен в данном пакете и вне пакета). По умолчанию, если спецификатор класса public не
задан, он устанавливается в дружественный (friendly или private-package).
Такой класс доступен только в текущем пакете. Спецификатор friendly так же,
как и private-package, при объявлении вообще не используется и не является
ключевым словом языка. Это слово используется в сленге программистов, чтобы обозначить значение по умолчанию.
Кроме этого, спецификатор может быть final (класс не может иметь подклассов) и abstract (класс может содержать абстрактные нереализованные методы, объект такого класса создать нельзя).
Класс наследует все свойства и методы суперкласса (базового класса), указанного после ключевого слова extends, и может включать множество интерфейсов, перечисленных через запятую после ключевого слова implements.
Интерфейсы относительно похожи на абстрактные классы, содержащие только
статические константы и не имеющие конструкторов, но имеют целый ряд
серьезных архитектурных различий.
Все классы любого приложения условно разделяются на две группы: классы — носители информации, и классы, работающие с этой информацией.
Классы, обладающие информацией, содержат данные о предметной области
приложения. Например, если приложение предназначено для управления воздушным движением, то предметной областью будут самолеты, билеты, багаж,
пассажиры и пр. При проектировании классов информационных экспертов
важна инкапсуляция, обеспечивающая значениям полей классов корректность
информации.
В качестве примера с нарушением инкапсуляции можно рассмотреть класс
Coin в приложении по обработке монет.
// # 7 # простой пример класса носителя информации # Coin.java
package by.epam.learn.bean;
public class Coin {
public double diameter; // encapsulation violation
private double weight; // correct encapsulation
public double getDiameter() {
return diameter;
}

19

JAVA FROM EPAM
public void setDiameter(double value) {
if (value > 0) {
diameter = value;
} else {
diameter = 0.01; // default value
}
}
public double takeWeight() { // incorrect method name
return weight;
}
public void setWeight(double value) {
weight = value;
}
}

Класс Coin содержит два поля diameter и weight, помеченные как public
и private. Значение поля weight можно изменять только при помощи методов,
например, setWeight (double value). В некоторых ситуациях замена некорректного значения на значение по умолчанию может привести к более грубым
ошибкам в дальнейшем, поэтому часто вместо замены производится генерация
исключения. Поле diameter доступно непосредственно через объект класса
Coin. Поле, объявленное таким способом, считается объявленным с нарушением «тугой» инкапсуляции, следствием чего может быть нарушение корректности информации, как это показано ниже:
// # 8 # демонстрация последствий нарушения инкапсуляции # CoinMain.java
package by.epam.learn.main;
import by.epam.learn.bean.Coin;
public class CoinMain {
public static void main(String[] args) {
Coin coin = new Coin();
coin.diameter = -0.12; // incorrect: direct access--> ob.setWeight(100);
// coin.weight = -150; // field is not available: compile error
}
}

Чтобы поле diameter стало недоступно напрямую, акомпиляция кода вида
coin.diameter = -0.12;

стала невозможной, следует поле diameter класса Coin объявить в виде
private double diameter;

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

20

ВВЕДЕНИЕ В ООП И ФП
// # 9 # «туго» инкапсулированный класс (Java Bean) # Coin.java
package by.epam.learn.bean;
public class Coin {
private double diameter;
private double weight;
public double getDiameter() {
return diameter;
}
public void setDiameter(double value) {
if(value > 0) {
diameter = value;
} else {
System.out.println("Negative diameter!");
}
}
public double getWeight() { // correct name
return weight;
}
public void setWeight(double value) {
weight = value;
}
}

Проверка корректности входящей извне информации осуществляется в методе setDiameter(double value) и позволяет уведомить о нарушении инициализации объекта. Доступ к public-методам объекта класса осуществляется только
после создания объекта данного класса.
/* # 10 # создание объекта, доступ к полям и методам объекта # CompareCoin.java
# CoinMain.java */
package by.epam.learn.action;
import by.epam.learn.bean.Coin;
public class CompareCoin {
public void compareDiameter(Coin first, Coin second) {
double delta = first.getDiameter() - second.getDiameter();
if (delta > 0) {
System.out.println("The first coin is more than the second for " + delta);
} else if (delta == 0) {
System.out.println("Coins have the same diameter");
} else {
System.out.println("The second coin is more than the first on " + -delta);
}
}
}
package by.epam.learn.main;
import by.epam.learn.bean.Coin;
import by.epam.learn.action.CompareCoin;

21

JAVA FROM EPAM
public class CoinMain {
public static void main(String[] args) {
Coin coin1 = new Coin();
coin1.setDiameter(-0.11); // error message
coin1.setDiameter(0.12); // correct
coin1.setWeight(150);
Coin coin2 = new Coin();
coin2.setDiameter(0.21);
coin2.setWeight(170);
CompareCoin compare = new CompareCoin();
compare.compareDiameter(coin1, coin2);
}
}

Компиляция и выполнение данного кода приведут к выводу на консоль следующей информации:
Negative diameter!
The second coin is more than the first on 0.09
Объект класса создается за два шага. Сначала объявляется ссылка на объект
класса. Затем с помощью оператора new создается экземпляр объекта, на­
пример:
Coin coin; // declaring an object reference
coin = new Coin(); // object instantiation

Однако эти два действия обычно объединяют в одно:
Coin coin = new Coin();

Оператор new вызывает конструктор, в данном примере используется конструктор по умолчанию без параметров, но в круглых скобках могут размещаться аргументы, передаваемые конструктору, если у класса объявлен конструктор с параметрами. Операция присваивания для объектов означает, что
две ссылки будут указывать на один и тот же участок памяти.
Метод compareDiameter(Coin first, Coin second) выполняет два действия,
которые следует разделять: выполняет сравнение и печатает отчет. Действия
слишком различны по природе, чтобы быть совмещенными. Естественным решением будет изменить возвращаемое значение метода на int и оставить в нем
только вычисления.
/* # 11 # метод сравнения экземпляров по одному полю # */
public int compareDiameter(Coin first, Coin second) {
int result = 0;
double delta = first.getDiameter() - second.getDiameter();
if (delta > 0) {
result = 1;

22

ВВЕДЕНИЕ В ООП И ФП
} else if (delta < 0) {
result = -1;
}
return result;
}

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

Объектные ссылки
Java работает не с объектами, а со ссылками на объекты, размещаемыми
в динамической памяти с помощью оператора new. Это объясняет то, что операции сравнения ссылок на объекты не имеют смысла, так как при этом сравниваются адреса. Для сравнения объектов на эквивалентность по значению
необходимо использовать специальные методы, например, equals(Object ob).
Этот метод наследуется в каждый класс из суперкласса Object, который лежит
в корне дерева иерархии всех классов и должен переопределяться в подклассе
для определения эквивалентности содержимого двух объектов этого класса.
/* # 12 # сравнение ссылок и объектов # ComparisonString.java */
package by.epam.learn.main;
public class ComparisonString {
public static void main(String[ ] args) {
String str1, str2;
str1 = "Java";
str2 = str1; // variable refers to the same string
System.out.println("comparison of references " + (str1 == str2)); // true
str2 = new String("Java"); // is equivalent to str2 = new String(str1);
System.out.println("comparison of references "+ (str1 == str2)); // false
System.out.println("comparison of values " + str1.equals(str2)); // true
}
}

В результате выполнения действия str2 = str1 получается, что обе ссылки
ссылаются на один и тот же объект. Оператор «==» возвращает true при сравнении ссылок только в том случае, если они ссылаются на один и тот же объект.
Если же ссылку str2 инициализировать конструктором new String(str1), то
создается новый объект в другом участке памяти, который инициализируется
значением, взятым у объекта str1. В итоге существуют две ссылки, каждая из
которых независимо ссылается на объект, который никак физически не связан
с другим объектом. Поэтому оператор сравнения ссылок возвращает результат
false, так как ссылки ссылаются на различные участки памяти. Объекты обладают одинаковыми значениями, что легко определяется вызовом метода
equals(Object ob).
23

JAVA FROM EPAM
Если в процессе разработки возникает необходимость в сравнении по значению объектов классов, созданных программистом, для этого следует переопределить в данном классе метод equals(Object ob) в соответствии с теми критериями сравнения, которые существуют для объектов данного типа или по
стандартным правилам, заданным в документации.

Консольный ввод\вывод
Консоль предоставляет пользователю простой способ взаимодействия
с программой посредством потоков ввода\вывода. В Java взаимодействие
с консолью обеспечивает класс System, а его статические поля in, out и err
обеспечивают ввод\вывод. Для вывода информации на консоль используют методы System.out.println() и другие.
Взаимодействие с консолью с помощью потока (объекта класса) System.in
представляет собой один из простейших способов ввода. При создании первых
приложений такого рода передача в них информации является единственно доступной для начинающего программиста. В следующем примере рассматривается ввод информации в виде символа из потока ввода, связанного с консолью,
и последующего вывода на консоль символа и его числового кода.
// # 13 # чтение символа из потока System.in # ReadCharMain.java 24
package by.epam.learn.console;
import java.io.IOException;
public class ReadCharMain {
public static void main(String[] args) {
int x;
try {
x = System.in.read();
char c = (char)x;
System.out.println("Character Code: " + c + " =" + x);
}
catch (IOException e) {
System.err.println("i\o error " + e);
}
}
}

Обработка исключительной ситуации IOException, которая может возникнуть в операциях ввода/вывода и в любых других взаимодействиях с внешними источниками данных, осуществляется в методе main() с помощью реализации блока try-catch. Если ошибок при выполнении не возникает, выполняется
блок try, в противном случае генерируется исключительная ситуация, и выполнение программы перехватывает блок catch.
24

ВВЕДЕНИЕ В ООП И ФП
Ввод информации осуществляется посредством чтения строки из консоли
с помощью возможностей объекта класса Scanner, имеющего возможность соединяться практически с любым потоком/источником информации: строкой,
файлом, сокетом, адресом в интернете, с любым объектом, из которого можно
получить ссылку на поток ввода.
// # 14 # чтение строки из консоли # ScannerMain.java
package by.epam.learn.console;
import java.util.Scanner;
public class ScannerMain {
public static void main(String[] args) {
System.out.println("Enter name and press & number and press :");
Scanner scan = new Scanner(System.in);
String name = scan.next();
System.out.println("hello, " + name);
int num = scan.nextInt();
System.out.println("number= " + num);
scan.close();
}
}

В результате запуска приложения будет выведено, например, следующее:
Enter name and press & number and press :
ostap
hello, ostap
777
number= 777
Класс Scanner объявляет ряд методов для ввода: next(), nextLine(), nextInt(),
nextDouble() и др.
Позже будут рассмотрены более удобные способы извлечения информации из потока ввода с помощью класса Scanner, в качестве которого может
фигурировать не только консоль, но и дисковый файл, строка, сокетное соединение и пр.

Base code conventions
При выборе имени класса, поля, метода использовать цельные слова, полностью исключить сокращения, кроме общепринятых. По возможности опускать предлоги и очевидные связующие слова. Аббревиатуры использовать
только в том случае, когда они очевидны. Если избежать сокращения не получается, надо помнить, что начало важнее конца, согласные важнее гласных.
Имя класса всегда пишется с большой буквы: Coin, Developer.
25

JAVA FROM EPAM
Если имя класса состоит из двух и более слов, то второе и следующие слова
пишутся слитно с предыдущим и начинаются с большой буквы: AncientCoin,
FrontendDeveloper.
Имя метода всегда пишется с маленькой буквы: perform(), execute().
Если имя метода состоит из двух и более слов, то второе и следующие слова
пишутся слитно с предыдущим и начинаются с большой буквы: performTask(),
executeBaseAction().
Имя поля класса, локальной переменной и параметра метода всегда пишутся с маленькой буквы: weight, price.
Если имя поля класса, локальной переменной и параметра метода состоит
из двух и более слов, то второе и следующие слова пишутся слитно с предыдущим и начинаются с большой буквы: priceTicket, typeProject.
Константы и перечисления пишутся в верхнем регистре: DISCOUNT,
MAX_RANGE.
Все имена пакетов пишутся с маленькой буквы. Сокращения допустимы
только в случае, если имя пакета слишком длинное: 10 или более символов.
Использование цифр и других символов нежелательно.

Вопросы к главе 1
1. Что имеется в виду, когда говорится: Java-язык программирования и Javaплатформа?
2. Расшифровать аббревиатуры JVM, JDK и JRE. Показать, где они физически расположены и что собой представляют.
3. JVM-JDK-JRE. Кто кого включает и как взаимодействуют.
4. Как связаны имя Java-файла и классы, которые в этом файле объявляются?
5. Как скомпилировать и запустить класс, используя командную строку?
6. Что такое classpath? Зачем в переменных среды окружения прописывать
пути к установленному JDK?
7. Если в classpath есть две одинаковые библиотеки (или разные версии одной
библиотеки), объект класса из какой библиотеки создастся?
8. Объяснить различия между терминами «объект» и «ссылка на объект».
9. Какие области памяти использует Java для размещения простых типов,
объектов, ссылок, констант, методов, пул строк и т.д.
10. Почему метод main() объявлен как public static void?
11. Возможно ли в сигнатуре метода main() поменять местами слова static
и void?
12. Будет ли вызван метод main() при запуске приложения, если слова static
или public отсутствуют?
13. Классы какого пакета импортируются в приложение автоматически?

26

ВВЕДЕНИЕ В ООП И ФП

Задания к главе 1
Вариант A
1. Приветствовать любого пользователя при вводе его имени через командную строку.
2. Отобразить в окне консоли аргументы командной строки в обратном порядке.
3. Вывести заданное количество случайных чисел с переходом и без перехода
на новую строку.
4. Ввести пароль из командной строки и сравнить его со строкой-образцом.
5. Ввести целые числа как аргументы командной строки, подсчитать их суммы и произведения. Вывести результат на консоль.
6. Вывести фамилию разработчика, дату и время получения задания, а также
дату и время сдачи задания.

Вариант B
Ввести с консоли n целых чисел. На консоль вывести:
Четные и нечетные числа.
Наибольшее и наименьшее число.
Числа, которые делятся на 3 или на 9.
Числа, которые делятся на 5 и на 7.
Все трехзначные числа, в десятичной записи которых нет одинаковых
цифр.
6. Простые числа.
7. Отсортированные числа в порядке возрастания и убывания.
8. Числа в порядке убывания частоты встречаемости чисел.
9. «Счастливые» числа.
10. Числа-палиндромы, значения которых в прямом и обратном порядке совпадают.
11. Элементы, которые равны полусумме соседних элементов.
1.
2.
3.
4.
5.

Тестовые задания к главе 1
Вопрос 1.1.
Какие объявления параметров корректны для метода public static void main?
(выбрать два)
a) String args []
b) String [] args
c) Strings args []
d) String args
27

JAVA FROM EPAM

Вопрос 1.2.
Какой из предложенных пакетов содержит class System? (выбрать один)
a) java.io
b) java.base
c) java.util
d) java.lang

Вопрос 1.3.
Какая команда выполнит компиляцию Java приложения First.java? (выбрать
один)
a) javac First
b) java First.class
c) javac First.java
d) java First.java
e) java First

Вопрос 1.4.
Какие слова являются ключевыми словами Java? (выбрать два)
a) classpath
b) for
c) main
d) out
e) void

Вопрос 1.5.
Дан код:
public class Main {
public static void main(String[] args) {
for(int x = 0; x < 1; x++)
System.out.print(x);
System.out.print(x);
}
}

Что будет в результате компиляции и запуска? (выбрать один)
a) 01
b) 00
c) 11
d) compilation fails
e) runtime error

28

ВВЕДЕНИЕ В ООП И ФП

Вопрос 1.6.
Дан код:
Каков будет вывод в консоль, если код запускается из командной строки
java P R I V E T ? (выбрать один)
public class P {
  public static void main(String[] s) {
    System.out.print(s[1] + s[3] + s[2] + s[1]);
  }
}

a)
b)
c)
d)
e)

PIRP
IEVI
RVIR
compilation fails
runtime error

Глава 2

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
Программирование всегда осуждалось
светскими, духовными и прочими властями.

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

Базовые типы данных и литералы
Java — язык объектно-ориентированного программирования, однако не все
данные в языке есть объекты. Для повышения производительности в нем, кроме объектов, используются базовые типы данных, значения которых (литералы) размещаются в стековой памяти. Для каждого базового типа имеются также классы-оболочки, которые инкапсулируют данные базовых типов
в объекты, располагаемые в динамической памяти (heap). Базовые типы обеспечивают более высокую производительность вычислений по сравнению
с объектами классов-оболочек и другими объектами. Что является основной
причиной применения в Java базовых типов, а не объектной модели пол­
ностью. Значения базовых типов данных, записанных по правилам языка Java,
называют литералами.
Определено восемь базовых типов данных, размер каждого из которых
остается неизменным независимо от платформы (рис. 2.1.).
Беззнаковых типов в Java не существует. Каждый тип данных определяет
множество значений и их представление в памяти. Для каждого типа определен набор операций над его значениями.
В Java используются целочисленные литералы, например: 42 — целое (int)
десятичное число, 042 — восьмеричное целое число, 0х42b — шестнадцатеричное целое число, 0b101010 — двоичное целое число. Целочисленные литералы по умолчанию относятся к типу int. Если необходимо определить длинный литерал типа long, в конце указывается символ L (например: 0xffffL).
Если значение числа больше значения, помещающегося в int (2147483647), то
Java автоматически полагает, что оно типа long.
В Java для удобства восприятия литералов стало возможно использовать
знак «_» при объявлении больших чисел, то есть вместо int value = 4200000
30

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ

Рис. 2.1. Базовые типы данных и их свойства

можно записать int value = 4_200_000. Эта форма применима и для чисел
с плавающей запятой. Однако некорректно: _7 или 7_.
Литералы с плавающей точкой записываются в виде 1.618 или в экспоненциальной форме 0.117E-5 и относятся к типу double. Таким образом, действительные числа относятся к типу double. При объявлении такого литерала
можно использовать символы d и D, а именно 1.618d. Если необходимо определить литерал типа float, то в конце литерала необходимо добавить символ
F или f. По стандарту IEEE 754 введены понятие бесконечности +Infinity
и –Infinity, число большее или меньшее любого другого числа при делении
на ноль в типах с плавающей запятой, а также значение NaN (Not a Number),
которое может быть получено, например, при извлечении квадратного корня
из отрицательного числа.
К булевским литералам относятся значения true и false. Литерал null — значение по умолчанию для объектной ссылки.
Символьные литералы определяются в апострофах ('a', '\n', '\042', '\ub76f').
Для размещения символов используется формат Unicode, в соответствии с которым для каждого символа отводится два байта. В формате Unicode первый байт
содержит код управляющего символа или национального алфавита, а второй
байт соответствует стандартному ASCII коду, как в C++. Любой символ можно
представить в виде '\ucode', где code представляет двухбайтовый шестнадцатеричный код символа. Java поддерживает управляющие символы, не имеющие
графического изображения; '\n' — новая строка, '\r' — переход к началу, '\f' —
новая страница, '\t' — табуляция, '\b' — возврат на один символ, '\uxxxx' —
шестнадцатеричный символ Unicode, '\ddd' — восьмеричный символ и др.
31

JAVA FROM EPAM
В настоящее время Java обеспечивает поддержку стандарта Unicode 10.0.0 возможностями классов Character, String.
Переменные могут быть либо членами класса, либо локальными переменными метода, либо параметрами метода. По стандартным соглашениям имена
переменных не могут начинаться с цифры, в именах не могут использоваться
символы арифметических и логических операторов, а также символ '#'.
Применение символов '$' и '_' допустимо, в том числе и в первой позиции имени, но нежелательно. Каждая переменная должна быть объявлена с одним из
указанных выше типов.
Можно конечно записать такой корректный с точки зрения компилятора, но
абсолютно бессмысленый код:
int _ = 2;
int __ = 3;
int ___ = _ * __;

но это будет яркий пример антипрограммирования. В приведенном фрагменте
ни одна переменная не указывет на смысл хранимого им значения и результата
вычислений.
Но если переменным дать имена,
int height = 2;
int width = 3;
int rectangleArea = height * width;

то станет ясно, что здесь приведен процесс вычисления площади прямоугольника.
Переменная базового типа, объявленная как член класса, хранит нулевое
значение, соответствующее своему типу. Если переменная объявлена как локальная переменная в методе, то перед использованием она обязательно должна быть проинициализирована, так как локальная переменная не инициализируется по умолчанию нулем. Область действия и время жизни такой переменной
ограничена блоком {}, в котором она объявлена, то есть код
int height = 2;
{ int width = 3;
}
int rectangleArea = height * width;

компилироваться не будет, так как переменная width недоступна (или не видна) вне блока {}.
Строки, заключенные в двойные апострофы, считаются литералами и размещаются в пуле литералов, но в то же время такие строки представляют собой объекты класса String. При инициализации строки создается объект класса String.
При работе со строками, кроме методов класса String, можно использовать единственный в языке перегруженный оператор «+» конкатенации (слияния) строк.
Конкатенация строки с объектом любого другого типа добавляет к исходному
32

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
объекту-строке строковое представление объекта другого типа. Строковый литерал заключается в двойные кавычки, это не ASCII-строка, а объект из набора (массива) символов. Строковые литералы в большинстве своем должны объявляться
как константы final static поля классов для экономии памяти.
String name = "Sun";
name += '1';
name = name + 42;

в тоже время будет некорректно
str ="javac" - "c"; // error. subtraction of a line is impossible.

Оператор «-» для строк и объектов не перегружен. Самостоятельно запрограммировать перегрузку оператора также невозможно, так как перегрузка
операторов в Java не реализована.
В арифметических выражениях автоматически выполняются расширяющие
преобразования типа
byte  short  int  long  float  double
Это значит, что любая операция с участием различных типов даст результат,
тип которого будет соответствовать большему из типов операндов. Например,
результатом сложения значений типа double и long будет значение типа double.
Java автоматически расширяет тип каждого byte или short операнда до int
в арифметических выражениях. Для сужающих диапазон значений преобразований необходимо производить явное преобразование вида (type)value. На­пример:
int number
byte value
value = 42
value = 42

=
=
+
+

42;
(byte) number; // transformation int to byte
1; // correct // line 1
101; // compile error // line 2

строка, помеченная line 1, компилируется, несмотря на то, что операция сложения дает результатом int, но так как операнды константы, то компилятор вычисляет результирующее значение 43, которое не выходит за пределы допустимых значений типа byte (от -128 до 127), и преобразует результат к byte.
В строке line 2 компилятор вычисляет значение 143, и оно выходит за допустимые пределы, поэтому и не может быть преобразовано к byte.
byte b = 1;
final byte B = 2;
b = B + 1; // ok!

Аналогичная ситуация возникает и в случае сложения константы B c числовой константой, если результат не выходит за границы типа byte.
Далее фрагмент
byte b = 1;
b = b + 100;

33

JAVA FROM EPAM
не компилируется, так как в выражении присутствует переменная b и компилятор в общем случае предупреждает, что результат операции может выходить за
допустимые пределы. Проблема компиляции решается явным преобразованием:
b = (byte)(b + 100);

При явном преобразовании (type)value возможно усечение значения. Если
значение b изменить так, чтобы результат выходил за пределы границ для byte:
byte b = 42;
b = (byte)(b + 100);

то b получит значение -114, и программист должен понимать, какой смысл ему
в этом результате.
Приведение типов не потребуется и в случае, если результатом будет тип
byte, а в операции используется константа типа int:
byte b = 42;
final int VALUE = 1;
b = b + VALUE;

где компилятор определяет результат 43 как допустимый.
При инициализации полей класса и локальных переменных методов с использованием арифметических операторов автоматически выполняется неявное приведение литералов к объявленному типу без необходимости его явного
указания, если только их значения находятся в допустимых пределах. В операциях присваивания нельзя присваивать переменной значение более длинного
типа, в этом случае необходимо явное преобразование типа. Исключение составляют операторы инкремента «++», декремента «– –» и сокращенные операторы (+=, /= и т.д.), которые сами выполняют приведение типов:
byte b = 42;
int i = 1;
b += i++; // ok!
b += 1000; // ok!

В Java 10 введено понятие type inference — исключение из объявления типа
локальной переменной с помощью var. Теперь при объявлении локальных переменных метода вместо
int counter = 1;
String name = new String();

можно записать
var counter = 1;
var name = new String();

При просмотре данного выражения компилятор обрабатывает правую часть выражения и присваивает его тип переменной. var — зарезервированное имя типа, не
является ключевым словом и может использоваться как имя переменной или метода.
34

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
То есть корректны объявления переменной и метода:
var var = 1;
void var(){}

Применение var экономит несколько символов при создании кода и избегает дублирования имен типов при объявлении переменной.
Рекомендуется избегать применения var в случае, когда значение типа данных неочевидно при прочтении кода:
var a = ob.method();

Документирование кода
В языке Java используются блочные и однострочные комментарии /* */ и //.
Введен также особый вид комментария /** */, который содержит описание
класса/метода/поля с помощью дескрипторов вида:
@author — задает сведения об авторе;
@version — задает номер версии класса;
@exception — задает имя класса исключения;
@param — описывает параметры, передаваемые методу;
@return — описывает тип, возвращаемый методом;
@deprecated — указывает, что метод устаревший и у него есть более совершенный аналог;
@since — определяет версию, с которой метод (член класса, класс) присутствует;
@throws — описывает исключение, генерируемое методом;
@see — что следует посмотреть дополнительно.

Рис. 2.2. Дескрипторы документирования кода

35

JAVA FROM EPAM

Рис. 2.3. Сгенерированная документация для класса Object

Из java-файла, содержащего такие комментарии, соответствующая утилита
javadoc.exe может извлекать информацию для документирования классов
и сохранения ее в виде html-документа. В качестве примера и образца для подражания следует рассматривать исходный код языка Java и документацию, сгенерированную на его основе (рис. 2.3.).
/* # 1 # фрагмент класса Object с дескрипторами документирования # Object.java */
package java.lang;
/**
*Class {@code Object} is the root of the class hierarchy.
*Every class has {@code Object} as a superclass. All objects,
* including arrays, implement the methods of this class.
*@author unascribed
*@see
java.lang.Class

36

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
*@since JDK1.0
*/
public class Object {
/**
*Indicates whether some other object is "equal to" this one.
* MORE COMMENTS HERE
*@param obj the reference object with which to compare.
*@return {@code true} if this object is the same as the obj
*argument; {@code false} otherwise.
*@see
#hashCode()
*@see
java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
/**
*Creates and returns a copy of this object.
*MORE COMMENTS HERE
*@return
a clone of this instance.
*@exception CloneNotSupportedException if the object's class does not
*support the {@code Cloneable} interface. Subclasses
*that override the {@code clone} method can also
*throw this *exception to indicate that an instance cannot be cloned.
@see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
// more code here
}

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

Операторы
Операторы Java практически совпадают с операторами в других современных языках программирования и имеют приоритет, как приведенный на рисунке 2.4. Операторы работают с базовыми типами, для которых они определены,
и объектами классов-оболочек над базовыми типами. Кроме этого, операторы
«+» и «+=» производят также действия по конкатенации операндов типа String.
Логические операторы «==», «!=» и оператор присваивания «=» применимы
37

JAVA FROM EPAM
к операндам любого объектного и базового типов, а также литералам.
Применение оператора присваивания к объектным типам часто приводит
к ошибке несовместимости типов во время выполнения приложения с генерацией исключения ClassCastException, поэтому такие операции необходимо
контролировать. Деление на ноль в целочисленных типах вызывает исключительную ситуацию, переполнение не контролируется.

Рис. 2.4. Таблица приоритетов операций

Оператор «=» во всех операциях имеет самый низкий приоритет и выполняется самым последним.
int
int
int
h =

f;
g;
h;
g = f = 42;

В выражении:
int result= 4 + 2 * 7 - 8;

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

Сложение

/

+=


Сложение (с присваиванием)
Бинарное вычитание
и унарное изменение знака
Вычитание
(с присваиванием)
Умножение

/=
%

–=
*
*=

38

Умножение
(с присваиванием)

%=
++
––

Деление (или выделение целой части
для целочисленных операндов)
Деление (с присваиванием)
Остаток от деления (деление по
модулю)
Остаток от деления
(с присваиванием)
Инкремент (увеличение значения на
единицу)
Декремент (уменьшение значения на
единицу)

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
Для целочисленных типов операция деления отбрасывает остаток от деления (деление нацело).
int x = 1;
int y = 3;
int z = x / y;

Результат: z будет равно 0.
По этой же причине следующий фрагмент также даст результат 0.
z = x / y * y;

Если же операция деления производится для чисел с плавающей запятой, то
результат будет, как и в обычной арифметике.
float f = 1;
int y = 3;
float r = f / y * y;

Результат: r будет равно 1.0.
Оператор «%» позволяет получить остаток от деления для всех числовых
типов данных:
int x = 1;
int y = 3;
int z = x % y;

Результат: z будет равно 1.
Операторы инкремента и декремента в общем случае аналогичны действию
i = i +1 и i = i -1, но при присваивании нужно учитывать префиксную или постфиксную запись оператора. Префиксная запись первым действием увеличивает значение операнда на единицу и затем присваивает результат, постфиксная же работает в обратном порядке.
int i = 0;
int j = i++; // j=0 and i=1
int k = ++i; // i=2 and k=2

Результат: j получит значение 0, k получит значение 2, i получит значение 2.
По-особому работает постфиксный оператор в случае использования с той
же самой переменной. В этом случае значение постфиксного увеличения на
единицу утрачивается, так как присваивание уже произошло.
int i = 0;
i = i++;

Результат: i получит значение 0.

39

JAVA FROM EPAM
Битовые операторы над целочисленными типами
|
|=
&
&=

Или
Или (с присваиванием)
И
И (c присваиванием)

>>
>>=
>>>
>>>=

^
^=

Исключающее или
Исключающее или
(c присваиванием)
Унарное отрицание

>>" + i +"

=
=
=
=
"
=
=

"
"
"
"
=
"
"

+
+
+
+
"
+
+

(b1|b2));
(b1&b2));
(b1^b2));
~b2);
+ (b1>>i));
(b1>i));

Результатом выполнения данного кода будет:
14|9 = 15
14&9 = 8
14^9 = 7
~9 = -10
14>>1 = 7
14>2 = 3
Операторы отношения
<

>=
!=

Больше
Больше либо равно
Не равно

Эти операторы применяются для сравнения символов, целых и вещественных чисел, а также для сравнения ссылок при работе с объектами.
Логические операторы
||

40

Или

&&

И

!

Унарное отрицание

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
Логические операции выполняются только над значениями типов boolean
и Boolean (true или false).
Если один из операндов оператора «||» имеет значение true, то и результат
всей операции равен true.
boolean a = true;
boolean b = false;
System.out.println(a || b);

Результат: true.
Если один из операндов оператора «&&» имеет значение false, то и результат всей операции равен false. Если оба операнда имеют значения true, то только в этом случае результатом оператора будет true.
System.out.println(a && b);

Результат: false.
При работе логических операторов в ситуации, если результат определяется
одним операндом и нет необходимости проверять значение второго, то виртуальная машина к нему и не обращается. Проиллюстрировать такой функционал можно следующим примером:
int x = 0;
int y = 0;
System.out.println( x++ == y++ || x++ != y++);
System.out.println("x=" + x + ", y=" + y);

Результат:
true
x=1, y=1.
Выражение справа от оператора «||» вычислилось, а выражение слева —
нет.
В случае с битовым оператором «|» всегда вычисляются оба операнда, как
в следующем фрагменте:
System.out.println( x++ == y++ | x++ != y++);
System.out.println("x=" + x + ", y=" + y);

Результат:
true
x=2, y=2.
В операторе «&&» в случае истинного значения первого операнда будет вычисляться второй. Если же первый операнд имеет значение false, то второй
выполнен не будет.
System.out.println( x++ == y++ && x++ != y++);
System.out.println("x=" + x + ", y=" + y);

41

JAVA FROM EPAM
Результат:
false
x=2, y=2.
Если логические операторы расположены последовательно, то действия выполняются до тех пор, пока результат не будет очевиден для конкретного оператора. В следующем фрагменте отработает только первый операнд, так для
оператора «||» результат выражения будет определяться его значением true.
System.out.println( x++ == y++ || x++ != y++ && x++ == y++);
System.out.println("x= " + x + ", y= " + y);

Результат:
true
x=1, y=1.
Если же операция состоит из битовых операторов, то вычисляются все без
исключения операнды.
System.out.println( x++ == y++ | x++ != y++ & x++ == y++);
System.out.println("x= " + x + ", y= " + y);

Результат:
true
x=3, y=3.
К логическим операторам относится также оператор определения принадлежности типа instanceof и тернарный оператор «?:» (if-then-else).
Тернарный оператор «?:» используется в выражениях вида:
boolean_value ? expression_one : expression_two;
type value = boolean_value ? expression_one : expression_two;

Если boolean_value равно true, вычисляется значение выражения expression_one,
и оно становится результатом всего оператора, иначе результатом является значение выражения expression_two, которое может быть присвоено значению value.
Например:
int determineBonus(int numberProducts) {
int bonus = numberProducts > 7 ? 10 : 0;
return bonus;
}

Если число купленных предметов более семи, то клиент получает bonus в размере десятипроцентной скидки, в противном случае скидка не предоставляется.
Такое применение делает оператор простым для понимания так же, как и следующий вариант:
int result = experience > requirements ? acceptToProject() : learnMore();

где оба метода должны возвращать значение типа int.
42

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
Оператор instanceof возвращает значение true, если объект является экземпляром данного типа. Например, для иерархии наследования:
/* # 2 # учебные курсы в учебном заведении: иерархия */
class Course {
void method(){}
}
class BaseCourse extends Course {/* */}
class FreeCourse extends BaseCourse {/* */}
class OptionalCourse extends Course {/* */}

Применение оператора instanceof может выглядеть следующим образом при
вызове метода doAction(Course course):
void doAction(Course course) {
if (course instanceof BaseCourse) {
// for BaseCourse and FreeCourse
BaseCourse base = (BaseCourse)course;
base.method();
} else if (course instanceof OptionalCourse) {
// for OptionalCourse
OptionalCourse optional = (OptionalCourse)course;
optional.method();
} else {
// for Course or null
}
}

Результатом действия оператора instanceof
будет истина, если объект является объектом
данного класса или одного из его подклассов,
но не наоборот. Проверка на принадлежность
объекта к классу Object всегда даст истину,
поскольку любой класс является наследником класса Object. Результат применения
этого оператора по отношению к ссылке на
значение null — всегда ложь, потому что null
Рис. 2.5. Иерархия наследования
нельзя причислить к какому-либо классу.
В то же время литерал null можно передавать
в методы по ссылке на любой объектный тип
и использовать в качестве возвращаемого значения. Базовому типу значение
null присвоить нельзя так же, как использовать ссылку на базовый тип в операторе instanceof.
В Java 14 в операторе instanceof были объединены проверка на тип объекта
и его пре­образование к этому типу. Тогда метод doAction(Course course) можно переписать в виде:
43

JAVA FROM EPAM
void doAction(Course course) {
if (course instanceof BaseCourse base) {
base.method();
} else if (course instanceof OptionalCourse optional) {
optional.method();
} else {
// code
}
}

где method() — полиморфный метод класса Course, переопределенный в подклассах.

Классы-оболочки
Кроме базовых типов данных, в языке Java используются соответствующие
классы-оболочки (wrapper-классы) из пакета java.lang: Boolean, Character,
Integer, Byte, Short, Long, Float, Double. Объекты этих классов хранят те же
значения, что и соответствующие им базовые типы.
Объект любого из этих классов представляет собой экземпляр класса в динамической памяти, в котором хранится его неизменяемое значение. Значения
базовых типов хранятся в стеке и не являются объектами.
Классы, соответствующие числовым базовым типам, находятся в библиотеке java.lang, являются наследниками абстрактного класса Number и реализуют

Рис. 2.6. Иерархия классов-оболочек

44

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
интерфейс Comparable. Этот интерфейс определяет возможность сравнения объектов одного типа между собой с помощью метода int compareTo(T ob).
Объекты классов-оболочек по умолчанию получают значение null. Как
было сказано выше, базовые типы данных по умолчанию инициализируются нулями.
Создаются экземпляры числовых, символьного и булевского, классов с помощью
статических методов valueOf(String s), parseType(String s) и decode(String s) с параметрами типа String или статического метода-фабрики valueOf(type v), где
type параметр базового типа.
Integer value = Integer.valueOf(42);

В следующем примере рассмотрено три стандартных способа создания объекта Integer на основе преобразования строки в число:
/* # 3 # преобразование строки в целое число # */
String arg = "42";
try {
int value1 = Integer.parseInt(arg);
Integer value2 = Integer.valueOf(arg);
Integer value3 = Integer.decode(arg);
} catch (NumberFormatException e) {
System.err.println("invalid number format: " + arg + " : " + e);
}

Использование конструктора для создания объектов не применяется.
Для устойчивой работы приложения все операции по преобразованию строки в типизированные значения желательно заключать в блок try-catch для перехвата и обработки возможного исключения.
Метод decode(String s) преобразует строки, заданные в виде восьмеричных
или шеснадцатеричных литералов в соответстующие им десятичные числа.
Integer.decode("0x22");
Integer.decode("042");

У некоторых из приведенных методов также есть перегруженные версии
при использовании разных систем счисления и представления чисел.
Методы valueOf(String s, int radix) и parseInt(String s, int radix) преобразуют
число в строке и в указанной системе счисления в десятичное число, например:
Integer.valueOf("42", 8);
Integer.valueOf("100010", 2);
Integer.valueOf("22", 16);

Результатом преобразования во всех случаях будет число 34.
Объект класса-оболочки может быть преобразован к базовому типу обычным присваиванием или методом typeValue(), где type один из базовых типов
данных.
45

JAVA FROM EPAM
Объект класса Boolean также следует создавать с помощью статических методов:
boolean bool1 = Boolean.parseBoolean("TrUe"); // true
Boolean bool2 = Boolean.valueOf("Hello"); // false
Boolean bool3 = Boolean.valueOf(true);

При этом если в качестве параметра выступает строка со словом true, записанном символами в любом регистре, то результатом будет всегда истинное
значение. Строка с любым другим набором символов всегда при преобразовании дает значение false.
Булевский объект также задается константами:
Boolean bool4 = Boolean.TRUE;
Boolean bool5 = Boolean.FALSE;

В классе Boolean объявлены три статических метода Boolean.
logicalAnd(boolean value1, boolean value2), Boolean.logicalOr(boolean value1,
boolean value2), Boolean.logicalXor(boolean value1, boolean value2), заменяющие базовые логические операции. Теперь вместо выражения boolean_expr1
&& boolean_expr2 возможна альтернатива Boolean.logicalAnd(boolean_expr1,
boolean_expr2).
Класс Character не является подклассом Number, и этому классу нет необходимости поддерживать интерфейс классов, предназначенных для хранения
результатов арифметических операций. Вместо этого класс Character имеет
большое число специфических методов для работы с символами и их представлением. Ниже приведены способы преобразования символа в число, если
символ является числовым или представлен кодом числа.
сhar ch0 = '7';
Character ch1 = Character.valueOf(ch0);
int value0 = Character.digit(ch0, 10); // Character into int, value0 will be 7
int value1 = Character.digit(55, 10); // CodePoint into int, value1 will be 7
int value2 = Character.getNumericValue(ch1);

Обратное преобразование из типизированного значения (в частности int)
в строку можно выполнить следующими способами:
int value = 42;
String good1 = Integer.toString(value); // good
String good2 = String.valueOf(value); // good
String bad = "" + value; // very bad

В Java определен процесс автоматической инкапсуляции данных базовых
типов в соответствующие объекты оболочки и обратно (автоупаковка/автораспаковка). При этом нет необходимости в явном создании соответствующего
объекта с использованием методов:
Integer iob = 420; // autoboxing
Integer i = 42; // integer cache from -128 to 127

46

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
При инициализации объекта класса-оболочки значением базового типа
преобразование типов в некоторых ситуациях необходимо указывать явно,
то есть код
Float f = 42; // it will be correct (float)42 or 42F instead of 42

вызывает ошибку компиляции.
C другой стороны, однако справедливо:
Float f1 = Float.valueOf(42);
Float f0 = Float.valueOf("42");// or valueOf("42f") or valueOf("42.0")

Автораспаковка — процесс извлечения из объекта-оболочки значения базового типа. Вызовы методов intValue(), doubleValue() и им подобных для преобразования объектов в значения базовых типов становятся излишними.
Допускается участие объектов оболочек в арифметических операциях, однако не следует этим злоупотреблять, поскольку упаковка/распаковка является
ресурсоемким процессом:
// autoboxing & unboxing:
Integer i = 420; // autoboxing
++i; // unboxing + operation + autoboxing
int j = i; // unboxing

Однако следующий код генерирует исключительную ситуацию
NullPointerException при попытке присвоить базовому типу значение null
объекта класса Integer. Литерал null — не объект, поэтому и не может быть
преобразован к литералу «0»:
Integer j = null; // the object is not instantiated ! This is not zero!
int i = j; // generating an exception at runtime

Сравнение объектов классов оболочек между собой происходит по ссылкам, как и для других объектных типов. Для сравнения значений объектов следует использовать метод equals().
int i = 128;
Integer a = i; // autoboxing
Integer b = i;
System.out.println("a==i " + (a == i)); // true – unboxing and comparing values
System.out.println("b==i " + (b == i)); // true
System.out.println("a==b " + (a == b)); // false - references to different objects
System.out.println("equals ->" + a.equals(i) + b.equals(i) + a.equals(b)); // true true true

Метод equals() сравнивает не значения объектных ссылок, а значения объек­
тов, на которые установлены эти ссылки. Поэтому вызов a.equals(b) возвра­
щает значение true.
Если в приведенном выше примере значению int i будет присвоено значение в диапазоне от -128 до 127, то операции сравнения объектных и базовых
ссылок будут давать результат true во всех случаях. Литералы указанного
47

JAVA FROM EPAM
диапазона кэшируются по умолчанию, для чего используется класс IntegerCache.
Дело в том, что при создании кода очень часто применяются литералы небольших значений типа -1, 0, 1, 8, 16, и чтобы не увеличивать затраты памяти на
создание новых объектов, применяется кэширование. Диапазон кэширования
может быть расширен параметрами виртуальной машины.
Значение базового типа может быть передано в метод equals(). Однако
ссылка на базовый тип не может вызывать методы:
i.equals(a); // compile error

При автоупаковке значения базового типа возможны ситуации с появлением
некорректных значений и непроверяемых ошибок.
Существуют два класса для работы с высокоточной арифметикой —
java.math.BigInteger и java.math.BigDecimal, которые поддерживают целые
числа и числа с фиксированной точкой произвольной длины. При вычислениях
для типов double и float возникает проблема точности, так как для этих типов
существует понятие «машинный ноль», это означает, что при вычислениях после 15-го разряда для double и после 6-го для float появляются «лишние» значащие цифры. Для устранения таких побочных эффектов следует использовать
BigDecimal:
float res = 0.4f - 0.3f;
BigDecimal big1 = new BigDecimal("0.4");
BigDecimal big2 = new BigDecimal("0.3");
BigDecimal bigRes = big1.subtract(big2, MathContext.DECIMAL32);

Результатом выполнения будет:
0.099999994
0.1
Константы класса MathContext определяют число знаков после запятой
при округленных вычислениях.
Для операций сложения, умножения и деления применяются методы add(),
multiply() и divide(), которые представлены в нескольких перегруженных версиях.
Проблема «машинного нуля» проявляется и при операциях сравнения,
а именно результатом выполнения следующего оператора
boolean res1 = 1.00000001f == 1.00000002f;

так же, как и
boolean res2 = 1 == 1f / 3 * 3;

будет true.

48

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ

Оператор условного перехода
Оператор условного перехода if имеет следующий синтаксис:
if (boolean_value) {
// line 1: main success scenario
} else {
// line 2: scenario variation
}

Если выражение boolean_value принимает значение true, то выполняется
группа операторов line 1, иначе — группа операторов line 2.
if (boolean_value) {
System.out.println("value is Valid");
} else {
System.out.println("value is Broken");
}

В отличие от приведенного варианта
if (boolean_value) {
System.out.println("value is Valid");
}
System.out.println("More Opportunities"); // this code will always be executed

оператор else может отсутствовать, в этом случае операторы, расположенные
после окончания оператора if, выполняются вне зависимости от значения булевского выражения оператора if. Фигурные скобкиобозначают составной
оператор.
Если после оператора if следует только одна инструкция для выполнения, то
фигурные скобки для выделения блока кода можно опустить, но делать это
нежелательно.
if (boolean_value)
ifCode(); // depends on the condition
restOfCode(); // this code will always be executed

При корректировке кода может понадобиться добавить строку кода, например, вызов метода checkRole() перед вызовом ifCode(). Поведение программы
резко изменится и не будет соответствовать идее, что метод ifCode() вызывается только при истинности условия boolean_value.
if (boolean_value)
checkRole(); // depends on the condition
ifCode(); // this code will always be executed !!!
restOfCode();

Теперь указанный метод будет вызываться независимо от выполнения условного оператора. Во избежание таких нелепых ошибок следует использовать
49

JAVA FROM EPAM
фигурные скобки даже при одной исполняемой строке кода. К тому же код будет выглядеть более понятным, что немаловажно.
if (boolean_value) {
checkRole();
ifCode();
}
restOfCode();

Этих же правил следует придерживаться и для оформления циклов.
Если в операторе if-else выполняются действия в одну строку кода, то более
выгодно заменить его на тернарный оператор. Тогда вместо:
int value = 1;
if (value 1;
case "moderator" -> 2;
case "admin" -> 3;
default -> {
System.out.println("non-authentic role = " + role);
yield -1;
}
};
}

При использовании в блоке кода внутри switch новое ключевое слово yield
возвращает значение из блока кода.
Возвращаемое значение может присваиваться результату прямым присваиванием:
/* # 6 # прямое присваивание оператора switch */
public int defineLevel(String role) {
var result = switch (role) {
case "guest" -> 0;
case "client" -> 1;
case "moderator" -> 2;
case "admin" -> 3;
default -> {
System.out.println("non-authentic role = " + role);
yield -1;
}
};
return result;
}

Вместо нескольких инструкций case появилась возможность перечислять
их через запятую:
53

JAVA FROM EPAM
/* # 7 # список значений в операторе switch */
int value = 1;
switch (value) {
case 1, 2, 3, 4 -> System.out.println("1, 2, 3 or 4");
case 777 -> System.out.println("Range: " + value);
case 0 -> System.out.println("0");
default -> System.out.println("Default");
}

Возвращаемое значение оператора switch можно также сразу передавать
как параметр метода:
/* # 8 # оператор switch как метод */
System.out.println(
switch (value) {
case 2, 3, 4 -> "2,3 or 4";
case 777 -> "Range: " + value;
case 0 -> "0";
default -> "Default";
});

Циклы
Цикл представляет собой последовательность операций, которые могут выполняться более одного раза и завершаться при выполнении некоторого граничного условия.
В Java существует четыре вида циклов. Основные три:
Цикл с предусловием — while (boolean_value) { /* code */ }
Цикл с постусловием — do { /* code */ } while (boolean_value);
Цикл с параметром — for (expression_1; boolean_value; expression_3) { /* code */ }
Циклы выполняются до тех пор, пока булевское выражение boolean_value
равно true.
Тело цикла do\while будет выполнено минимум один раз:
int value = 42;
do {
value++;
} while (value < 40);

Условие перехода на следующую итерацию проверяется в конце цикла.
Если заменить его циклом while:
int value = 42;
while(value < 40) {
value++;
}

то тело такого цикла не будет выполнено ни разу.
54

ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
При разработке этих видов циклов следует аккуратно проверять код, чтобы
цикл не «сорвался» в бесконечный цикл.
В цикле с параметром, по традиции, expression_1 — начальное выражение,
boolean_value — условие выполнения цикла, expression_3 — выражение, выполняемое в конце итерации цикла (как правило, это изменение начального значения).
for (int i = 0; i id == o.getOrderId())
.collect(Collectors.toList());
if(result.size() != 0) {
order = result.get(0);
}
return order;
}
}

который возвратит null, если требуемый объект не найден.
Замена возвращаемого значения на Optional делает код метода проще:
/* # 9 # метод, который возвращает объект в оболочке */
public Optional findById(List orders, long id) {
List result = orders.stream()

.filter(o -> id == o.getOrderId())
.collect(Collectors.toList());
Optional optionalOrder =
result.size() != 0 ? Optional.of(result.get(0)) : Optional.empty();
return optionalOrder;
}

Возможности Stream будут подробно рассмотрены в другой главе.
Статические методы класса создают новый объект:
Optional empty() — создает «пустой» объект;
Optional of(T value) — создает объект с ненулевым содержимым;
79

JAVA FROM EPAM
Optional ofNullable(T value) — создает объект, содержимое которого может быть нулевым.
Код этого метода можно сделать еще короче:
public Optional findById(List orders, long id) {
Optional optionalOrder = orders.stream()
.filter(o -> id == o.getOrderId())
.findAny();
return optionalOrder;
}

Каким же образом тогда работать с возвращаемым значение такого метода?
Возможностей довольно много.
/* # 10 # обработка Optional # OptionalMain.java*/
package by.epam.learn.optional;
import by.epam.learn.entity.Order;
import java.util.*;
public class OptionalMain {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Order(71L, 100D));
list.add(new Order(18L, 132D));
list.add(new Order(24L, 210D));
list.add(new Order(35L, 693D));
OrderAction action = new OrderAction();
Optional optionalOrder = action.findById(list, 24); // replaced by 23
if(optionalOrder.isPresent()) {
System.out.println(optionalOrder.get());
}
Set set = new HashSet();
optionalOrder.ifPresent(set::add);// or o->set.add(o)
System.out.println(set);
}
}

Некоторые методы класса:
T get() — извлекает объект из оболочки, но если оболочка пуста, то генерирует NullPointerException, поэтому лучше сначала удостовериться в наличии
объекта в оболочке;
boolean isPresent(), boolean isEmpty() — проверяют пуста оболочка или
нет;
void ifPresent(Consumer task) {
return roundMark() == task.roundMark();
}

Тогда при вызове task1.equalsToMark(task) ошибки компиляции не возникнет, и метод выполнит свою расширенную функциональность по сравнению объектов класса Task, инициализированных объектами различных допустимых типов. В противном случае было бы необходимо создавать новые
перегруженные методы.
Для generic-типов существует целый ряд ограничений. Например, невозможно выполнить явный вызов конструктора generic-типа:
class FailedClass {
private T t = new T();
}

так как компилятор не знает, какой конструктор может быть вызван и какой
объем памяти должен быть выделен при создании объекта.
По аналогичным причинам generic-поля не могут быть статическими, статические методы не могут иметь generic-параметры, например:
/* # 20 # неправильное объявление и использование полей параметризованного
класса # FailedTwo.java */
class FailedTwo {
static T t;
}
class FailedThree {
K k;

92

КЛАССЫ И МЕТОДЫ
static void takeKey(K k) {
// ...
}
}

Параметризованные методы
Параметризованный (generic) метод определяет базовый набор операций,
которые будут применяться к разным типам данных, получаемых методом в качестве параметра, и может быть записан, например, в виде:
ReturnТype method(T arg) { }
static T[] method(int count, T arg) { }

Описание типа должно находиться перед возвращаемым типом. Запись первого вида означает, что в метод можно передавать объекты, типы которых являются подклассами класса, указанного после extends. Второй способ объявления метода никаких ограничений на передаваемый тип не ставит.
Generic-методы могут находиться как в параметризованных классах, так и в
обычных. Параметр метода может не иметь никакого отношения к параметру
своего generic-класса. Причем такому методу разрешено быть статическим,
так как параметризацию обеспечивает сам метод, а не класс, в котором он объявлен. Метасимволы применимы и к generic-методам.
/* # 21 # параметризованные конструкторы и методы # SimpleAction.java */
package by.epam.learn.method;
public class SimpleAction {
public SimpleAction (T course) {
// code
}
public SimpleAction() {
// code
}
public float calculateMark(T course) {
// code
}
public boolean printReport(T course) {
// code
}
public void check() {
// code
}
}

Создание экземпляра с параметром и вызов параметризованного метода
с параметром выглядят следующим образом:
SimpleAction action = new SimpleAction(new Course());
action.printReport(new Course(42));

93

JAVA FROM EPAM
Создание экземпляра с использованием параметризованного конструктора
без параметров требует указания типа параметра перед именем конструктора:
action = new SimpleAction();

Аналогично для метода без параметров:
action.check();

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

а в случае необходимости передачи параметров других типов:
(Тype1 t1, Тype2 t2, ТypeN tn, Тype... args)

/* # 22 # определение количества параметров метода # DemoVarargMain.java */
package by.epam.learn.varargs;
public class DemoVarargMain {
public static int defineArgCount(Integer... args) {
if (args.length != 0) {
for (int element : args) {
System.out.printf("arg:%d ", element);
}
} else {
System.out.print("No arg ");
}

94

КЛАССЫ И МЕТОДЫ
return args.length;
}
public static void main(String ... args) {
System.out.println("N=" + defineArgCount(7, 42, 555));
Integer[] arr = { 1, 2, 3, 4, 5, 42, 7 };
System.out.println("N=" + defineArgCount(arr));
System.out.println(defineArgCount());
// defineArgCount(arr, arr); // compile error
// defineArgCount(71, arr); // compile error
}
}

В результате выполнения этой программы будет выведено:
arg:7 arg:42 arg:555 N=3
arg:1 arg:2 arg:3 arg:4 arg:5 arg:42 arg:7 N=7
No arg 0
В примере приведен простейший метод с переменным числом параметров.
Метод defineArgCount() выводит все переданные ему аргументы и возвращает их количество. При передаче параметров в метод из них автоматически создается массив. Второй вызов метода в примере позволяет передать в метод
массив. Метод может быть вызван и без аргументов.
Чтобы передать несколько массивов в метод по ссылке, следует использовать следующее объявление:
method(Тype[]... args) { }

Методы с переменным числом аргументов могут быть перегружены:
void method(Integer...args) { }
void method(int x1, int x2) { }

Недопустимый способ:
void method(Тype... args) { }
void method(Тype[] args) { }

В следующем примере приведены три перегруженных метода и несколько
вариантов их вызова. Отличительной чертой является возможность метода
с аргументом Object… args принимать не только объекты, но и массивы:
/* # 23 # передача массивов # OverloadMain.java */
package by.epam.learn.overload;
public class OverloadMain {
public static void printArgCount(Object... args) { // 1
System.out.println("Object args: " + args.length);
}
public static void printArgCount(Integer[]...args) { // 2
System.out.println("Integer[ ] args: " + args.length);
}

95

JAVA FROM EPAM
public static void printArgCount(int... args) { // 3
System.out.print("int args: " + args.length);
}
public static void main(String... args) {
Integer[] i = { 1, 2, 3, 42, 5 };
printArgCount(7, "No", true, null);
printArgCount(i, i, i);
printArgCount(i, 4, 42);
printArgCount(i); // call method 1
printArgCount(42, 7);
// printArgCount(); //compile error: overload uncertainty!
}
}

В результате будет выведено:
Object args: 4
Integer[] args: 3
Object args: 3
Object args: 5
int args: 2
При передаче в метод printArgCount() единичного массива i компилятор отдает
предпочтение методу с параметром Object… args. Так как имя массива является
объектной ссылкой, указанный параметр будет ближайшим. Метод с параметром
Integer[]…args не вызывается, потому что ближайшей объектной ссылкой для него
будет Object[]…args. Метод с параметром Integer[]…args будет вызван для единичного массива только в случае отсутствия метода с параметром Object…args.
При вызове метода без параметров возникает неопределенность из-за невозможности однозначного выбора.
Не существует также ограничений и на переопределение подобных методов.
Единственным ограничением является то, что параметр вида Тype…args
должен быть единственным и последним в объявлении списка параметров метода, то есть записи вида: (Тype1… args, Тype2 obj) и (Тype1… args, Тype2…
args) приведут к ошибке компиляции.

Перечисления
При разработке приложений достаточно часто встречаются сущности, число значений которых ограничено естественным образом: страны мира, месяцы года, дни недели, марки транспортных средств, типы пользователей и многие другие. В этих случаях до Java 5 прибегали к следующему решению:
/* # 24 # объявление набора констант # RoleOldStyle.java */
package by.epam.learn.type;
public class RoleOldStyle {

96

КЛАССЫ И МЕТОДЫ
public final static int GUEST = 0;
public final static int CLIENT = 1;
public final static int MODERATOR = 2;
public final static int ADMIN = 3;
private RoleOldStyle(){}
}

Обрабатывались такие значения следующим образом:
/* # 25 # использование набора констант # RoleOldStyleMain.java */
package by.epam.learn.type;
public class RoleOldStyleMain {
public static void main(String[] args) {
int role = 1;
switch (role){
case 0:
System.out.println("guest can only watch");
case 1:
System.out.println("client can place orders");
case 2:
System.out.println("moderator can manage his section");
case 3:
System.out.println("admin controls everything");
}
}
}

Не очень очевидное решение. Но Java 5 был введен класс-перечисление,
и тогда в данной задаче код становится более читаемым:
/* # 26 # объявление набора констант-элементов перечисления # Role.java
# RoleMain.java */
package by.epam.learn.type;
public enum Role {
GUEST, CLIENT, MODERATOR, ADMIN
}
package by.epam.learn.type;
public class RoleMain {
public static void main(String[] args) {
Role role = Role.valueOf("Admin".toUpperCase());
switch (role){
case GUEST:
System.out.println(role + " can only watch");
case CLIENT:
System.out.println(role + " can place orders");
case MODERATOR:
System.out.println(role + " can manage his section");

97

JAVA FROM EPAM
case ADMIN:
System.out.println(role + " controls everything");
}
}
}

Типобезопасные перечисления (typesafe enums) в Java представляют собой
классы и являются подклассами абстрактного класса java.lang.Enum. Вместо
слова class при описании перечисления используется слово enum. При этом объекты перечисления инициализируются прямым объявлением без помощи оператора new. При инициализации хотя бы одного перечисления происходит инициализация всех без исключения оставшихся элементов данного перечисления.
В операторах case используются константы без уточнения типа перечисления, так как его тип определен в switch.
Перечисление как подкласс класса Enum может содержать поля, конструкторы и методы, реализовывать интерфейсы. Каждый элемент enum может использовать методы:
static enumType[] values() — возвращает массив, содержащий все элементы перечисления в порядке их объявления;
static T valueOf(Class enumType, String arg) —
создает элемент перечисления, соответствующий заданному типу и значению
передаваемой строки;
static enumType valueOf(String arg) — создает элемент перечисления, соответствующий значению передаваемой строки;
int ordinal() — возвращает позицию элемента перечисления, начиная
с нуля, следствием чего является возможность сравнения элементов перечисления между собой на больше\меньше соответсвующими операторами;
String name() — возвращает имя элемента, так же как и toString();
int compareTo(T t) — сравнивает элементы на больше, меньше либо равно.
Пример создания объекта:
Role role = Role.valueOf("client".toUpperCase());

Класс перечисления может объявлять собственные методы, и, следовательно, экземпляры перечисления могут к этим методам обращаться. Перечисления представляют собой классы, а классам положено явно или неявно объявлять конструкторы.
/* # 27 # перечисление с полями и методами # Role.java */
package by.epam.learn.type;
public enum Role {
GUEST("guest"), CLIENT("client"), MODERATOR("moderator"), ADMIN("administrator");
private String typeName;
Role(String typeName) {
this.typeName = typeName;
}
}

98

КЛАССЫ И МЕТОДЫ
/* # 28 # объявление перечисления с методом # Shape.java */
package by.epam.learn.type;
public enum Shape {
RECTANGLE, TRIANGLE, CIRCLE;
public double defineSquare(double ... x) {
// here may be checking the parameters for correctness
double area;
switch (this) {
case RECTANGLE:
area = x[0] * x[1];
break;
case TRIANGLE:
area = x[0] * x[1] / 2;
break;
case CIRCLE:
area = Math.pow(x[0], 2) * Math.PI;
break;
default:
throw new EnumConstantNotPresentException(getDeclaringClass(), name());
}
return area;
}
}

/* # 29 # применение перечисления # ShapeMain.java */
package by.epam.learn.type;
public class ShapeMain {
public static void main(String args[ ]) {
double x = 2, y = 3;
Shape sh;
sh = Shape.RECTANGLE;
System.out.printf("%9s = %5.2f%n", sh, sh.defineSquare (x, y));
sh = Shape.valueOf(Shape.class, "TRIANGLE");
System.out.printf("%9s = %5.2f%n", sh, sh.defineSquare (x, y));
sh = Shape.CIRCLE;
System.out.printf("%9s = %5.2f%n", sh, sh.defineSquare (x));
}
}

В результате будет выведено:
RECTANGLE = 6,00
TRIANGLE = 3,00
CIRCLE = 12,57
Каждый из элементов перечисления в данном случае содержит арифметическую операцию, ассоциированную с методом defineSquare(). Без throw данный
код не будет компилироваться, так как компилятор не исключает появления
99

JAVA FROM EPAM
неизвестного элемента. Данная инструкция позволяет указать на возможную
ошибку при появлении необъявленной фигуры. Поэтому и при введении нового элемента желательно добавлять соответствующий ему case.
Перечисление является классом, поэтому в его теле можно объявлять, кроме
методов, также поля и конструкторы. Все конструкторы вызываются автоматически при инициализации любого из элементов. Конструктор не может быть
объявлен со спецификаторами public и protected, так как не вызывается явно
и перечисление не может быть суперклассом. Поля перечисления используются
для сохранения дополнительной информации, связанной с его элементом.
Метод toString() реализован в классе Enum для вывода элемента в виде
строки. Если переопределить метод toString() в конкретной реализации перечисления, то можно выводить не только значение элемента, но и значения его
полей, то есть предоставить полную информацию об объекте, как и определяется контрактом метода.
Метод ordinal() определяет порядковый номер элемента перечисления,
обратная задача не совсем тривиальна, но решаема. Определение элемента перечисления по позиции с применением метода values() преобразования элементов перечисления в массив:
public static Optional valueOf(int roleId) {
return Arrays.stream(values())
.filter(roleType -> roleType.ordinal() == roleId)
.findFirst();
}

При разработке класса перечисления возможны различные ошибки. В частности:
enum WrongPlanType {
AIRBUS_A320, AIRBUS_A380, AIRBUS_A330, BOEING_737_800, BOEING_777
}
class WrongPlane {
private WrongPlanType type;
}

Для описания типов и марок самолетов создано перечесление, но такой класс
в итоге может оказаться очень большим из-за количества моделей конкретного
производителя. Решить проблему легко разделением ответственности за хранение типа производителя в перечислении, а марки модели в обычной строке:
enum PlanType {
AIRBUS, BOEING
}
class Plane {
private PlanType type;
private String model;
}

100

КЛАССЫ И МЕТОДЫ






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

Immutable и record
Если в системе необходим объект, внутреннее состояние которого нельзя
изменить, то процедура реализации такой задачи представляется в виде:
/* # 30 # класс для создания неизменяемых объектов # ImmutableType.java */
package by.bsu.learn.immutable;
public final class ImmutableType {
private String name;
private int id;
public ImmutableType(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
// equals, hashCode, toString
}

Такой объект от создания и до уничтожения не может быть изменен, что
уменьшает затраты на безопасность при использовании в конкурирующих операциях. Классов с неизменяемым внутренним состоянием в стандартной реализации Java достаточно много. Следует вспомнить класс String.
Класс с поведением Immutable, тем не менее, может содержать методы для
создания объекта того же типа с внутренним состоянием, отличающимся от
исходного, что оправданно с точки зрения ресурсов, только если такие изменения происходят не слишком часто.
Если полем такого immutable класса необходимо объявить изменяемый тип,
то следует предусмотреть, чтобы соответствующий ему get-тер возвращал копию или неизменяемый объект, а не ссылку на объект. Например, если поле
такого класса объявлено как
List strings;

то метод может выглядеть так:
101

JAVA FROM EPAM
public List getStrings() {
String[] arr = {};
return List.of(strings.toArray(arr));
}

Ключевое слово record представляет еще один способ создать класс с неизменяемыми экземплярами. Этот класс является подклассом класса java.lang.Record
и, естественно, не может наследовать другой класс, а также сам не может выступать в роли суперкласса. Реализация интерфейсов разрешается. Также класс
record может объявлять собственные методы.
/* # 31 # класс-record для создания неизменяемых объектов # ImmutableRec.java */
package by.epam.learn.immutable;
public record ImmutableRec(String name, int id) {
void method() {}
}

Такая запись гарантирует неизменяемость значений полей записи и избавляет
от необходимости создавать конструктор, методы equals(Object o), hashCode()
и toString(), которые для record генерируются автоматически. Вместо геттеров
генерируются методы с именем поля. В данном случае это name() и id().
/* # 32 # методы класса-record # RecordMain.java */
package by.epam.learn.immutable;
public class RecordMain {
public static void main(String[] args) {
ImmutableRec object = new ImmutableRec("Jan", 777);
System.out.println(object.id());
System.out.println(object.name());
System.out.println(object.toString());
ImmutableRec object2 = new ImmutableRec("Jan", 777);
System.out.println(object.equals(object2));
}
}

В результате будет выведено:
777
Jan
ImmutableRec[name=Jan, id=777]
true

Декомпозиция
Корпоративные информационные системы предоставляют пользователю
огромное количество сервисов и манипулируют очень большим количеством
102

КЛАССЫ И МЕТОДЫ
самой разнообразной информации. В разработке таких сложных и многообразных систем участвуют зачастую сотни программистов-разработчиков. Такие
системы состоят из большого количества подсистем и программных модулей,
которые взаимодействуют между собой через ограниченное число интерфейсов (методов). Главное же заключается в том, что система, состоящая из сотен
тысяч (миллионов) строк кода, не может создаваться, существовать, развиваться и поддерживаться, если при ее разработке не использовались принципы объектно-ориентированного программирования, в частности, декомпозиция.
Под декомпозицией следует понимать определение физических и логических сущностей, а также принципов их взаимодействия на основе анализа
предметной области создаваемой системы.
Все системы состоят из классов. Все классы системы взаимодействуют
с теми или иными классами этой же системы.
Объяснение принципов декомпозиции можно рассмотреть на простейшем
примере. Пусть требуется решить следующую задачу: создать систему, позволяющую умножать целочисленные матрицы друг на друга.
Начинающий программист, знающий о том, что существуют классы, конструкторы и методы, может предложить решение поставленной проблемы
в следующем виде:
/* # 33 # произведение двух матриц # Matrix.java */
public class Matrix {
private int[][] a;
private int n;
private int m;
public Matrix(int nn, int mm) {
n = nn;
m = mm;
// creation and filling with random values
a = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = (int)(Math.random() * 10);
}
}
show();
}
public Matrix(int nn, int mm, int k) {
n = nn;
m = mm;
a = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = k;
}
}

103

JAVA FROM EPAM
if(k != 0) {
show();
}
}
public void show() {
System.out.println("matrix : " + a.length + " by " + a[0].length);
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
System.out.print(a[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
int n = 2, m = 3, l = 4;
Matrix p = new Matrix(n, m);
Matrix q = new Matrix(m, l);
Matrix r = new Matrix(n, l, 0);
for (int i = 0; i < p.a.length; i++) {
for (int j = 0; j < q.a[0].length; j++) {
for (int k = 0; k < p.a[0].length; k++) {
r.a[i][j] += p.a[i][k] * q.a[k][j];
}
}
}
System.out.println("matrix multiplication result: ");
r.show();
}
}

Программа полностью работоспособна, но следует взглянуть на нее внимательнее:
• создан только один класс, маловато, но и задача невелика;
• класс обладает лишними полями, значения которых зависят от значений
других полей;
• в классе объявлены два конструктора, оба выделяют память под матрицу
и заполняют ее элементами, переданными или сгенерированными. Оба конструктора решают похожие задачи и не проверяют на корректность входные
значения, т.к. решают слишком обширные задачи;
• определен метод show() для вывода матрицы на консоль, что ограничивает
способы общения класса с внешними для него классами;
• задача умножения решается в методе main(), и класс является одноразовым,
т.е. для умножения двух других матриц придется код умножения копировать
в другое место;
• реализован только основной положительный сценарий, например, не выполняется проверка размерности при умножении, и, как следствие, отсутствует реакция приложения на некорректные данные.
104

КЛАССЫ И МЕТОДЫ
Ниже приведена попытка переработки (рефакторинга) созданного приложения таким образом, чтобы существовала возможность поддержки и расширения возможности системы при возникновении сопутствующих задач.
/* # 34 # класс хранения матрицы # Matrix.java */
package by.epam.learn.entity;
import by.epam.learn.exception.MatrixException;
public class Matrix {
private int[][] a;
public Matrix(int[][] a) {
this.a = a;
}
public Matrix(int n, int m) throws MatrixException {
if (n < 1 || m < 1) {// check input
throw new MatrixException();
}
a = new int[n][m];
}
public int getVerticalSize() {
return a.length;
}
public int getHorizontalSize() {
return a[0].length;
}
public int getElement(int i, int j) throws MatrixException {
if (checkRange(i, j)) { // check i & j
return a[i][j];
} else {
throw new MatrixException();
}
}
public void setElement(int i, int j, int value) throws MatrixException {
if (checkRange(i, j)) {
a[i][j] = value;
} else {
throw new MatrixException();
}
}
@Override
public String toString() {
final String BLANK = " ";
StringBuilder s = new StringBuilder("\nMatrix : " + a.length + "x"
+ a[0].length + "\n");
for (int [ ] row : a) {
for (int value : row) {
s.append(value).append(BLANK);
}
s.append("\n");
}

105

JAVA FROM EPAM
return s.toString();
}
private boolean checkRange(int i, int j) {// check matrix range
if (i < 0 || i > a.length - 1 || j < 0 || j > a[0].length - 1) {
return false;
} else {
return true;
}
}
}

В классе Matrix объявлен private-метод checkRange(int i, int j) для проверки параметров на предельные допустимые значения во избежание невынуж­
денных ошибок. Однако условный оператор if в этом методе выглядит запутанным, во-первых, нарушая правило положительного сценария, то есть разумно
ожидать, что параметры метода с индексами элемента матрицы будут корректными, и, исходя из этого, строить условие, а в представленном варианте все
наоборот; во-вторых, при значении true в условии оператора метод возвращает
значение false, что выглядит противоречивым.
Метод следует переписать в виде:
private boolean checkRange(int i, int j) {
if (i >= 0 && i < a.length && j >= 0 && j < a[0].length) {
return true;
} else {
return false;
}
}

или, что еще лучше:
return (i >= 0 && i < a.length && j >= 0 && j < a[0].length);

заменив условие в операторе на противоположное, возвращая непротиворечащее результату значение.
/* # 35 # класс-создатель матрицы # MatrixCreator.java */
package by.epam.learn.creator;
import by.epam.learn.entity.Matrix;
import by.epam.learn.exception.MatrixException;
public class MatrixCreator {
public void fillRandomized(Matrix matrix, int minValue, int maxValue) {
int v = matrix.getVerticalSize();
int h = matrix.getHorizontalSize();
for(int i = 0; i < v; i++) {
for(int j = 0; j < h; j++) {
try {
int value = (int) ((Math.random() * (maxValue - minValue)) + minValue);
matrix.setElement(i, j, value);

106

КЛАССЫ И МЕТОДЫ
} catch (MatrixException e) {
// log: exception impossible
}
}
}
}
// public int[][] createArray(int n, int m, int minValue, int maxValue) {/*code*/
// public void createFromFile(Matrix matrix, File f) { /* code */ }
// public void createFromStream(Matrix matrix, Stream stream) { /* code */ }
}

Инициализация элементов матрицы различными способами вынесена в отдельный класс, методы которого могут в зависимости от условий извлекать
значения для инициализации элементов из различных источников.
/* # 36 # класс действия над матрицей # Multiplicator.java */
package by.epam.learn.action;
import by.epam.learn.entity.Matrix;
import by.epam.learn.exception.MatrixException;
public class Multiplicator {
public Matrix multiply(Matrix p, Matrix q) throws MatrixException {
int v = p.getVerticalSize();
int h = q.getHorizontalSize();
int controlSize = p.getHorizontalSize();
if (controlSize != q.getVerticalSize()) {
throw new MatrixException("incompatible matrices");
}
Matrix result = new Matrix(v, h);
try {
for (int i = 0; i < v; i++) {
for (int j = 0; j < h; j++) {
int value = 0;
for (int k = 0; k < controlSize; k++) {
value += p.getElement(i, k) * q.getElement(k, j);
}
result.setElement(i, j, value);
}
}
} catch (MatrixException e) {
// log: exception impossible
}
return result;
}
}

Все манипуляции взаимодействия объектов-матриц между собой и c другими объектами должны быть сгруппированы и вынесены в отдельные логические классы, что дает возможность другому разработчику быстро разобраться
в смысле класса и модернизировать его.
107

JAVA FROM EPAM
/* # 37 # исключительная ситуация при индексировании объекта-матрицы
# MatrixException.java */
package by.epam.learn.exception;
public class MatrixException extends Exception {
public MatrixException() {
}
public MatrixException(String message) {
super(message);
}
public MatrixException(String message, Throwable cause) {
super(message, cause);
}
public MatrixException(Throwable cause) {
super(cause);
}
}

Создание собственных исключений позволяет разработчику при их возникновении быстро локализовать и исправить ошибку.
/* # 38 # класс, запускающий приложение # MatrixMain.java */
package by.epam.learn.main;
import by.epam.learn.action.Multiplicator;
import by.epam.learn.creator.MatrixCreator;
import by.epam.learn.entity.Matrix;
import by.epam.learn.exception.MatrixException;
public class MatrixMain {
public static void main(String[] args) {
try {
MatrixCreator creator = new MatrixCreator();
Matrix p = new Matrix(2, 3);
creator.fillRandomized(p, 2, 8);
System.out.println("Matrix first is: " + p);
Matrix q = new Matrix(3, 4);
creator.fillRandomized(q, 2, 7);
System.out.println("Matrix second is: " + q);
Multiplicator multiplicator = new Multiplicator();
Matrix result = multiplicator.multiply(p, q);
System.out.println("Matrices product is " + result);
} catch (MatrixException e) {
System.err.println("Error of creating matrix " + e);
}
}
}

Одним из вариантов выполнения кода может быть следующий:
Matrix first is:
Matrix: 2x3
108

КЛАССЫ И МЕТОДЫ
347
472
Matrix second is:
Matrix: 3x4
3366
5655
4334
Matrices product is
Matrix: 2x4
57 54 59 66
55 60 65 67
Выше был приведен пример простейшей декомпозиции. При разработке
приложений любой сложности всегда следует производить анализ предметной
области, определять абстракции и разделять задачу на логические взаимодействующие части. Тем не менее границы, отделяющие хороший дизайн приложения от посредственного, достаточно размыты и зависят от общего уровня
компетентности команды разработчиков и правил, принятых в проекте.

Рекомендации при проектировании классов
При создании класса следует придерживаться некоторых правил. Принцип
единственной ответственности. Каждый класс должен иметь простое назначение. Решать в идеале единственную задачу. Классу следует давать такое имя,
чтобы его пользователю была понятна роль класса в пакете или приложении.
Если класс отвечает за хранение информации, то функциональность работы
с этой информацией должна быть базовой. Манипулированием информа­цией
через объект должны заниматься другие классы, которых может оказаться достаточно много.
Класс должен быть разработан так, чтобы внесение в него изменений было
относительно простой задачей.
Код конструктора должен заниматься только инициализацией объекта.
Следует избегать вызовов из конструктора других методов, за исключением
final, static, private. Иначе такой метод может быть переопределен в подкласс
и исказить процесс инициализации объекта.
Использовать инкапсуляцию нестатических и неконстантных полей.
Классы-сущности должны применять для доступа к полям классов, хранящих информацию, корректные методы типа get, set, is, а также желательно реализовать методы equals(), hashCode(), clone(), toString() и имплементировать
интерфейсы Comparable и Serializable.
Если разрабатываемый класс кажется сложным, следует разбить его на несколько простых.
109

JAVA FROM EPAM
По возможности избегать слишком длинных методов. От 25‒30-и строк —
длинный метод. Следует, если это возможно, разбить метод на несколько, или
даже создать для этой цели новый класс.
Если метод используется только другими методами этого класса, следует
объявлять его как private.
Определить и распределить по разным классам функциональности, которые
могут изменяться в процессе разработки, от тех, которые будут постоянными.
Хороший дизайн кода отличается высоким уровнем декомпозиции.
Если в разных участках класса или нескольких классов востребован один
и тот же фрагмент кода, следует выделить его в отдельный метод.
Избегать длинного списка аргументов. Приближаясь к числу семь, список
аргументов становится не воспринимаемым при чтении. Возможно, следует
объединить группы аргументов в новый тип данных.
Не использовать «волшебные числа», «волшебные строки». Логичнее вынести их в final static атрибуты или за пределы кода, например, в файл.

Вопросы к главе 3
1. Дать определение таким понятиям как «класс» и «объект». Привести примеры объявления класса и создания объекта класса. Какие спецификаторы
можно использовать при объявлении класса?
2. Как определить, какие поля и методы необходимо определить в классе?
Привести пример. Какие спецификаторы можно использовать с полями,
а какие с методами (и что они значат)?
3. Что такое конструктор? Как отличить конструктор от любого другого метода? Сколько конструкторов может быть в классе?
4. Что такое конструктор по умолчанию? Может ли в классе совсем не быть конструкторов? Объяснить, какую роль выполняет оператор this() в конструкторе?
5. Какова процедура инициализации полей класса и полей экземпляра класса?
Когда инициализируются поля класса, а когда — поля экземпляров класса?
Какие значения присваиваются полям по умолчанию? Где еще в классе полям могут быть присвоены начальные значения?
6. JavaBeans: основные требования к классам Bean-компонентов, соглашения
об именах.
7. В каких областях памяти хранятся значения и объекты, массивы?
8. Дать определение перегрузке методов. Чем удобна перегрузка методов?
Указать, какие методы могут перегружаться, и какими методами они могут
быть перегружены?
9. Можно ли перегрузить методы в базовом и производном классах? Можно ли
private метод базового класса перегрузить public методом производного?
10. Можно ли перегрузить конструкторы, и можно ли при перегрузке конструкторов менять атрибуты доступа у конструкторов?
110

КЛАССЫ И МЕТОДЫ
11. Свойства конструктора. Способы его вызова.
12. Объяснить, что такое раннее и позднее связывание? Перегрузка — это раннее или позднее связывание?
13. Объяснить правила, которым следует компилятор при разрешении перегрузки; в том числе, если аргументы методов перегружаются примитивными типами, между которыми возможно неявное приведение, или ссылочными типами, состоящими в иерархической связи.
14. Перегрузка. Можно ли менять модификатор доступа метода, если да, то
каким образом?
15. Перегрузка. Можно ли менять возвращаемый тип метода, если да, то как?
Можно ли менять тип передаваемых параметров?
16. Объяснить, что такое неявная ссылка this? В каких методах эта ссылка присутствует, а в каких — нет, и почему?
17. Что такое финальные поля, какие поля можно объявить со спецификатором
final? Где можно инициализировать финальные поля?
18. Что такое статические поля, статические финальные поля и статические методы. К чему имеют доступ статические методы? Можно ли перегрузить и переопределить статические методы? Наследуются ли статические методы?
19. Что такое логические и статические блоки инициализации? Сколько их может быть в классе, в каком порядке они могут быть размещены и в каком
порядке вызываются?
20. Что представляют собой методы с переменным числом параметров, как передаются параметры в такие методы, и что представляет собой такой параметр в методе? Как осуществляется выбор подходящего метода, при использовании перегрузки для методов с переменным числом параметров?
21. Каким образом передаются переменные в методы, по значению или по
ссылке?
22. Mutable и Immutable классы. Привести примеры. Как создать класс, который будет immutable?
23. Какие свойства у класса, объявленного как record?
24. Что такое static? Что будет, если значение атрибута изменить через объект
класса? Всегда ли static поле содержит одинаковые значения для всех его
объектов?
25. Generics. Что это такое и для чего применяются. Во что превращается во
время компиляции и выполнения? Использование wildcards.
26. Что такое enum? Какими свойствами обладает? Область применения.
27. Класс Optional. Как помогает бороться с проблемой возвращения методом
значения null?

111

JAVA FROM EPAM

Задания к главе 3
Вариант А
Создать классы, спецификации которых приведены ниже. Определить конструкторы и методы setТип(), getТип(), toString(). Определить дополнительно
методы в классе, создающем массив объектов. Задать критерий выбора данных
и вывести эти данные на консоль. В каждом классе, обладающем информацией,
должно быть объявлено несколько конструкторов.
1. Student: id, Фамилия, Имя, Отчество, Дата рождения, Адрес, Телефон,
Факультет, Курс, Группа.
Создать массив объектов. Вывести:
a) список студентов заданного факультета;
b) списки студентов для каждого факультета и курса;
c) список студентов, родившихся после заданного года;
d) список учебной группы.
2. Customer: id, Фамилия, Имя, Отчество, Адрес, Номер кредитной карточки,
Номер банковского счета.
Создать массив объектов. Вывести:
a) список покупателей в алфавитном порядке;
b) список покупателей, у которых номер кредитной карточки находится
в заданном интервале.
3. Patient: id, Фамилия, Имя, Отчество, Адрес, Телефон, Номер медицинской
карты, Диагноз.
Создать массив объектов. Вывести:
a) список пациентов, имеющих данный диагноз;
b) список пациентов, номер медицинской карты которых находится в заданном интервале.
4. Abiturient: id, Фамилия, Имя, Отчество, Адрес, Телефон, Оценки.
Создать массив объектов. Вывести:
a) список абитуриентов, имеющих неудовлетворительные оценки;
b) список абитуриентов, у которых сумма баллов выше заданной;
c) выбрать заданное число n абитуриентов, имеющих самую высокую
сумму баллов (вывести также полный список абитуриентов, имеющих
полупроходную сумму).
5. Book: id, Название, Автор(ы), Издательство, Год издания, Количество
страниц, Цена, Тип переплета.
Создать массив объектов. Вывести:
a) список книг заданного автора;
b) список книг, выпущенных заданным издательством;
c) список книг, выпущенных после заданного года.
6. House: id, Номер квартиры, Площадь, Этаж, Количество комнат, Улица, Тип
здания, Срок эксплуатации.
112

КЛАССЫ И МЕТОДЫ
Создать массив объектов. Вывести:
a) список квартир, имеющих заданное число комнат;
b) список квартир, имеющих заданное число комнат и расположенных на
этаже, который находится в заданном промежутке;
c) список квартир, имеющих площадь, превосходящую заданную.
7. Phone: id, Фамилия, Имя, Отчество, Адрес, Номер кредитной карточки,
Дебет, Кредит, Время городских и междугородных разговоров.
Создать массив объектов. Вывести:
a) сведения об абонентах, у которых время внутригородских разговоров
превышает заданное;
b) сведения об абонентах, которые пользовались междугородной связью;
c) сведения об абонентах в алфавитном порядке.
8. Car: id, Марка, Модель, Год выпуска, Цвет, Цена, Регистрационный номер.
Создать массив объектов. Вывести:
a) список автомобилей заданной марки;
b) список автомобилей заданной модели, которые эксплуатируются больше n лет;
c) список автомобилей заданного года выпуска, цена которых больше указанной.
9. Product: id, Наименование, UPC, Производитель, Цена, Срок хранения,
Количество.
Создать массив объектов. Вывести:
a) список товаров для заданного наименования;
b) список товаров для заданного наименования, цена которых не превосходит заданную;
c) список товаров, срок хранения которых больше заданного.
10. Train: Пункт назначения, Номер поезда, Время отправления, Число мест
(общих, купе, плацкарт, люкс).
Создать массив объектов. Вывести:
a) список поездов, следующих до заданного пункта назначения;
b) список поездов, следующих до заданного пункта назначения и отправляющихся после заданного часа;
c) список поездов, отправляющихся до заданного пункта назначения
и имеющих общие места.
11. Bus: Фамилия и инициалы водителя, Номер автобуса, Номер маршрута,
Марка, Год начала эксплуатации, Пробег.
Создать массив объектов. Вывести:
a) список автобусов для заданного номера маршрута;
b) список автобусов, которые эксплуатируются больше заданного срока;
c) список автобусов, пробег у которых больше заданного расстояния.
12. Airline: Пункт назначения, Номер рейса, Тип самолета, Время вылета, Дни
недели.
113

JAVA FROM EPAM
Создать массив объектов. Вывести:
a) список рейсов для заданного пункта назначения;
b) список рейсов для заданного дня недели;
c) список рейсов для заданного дня недели, время вылета для которых
больше заданного.

Вариант В
Реализовать методы сложения, вычитания, умножения и деления объектов (для
тех классов, объекты которых могут поддерживать арифметические действия).
1. Определить класс Дробь (Рациональная Дробь) в виде пары чисел m и n.
Объявить и инициализировать массив из k дробей, ввести/вывести значения для массива дробей. Создать массив/список/множество объектов и передать его в метод, который изменяет каждый элемент массива с четным
индексом путем добавления следующего за ним элемента.
2. Определить класс Комплекс. Создать массив/список/множество размерности n из комплексных координат. Передать его в метод, который выполнит
сложение/умножение его элементов.
3. Определить класс Квадратное уравнение. Реализовать методы для поиска
корней, экстремумов, а также интервалов убывания/возрастания. Создать
массив/список/множество объектов и определить наибольшие и наименьшие по значению корни.
4. Определить класс Полином степени n. Объявить массив/список/множество из m полиномов и определить сумму полиномов массива.
5. Определить класс Интервал с учетом включения/невключения концов.
Создать методы по определению пересечения и объединения интервалов,
причем интервалы, не имеющие общих точек, пересекаться/объединятся не
могут. Объявить массив/список/множество и n интервалов и определить
расстояние между самыми удаленными концами.
6. Определить класс Точка на плоскости (в пространстве) и во времени.
Задать движение точки в определенном направлении. Создать методы по
определению скорости и ускорения точки. Проверить для двух точек возможность пересечения траекторий. Определить расстояние между двумя
точками в заданный момент времени.
7. Определить класс Треугольник на плоскости. Определить площадь и периметр треугольника. Создать массив/список/множество объектов и подсчитать количество треугольников разного типа (равносторонний, равнобед­
ренный, прямоугольный, произвольный). Определить для каждой группы
наибольший и наименьший по площади (периметру) объект.
8. Определить класс Четырехугольник на плоскости. Определить площадь
и периметр четырехугольника. Создать массив/список/множество объектов
и подсчитать количество четырехугольников разного типа (квадрат,
114

КЛАССЫ И МЕТОДЫ
прямоугольник, ромб, произвольный). Определить для каждой группы наибольший и наименьший по площади (периметру) объект.
9. Определить класс Окружность на плоскости. Определить площадь и периметр. Создать массив/список/множество объектов и определить группы
окружностей, центры которых лежат на одной прямой. Определить наибольший и наименьший по площади (периметру) объект.
10. Определить класс Прямая на плоскости (пространстве). Определить точки
пересечения прямой с осями координат. Определить координаты пересечения двух прямых. Создать массив/список/множество объектов и определить группы параллельных прямых.

Вариант С
1. Определить класс Полином c коэффициентами типа Рациональная
Дробь. Объявить массив/список/множество из n полиномов иопределить
сумму полиномов массива.
2. Определить класс Прямая на плоскости (в пространстве), параметры которой задаются с помощью Рациональной Дроби. Определить точки пересечения прямой с осями координат. Определить координаты пересечения
двух прямых. Создать массив/список/множество объектов и определить
группы параллельных прямых.
3. Определить класс Полином с коэффициентами типа Комплексное число.
Объявить массив/список/множество из m полиномов и определить сумму
полиномов массива.
4. Определить класс Дробь в виде пары (m, n) с коэффициентами типа
Комплексное число. Объявить и инициализировать массив из k дробей, ввести/вывести значения для массива дробей. Создать массив/список/множество объектов и передать его в метод, который изменяет каждый элемент массива с четным индексом путем добавления следующего за ним элемента.
5. Определить класс Комплекс, действительная и мнимая часть которой
представлены в виде Рациональной Дроби. Создать массив/список/множество размерности n из комплексных координат. Передать его в метод, который выполнит сложение/умножение его элементов.
6. Определить класс Окружность на плоскости, координаты центра которой
задаются с помощью Рациональной Дроби. Определить площадь и периметр. Создать массив/список/множество объектов и определить группы
окружностей, центры которых лежат на одной прямой. Определить наибольший и наименьший по площади (периметру) объект.
7. Определить класс Точка в пространстве, координаты которой задаются
с помощью Рациональной Дроби. Создать методы по определению расстояния между точками и расстояния до начала координат. Проверить для
трех точек возможность нахождения на одной прямой.
115

JAVA FROM EPAM
8. Определить класс Точка в пространстве, координаты которой задаются
с помощью Комплексного числа. Создать методы по определению расстоя­
ния между точками и расстояния до начала координат.
9. Определить класс Треугольник на плоскости, вершины которого имеют
тип Точка. Определить площадь и периметр треугольника. Создать массив/список/множество объектов и подсчитать количество треугольников
разного типа (равносторонний, равнобедренный, прямоугольный, произвольный). Определить для каждой группы наибольший и наименьший по
площади (периметру) объект.
10. Определить класс Четырехугольник на плоскости, вершины которого
имеют тип Точка. Определить площадь и периметр четырехугольника.
Создать массив/список/множество объектов и подсчитать количество четырехугольников разного типа (квадрат, прямоугольник, ромб, произвольный). Определить для каждой группы наибольший и наименьший по площади (периметру) объект.
11. Определить класс Вектор. Реализовать методы инкремента, декремента,
индексирования. Определить массив из m объектов. Каждую из пар векторов передать в методы, возвращающие их скалярное произведение и длины. Вычислить и вывести углы между векторами.
12. Определить класс Вектор. Реализовать методы для вычисления модуля
вектора, скалярного произведения, сложения, вычитания, умножения на
константу. Объявить массив объектов. Написать метод, который для заданной пары векторов будет определять, являются ли они коллинеарными или
ортогональными.
13. Определить класс Вектор в R3. Реализовать методы для проверки векторов
на ортогональность, проверки пересечения неортогональных векторов,
сравнения векторов. Создать массив из m объектов. Определить компланарные векторы.
14. Определить класс Булева матрица (BoolMatrix). Реализовать методы для
логического сложения (дизъюнкции), умножения и инверсии матриц.
Реализовать методы для подсчета числа единиц в матрице и упорядочения
строк в лексикографическом порядке.
15. Построить класс Булев вектор (BoolVector). Реализовать методы для выполнения поразрядных конъюнкции, дизъюнкции и отрицания векторов,
а также подсчета числа единиц и нулей в векторе.
16. Определить класс Множество символов. Реализовать методы для определения принадлежности заданного элемента множеству; пересечения, объединения, разности двух множеств. Создать методы сложения, вычитания,
умножения (пересечения), индексирования, присваивания. Создать массив
объектов и передавать пары объектов в метод другого класса, который
строит множество, состоящее из элементов, входящих только в одно из заданных множеств.
116

КЛАССЫ И МЕТОДЫ
17. Определить класс Определенный интеграл с аналитической подынтегральной функцией. Создать методы для вычисления значения по формуле
левых прямоугольников, по формуле правых прямоугольников, по формуле
средних прямоугольников, по формуле трапеций, по формуле Симпсона
(параболических трапеций).
18. Определить класс Массив. Создать методы сортировки: обменная сортировка (метод пузырька); обменная сортировка «Шейкер-сортировка», сортировка посредством выбора (метод простого выбора), сортировка вставками: метод хеширования (сортировка с вычислением адреса), сортировка
вставками (метод простых вставок), сортировка бинарного слияния, сортировка Шелла (сортировка с убывающим шагом).

Тестовые задания к главе 3
Вопрос 3.1.
Дано объявление класса: class A{}. Выбрать корректное объявление конструктора (выбрать один).
a) A() {this.super();}
b) A() {Object.super();}
c) A() {A.super();}
d) A() {}

Вопрос 3.2.
Дан код:
class Base{
void method(int i) {
System.out.print(" int ");
}
void method(long i) {
System.out.print(" long ");
}
}
public class Main {
public static void main(String[] args) {
Base base = new Base();
base.method(1L);
base.method(1_000_000);
}
}

Что будет выведено в результате компиляции и выполнения кода? (выбрать один)
a) int int
b) long long
117

JAVA FROM EPAM
c) int long
d) long int
e) compilation fails

Вопрос 3.3.
Какие из следующих объявлений представляют корректное объявление метода? (выбрать три)
a) protected abstract void method();
b) final static void method(){}
c) public protected void method(){}
d) default void method();
e) private final void method(){}
f) public static method(){}

Вопрос 3.4.
Какие из следующих объявлений представляют корректное объявление
класса, объявленного в пакете? (выбрать два)
a) final abstract class Type {}
b) public static class Type {}
c) final public class Type {}
d) protected abstract class Type {}
e) class Type {}
f) abstract default class Type {}

Вопрос 3.5.
Дан код:
class Hope {
static void action(){
System.out.print(1);
}
}
class Quest {
static Hope hope;
public static void main(String[] args) {
hope.action();
}
}

Каким будет результат компиляции и запуска приложения? (выбрать один)
a) compilation fails
b) NullPointerException при запуске
c) 1
d) null
118

КЛАССЫ И МЕТОДЫ

Вопрос 3.6.
Дан код:
public class A {
int a;
A(int a) {
this.a = a;
}
}
public class Quest {
public static void main(String[] args) {
A a1 = new A(0);
A a2 = new A(0);
System.out.print(a1 == a2);
System.out.print(", " + a1.equals(a2));
System.out.print(", " + (a1.hashCode()==a1.hashCode()));
}
}

Каким будет результат компиляции и запуска приложения? (выбрать один)
a) false, true, false
b) false, false, false
c) false, true, true
d) false, false, true
e) true, true, false

Вопрос 3.7.
Дан код:
class GenericNum {
T number;
GenericNum(T t) {
number = t;
}
T get() {
return number;
}
}
class GenericsMain {
public static void main(String[] args) {
GenericNum i1 = new GenericNum(500);
GenericNum i2 = new GenericNum(500);
System.out.print(i1.get() == i2.get());
System.out.print(i1.get().intValue() == i2.get().intValue());
}
}

Каким будет результат компиляции и запуска программы? (выбрать один)
119

JAVA FROM EPAM
a)
b)
c)
d)

falsefalse
falsetrue
truefalse
truetrue

Вопрос 3.8.
Дан код:
public class Quest9 {
enum Numbers {ONE, TWO, THREE, FOUR, FIVE}
public static void main(String[] args) {
Numbers n = Numbers.ONE;
}
}

Сколько раз будет вызван конструктор перечисления Numbers?
a) 0
b) 1
c) 5

Вопрос 3.9.
Какие из перечисленных методов класса Object являются final-методами?
(выбрать четыре)
a) getClass()
b) finalize()
c) notify()
d) wait()
e) notifyAll()
f) clone()

Глава 4

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
Если делегированию полномочий уделять внимание, ответственность накопится внизу, подобно осадку.
Закон делегирования Раска

Наследование
Отношение между классами, при котором характеристики одного класса
(суперкласса) передаются другому классу (подклассу) без необходимости их
повторного определения, называется наследованием.
Подкласс наследует поля и методы суперкласса, используя ключевое слово
extends. Класс может также реализовать любое число интерфейсов, используя
ключевое слово implements. Подкласс имеет прямой доступ ко всем открытым
переменным и методам родительского класса, как будто они находятся в подклассе. Исключение составляют члены класса, помеченные private (во всех
случаях) и «по умолчанию» для подкласса в другом пакете. В любом случае
(даже если ключевое слово extends отсутствует) класс автоматически наследует свойства суперкласса всех классов — класса Object.
Множественное наследование классов запрещено, аналог предоставляет реа­
лизация интерфейсов, которые не являются классами и содержат описание набора методов, задающих поведение объекта класса, реализующего эти интерфейсы. Наличие общих методов, которые должны быть реализованы в разных
классах, обеспечивают им сходную функциональность.
Подкласс дополняет члены суперкласса своими полями и\или методами
и\или переопределяет методы суперкласса. Если имена методов совпадают,
а параметры различаются, то такое явление называется перегрузкой методов
(статическим полиморфизмом).
Если же совпадают имена и параметры методов, то этот механизм называется
динамическим полиморфизмом. То есть в подклассе можно объявить (пере­опре­
делить) метод с тем же именем, списком параметров и возвращаемым зна­
чением, что и у метода суперкласса.
Способность ссылки динамически определять версию переопределенного
метода в зависимости от переданного по ссылке типа объекта называется полиморфизмом.
121

JAVA FROM EPAM
Полиморфизм является основой для реализации механизма динамического
или «позднего связывания».
В следующем классе переопределяемый метод doPayment() находится в суперклассе CardAction и его подклассе CreditCardAction. В соответствии
с принципом полиморфизма по ссылке вызывается метод наиболее близкий
к текущему объекту.
/* # 1 # наследование класса и переопределение метода # CardAction.java
# CreditCardAction.java # CardRunner.java */
package by.epam.learn.inheritance;
public class CardAction {
public void doPayment(double amountPayment) {
System.out.println("complete from debt card");
}
}
package by.epam.learn.inheritance;
public class CreditCardAction extends CardAction {
@Override // is used when override a method in sub class
public void doPayment(double amountPayment) { // override method
System.out.println("complete from credit card");
}
public boolean checkCreditLimit() { // own method
return true;
}
}
package by.epam.learn.inheritance;
public class CardRunner {
public static void main(String[] args) {
CardAction action1 = new CardAction();
CardAction action2 = new CreditCardAction();
CreditCardAction cc = new CreditCardAction();
// CreditCardAction cca = new CardAction(); // compile error: class cast
action1.doPayment(15.5); // method of CardAction
action2.doPayment(21.2); // polymorphic method: CreditCardAction
// dc2.checkCreditLimit(); // compile error: non-polymorphic method
((CreditCardAction) action2).checkCreditLimit(); // ок
cc.doPayment(7.0); // polymorphic method: CreditCardAction
cc.checkCreditLimit(); // non-polymorphic method CreditCardAction
((CreditCardAction) action1).checkCreditLimit(); // runtime error: class cast
}
}

Объект по ссылке action1 создается при помощи вызова конструктора класса CardAction и, соответственно, при вызове метода doPayment() вызывается
версия метода из класса CardAction. При создании объекта action2 ссылка
типа CardAction инициализируется объектом типа CreditCardAction. При
122

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
таком способе инициализации ссылка на суперкласс получает доступ к методам, переопределенным в подклассе.
При объявлении совпадающих по сигнатуре (имя, тип, область видимости) полей в суперклассе и подклассах их значения не переопределяются и никак не пересекаются, т.е. существуют в одном объекте независимо друг от друга. Такое решение является плохим примером кода, который не используется в практическом
программировании. Не следует использовать вызов методов, которые можно переопределить, в конструкторе. Это действие может привести к некорректной работе
конструктора при инициализации полей объекта и в целом некачественному созданию объекта. Для доступа к полям текущего объекта можно использовать указатель this, для доступа к полям суперкласса — указатель super.
К чему может привести вызов полиморфных методов в конструкторе и объявление идентичных полей иерархии наследования рассмотрено ниже:
/* # 2 # вызов полиморфного метода из конструктора # Dumb.java # Dumber.java */
package by.epam.learn.inheritance;
class Dumb {
{
this.id = 6;
}
int id;
Dumb() {
System.out.println("constructor Dumb ");
id = getId(); // ~ this.getId(); // ~ Dumb.this.getId();
System.out.println(" id=" + id);
}
int getId() { // 1
System.out.println("getId() of Dumb ");
return id;
}
}
class Dumber extends Dumb {
int id = 9;
Dumber() {
System.out.println("constructor Dumber");
id = this.getId();
System.out.println(" id=" + id);
}
@Override
int getId() { // 2
System.out.println("getId() of Dumber");
return id;
}
}

В результате создания экземпляра Dumb dumb = new Dumber() последовательно будет выведено:
123

JAVA FROM EPAM
constructor Dumb
getId() of Dumber
id=0
constructor Dumber
getId() of Dumber
id=9
Метод getId() объявлен в классе Dumb и переопределен в его подклассе
Dumber. Перед вызовом конструктора Dumber() вызывается конструктор класса Dumb. Но так как создается объект класса Dumber, то вызывается ближайший к создаваемому объекту метод getId(), объявленный в классе Dumber,
который, в свою очередь, оперирует полем id, еще не проинициализированным
для класса Dumber. В результате id получит значение по умолчанию, т.е. ноль.
Разработчик класса Dumb предполагал, что объект будет создаваться по его
правилам, и метод будет работать так всегда. Но если метод переопределен
в подклассе, то, соответственно, и в конструкторе суперкласса будет вызвана
переопределенная версия, которая может выполнять совершенно другие действия, и создание объекта пойдет уже по другому пути.
Поля с одинаковыми именами в подклассе не замещаются. Объект подкласса будет иметь в итоге два поля с одинаковыми именами. У разработчика появится проблема их различить. Воспользовавшись преобразованием типов вида
((Dumber) dumb).id, можно получить доступ к полю id из подкласса, но это
в случае открытого доступа. Если поле приватное, то эта простая задача становится проблемой.
Практического смысла в одинаковых именах полей в иерархии наследования не просматривается. Это просто ошибка проектирования, которой следует
избегать.
Методы, объявленные как private, не переопределяются.
/* # 3 # попытка наследования приватного метода # Dumb.java # Dumber.java */
class Dumb {
private void action() {
System.out.println("Dumb");
}
}
class Dumber extends Dumb {
@Override // compile error
void action() {
System.out.println("Dumber");
}
}

Аннотация @Override будет выдавать ошибку компиляции из-за того, что
она просто не видит переопределяемый метод.
124

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
Без этой аннотации код будет компилироваться, только цепочка переопределения будет начинаться с версии метода в подклассе и никак не будет связана
с версией метода суперкласса.

Классы и методы final
Запрещено переопределять метод в порожденном классе, если в суперклассе он объявлен со спецификатором final:
/* # 4 # попытка переопределения final-метода # MasterCardAction.java
# VisaCardAction.java */
package by.epam.learn.inheritance;
public class MasterCardAction extends CreditCardAction{
@Override// doPayment() cannot be polymorphic
public final void doPayment(double amountPayment) {
System.out.println("complete from MasterCard");
}
}
package by.epam.learn.inheritance;
public class VisaCardAction extends MasterCardAction {
//@Override
//public void doPayment(double amountPayment) {// impossible method
//}
}

Если разработчик объявляет метод как final, следовательно, он считает, что
его версия в этой ветви наследования метода окончательна и переопределению\совершенствованию не подлежит.
Что же общего у final-метода со статическим? Версия статического метода
жестко определяется на этапе компиляции. И если final-метод вызван на объекте класса MasterCardAction, в котором он определен, или на объекте любого его подкласса, например, VisaCardAction, то также в точке вызова будет
зафиксирован вызов именно этой версии метода.
/* # 5 # вызов полиморфного метода # CardService.java */
public class CardService {
public void doService(CardAction action, double amount){
action.doPayment(amount);
}
}

При передаче в метод doService() объектов классов CardAction или
CreditCardAction будут вызваны их собственные версии методов doPayment(),
определенные в каждом из классов, что представляет собой еще одну иллюстрацию полиморфизма.
125

JAVA FROM EPAM
При передаче же в метод doService() объектов классов MasterCardAction или
VisaCardAction будет вызвана версия метода doPayment(), определенная в классе
MasterCardAction, так как в классе VisaCardAction метод не определен, то будет
вызвана версия метода ближайшая по восходящей цепочке наследования.
Применение final-методов также показательно при разработке конструктора класса. Процесс инициализации экземпляра должен быть строго определен
и не подвергаться изменениям. Исключить подмену реализации метода, вызываемого в конструкторе, следует объявлением метода как final, т.е. при этом
метод не может быть переопределен в подклассе. Подобное объявление гарантирует обращение именно к этой реализации. Корректное определение вызова
метода класса из конструктора представлено ниже:
/* # 6 # вызов нестатического final-метода из конструктора # AutenticationService.java */
package by.epam.learn.service;
public class AutenticationService {
public AutenticationService() {
authenticate();
}
public final void authenticate() {
//appeal to the database
}
}

Если убрать final в объявлении метода суперкласса, то становится возможным переопределить метод в подклассе.
/* # 7 # переопределение не final-метода # NewAutenticationService.java */
package by.epam.learn.service;
public class NewAutenticationService extends AutenticationService {
@Override
public void authenticate() {
//empty method
}
}

Тогда при создании объекта подкласса конструктор суперкласса вызовет
версию метода из подкласса как самую близкую по типу создаваемого объекта,
и проверка данных в БД не будет выполнена.
Рекомендуется при разработке классов из конструкторов вызывать методы,
на которые не распространяются принципы полиморфизма. Метод может быть
еще объявлен как private или static с таким же результатом.
Нельзя создать подкласс для класса, объявленного со спецификатором final:
public final class String { /* code */ }
class MegaString extends String { /* code */ } //impossible: compile error

126

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
Если необходимо создать собственную реализацию с возможностями finalкласса, то создается класс-оболочка, где в качестве поля представлен finalкласс. В свою очередь, необходимые или даже все методы делегируются из
final-класса, и им придается необходимая разработчику функциональность.
Такой подход гарантирует невозможность прямого использования классаоболочки вместо оборачиваемого класса и наоборот.
// # 8 # класс-оболочка для класса String # WrapperString.java
package by.epam.learn.string;
public class WrapperString {
private String str;
public WrapperString() {
str = new String();
}
public WrapperString(String str) {
this.str = str;
}
public int length() {// delegate method
return str.length();
}
public boolean isEmpty() {// delegate method
return str.isEmpty();
}
public int indexOf(int arg) { // delegate method
int value = // new realization
return value;
}
// other methods
}

Класс WrapperString не является наследником класса String, и его объект
не может быть использован для передачи по ссылке на класс String. Класс
WrapperString, в свою очередь, уже может быть суперклассом, поэтому его
поведение можно изменять.

Использование super и this
Ключевое слово super применяется для обращения к конструктору суперкласса и для доступа к полю или методу суперкласса. Например:
super(parameters); // call to superclass constructor
super.id = 42; // superclass attribute reference
super.getId(); // superclass method call

Первая форма super применяется только в конструкторах для обращения
к конструктору суперкласса только в качестве первой строки кода конструктора и только один раз.
127

JAVA FROM EPAM
Вторая форма super используется для доступа из подкласса к переменной id
суперкласса. Третья форма специфична для Java и обеспечивает вызов из подкласса метода суперкласса, что позволяет избежать рекурсивного вызова в случае, если вызываемый с помощью super метод переопределен в данном подклассе. Причем, если в суперклассе этот метод не определен, то будет осуществлять­ся
поиск по цепочке наследования до тех пор, пока он не будет найден.
Во всех случаях с использованием super можно обратиться только к ближайшему суперклассу, т.е. «перескочить» через суперкласс, чтобы обратиться
к его суперклассу, невозможно.
Следующий код показывает, как, используя this, можно строить одни конструкторы на основе использования возможностей других.
// # 9 # super и this в конструкторе # Point1D.java # Point2D.java # Point3D.java
package by.epam.learn.point;
public class Point1D {
private int x;
public Point1D(int x) {
this.x = x;
}
}
package by.epam.learn.point;
public class Point2D extends Point1D {
private int y;
public Point2D(int x, int y) {
super(x);
this.y = y;
}
}
package by.epam.learn.point;
public class Point3D extends Point2D {
private int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
public Point3D() {
this(-1, -1, -1); // call Point3D constructor with parameters
}
}

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

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
преимущество перед полем класса и закрывает к нему доступ. Чтобы получить дос­
туп к полю класса, требуется воспользоваться явной ссылкой this перед именем
поля, так как поле класса является частью объекта, а локальная переменная нет.
Инструкция this() должна быть единственной в вызывающем конструкторе
и быть первой по счету выполняемой операцией, иначе возникает возможность вызова нескольких конструкторов суперкласса или ветвления при обращении к конструктору суперкласса. Компилятор выполнять подобные действия запрещает.
Возможна ситуация с зацикливанием обращений конструкторов друг к другу, что также запрещено:
// # 10 # ошибка с зацикливанием вызова конструктора # Point1D.java
public class Point1D {
private int x;
public Point1D(int x) { // recursive constructor invocation
this();
this.x = x;
}
public Point1D() { // recursive constructor invocation
this(42);
}
}

Переопределение методов и полиморфизм
Способность Java делать выбор метода, исходя из типа объекта во время
выполнения, называется «поздним связыванием». При вызове метода его поиск происходит сначала в данном классе, затем в суперклассе, пока метод не
будет найден или не достигнут Object — суперкласс для всех классов.
Если два метода с одинаковыми именами и возвращаемыми значениями находятся в одном классе, то списки их параметров должны отличаться. То же
относится к методам, наследуемым из суперкласса. Такие методы являются
перегружаемыми (overloading). При обращении вызывается доступный метод,
список параметров которого совпадает со списком параметров вызова.
Если объявление метода подкласса полностью, включая параметры, совпадает с объявлением метода суперкласса (порождающего класса), то метод подкласса переопределяет (overriding) метод суперкласса. Переопределение методов является основой концепции динамического связывания, реализующей
полиморфизм. Когда переопределенный метод вызывается через ссылку суперкласса, Java определяет, какую версию метода вызвать, основываясь на типе
объекта, на который имеется ссылка. Таким образом, тип объекта определяет
версию метода на этапе выполнения. В следующем примере рассматривается
реализация полиморфизма на основе динамического связывания. Так как
129

JAVA FROM EPAM
суперкласс содержит методы, переопределенные подклассами, то объект суперкласса будет вызывать методы различных подклассов в зависимости от
того, на объект какого подкласса у него имеется ссылка.
/* # 11 # динамическое связывание методов # Point1D.java # Point2D.java
# Point3D.java # PointReport.java # PointMain.java */
package by.epam.learn.point;
public class Point1D {
private int x;
public Point1D(int x) {
this.x = x;
}
public double length() {
return Math.abs(x);
}
@Override
public String toString() {
return " x=" + x;
}
}
package by.epam.learn.point;
public class Point2D extends Point1D {
private int y;
public Point2D(int x, int y) {
super(x);
this.y = y;
}
@Override
public double length() {
return Math.hypot(super.length(), y);
/* just length() is impossible, because the method will call itself, which will
lead to infinite recursion and an error at runtime */
}
@Override
public String toString() {
return super.toString() + " y=" + y;
}
}
package by.epam.learn.point;
public class Point3D extends Point2D {
private int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
public Point3D() {
this(-1, -1, -1);
}

130

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
@Override
public double length() {
return Math.hypot(super.length(), z);
}
@Override
public String toString() {
return super.toString() + " z=" + z;
}
}
package by.epam.learn.point;
public class PointReport {
public void printReport(Point1D point) {
System.out.printf("length=%.2f %s%n", point.length(), point);
// out.print(point.toString()) is identical to out.print(point)
}
}
package by.epam.learn.point;
public class PointMain {
public static void main(String[] args) {
PointReport report = new PointReport();
Point1D point1 = new Point1D(-7);
report.printReport(point1);
Point2D point2 = new Point2D(3, 4);
report.printReport(point2);
Point3D point3 = new Point3D(3, 4, 5);
report.printReport(point3);
}
}

Результат:
length=7.00 x=-7 length=5.00 x=3 y=4 length=7.07 x=3 y=4 z=5
Аннотация @Override позволяет выделить в коде переопределенный метод
и сгенерирует ошибку компиляции в случае, если программист изменит имя
метода, типы его параметров или их количество в описании сигнатуры полиморфного метода.
Следует помнить, что при вызове метода обращение super всегда производится к ближайшему суперклассу. Переадресовать вызов, минуя суперкласс,
невозможно! Аналогично при вызове super() в конструкторе обращение происходит к соответствующему конструктору непосредственного суперкласса.
Основной вывод: выбор версии переопределенного метода производится на этапе выполнения кода.
Все нестатические методы Java являются виртуальными (ключевое слово
virtual, как в C++, не используется).
Статические методы можно перегружать и «переопределять» в подклассах,
но их доступность всегда зависит от типа ссылки и атрибута доступа, и никогда — от типа самого объекта.
131

JAVA FROM EPAM

Методы подставки
После выхода пятой версии языка появилась возможность при переопределении методов указывать другой тип возвращаемого значения, в качестве которого можно использовать только типы, находящиеся ниже в иерархии наследования, чем тип возвращаемого значения метода суперкласса.
/* # 12 # методы-подставки # Point1DCreator.java # Point2DCreator.java # PointMain.java*/
package by.epam.learn.inheritance.service;
import by.epam.learn.point.Point1D;
import java.util.Random;
public class Point1DCreator {
public Point1D create() {
System.out.println("log in Point1DCreator");
return new Point1D(new Random().nextInt(100));
}
}
package by.epam.learn.inheritance.service;
import by.epam.learn.point.Point2D;
import java.util.Random;
public class Point2DCreator extends Point1DCreator {
@Override
public Point2D create() {
System.out.println("log in Point2DCreator");
Random random = new Random();
return new Point2D(random.nextInt(10), random.nextInt(10));
}
}
package by.epam.learn.inheritance;
import by.epam.learn.inheritance.service.Point1DCreator;
import by.epam.learn.inheritance.service.Point2DCreator;
import by.epam.learn.point.Point1D;
import by.epam.learn.point.Point2D;
public class PointMain {
public static void main(String[] args) {
Point1DCreator creator1 = new Point2DCreator();
// Point2D point = creator1.create(); // compile error
Point1D pointA = creator1.create(); /* when compiling - overload,

when running – overriding */
System.out.println(pointA);
Point2DCreator creator2 = new Point2DCreator();
Point2D pointB = creator2.create();
System.out.println(pointB);
}
}

В обоих случаях создания объекта будут созданы объекты класса Point2D:
132

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
log in Point2DCreator
x=5 y=4
log in Point2DCreator
x=2 y=7
В данной ситуации при компиляции в подклассе Point2DCreator создаются
два метода create(). Один имеет возвращаемое значение Point2D, другой (явно
невидимый) — Point1D. При обращении к методу create() версия метода определяется «ранним связыванием» без использования полиморфизма, но при выполнении срабатывает полиморфизм и вызывается метод с возвращаемым значением Point2D.
Получается, что на этапе компиляции метод-подставка ведет себя как обычный метод, видимый по типу ссылки, а на этапе выполнения включается механизм переопределения и срабатывает метод из подкласса.
На практике методы подставки могут использоваться для расширения возможностей класса по прямому извлечению (без преобразования) объектов подклассов, инициализированных в ссылке на суперкласс.

«Переопределение» статических методов
Для статических методов принципы «позднего связывания» не работают.
Динамический полиморфизм к статическим методам класса неприменим, так
как обращение к статическому атрибуту или методу осуществляется по типу
ссылки, а не по типу объекта, через который производится обращение. Версия
вызываемого статического метода всегда определяется на этапе компиляции.
При использовании ссылки для доступа к статическому члену компилятор при
выборе метода учитывает тип ссылки, а не тип объекта, ей присвоенного.
/* # 13 # поведение статического метода при «переопределении» # StaticMain.java */
class StaticDumb {
public static void go() {
System.out.println("go() from StaticDumb ");
}
}
class StaticDumber extends StaticDumb {
//@Override – compile error
public static void go() { // similar to dynamic polymorphism
System.out.println("go() from StaticDumber ");
}
}
class StaticMain {
public static void main(String[ ] args) {
StaticDumb dumb = new StaticDumber();

133

JAVA FROM EPAM
dumb.go(); // warning: static member accessed via instance reference
StaticDumber dumber = null;
dumber.go(); // will not NullPointerException !
}
}

В результате выполнения данного кода будет выведено:
go() from StaticDumb
go() from StaticDumber
При таком способе инициализации объекта dumb метод go() будет вызван
из класса StaticDumb. Если же спецификатор static убрать из объявления методов, то вызов методов будет осуществляться в соответствии с принципами
полиморфизма.
Статические методы всегда следует вызывать через имя класса, в котором
они объявлены, а именно:
StaticDumb.go();
StaticDumber.go();

Вызов статических методов через объект считается нетипичным и нарушаю­
щим смысл статического определения метода.

Абстракция
Множество моделей предметов реального мира обладают некоторым набором
общих характеристик и правил поведения. Абстрактное понятие «Гео­метрическая
фигура» может содержать описание геометрических параметров и расположения
центра тяжести в системе координат, а также возможности определения площади
и периметра фигуры. Однако в общем случае дать конкретную реализацию приведенных характеристик и функциональности невозможно ввиду слишком общего их определения. Для конкретного понятия, например, «Квадрат», дать описание линейных размеров и определения площади и периметра не составляет труда.
Абстрагирование понятия должно предоставлять абстрактные характеристики
предмета реального мира, а не его ожидаемую реализацию. Грамотное выделение
абстракций позволяет структурировать код программной системы в целом и пов­
торно использовать абстрактные понятия для конкретных реализаций при определении новых возможностей абстрактной сущности.
Абстрактные классы объявляются с ключевым словом abstract и содержат
объявления абстрактных методов, которые не реализованы в этих классах,
а будут реализованы в подклассах. Абстрактный класс может и не содержать
вовсе абстрактных методов. Предназначение такого класса — быть вершиной
иерархии его различных реализаций.
Объекты таких классов нельзя создать с помощью оператора new, но можно
создать объекты подклассов, которые реализуют все эти методы. При этом
134

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
допустимо объявлять ссылку на абстрактный класс, но инициализировать ее
можно только объектом производного от него класса. Абстрактные классы могут содержать и полностью реализованные методы, а также конструкторы
и поля данных.
С помощью абстрактного класса объявляется контракт (требования к функциональности) для его подклассов. Примером может служить уже рассмотренный выше абстрактный класс Number и его подклассы Byte, Float и другие.
Класс Number объявляет контракт на реализацию ряда методов по преобразованию данных к значению конкретного базового типа, например, floatValue().
Можно предположить, что реализация метода будет различной для каждого из
классов-оболочек. Объект класса Number нельзя создать явно при помощи его
собственного конструктора.
/* # 14 # абстрактный класс и метод # AbstractCardAction.java */
package by.epam.learn.inheritance;
public abstract class AbstractCardAction {
private long actionId;
public AbstractCardAction() {
}
public void check() {
}
public abstract void doPayment(double amountPayment);
}

/* # 15 # подкласс абстрактного класса # CreditCardAction.java # */
package by.epam.learn.inheritance;
public class CreditCardAction extends AbstractCardAction {
@Override
public void doPayment(double amountPayment) {
// code
}
}

Ссылка action на абстрактный суперкласс инициализируется объектом подкласса, в котором реализованы все абстрактные методы суперкласса:
AbstractCardAction action;
// action = new AbstractCardAction(); //compile error: cannot create object!
action = new CreditCardAction();
action.doPayment(7);

С помощью этой ссылки могут вызываться также и неабстрактные методы
абстрактного класса, если они не переопределены в подклассе:
action.check();

135

JAVA FROM EPAM

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

Суперкласс AbstractQuest предоставляет общий интерфейс (методы) для
своих подклассов. Подклассы DragnDropQuest и SingleChoiceQuest переопре­
деляют эти определения для обеспечения уникального поведения.
Небольшое приложение демонстрирует возможности полиморфизма.
/* # 16 # общий пример на полиморфизм # AbstractQuest.java # DragnDropQuest.
java # SingleChoiceQuest.java # Answer.java # QuestFactory.java # QuizMain.java */
package by.epam.learn.inheritance.quiz;
public abstract class AbstractQuest {
private long questId;
private String content;
// constructors, methods
public abstract boolean check(Answer answer);
}
package by.epam.learn.inheritance.quiz;
import java.util.Random;
public class DragnDropQuest extends AbstractQuest {
// constructors, methods
@Override
public boolean check(Answer answer) {
return new Random().nextBoolean(); // demo
}
}
package by.epam.learn.inheritance.quiz;
import java.util.Random;
public class SingleChoiceQuest extends AbstractQuest {
// constructors, methods
@Override
public boolean check(Answer answer) {
return new Random().nextBoolean();
}
}
package by.epam.learn.inheritance.quiz;

136

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
public class Answer {
// fields, constructors, methods
}
package by.epam.learn.inheritance.quiz;
public class QuestFactory { // pattern Factory Method (simplest)
public static AbstractQuest getQuestFromFactory(int mode) {
switch (mode) {
case 0:
return new DragnDropQuest();
case 1:
return new SingleChoiceQuest();
default:
throw new IllegalArgumentException("illegal mode");
// assert false; // bad
// return null; // ugly
}
}
}
package by.epam.learn.inheritance.quiz;
import java.util.Random;
public class TestGenerator {
public AbstractQuest[] generateTest(final int NUMBER_QUESTS, int maxMode) {
AbstractQuest[] test = new AbstractQuest[NUMBER_QUESTS];
for (int i = 0; i < test.length; i++) {
int mode = new Random().nextInt(maxMode); // stub
test[i] = QuestFactory.getQuestFromFactory(mode);
}
return test;
}
}
package by.epam.learn.inheritance.quiz;
public class TestAction {
public int checkTest(AbstractQuest[] test) {
int counter = 0;
for (AbstractQuest s : test) {
// вызов полиморфного метода
counter = s.check(new Answer()) ? ++counter : counter;
}
return counter;
}
}
package by.epam.learn.inheritance.quiz;
public class QuizMain {
public static void main(String[] args) {
TestGenerator generator = new TestGenerator();
AbstractQuest[] test = generator.generateTest(60, 2); // 60 questions of 2 types
// here should be the code of the test process …
TestAction action = new TestAction();
int result = action.checkTest(test);
System.out.println(result + " correct answers, " + (60 - result) + " incorrect");
}
}

137

JAVA FROM EPAM
В процессе выполнения приложения будет случайным образом сформирован массив-тест из вопросов разного типа, будет выполнена проверка теста,
а общая информация об ответах на них будет выведена на консоль:
27 correct answers, 33 incorrect
Класс QuestFactory содержит метод getQuestFromFactory(int numMode),
который возвращает ссылку на случайно выбранный объект подкласса класса
AbstractQuest каждый раз, когда он вызывается. Приведение к базовому типу производится оператором return, который возвращает ссылку на DragnDropQuest или
SingleChoiceQuest. Метод main() содержит массив из ссылок AbstractQuest, заполненный с помощью вызова getQuestFromFactory(). На этом этапе известно, что
имеется некоторое множество ссылок на объекты базового типа и ничего больше (не
больше, чем знает компилятор). Kогда происходит перемещение по этому массиву,
метод check() вызывается для каждого случайным образом выбранного объекта.
Если понадобится в дальнейшем добавить в систему, например, класс
MultiplyChoiceQuest, то это потребует только переопределения метода check()
и добавления одной строки в код метода getQuestFromFactory(), что делает
систему легко расширяемой.
Невозможно приравнивать ссылки на классы, находящиеся в разных ветвях
наследования, так как не существует никакого способа привести один такой тип
к другому.

Класс Object
На вершине иерархии классов находится класс Object, суперкласс для всех
классов. Изучение класса Object и его методов необходимо, т.к. его свойствами
обладают все классы Java. Ссылочная переменная типа Object может указывать на объект любого другого класса, на любой массив, так как массив реализован как класс-наследник Object.
В классе Object определен набор методов, который наследуется всеми классами:
• protected Object clone() — создает и возвращает копию вызывающего объекта;
• public boolean equals(Object ob) — предназначен для использования и переопределения в подклассах с выполнением общих соглашений о сравнении содержимого двух объектов одного и того же типа;
• public Class 0) : "incorrect PoolSize= " + size;
// more code

Правописание инструкции assert:
assert bool_exp : expression;
assert bool_exp;

Выражение bool_exp может принимать только значение типов boolean или
Boolean, а expression — любое значение, которое может быть преобразовано
к строке. Если логическое выражение получает значение false, то генерируется
исключение AssertionError и выполнение программы прекращается с выводом на консоль значения выражения expression (если оно задано).
Механизм assertion хорошо подходит для проверки инвариантов, например,
перечислений:
enum Mono { WHITE, BLACK }
String str = "WHITE"; // "GRAY"
Mono mono = Mono.valueOf(str);

285

JAVA FROM EPAM
// more code
switch (mono) {
case WHITE : // more code
break;
case BLACK : // more code
break;
default :
assert false : "Colored!";
}

Создатели языка не рекомендуют использовать assertion при проверке параметров public-методов. В таких ситуациях лучше рассматривать возможность
генерации исключения одного из типов: IllegalArgumentException или собственное исключение. Нет также особого смысла в механизме assertion при проверке пограничных значений переменных, поскольку исключительные ситуации генерируются в этом случае без посторонней помощи.
Механизм assertion можно включать для отдельных классов или пакетов
при запуске виртуальной машины в виде:
java -enableassertions RunnerMain

или
java -ea RunnerMain

Для выключения assertion применяется -disableassertions или -da.

Вопросы к главе 9
1. Что для программы является исключительной ситуацией? Какие существуют
способы обработки ошибок в программах?
2. Что такое исключение для Java-программы? Что значит «программа генерировала\выбросила исключение»? Привести пример, когда исключения
генерируются виртуальной машиной (автоматически) и когда необходимо
их генерировать вручную.
3. Привести иерархию классов-исключений, делящую исключения на проверяемые и непроверяемые. В чем особенности проверяемых и непроверяемых исключений?
4. Объяснить работу оператора try-catch-finally. Когда данный оператор следует использовать? Сколько блоков catch может соответствовать одному
блоку try?
5. Можно ли вкладывать блоки try друг в друга, можно ли вложить блок try
в catch или finally? Как происходит обработка исключений, выброшенных
внутренним блоком try, если среди его блоков catch нет подходящего?
6. Что называют стеком операторов try? Как работает блок try с ресурсами?
7. Указать правило расположения блоков catch в зависимости от типов перехватываемых исключений. Может ли перехваченное исключение быть
286

ИСКЛЮЧЕНИЯ И ОШИБКИ
сгенерировано снова, и, если да, то как и кто в этом случае будет обрабатывать повторно сгенерированное исключение? Может ли блок catch выбрасывать иные исключения, и если да, то привести пример, когда это может
быть необходимо.
8. Когда происходит вызов блока finally? Существуют ли ситуации, когда
блок finally не будет вызван? Может ли блок finally выбрасывать исключения? Может ли блок finally выполниться дважды?
9. Как генерировать исключение вручную? Объекты каких классов могут
быть генерированы в качестве исключений? Можно ли генерировать два
исключения одновременно?
10. Объяснить, как работают операторы throw и throws. В чем их отличия?
11. Объяснить правила реализации секции throws при переопределении метода и при описании конструкторов производного класса.
12. Как ведет себя блок throws при работе с проверяемыми и непроверяемыми
исключениями?
13. Каков будет результат создания объекта, если конструктор при работе сгенерирует исключительную ситуацию?
14. Нужно ли генерировать исключения, входящие в Java SE? Как создать собственные классы исключений?

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

Тестовые задания к главе 9
Вопрос 9.1.
Дан код:
class Quest {
static void method() throws ArithmeticException {
int i = 7 / 0;

287

JAVA FROM EPAM
try {
double d = 77.0;
d /= 0.0;
} catch (ArithmeticException e) {
System.out.print("E1");
} finally {
System.out.print("Finally ");
}
}
public static void main(String[] args) {
try {
method();
} catch (ArithmeticException e) {
System.out.print("E0");
}
}
}

Каким будет результат компиляции и запуска? (выбрать один)
a) E0Finally
b) E0E1
c) E1Finally
d) E0
e) FinallyE0
f) runtime error

Вопрос 9.2.
Дан код:
class ColorException extends Exception {}
class WhiteException extends ColorException {}
abstract class Color {
abstract void method() throws ColorException;
}
class White extends Color {
void method() throws WhiteException {
throw new WhiteException();}
public static void main (String[] args) {
White white = new White();
int a, b, c;
a = b = c = 0;
try {
white.method();
a++;
} catch (WhiteException e) {
b++;
} finally {
c++;

288

ИСКЛЮЧЕНИЯ И ОШИБКИ
}
System.out.print(a + " " + b + " " + c);
}}

Какой результат выведется на консоль при попытке компиляции и запуска
этого кода? (выбрать один)
a) 1 1 1
b) 0 0 1
c) 0 1 1
d) 1 0 1
e) compilation fails

Вопрос 9.3.
Дана иерархия классов:
class A{
A() throws IOException{}
}
class B extends A {}

Как следует объявить конструктор класса B, чтобы код компилировался без
ошибок? (выбрать два)
a) B() throws IOException{}
b) B() throws FileNotFoundException{}
c) B(){}
d) B() throws IllegalArgumentException{}
e) B() throws Exception {}

Вопрос 9.4.
Дан код:
class A{
void f() throws FileNotFoundException {}
}
class B extends A{}

Каким образом можно переопределить метод m() в классе B, не вызвав при
этом ошибку компиляции? (выбрать три)
a) void m() throws Exception {}
b) void m() throws IOException {}
c) void m(){}
d) void m() throws FileNotFoundException {}
e) void m() throws FileNotFoundException, IOException {}
f) void m() throws ExceptionInInitializerError {}

Глава 10

ПОТОКИ ВВОДА/ВЫВОДА
Вопрос. Я скачал из интернета файл.
Теперь мне он больше не нужен.
Как закачать его обратно?
Ответ. Вот из-за таких, как ты,
скоро в интернете
все файлы кончатся.
Цитата из сети

Потоки ввода/вывода используются для передачи данных в файловые потоки,
на консоль или на сетевые соединения. Потоки представляют собой объекты соответствующих классов. Пакеты ввода/вывода java.io, java.nio предоставляют
пользователю большое число классов и методов и постоянно обновляются.

Байтовые и символьныепотоки
ввода/вывода
При разработке приложения регулярно возникает необходимость ввода информации из какого-либо источника и хранения результатов. Действия по чтению/записи информации представляют собой стандартный вид деятельности,
связанный с передачей и извлечением последовательности байтов из потоков.
Все потоки ввода последовательности байтов являются подклассами абстрактного класса InputStream, потоки вывода — подклассами абстрактного
класса OutputStream. При работе с файлами используются подклассы этих
классов, соответственно, FileInputStream и FileOutputStream, конструкторы
которых открывают поток и связывают его с соответствующим физическим
файлом. Существуют классы-потоки для ввода массивов байтов, строк, объектов, а также для выбора данных из файлов и сетевых соединений.
Для чтения байта и массива байтов используются реализации абстрактных
методов int read() и int read(byte[] b) класса InputStream. Метод int read()
возвращает –1, если достигнут конец потока данных, поэтому возвращаемое
значение имеет тип int, а не byte. При взаимодействии с информационными
потоками возможны различные исключительные ситуации, поэтому обработка
исключений вида try-catch при использовании методов чтения и записи являет­
ся обязательной.
290

ПОТОКИ ВВОДА/ВЫВОДА
В классе FileInputStream метод read() читает один байт из файла, а поток
System.in как объект подкласса InputStream позволяет вводить байт с консоли.
Реализация абстрактного метода write(int b) класса OutputStream записывает одно значение в поток вывода. Оба эти метода блокируют поток до тех
пор, пока байт не будет записан или прочитан. После окончания чтения или
записи в поток его всегда следует закрывать с помощью метода close() для того,
чтобы освободить ресурсы приложения. Класс FileOutputStream используется для вывода одного или нескольких байт информации в файл.
Поток ввода связывается с одним из источников данных, в качестве которых
могут быть использованы массив байтов, строка, файл, «pipe»-канал, сетевые
соединения и др. Набор классов для взаимодействия с перечисленными источниками приведен на рисунках 10.1 и 10.2.
Класс BufferedInputStream присоединяет к потоку буфер для упрощения
и ускорения следующего доступа.
Для вывода данных в поток используются следующие классы.
Абстрактный класс FilterOutputStream используется как шаблон для настройки производных классов. Класс BufferedOutputStream присоединяет буфер к потоку для ускорения вывода и ограничения доступа к внешним устройствам.
Начиная с версии 1.2, пакет java.io подвергся значительным изменениям.
Появились новые классы, которые производят скоростную обработку потоков,
хотя и не полностью перекрывают возможности классов предыдущей версии.
Для обработки символьных потоков в формате Unicode применяется отдельная иерархия подклассов абстрактных классов Reader и Writer, которые
почти полностью повторяют функциональность байтовых потоков, но являются более актуальными при передаче текстовой информации.

Рис. 10.1. Иерархия классов байтовых потоков ввода

291

JAVA FROM EPAM

Рис. 10.2. Иерархия классов байтовых потоков вывода

Рис. 10.3. Иерархия символьных потоков ввода

Рис. 10.4. Иерархия символьных потоков вывода

292

ПОТОКИ ВВОДА/ВЫВОДА
Например, аналогом класса FileInputStream является класс FileReader.
Такой широкий выбор потоков позволяет выбрать наилучший способ записи
в каждом конкретном случае.
В примерах по возможности используются способы инициализации для
различных семейств потоков ввода/вывода.
/* # 1 # чтение одного символа (байта) и массива из потока ввода # InputMain.java */
package by.epam.learn.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
public class InputMain {
public static void main(String[] args) {
FileInputStream input = null;
try {
input = new FileInputStream("data/info.txt");
int code = input.read();
System.out.println(code + " char = " + (char)code);
byte[] arr = new byte[16];
int numberBytes = input.read(arr);
System.out.println("numberBytes = " + numberBytes);
System.out.println(Arrays.toString(arr));
// input.close(); // wrong
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(input != null) {
input.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Конструктор FileInputStream(file) открывает поток input и связывает его
с файлом, где директория data в корне проекта должна существовать.
Если файла по указанному пути не существует, то при попытке инициализации потока с несуществующим файлом будет сгенерировано исключение:
Ошибка файла: java.io.FileNotFoundException: data\info.txt (The system
cannot find the file specified)
293

JAVA FROM EPAM
Если файл существует, то информация из него будет считана по одному символу; и результаты чтения, и количество прочитанных символов будут выведены на консоль.
Для закрытия потока используется метод close(). Закрытие потока должно
произойти при любом исходе чтения: удачном или с генерацией исключения.
Гарантировать закрытие потока может только помещение метода close() в блок
finally. При чтении из потока можно пропустить n байт с помощью метода long
skip(long n).
Закрытие потока ввода/вывода в блоке finally принято как негласное правило и является признаком хорошего кода. Однако данная конструкция выглядит
достаточно громоздко. Начиная с Java 7 возможно автоматическое закрытие
потоков ввода/вывода без явного вызова метода close() и блока finally:
try (FileReader is = new FileReader(new File("data\\info.txt"))) {
// code
} catch (IOException e) {
System.err.println("file error: " + e);
}

C этой версии в список интерфейсов, реализуемых практически всеми классами потоков, добавлен интерфейс AutoCloseable. Метод close() вызывается
неявно для всех потоков, открытых в инструкции
try(iostream1; iostream2;…; iostreamN)

Например:
try (
Writer writer = new FileWriter(fileIn);
OutputStream out = new FileOutputStream(fileOut)
)

Для вывода символа (байта) или массива символов (байтов) в поток используются потоки вывода — объекты подкласса FileWriter суперкласса Writer
или подкласса FileOutputStream суперкласса OutputStream. В следующем
примере для вывода в связанный с файлом поток используется метод write().
// # 2 # вывод массива в поток в виде символов и байт # OutMain.java
package by.epam.learn.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutMain {
public static void main(String[] args) {
try (FileOutputStream output = new FileOutputStream("data/out.txt", true)) {
output.write(48);
byte[] value = {65, 67, 100};
output.write(value);
} catch (FileNotFoundException e) {

294

ПОТОКИ ВВОДА/ВЫВОДА
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

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

File, Path и Files
Для работы с физическими файлами и каталогами (директориями), расположенными на внешних носителях, в приложениях Java используются классы
из пакетов java.io и java.nio.
В Java 7 были добавлены класс java.nio.file.Files и интерфейс java.nio.file.Path,
дублирующие и существенно расширяющие возможности класса java.io.File,
возможности которого будут рассмотрены ниже.
Интерфейс Path представляет более совершенный аналог класса File, а класс
Files, по сути, утилитный класс, содержащий только статические методы для
доступа к файлам, директориям, их свойствам и их содержимому.
Получить объект Path можно как из объекта File:
File file = new File("data/info.txt");
Path path = file.toPath();

так и прямой инициализацией:
Path path1 = Paths.get("data/info.txt");

или
Path path2 = FileSystems.getDefault().getPath("data/info.txt");

Доступ и управление файловой системой, доступной для текущей версии
JVM, осуществляют классы java.nio.file.FileSystem и java.nio.file.FileSystems.
Класс FileSystems определяет набор статических методов для получения и создания файловых систем. Вызов метода FileSystems.getDefault() предоставляет
доступ к текущей файловой системе.
Найти все файлы с расширением .java из заданной директории src с по­
мощью метода Files.find(Path start, int maxDepth, BiPredicate matcher, FileVisitOption... options) позволит следующий
фрагмент кода:
Path path = FileSystems.getDefault().getPath("src");
if (Files.exists(path) && Files.isDirectory(path)) {
int maxDepth = 5;

295

JAVA FROM EPAM
try (Stream streamDir = Files.find(path, maxDepth,
(p, a) -> String.valueOf(p).endsWith(".java"))) {
long counter = streamDir
.map(String::valueOf)
.peek(System.out::println)
.count();
System.out.println("found: " + counter);
} catch (IOException e) {
e.printStackTrace();
}
}

В результате будут выведены на консоль все файлы с расширением .java из
указанной директории и всех поддиректорий с глубиной погружения 5, а также
подсчитано их общее число.
Метод Files.walk(Path start, int maxDepth) позволяет получить список всех
файлов и подкаталогов указанного каталога:
Path start = Paths.get("src");
int maxDepth = 5;
try (Stream pathStream = Files.walk(start, maxDepth)) {
pathStream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}

При работе с файлами и директориями\каталогами лучше воспользоваться
возможностями пакета java.nio.file, так как его классы позволяют учитывать
очень широкий набор свойств объектов.
Класс java.io.File обладает достаточно ограниченной функциональностью.
Класс File служит для хранения и обработки в качестве объектов каталогов
и имен файлов. Этот класс не содержит методы для работы с содержимым файла, но позволяет манипулировать такими свойствами файла, как право доступа,
дата и время создания, путь в иерархии каталогов, создание, удаление файла,
изменение его имени и каталога и т.д.
Объект класса File создается одним из способов:
File
File
File
File

file = new File("\\com\\file.txt");
dir = new File("c:/jdk/src/java/io");
file1 = new File(dir, "File.java");
file2 = new File("c:\\com", "file.txt");

В первом случае создается объект, соответствующий файлу, во втором —
подкаталогу. Третий и четвертый случаи практически идентичны. Для создания объекта указывается каталог и имя файла.
При создании объекта класса File любым из конструкторов компилятор не
выполняет проверку на существование физического файла с заданным путем.
Существует разница между разделителями, употребляющимися при записи
пути к файлу: для системы Unix — «/», а для Windows — «\\». Для случаев,
296

ПОТОКИ ВВОДА/ВЫВОДА
когда неизвестно, в какой системе будет выполняться код, предусмотрены специальные поля в классе File:
public static final String separator;
public static final char separatorChar;

С помощью этих полей можно задать путь, универсальный в любой cистеме:
File file = new File(File.separator + "com" + File.separator + "data.txt");

Также предусмотрен еще один тип разделителей для директорий:
public static final String pathSeparator;
public static final char pathSeparatorChar;

К примеру, для ОС Unix значение pathSeparator принимает значение «:»,
а для OC MS-DOS — «;».
Некоторые возможности класса File представлены в следующем примере:
/* # 3 # работа с файловой системой: FileMain.java */
package by.epam.learn.io;
import java.io.*;
import java.time.Instant;
public class FileMain {
public static void main(String[] args) {
File file = new File("data" + File.separator + "info.txt");
if (file.exists() && file.isFile()) {
System.out.println("Path " + file.getPath());
System.out.println("Absolute Path " + file.getAbsolutePath());
System.out.println("Size " + file.length());
System.out.println("Dir " + file.getParent());
file.delete();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
File dir = new File("data");
if (dir.exists() && dir.isDirectory()) {
for (File current : dir.listFiles()) {
long millis = current.lastModified();
Instant date = Instant.ofEpochMilli(millis);
System.out.println(current.getPath() + "\t" + current.length() + "\t" + date);
}
File root = File.listRoots()[0];
System.out.printf("\n%s %,d from %,d free bytes", root.getPath(), root.getUsableSpace(),
root.getTotalSpace());
}
}
}

297

JAVA FROM EPAM
В результате файл data\info.txt будет очищен, а на консоль выведено:
Path data\info.txt
Absolute Path C:\Users\Ihar_Blinou\IdeaProjects\Learn1\data\info.txt
Size 0
Dir data
data\info.txt 0
2019-03-14T17:27:39.978
data\out.dat 90
2019-03-12T16:50:37.822
data\out.txt
21
2019-03-12T15:38:37.312
data\res.txt
105
2019-03-13T15:49:19.761
C:\ 50,595,426,304 from 255,820,034,048 free bytes
У каталога как объекта класса File есть дополнительное свойство — просмотр списка имен файлов с помощью методов list(), listFiles(), listRoots().

Чтение из потока
В отличие от Java 1.1 в языке Java 1.2 для ввода используется не байтовый,
а символьный поток. В этой ситуации для ввода используется подкласс
BufferedReader абстрактного класса Reader и методы read() и readLine() для
чтения символа и строки соответственно. Этот поток для организации чтения
из файла лучше всего инициализировать объектом класса FileReader в виде:
new BufferedReader(new FileReader(new File("data\\res.txt")));

Возможности по чтению информации из файла были существенно расширены в Java 7 и Java 8 с появлением класса Files и методов чтения в Stream.
Чтение из файла можно произвести следующим образом:
// # 4 # чтение строк из файла # ReadStringMain.java
package by.epam.learn.io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReadStringMain {
public static void main(String[] args) {
String stringLines = "";
try (BufferedReader reader =
new BufferedReader(new FileReader("data\\res.txt"))){
String tmp;
while ((tmp = reader.readLine()) != null) { //java 2
stringLines += tmp;
}
} catch (IOException e) {
e.printStackTrace();
}

298

ПОТОКИ ВВОДА/ВЫВОДА
System.out.println(stringLines);
}
}

Чтение производится по одной строке за каждую итерацию цикла. Строки
в цикле суммируются. Когда метод readLine() встречает символ конца файла,
то возвращает null.
В Java 7 в классе Files появился метод readAllLines(), считывающий сразу
весь файл в список строк.
// # 5 # чтение строк из файла как в java 7 # ReadStringMain1.java
package by.epam.learn.io;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class ReadStringMain1 {
public static void main(String[] args) {
String dirName = "data";
String filename = "res.txt";
Path path = FileSystems.getDefault().getPath(dirName, filename);
try {//java7
List lines = Files.readAllLines(path, StandardCharsets.UTF_8);
System.out.println(lines);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Появление Java 8 добавило новые методы по чтению строк файла в объект
Stream, с возможностью дальнейшего преобразования в строку или коллекцию.
В класс BufferedReader добавлен метод lines() чтения файла в Stream:
// # 6 # чтение строк из файла как в java 8 # ReadStringMain2.java
package by.epam.learn.io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ReadStringMain2 {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(
new FileReader("data\\res.txt"));
Stream stream = reader.lines()) { // java 8
String lines = stream.collect(Collectors.joining());

299

JAVA FROM EPAM
System.out.println(lines);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Результаты чтения были преобразованы в строку.
В класс Files добавлен статический метод newBufferedReader(Path path),
создающий объект BufferedReader.
// # 7 # чтение строк из файла как в java 8 # ReadStringMain3.java
package by.epam.learn.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class ReadStringMain3 {
public static void main(String[] args) {
Path path = Paths.get("data\\res.txt");
try (Stream stream = Files.newBufferedReader(path).lines()) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}

В тот же класс Files добавлен статический метод lines(), возвращающий
Stream из строк.
// # 8 # чтение строк из файла как в java 8 # ReadStringMain4.java
package by.epam.learn.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ReadStringMain4 {
public static void main(String[] args) {
Path path = Paths.get("data\\res.txt");
try(Stream streamLines = Files.lines(path)) {
String result = streamLines.collect(Collectors.joining());
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}

300

ПОТОКИ ВВОДА/ВЫВОДА

Предопределенные стандартные потоки
Система вывода языка Java содержит стандартные потоки вывода значений
простых типов, объектов и ошибок в удобочитаемом виде. Класс System пакета java.lang содержит поля out, err — ссылки на объекты байтового потока
PrintStream, объявленные со спецификаторами public static, являющиеся
стандартными потоками ввода, вывода и вывода ошибок соответственно.
Эти потоки связаны с консолью, но могут быть переназначены на другое
устройство.
Для назначения вывода текстовой информации в произвольный поток следует использовать символьный поток PrintWriter, являющийся подклассом
абстрактного класса Writer. Кроме того, невозможно оборачивать символьный
поток байтовым, в то время как обратное возможно.
Для вывода информации в файл или в любой другой поток стоит организовать следующую последовательность инициализации потоков с помощью класса
PrintWriter:
new PrintWriter(new BufferedWriter(new FileWriter(new File("text\\data.txt"))));

В итоге класс PrintWriter выступает классом-оберткой для класса
BufferedWriter, так же, как и класс BufferedReader для FileReader. По окончании работы с потоками закрывать следует только самый последний класс.
В данном случае — PrintWriter. Все остальные, в него обернутые, будут зак­
рыты автоматически.
В приведенном ниже примере демонстрируется вывод в файл строк и чисел
с плавающей точкой.
// # 9 # буферизованный вывод в файл # PrintMain.java
package by.epam.learn.io;
import java.io.*;
public class PrintMain {
public static void main(String[] args) {
double[] values = {1.10, 1.2};
try(PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("data\\r.txt")))){
for (double value: values) {
writer.printf("Java %.2g%n", value);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Для вывода данных в файл в текстовом формате использовался фильтрованный поток вывода PrintWriter и метод printf(). После соединения этого потока
301

JAVA FROM EPAM
с дисковым файлом посредством символьного потока BufferedWriter и удобного средства записи в файл FileWriter, становится возможной запись текстовой информации с помощью обычных методов println(), print(), printf(),
format(), write(), append(), что и показано в следующем фрагменте кода:
double[] values = {14.10, 17};
try(PrintStream stream = new PrintStream(new FileOutputStream("data\\res.txt"))){
for (double value: values) {
stream.printf("Java %.2g%n", value);
System.setOut(stream);
System.out.printf("%.2g%n", value);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}

Сериализация объектов
Кроме данных базовых типов, в поток можно отправлять объекты классов
целиком в байтовом представлении для передачи клиентскому приложению,
а также для хранения в файле или базе данных.
Процесс преобразования объектов в потоки байтов для хранения называется сериализацией. Процесс извлечения объекта из потока байтов называется
десериализацией. Существует два способа сделать объект сериализуемым.
Для того, чтобы объекты класса могли быть подвергнуты процессу сериализации, этот класс должен имплементировать интерфейс java.io.Serializable. Все подклассы такого класса также будут сериализованы. Многие стандартные классы реа­
лизуют этот интерфейс. Этот процесс заключается в сериализации каждого поля
объекта, но только в том случае, если это поле не имеет спецификатора static или
transient. Спецификаторы transient и static означают, что поля, помеченные ими,
не могут быть предметом сериализации, но существует различие в десериализации. Так, поле со спецификатором transient после десериализации получает значение по умолчанию, соответствующее его типу (объектный тип всегда инициализируется по умолчанию значением null), а поле со спецификатором static получает
значение по умолчанию в случае отсутствия в области видимости объектов своего
типа, а при их наличии получает значение, которое определено для существующего
объекта. На поля, помеченные как final, ключевое слово transient не действует.
Интерфейс Serializable не имеет методов, которые необходимо реализовать,
поэтому его использование ограничивается упоминанием при объявлении
класса. Все действия в дальнейшем производятся по умолчанию. Для записи
объектов в поток необходимо использовать класс ObjectOutputStream. После
этого достаточно вызвать метод writeObject(Object ob) этого класса для сериа­
лизации объекта ob и пересылки его в выходной поток данных. Для чтения
используются, соответственно, класс ObjectInputStream и его метод readObject(),
302

ПОТОКИ ВВОДА/ВЫВОДА
возвращающий ссылку на класс Object. После этого следует преобразовать полученный объект к нужному типу.
Необходимо знать, что при использовании Serializable десериализация происходит следующим образом: под объект выделяется память, после чего его
поля заполняются значениями из потока. Конструктор сериализуемого класса
при этом не вызывается, но вызываются все конструкторы суперклассов в заданной последовательности до класса, имплементирующего Serializable.
/* # 10 # сериализуемый класс # Student.java */
package by.epam.learn.io;
import java.io.Serializable;
import java.util.StringJoiner;
public class Student implements Serializable {
static String faculty = "MMF";
private String name;
private int id;
private transient String password;
private static final long serialVersionUID = 2L;
public Student(String name, int id, String password) {
this.name = name;
this.id = id;
this.password = password;
}
@Override
public String toString() {
return new StringJoiner(", ", Student.class.getSimpleName() + "[", "]")
.add("name='" + name + "'").add("id=" + id)
.add("password='" + password + "'").toString();
}
}

/* # 11 # запись сериализованного объекта в файл # SerializationMain.java */
package by.epam.learn.io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationMain {
public static void main(String[] args) {
try(ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("data/o.dat"))) {
Student student = new Student("Janka", 555777, "VKL_1410");
System.out.println(student);
output.writeObject(student);
} catch (IOException e) {
e.printStackTrace();
}
}
}

303

JAVA FROM EPAM
Объект student будет сериализован в файл, а в консоль выведено:
Student[name='Janka', id=555777, password='VKL_1410']
/* # 12 # десериализация объекта из файла # DeSerializationMain.java */
package by.epam.learn.io;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeSerializationMain {
public static void main(String[] args) {
Student.faculty ="GEO";
try(ObjectInputStream input = new ObjectInputStream(
new FileInputStream("data/out.dat"))) {
Student student = (Student)input.readObject();
System.out.println(student);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

В результате выполнения данного кода в консоль будет выведено:
Student[name='Janka', id=555777, password='null']
В итоге поля name и id нового объекта student сохранили значения, которые
им были присвоены до записи в файл. Полe passwоrd со спецификатором
transient получило значение по умолчанию, соответствующее типу (объектный тип всегда инициализируется по умолчанию значением null). Поле faculty,
помеченное как статическое, получает то значение, которое имеет это поле на
текущий момент, то есть при создании объекта student поле получило при инициализации значение MMF, а затем значение статического поля было изменено на GEO. Если же объекта данного типа не было в области видимости, а значение статического поля не было задано, то статическое поле также получает
значение по умолчанию.
Если полем класса является ссылка на другой тип, то необходимо, чтобы
агрегированный тип также реализовывал интерфейс Serializable, иначе при
попытке сериализации объекта такого класса будет сгенерировано исключение
NotSerializableException.
/* # 13 # класс, сериализация которого невозможна # Student.java */
public class Student implements Serializable {
static String faculty;
private int id;
private String name;
private transient String password;

304

ПОТОКИ ВВОДА/ВЫВОДА
private Address addr = new Address(); // non-serializable field
private static final long serialVersionUID = 2L;
// more code
}

Если класс Address не имплементирует интерфейс Serializable, а объявлен как:
public class Address {
}

то при таком объявлении класса Address сериализация объекта класса Student
невозможна.
При сериализации объекта класса, реализующего интерфейс Serializable, учитывается порядок объявления полей в классе. Поэтому при изменении порядка,
имен и типов полей или добавлении новых полей в класс структура информации,
содержащейся в сериализованном объекте, будет серьезно отличаться от новой
структуры класса. Поэтому десериализация может пройти некорректно. Этим обус­
ловлена необходимость добавления программистом в каждый класс, реализующий интерфейс Serializable, поля private static final long serialVersionUID на стадии разработки класса. Это поле содержит уникальный идентификатор версии
класса. Оно задается программистом или вычисляется по содержимому класса — полям, их порядку объявления, методам, их порядку объявления. Для этого
применяются специальные программы-генераторы UID.
Это поле записывается в поток при сериализации класса. Это тот случай,
когда static-поле сериализуется.
При десериализации значение этого поля сравнивается с имеющимся
у класса в виртуальной машине. Если значения не совпадают, инициируется
исключение java.io.InvalidClassException. Соответственно, при любом изменении в первую очередь полей класса значение поля serialVersionUID должно
быть изменено программистом или генератором.
Если набор полей класса и их порядок жестко определены, методы класса могут меняться. В этом случае сериализации и десериализации ничего не угрожает.
Вместо реализации интерфейса Serializable можно реализовать
Externalizable, который содержит два метода: writeExternal(ObjectOutput
out) и readExternal(ObjectInput in).
При использовании этого интерфейса в поток автоматически записывается
только идентификация класса. Сохранить и восстановить всю информацию
о состоянии экземпляра должен сам класс. Для этого в нем должны быть пере­
определены методы writeExternal() и readExternal() интерфейса Externalizable.
Эти методы должны обеспечить сохранение состояния, описываемого полями
самого класса и его суперкласса.
При восстановлении Externalizable-объекта экземпляр создается путем вызова конструктора без аргументов, после чего вызывается метод readExternal(),
поэтому необходимо проследить, чтобы в классе был конструктор по умолчанию. Для сохранения состояния вызываются методы ObjectOutput, с помощью
305

JAVA FROM EPAM
которых можно записать как примитивные, так и объектные значения. Для корректной работы в соответствующем методе readExternal() эти значения должны быть считаны в том же порядке.
Для чтения и записи в поток значений отдельных полей объекта ис­
пользуются методы внутренних классов: ObjectInputStream.GetField
и ObjectOutputStream.PutField.

Сериализация в XML
C понятием сериализации связан еще один важный термин, называемый
JavaBeans.
Какие требования должен выполнять класс, чтобы называться JavaBeanклассом?
—— объявление класса со спецификатором public;
—— объявление public-конструктора без параметров (по умолчанию);
—— объявление инкапсулированных полей;
—— объявление корректных get-еров и set-еров для каждого нестатического поля;
—— возможность сохранения объекта целиком – сериализация.
До выхода Java 1.4 единственным способом сериализации объекта была имплементация интерфейса Serializable. В Java 1.4 понятие сериализации было
расширено возможностью сохранения объекта в XML-файле с помощью метода writeObject(Object obj) класса java.beans.XMLEncoder, что само по себе
напоминает
механизм
сериализации
класса
ObjectOutputStream.
Десериализация легко выполняется методом Object readObject() класса
java.beans.XMLDecoder.
Чтобы класс Student мог быть подвергнут XML-сериализации, он должен
быть объявлен в виде:
/* # 14 # класс для сериализации в XML-файл # Student.java */
package by.epam.learn.io;
import java.io.Serializable;
import java.util.StringJoiner;
public class Student implements Serializable {
static String faculty = "MMF";
private String name;
private int id;
private transient String password;
private static final long serialVersionUID = 2L;
public Student(){}
public Student(String name, int id, String password) {
this.name = name;
this.id = id;
this.password = password;
}

306

ПОТОКИ ВВОДА/ВЫВОДА
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return new StringJoiner(", ", Student.class.getSimpleName() + "[", "]")
.add("name='" + name + "'").add("id=" + id)
.add("password='" + password + "'").toString();
}
}

Статические поля не сериализуются. Поля со спецификатором transient —
сериализуются в XML.
Реализация процесса приведена в следующем примере:
/* # 15 # запись сериализованного объекта в XML-файл # XmlSerializeMain.java */
package by.epam.learn.io;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
public class XmlSerializeMain {
public static void main(String[] args) {
try (XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(
new FileOutputStream("data\\serial.xml")))) {
Student student = new Student("Janka", 555777, "VKL_1410");
xmlEncoder.writeObject(student);
xmlEncoder.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try (XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(
new FileInputStream("data/serial.xml")))) {
Student student = (Student)xmlDecoder.readObject();
System.out.println(student);

307

JAVA FROM EPAM
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

Объект в результате в XML-файле сохраняется в виде:




555777


Janka


VKL_1410




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

Класс Scanner
Объект класса java.util.Scanner принимает форматированный объект или
ввод из потока и преобразует его в двоичное представление. При вводе могут
использоваться данные из консоли, файла, строки или любого другого источника, реализующего интерфейсы Readable, InputStream или ReadableByteChannel.
На настоящий момент класс Scanner предлагает наиболее удобный и полный
интерфейс для извлечения информации практически из любых источников.
Некоторые конструкторы класса:
Scanner(String source)
Scanner(InputStream source)
Scanner(Path source)
Scanner(Path source, String charset)

где source — источник входных данных, а charset — кодировка источника.
Объект класса Scanner читает лексемы из источника, указанного в конструкторе, например, из строки или файла. Лексема — это набор символов,
выделенный набором разделителей (по умолчанию пробельными символами).
В случае ввода из консоли следует определить объект:
Scanner console = new Scanner(System.in);

308

ПОТОКИ ВВОДА/ВЫВОДА
После создания объекта его используют для ввода информации в приложение, например, лексемы и строки,
String str1 = console.next();
String str2 = console.nextLine();

или типизированной лексемы, например, целого числа,
if(console.hasNextInt()) {
int number = console.nextInt();
}

В классе Scanner определены группы методов, проверяющих данные заданного типа на доступ для ввода. Для проверки наличия произвольной лексемы
используется метод hasNext(). Проверка конкретного типа производится с помощью одного из методов boolean hasNextТype() или boolean hasNextТype(int
radix), где radix — основание системы счисления. Например, вызов метода
hasNextInt() возвращает true, только если следующая входящая лексема — целое число. Если данные указанного типа доступны, они считываются с помощью
одного из методов Тype nextType(). Произвольная лексема считывается методом String next(). После извлечения любой лексемы текущий указатель устанавливается перед следующей лексемой.
В качестве примера можно рассмотреть форматированное чтение из файла
scan.txt, содержащего информацию следующего вида:
2 Java 1,6
true 1.7
// # 16 # разбор текстового файла # ScannerMain.java
package by.epam.learn.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Scanner;
public class ScannerMain {
public static void main(String[] args) {
String filename = "data\\scan.txt";
try(Scanner scan = new Scanner(new FileReader(filename))) {
while (scan.hasNext()) {
if (scan.hasNextInt()) {
System.out.println(scan.nextInt() + " :int");
} else if (scan.hasNextBoolean()) {
System.out.println(scan.nextBoolean() + " :boolean");
} else if (scan.hasNextDouble()) {
System.out.println(scan.nextDouble() + " :double");
} else {
System.out.println(scan.next() + " :String");
}
}

309

JAVA FROM EPAM
} catch (FileNotFoundException e) {
System.err.println(e);
}
}
}

В результате выполнения кода при белорусских региональных установках
операционной системы будет выведено:
2 :int
Java :String
1.6:double
true :boolean
1.7:String
Процедура проверки типа реализована методами типа hasNextType(). Такой
подход предпочтителен из-за отсутствия возможности возникновения исключительной ситуации, так как ее обработка требует на порядок больше ресурсов, чем нормальное течение программы.
Объект класса Scanner определяет границы лексемы, основываясь на
наборе разделителей. Можно задавать разделители с помощью метода
useDelimiter(Pattern pattern) или useDelimiter(String regex), где pattern
и regex содержит набор разделителей в виде регулярного выражения. При­ме­
нение метода useLocale(Locale loc) позволяет задавать правила чтения информации, принятые в заданной стране или регионе.
/* # 17 # применение разделителей и локалей # ScannerDelimiterMain.java*/
package by.epam.learn.io;
import java.util.Locale;
import java.util.Scanner;
public class ScannerDelimiterMain {
public static void main(String[] args) {
double sum = 0.0;
String numbersStr = "1,3;2,0; 8,5; 4,8;9,0; 1; 10;";
Scanner scan = new Scanner(numbersStr)
.useLocale(Locale.FRANCE) // change to Locale.US
.useDelimiter(";\\s*");
while (scan.hasNext()) {
if (scan.hasNextDouble()) {
sum += scan.nextDouble();
} else {
System.out.println(scan.next());
}
}
System.out.printf("Sum = " + sum);
scan.close();
}
}

310

ПОТОКИ ВВОДА/ВЫВОДА
В результате выполнения программы будет выведено:
Sum = 36.6
Если заменить Locale на американскую, то результат будет иным, так как
представление чисел с плавающей точкой отличается.
Использование шаблона «;\\s*» указывает объекту класса Scanner, что символ «;» и ноль или более пробелов следует рассматривать как разделитель.
Метод String findInLine(Pattern pattern) или String findInLine(String
pattern) ищет заданный шаблон в текущей строке текста. Если шаблон найден,
соответствующая ему подстрока извлекается из строки ввода. Если совпадений не найдено, то возвращается null.
Методы String findWithinHorizon(Pattern pattern, int count) и String
findWithinHorizon(String pattern, int count) производят поиск заданного шаб­
лона в ближайших count символах. Можно пропустить образец с помощью метода skip(Pattern pattern).
Если в строке ввода найдена подстрока, соответствующая образцу pattern,
метод skip() просто перемещается за нее в строке ввода и возвращает ссылку
на вызывающий объект. Если подстрока не найдена, метод skip() генерирует
исключение NoSuchElementException.

Архивация
Для хранения классов языка Java и связанных с ними ресурсов в языке Java
используются сжатые архивные jar-файлы.
Для работы с архивами в спецификации Java существуют два пакета — java.
util.zip и java.util.jar, соответственно для архивов zip и jar. Различие форматов
jar и zip заключается только в расширении архива zip. Пакет java.util.jar аналогичен пакету java.util.zip, за исключением реализации конструкторов и метода
void putNextEntry(ZipEntry e) класса JarOutputStream. Ниже будет рассмотрен только пакет java.util.jar. Чтобы переделать все ниже представленные примеры на использование zip-архива, достаточно всюду в коде заменить Jar на Zip.
Пакет java.util.jar позволяет считывать, создавать и изменять файлы форматов jar, а также вычислять контрольные суммы входящих потоков данных.
Класс JarEntry (подкласс ZipEntry) используется для предоставления доступа к записям jar-файла. Некоторые методы класса:
void setMethod(int method) — устанавливает метод сжатия записи;
void setSize(long size) — устанавливает размер несжатой записи;
long getSize() — возвращает размер несжатой записи;
long getCompressedSize() — возвращает размер сжатой записи.
У класса JarOutputStream существует возможность записи данных в поток
вывода в jar-формате. Он переопределяет метод write() таким образом, чтобы
311

JAVA FROM EPAM
любые данные, записываемые в поток, предварительно подвергались сжатию.
Основными методами класса являются:
void setLevel(int level) — устанавливает уровень сжатия. Чем больше уровень сжатия, тем медленней происходит работа с таким файлом;
void putNextEntry(ZipEntry e) — записывает в поток новую jar-запись.
Этот метод переписывает данные из экземпляра JarEntry в поток вывода;
void closeEntry() — завершает запись в поток jar-записи и заносит дополнительную информацию о ней в поток вывода;
void write(byte b[], int off, int len) — записывает данные из буфера b, начиная с позиции off, длиной len в поток вывода;
void finish() — завершает запись данных jar-файла в поток вывода без закрытия потока.
К примеру, необходимо архивировать файлы в указанной директории. Если директория содержит другие директории, то их файлы также должны архивироваться,
но на глубину не выше значения maxDepth равное 10, задаваемое в методе walk().
/* # 18 # создание jar-архива # PackJar.java */
package by.epam.learn.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.zip.Deflater;
public class PackJar {
private String jarFileName;
public final static int BUFFER = 2_048;
public PackJar(String jarFileName) throws FileNotFoundException {
if(!jarFileName.endsWith(".jar")) {
throw new FileNotFoundException(jarFileName + " incorrect archive name");
}
this.jarFileName = jarFileName;
}
public void pack(String dirName) throws FileNotFoundException {
Path dirPath = Paths.get(dirName);
if (Files.notExists(dirPath) || !Files.isDirectory(dirPath)) {
throw new FileNotFoundException(dirPath + " not found");
}
List listFilesToJar = null;
try {
listFilesToJar = Files.walk(dirPath, 10).filter(

312

ПОТОКИ ВВОДА/ВЫВОДА
f -> !Files.isDirectory(f)).collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
Path[] temp = {};
Path[] filesToJar = listFilesToJar.toArray(temp);
// actually archiving
try (FileOutputStream outputStream = new FileOutputStream(jarFileName);
JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) {
byte[] buffer = new byte[BUFFER];
jarOutputStream.setLevel(Deflater.DEFAULT_COMPRESSION);
for (int i = 0; i < filesToJar.length; i++) {
String file = filesToJar[i].toString();
jarOutputStream.putNextEntry(new JarEntry(file));
try (FileInputStream in = new FileInputStream(file)) {
int len;
while ((len = in.read(buffer)) > 0) {
jarOutputStream.write(buffer, 0, len);
}
jarOutputStream.closeEntry();
} catch (FileNotFoundException e) {
System.err.println("File not found " + e);
}
}
} catch (IllegalArgumentException e) {
System.err.println("incorrect data " + e);
} catch (IOException e) {
System.err.println("I/O error " + e);
}
}
}

Расширить класс до архивации файлов из вложенных директорий относительно просто.
Класс JarFile обеспечивает гибкий доступ к записям, хранящимся в jar-файле. Это достаточно эффективный способ, поскольку доступ к данным осуществляется гораздо быстрее, чем при считывании каждой отдельной записи.
Единственным недостатком является то, что доступ может осуществляться только для чтения. Метод entries() извлекает все записи из jar-файла. Этот метод
возвращает список экземпляров JarEntry — по одной для каждой записи в jarфайле. Метод getEntry(String name) извлекает запись по имени. Метод
getInputStream() создает поток ввода для записи. Этот метод возвращает поток
ввода, который может использоваться приложением для чтения данных записи.
/* # 19 # запуск процесса архивации # PackMain.java */
package by.epam.learn.io;
import java.io.FileNotFoundException;
public class PackMain {

313

JAVA FROM EPAM
public static void main(String[] args) {
String dirName = "name_of_directory";
try {
PackJar packJar = new PackJar("example.jar");
packJar.pack(dirName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

В результате выполнения кода будет создан архивный файл example.jar,
размещенный в корне проекта.
Класс JarInputStream читает данные в jar-формате из потока ввода. Он
переопределяет метод read() таким образом, чтобы любые данные, считываемые из потока, предварительно распаковывались.
Теперь следует распаковать файл из архива и разместить по заданному пути,
к которому добавится исходный путь к файлам.
/* # 20 # чтение jar-архива # UnPackJar.java */
package by.epam.learn.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class UnPackJar {
private Path destinationPath;
// buffer size when unpacking
public static final int BUFFER = 2_048;
public void unpack(String destinationDirectory, String nameJar) {
try (JarFile jarFile = new JarFile(nameJar)) {
jarFile.stream().forEach(entry -> {
String entryname = entry.getName();
System.out.println("Extracting: " + entry);
destinationPath = Paths.get(destinationDirectory, entryname);
// create directory structure
destinationPath.getParent().toFile().mkdirs();
// unpack the record, if it is not a directory
if (!entry.isDirectory()) {
writeFile(jarFile, entry);
}
});
} catch (IOException e) {
e.printStackTrace();
}

314

ПОТОКИ ВВОДА/ВЫВОДА
}
private void writeFile(JarFile jar, JarEntry entry) {
int currentByte;
byte data[] = new byte[BUFFER];
try (BufferedInputStream bufferedInput =
new BufferedInputStream(jar.getInputStream(entry));
FileOutputStream fileOutput =
new FileOutputStream(destinationPath.toString());
BufferedOutputStream bufferedOutput =
new BufferedOutputStream(fileOutput, BUFFER)) {
// write the file to disk
while ((currentByte = bufferedInput.read(data, 0, BUFFER)) > 0) {
bufferedOutput.write(data, 0, currentByte);
}
bufferedOutput.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}

При указании пути к архивному файлу и пути, по которому требуется разместить разархивированные файлы, следует указывать либо абсолютный путь,
либо путь относительно корневой директории проекта.
/* # 21 # запуск процесса архивации # UnPackMain.java */
package by.epam.learn.io;
public class UnPackMain {
public static void main(String[] args) {
// location and archive name
String nameJar = "example.jar";
// directory to which the files will be unpacked
String destinationPath = "c:\\tmp\\";
new UnPackJar().unpack(destinationPath, nameJar);
}
}

На консоль будет выведен список разархивированных файлов, например:
Extracting: data\inf\serial.xml
Extracting: data\info.txt
Extracting: data\message.properties
Extracting: data\messages2.properties
Extracting: data\out.dat
Extracting: data\out.txt
Extracting: data\res.txt
Extracting: data\thread.txt
315

JAVA FROM EPAM

Вопросы к главе 10
1. Что такое поток данных? Какие потоки данных существуют в Java?
Привести иерархию потоков ввода-вывода в Java.
2. Какие классы байтовых потоков ввода-вывода существуют?
3. Какие классы символьных потоков ввода-вывода существуют?
4. Как работают методы read() и write() базовых классов иерархии символьных и байтовых потоков? Сравнить.
5. Для чего используются классы BufferedInputStream, BufferedOutputStream,
BufferedReader, BufferedWriter?
6. Для чего используются классы FilterInputStream, FilterOutputStream,
FilterReader, FilterWriter?
7. Для чего применяются классы InputStreamReader и OutputStreamWriter?
8. Что такое упаковка (wrapping) потоков?
9. Какие существуют предопределенные потоки ввода-вывода в Java? Кто эти
потоки создает, и кто их закрывает?
10. Что такое сериализация, для чего нужна, когда применяется? Правила сериализации объектов.
11. Что такое десериализация? Правила десериализации объектов.
12. Будет ли повторно сериализоваться уже сериализованный объект?
13. Что получится при десериализации, если при сериализации сохраняемые
объекты имели ссылки на одни и те же объекты?
14. Как происходит десериализация? Вызывается ли конструктор при десериализации? Как десериализуются объекты, созданные от классов, у которых
базовые классы несериализуемые?
15. Как сериализовать и десериализовать объект? Какие классы и интерфейсы
для этого необходимо использовать? Какое статическое поле сериализуется?
16. Что происходит при сериализации/десериализации объекта-синглтона. Как
правильно сериализовать синглтон?
17. Ключевое слово transient, для чего нужно?
18. Возможно ли сохранить объект не в байт-код, а в XML-файл?

Задания к главе 10
Вариант A
В следующих заданиях требуется ввести последовательность строк из текстового потока и выполнить указанные действия. При этом могут рассматриваться два варианта:
• каждая строка состоит из одного слова;
• каждая строка состоит из нескольких слов.
Имена входного и выходного файлов, а также абсолютный путь к ним могут
быть введены как параметры командной строки или храниться в файле.
316

ПОТОКИ ВВОДА/ВЫВОДА
1. В каждой строке найти и удалить заданную подстроку.
2. В каждой строке стихотворения найти и заменить заданную подстроку на
подстроку иной длины.
3. В каждой строке найти слова, начинающиеся с гласной буквы.
4. Найти и вывести слова текста, для которых последняя буква одного слова
совпадает с первой буквой следующего слова.
5. Найти в строке наибольшее число цифр, следующих подряд.
6. В каждой строке стихотворения Максима Богдановича подсчитать частоту
повторяемости каждого слова из заданного списка и вывести эти слова
в порядке возрастания частоты повторяемости.
7. В каждом слове повести Владимира Короткевича «Дикая охота короля
Стаха» заменить первую букву слова на прописную.
8. Определить частоту повторяемости букв и слов в стихотворении Адама
Мицкевича.

Вариант B
Выполнить задания из варианта B гл. 4, сохраняя объекты приложения в одном или нескольких файлах с применением механизма сериализации. Объекты
могут содержать поля, помеченные как static, а также transient. Для изменения
информации и извлечения информации в файле создать специальный классконнектор с необходимыми для выполнения этих задач методами.

Вариант С
При выполнении следующих заданий для вывода результатов создавать новую директорию и файл средствами класса File.
1. Создать и заполнить файл случайными целыми числами. Отсортировать
содержимое файла по возрастанию.
2. Прочитать текст Java-программы и все слова public в объявлении атрибутов и методов класса заменить на слово private.
3. Прочитать текст Java-программы и записать в другой файл в обратном порядке символы каждой строки.
4. Прочитать текст Java-программы и в каждом слове длиннее двух символов
все строчные символы заменить прописными.
5. В файле, содержащем фамилии студентов и ихоценки, записать прописными буквами фамилии тех студентов, которые имеют средний балл более 7.
6. Файл содержит символы, слова, целые числа и числа с плавающей запятой.
Определить все данные, тип которых вводится из командной строки.
7. Из файла удалить все слова, содержащие от трех до пяти символов, но при
этом из каждой строки должно быть удалено только максимальное четное
количество таких слов.
8. Прочитать текст Java-программы и удалить из него все лишние пробелы
и табуляции, оставив только необходимые для разделения операторов.
317

JAVA FROM EPAM
9. Из текста Java-программы удалить все виды комментариев.
10. Прочитать строки из файла и поменять местами первое и последнее слова
в каждой строке.
11. Ввести из текстового файла, связанного с входным потоком, последовательность строк. Выбрать и сохранить m последних слов в каждой из последних n строк.
12. Из текстового файла ввести последовательность строк. Выделить отдельные слова, разделяемые пробелами. Написать метод поиска слова по образцу-шаблону. Вывести найденное слово в другой файл.
13. Сохранить в файл, связанный с выходным потоком, записи о телефонах и их владельцах. Вывести в файл записи, телефоны в которых начинаются на k и на j.
14. Входной файл содержит совокупность строк. Строка файла содержит строку
квадратной матрицы. Ввести матрицу в двумерный массив (размер матрицы
найти). Вывести исходную матрицу и результат ее транспонирования.
15. Входной файл хранит квадратную матрицу по принципу: строка представляет собой число. Определить размерность. Построить двумерный массив,
содержащий матрицу. Вывести исходную матрицу и результат ее поворота
на 90˚ по часовой стрелке.
16. В файле содержится совокупность строк. Найти номера строк, совпадающих
с заданной строкой. Имя файла и строка для поиска — аргументы командной
строки. Вывести строки файла и номера строк, совпадающих с заданной.

Тестовые задания к главе 10
Вопрос 10.1.
Для чего используется метод flush() класса OutputStream? (выбрать один)
a) очистка выходного буфера с последующим завершением операции вывода данных;
b) закрытие выходного потока;
c) установка минимально возможного размера выходного буфера;
d) вывод выходного буфера.

Вопрос 10.2.
Объектом какого класса является статическое поле out класса System? (выб­
рать один)
a) DataOutputStream
b) OutputStream
c) BufferedPrintStream
d) BufferedOutputStream
e) PrintStream
318

ПОТОКИ ВВОДА/ВЫВОДА

Вопрос 10.3.
Дан фрагмент кода:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter number: ");
// line 1

Какой из операторов следует вставить вместо line 1, чтобы из консоли было
прочитано число? (выбрать один)
a) int number = Integer.valueOf(reader.readLine());
b) int number = reader.read();
c) int number = new Scanner(reader.nextInt());
d) int number = String.parseInt(reader.readLine());
e) ни один из перечисленных.

Вопрос 10.4.
Дан код:
class Quest {
public static void main(String[] args) throws IOException {
Path source = Paths.get("datares/data/mail.properties");
Path destination = Paths.get("datares/mail.properties");
Files.copy(source, destination);
}
}

Каким будет результат запуска кода, если файл mail.properties существует
в директории datares/data ? (выбрать один)
a) генерация исключения NoSuchFileException;
b) генерация исключения FileAlreadyExistsException;
c) генерация исключения FileNotFoundException;
d) код выполнится корректно, но никаких изменений произведено не будет;
e) файл mail.properties будет скопирован в директорию datares.

Вопрос 10.5.
Дан фрагмент кода. Выбрать одно корректное утверждение:
BufferedWriter
BufferedWriter
BufferedWriter
BufferedWriter

a)
b)
c)
d)
e)
f)

b1
b2
b3
b4

=
=
=
=

new
new
new
new

BufferedWriter(new
BufferedWriter(new
BufferedWriter(new
BufferedWriter(new

File("data.txt")); // 1
FileWriter("data.txt")); // 2
PrintWriter("data.txt")); // 3
BufferedWriter(b3)); // 4

все строки скомпилируются корректно;
все строки скомпилируются некорректно;
ошибка компиляции в строке 1;
ошибка компиляции в строке 2;
ошибка компиляции в строке 3;
ошибка компиляции в строке 4.

Глава 11

КОЛЛЕКЦИИ И STREAM API
Программирование заставило дерево зацвести.
Алан Дж. Перлис

Общие определения
Коллекции — это хранилища или контейнеры, поддерживающие различные
способы накопления и упорядочения объектов с целью обеспечения возможностей эффективного доступа к ним. Они представляют собой реализацию абстрактных структур данных, поддерживающих три основные операции:
• добавление нового элемента в коллекцию;
• удаление элемента из коллекции;
• изменение элемента в коллекции.
В качестве других операций могут быть реализованы следующие: заменить,
просмотреть элементы, подсчитать их количество и др.
Для работы с коллекциями разработчиками был создан Collection Framework.
Применение коллекций обусловливается возросшими объемами обрабатываемой
информации. Когда счет используемых объектов идет на сотни тысяч или миллионов, массивы не обеспечивают ни должной скорости, ни экономии ресурсов.
Примером коллекции является стек (структура LIFO — Last In First Out), в котором всегда удаляется объект, вставленный последним. Для очереди (структура
FIFO — First In First Out) используется другое правило удаления: всегда уда­
ляется элемент, вставляемый первым. В абстрактных типах данных существует
несколько видов очередей: двусторонние очереди, кольцевые очереди, обобщенные очереди, в которых запрещены повторяющиеся элементы. Стеки и очереди
могут быть реализованы как на базе массива, так и на базе связного списка.
Коллекции в языке Java объединены в библиотеке классов java.util и представляют собой контейнеры для хранения и манипулирования объектами. До появления Java 2 эта библиотека содержала классы только для работы с простейшими структурами данных: Vector, Stack, Hashtable, BitSet, а также интерфейс
Enumeration для работы с элементами этих классов. Коллекции, появившиеся
в Java 2, представляют собой общую технологию хранения и доступа к объектам.
Скорость обработки коллекций повысилась по сравнению с предыдущей версией
языка за счет отказа от их потокобезопасности. Поэтому, если объект коллекции
может быть доступен из различных потоков, что наиболее естественно для распределенных приложений, возможно использование коллекции из Java 1.
320

КОЛЛЕКЦИИ И STREAM API
В Java 5 в новом пакете java.util.concurrent появились ограниченно потокобезопасные коллекции, гарантирующие более высокую производительность
в многопоточной среде для конкурирующих потоков.
Так как в коллекциях при практическом программировании хранится набор
ссылок на объекты одного типа, следует обезопасить коллекцию от появления
ссылок на другие, не разрешенные логикой приложения типы. Такие ошибки
при использовании нетипизированных коллекций выявляются на стадии выполнения, что повышает трудозатраты на исправление и верификацию кода. Поэтому,
начиная с версии Java SE 5, коллекции стали типизированными или generic.
Более удобным стал механизм работы с коллекциями, а именно:
• предварительное сообщение компилятору о типе ссылок, которые будут храниться в коллекции, при этом проверка осуществляется на этапе компиляции;
• отсутствие необходимости постоянно преобразовывать возвращаемые по
ссылке объекты (тип Object) к требуемому типу.
Структура коллекций характеризует способ, с помощью которого программы Java обрабатывают группы объектов. Так как Object — суперкласс для всех
классов, то в коллекции можно хранить объекты любого типа, кроме базовых.
Коллекции — это динамические массивы, связные списки, деревья, множества, хэш-таблицы, стеки, очереди.
Интерфейсы коллекций:
Map — карта отображения вида «ключ-значение»;
Collection — основной интерфейс коллекций, вершина иерархии коллекций List, Set. Также наследует интерфейс Iterable;
List — специализирует коллекции для обработки упорядоченного набора элементов;
Set — множество, содержащее уникальные элементы;
Queue — очередь, где элементы добавляются в один конец списка, а извлекаются из другого конца.
Все классы коллекций реализуют интерфейсы Serializable, Cloneable (кроме WeakHashMap).

«interface»
Set

«interface»
Collection

«interface»
Map

«interface»
List

«interface»
Queue

Рис. 11.1. Базовая иерархия коллекций

«interface»
Deque

321

JAVA FROM EPAM
В интерфейсе Collection определены методы, которые работают на всех
коллекциях:
boolean add(E obj) — добавляет obj к вызывающей коллекции и возвращает
true, если объект добавлен, и false, если obj уже элемент коллекции;
boolean remove(Object obj) — удаляет obj из коллекции;
boolean addAll(Collection