ББК 32.973.2-018.1
УДК 004.43

Э14

Эванс Б., Вербург М.

Э14

Java. Новое поколение разработки. — СПб.: Питер, 2014. — 560 с.: ил.

ISBN 978-5-496-00544-9
В этой книге представлен оригинальный и практичный взгляд на новые возможности Java 7 и новые языки для виртуальной машины Java (JVM), а также рассмотрены некоторые вспомогательные
технологии, необходимые для создания Java-программ завтрашнего дня.
Книга начинается с подробного описания новшеств Java 7, таких как работа с ресурсами в блоке
try (конструкция try-with-resources) и новый неблокирующий ввод-вывод (NIO.2). Далее вас ждет экспресс-обзор трех сравнительно новых языков для виртуальной машины Java — Groovy, Scala и Clojure.
Вы увидите четкие понятные примеры, которые помогут вам ознакомиться с десятками удобных
и практичных приемов. Вы изучите современные методы разработки, обеспечения параллелизма,
производительности, а также многие другие интересные темы.
В этой книге:
— новые возможности Java 7;
— вводный курс по работе с языками Groovy, Scala и Clojure;
— обсуждение проблем многоядерной обработки и параллелизма;
— функциональное программирование на новых языках для JVM;
— современные подходы к тестированию, сборке и непрерывной интеграции.

12+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)

ББК 32.973.2-018.1
УДК 004.43

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

ISBN 978-1617290060 англ.

© 2013 by Manning Publications Co. All rights reserved.

ISBN 978-5-496-00544-9

© Перевод на русский язык ООО Издательство «Питер», 2014

© Издание на русском языке, оформление ООО Издательство «Питер», 2014

Краткое содержание
Вступление. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Предисловие . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Благодарности. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Об этой книге. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Об авторах. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Иллюстрация на обложке . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
От издательства. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Часть 1. Разработка на Java 7
Глава 1. Введение в Java 7. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Глава 2. Новый ввод-вывод. . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

Часть 2. Необходимые технологии
Глава 3. Внедрение зависимостей . . . . . . . . . . . . . . . . . . . . . . . 98
Глава 4. Современная параллельная обработка. . . . . . . . . . . . 124
Глава 5. Файлы классов и байт-код . . . . . . . . . . . . . . . . . . . . . 173
Глава 6. Понятие о повышении производительности . . . . . . . . 209

6

Краткое содержание

Часть 3. Многоязычное программирование
на виртуальной машине Java
Глава 7. Альтернативные языки для виртуальной
машины Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Глава 8. Groovy — динамический приятель Java. . . . . . . . . . . . 285
Глава 9. Язык Scala — мощный и лаконичный . . . . . . . . . . . . . 316
Глава 10. Clojure: программирование повышенной
надежности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359

Часть 4. Создание многоязычного проекта
Глава 11. Разработка через тестирование. . . . . . . . . . . . . . . . 397
Глава 12. Сборка и непрерывная интеграция. . . . . . . . . . . . . . 431
Глава 13. Быстрая веб-разработка. . . . . . . . . . . . . . . . . . . . . . 474
Глава 14. О сохранении основательности . . . . . . . . . . . . . . . . 509

Приложения
Приложение A. Установка исходного кода java7developer. . . . 528
Приложение B. Синтаксис и примеры паттернов
подстановки. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537
Приложение C. Установка альтернативных языков
для виртуальной машины Java . . . . . . . . . . . . . . . . . . . . . . 539
Приложение D. Скачивание и установка Jenkins. . . . . . . . . . . 547
Приложение E. java7developer — Maven POM . . . . . . . . . . . . . 550

Оглавление
Вступление. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Предисловие. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Благодарности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Лондонское Java-сообщество. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
www.coderanch.com. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Manning publications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Особые благодарности. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Благодарности Бена Эванса. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Благодарности Мартина Вербурга . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

Об этой книге . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Как работать с этой книгой . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Для кого предназначена книга. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Дорожная карта. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Соглашения в коде и материал для скачивания . . . . . . . . . . . . . . . . . . . . 34
Требования к программному обеспечению. . . . . . . . . . . . . . . . . . . . . . . . 35

Об авторах. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Иллюстрация на обложке. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
От издательства. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

8

Оглавление

Часть 1. Разработка на Java 7
Глава 1. Введение в Java 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
1.1. Язык и платформа. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
1.2. Малое прекрасно — расширения языка Java,
или Проект «Монета» . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.3. Изменения в рамках проекта «Монета». . . . . . . . . . . . . . . . . . . . . . . 47
1.3.1. Строки в конструкции switch . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.3.2. Усовершенствованный синтаксис для числовых
литералов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.3.3. Усовершенствованная обработка исключений . . . . . . . . . . . . 51
1.3.4. Использование ресурсов в блоке try (try-with-resources). . . . . 53
1.3.5. Ромбовидный синтаксис. . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
1.3.6. Упрощенный вызов методов с переменным
количеством аргументов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.4. Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Глава 2. Новый ввод-вывод. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.1. История ввода-вывода в Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.1.1. Java 1.0–1.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.1.2. Java 1.4 и неблокирующий ввод-вывод . . . . . . . . . . . . . . . . . 63
2.1.3. Введение в NIO.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
2.2. Path — основа файлового ввода-вывода . . . . . . . . . . . . . . . . . . . . . . 64
2.2.1. Создание пути. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.2.2. Получение информации о пути . . . . . . . . . . . . . . . . . . . . . . . 68
2.2.3. Избавление от избыточности. . . . . . . . . . . . . . . . . . . . . . . . . 69
2.2.4. Преобразование путей. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
2.2.5. Пути NIO.2 и класс File, существующий в Java . . . . . . . . . . . . 71
2.3. Работа с каталогами и деревьями каталогов . . . . . . . . . . . . . . . . . . . 71
2.3.1. Поиск файлов в каталоге. . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
2.3.2. Движение по дереву каталогов . . . . . . . . . . . . . . . . . . . . . . . 72

9

Оглавление

2.4. Ввод-вывод файловой системы при работе с NIO.2 . . . . . . . . . . . . . . 74
2.4.1. Создание и удаление файлов. . . . . . . . . . . . . . . . . . . . . . . . . 75
2.4.2. Копирование и перемещение файлов . . . . . . . . . . . . . . . . . . 76
2.4.3. Атрибуты файлов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
2.4.4. Быстрое считывание и запись данных . . . . . . . . . . . . . . . . . . 82
2.4.5. Уведомление об изменении файлов. . . . . . . . . . . . . . . . . . . . 83
2.4.6. SeekableByteChannel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
2.5. Асинхронные операции ввода-вывода . . . . . . . . . . . . . . . . . . . . . . . . 85
2.5.1. Стиль с ожиданием. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
2.5.2. Стиль с применением обратных вызовов. . . . . . . . . . . . . . . . 89
2.6. Окончательная шлифовка технологии сокет — канал. . . . . . . . . . . . . 91
2.6.1. NetworkChannel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2.6.2. MulticastChannel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
2.7. Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

Часть 2. Необходимые технологии
Глава 3. Внедрение зависимостей. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
3.1. Дополнительные знания: понятие об инверсии управления
и внедрении зависимостей. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.1.1. Инверсия управления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.1.2. Внедрение зависимостей. . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.1.3. Переход к внедрению зависимостей . . . . . . . . . . . . . . . . . . 102
3.2. Стандартизированное внедрение зависимостей в Java. . . . . . . . . . . 107
3.2.1. Аннотация @Inject. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3.2.2. Аннотация @Qualifier. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
3.2.3. Аннотация @Named. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
3.2.4. Аннотация @Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
3.2.5. Аннотация @Singleton. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
3.2.6. Интерфейс Provider<T>. . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

10

Оглавление

3.3. Guice 3 — эталонная реализация внедрения зависимостей
в Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
3.3.1. Знакомство с Guice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
3.3.2. Морские узлы — различные связи в Guice . . . . . . . . . . . . . . 118
3.3.3. Задание области видимости для внедренных объектов
в Guice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.4. Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

Глава 4. Современная параллельная обработка. . . . . . . . . . . . . . . . 124
4.1. Теория параллелизма — базовый пример . . . . . . . . . . . . . . . . . . . . 125
4.1.1. Рассмотрение модели потоков в Java. . . . . . . . . . . . . . . . . . 125
4.1.2. Структурные концепции . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
4.1.3. Как и в каких случаях возникает конфликт . . . . . . . . . . . . . 128
4.1.4. Источники издержек . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.1.5. Пример обработчика транзакций. . . . . . . . . . . . . . . . . . . . . 130
4.2. Параллельная обработка с блочной структурой (до Java 5) . . . . . . . 131
4.2.1. Синхронизация и блокировки . . . . . . . . . . . . . . . . . . . . . . . 132
4.2.2. Модель состояния для потока . . . . . . . . . . . . . . . . . . . . . . . 133
4.2.3. Полностью синхронизированные объекты . . . . . . . . . . . . . . 134
4.2.4. Взаимные блокировки. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
4.2.5. Почему synchronized?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
4.2.6. Ключевое слово volatile. . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
4.2.7. Неизменяемость . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
4.3. Составные элементы современных параллельных приложений . . . . 142
4.3.1. Атомарные классы — java.util.concurrent.atomic . . . . . . . . . . 142
4.3.2. Блокировки — java.util.concurrent.locks. . . . . . . . . . . . . . . . . 143
4.3.3. CountDownLatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
4.3.4. ConcurrentHashMap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
4.3.5. CopyOnWriteArrayList. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
4.3.6. Очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

Оглавление

11

4.4. Контроль исполнения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
4.4.1. Моделирование задач. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
4.4.2. ScheduledThreadPoolExecutor. . . . . . . . . . . . . . . . . . . . . . . . 163
4.5. Фреймворк fork/join (ветвление/слияние). . . . . . . . . . . . . . . . . . . . 164
4.5.1. Простой пример fork/join. . . . . . . . . . . . . . . . . . . . . . . . . . . 165
4.5.2. ForkJoinTask и захват работы. . . . . . . . . . . . . . . . . . . . . . . . 168
4.5.3. Параллелизация проблем . . . . . . . . . . . . . . . . . . . . . . . . . . 168
4.6. Модель памяти языка Java (JMM). . . . . . . . . . . . . . . . . . . . . . . . . . . 169
4.7. Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

Глава 5. Файлы классов и байт-код. . . . . . . . . . . . . . . . . . . . . . . . . . . 173
5.1. Загрузка классов и объекты классов . . . . . . . . . . . . . . . . . . . . . . . . 174
5.1.1. Обзор — загрузка и связывание. . . . . . . . . . . . . . . . . . . . . . 174
5.1.2. Объекты классов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
5.1.3. Загрузчики классов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
5.1.4. Пример — загрузчики классов при внедрении
зависимостей. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
5.2. Использование дескрипторов методов. . . . . . . . . . . . . . . . . . . . . . . 181
5.2.1. MethodHandle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
5.2.2. MethodType. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
5.2.3. Поиск дескрипторов методов. . . . . . . . . . . . . . . . . . . . . . . . 183
5.2.4. Пример: сравнение рефлексии, использования
посредников и дескрипторов методов. . . . . . . . . . . . . . . . . . . . . . 184
5.2.5. Почему стоит выбирать дескрипторы методов. . . . . . . . . . . 187
5.3. Исследование файлов классов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
5.3.1. Знакомство с javap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
5.3.2. Внутренняя форма сигнатур методов. . . . . . . . . . . . . . . . . . 189
5.3.3. Пул констант. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
5.4. Байт-код. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
5.4.1. Пример: дизассемблирование класса. . . . . . . . . . . . . . . . . . 194
5.4.2. Среда времени исполнения. . . . . . . . . . . . . . . . . . . . . . . . . 196

. .5. . . . . . . . . 212 6. . . . . . 215 6.4. . . . . . . . . . . 214 6. . . . . . . .1. . . . . . . . . . . . . . . . Коды операций для контроля исполнения . . . . . . . . . . . . . .4. .2. . . . . Коды операций для активизации . . .2. . . . . .1. . . . . . . . . . . . . . 212 6. . . . . . . . . . .3. . . . . Пример: сцепление (конкатенация) строк . . .4. . . . . . . .1. . . . .4. . . . . . . . . . . . . . . . . .5. . . . . 216 6. . Умейте проводить измерения . . . . .9. . . . . . . . . . . .2. 199 5. . . . . . . . . 201 5. . . . . . . . Коды операций для загрузки и сохранения . .4. . . . . . .2. . . . . . . . . . . . . . . . . . . . . . . . . . . Введение в коды операций . . .3. . . . . . . . . Эффективность. . . . . . когда следует прекратить оптимизацию . . . . . 217 6. . . . . . . . . . . 213 6. . . . .6. . . . .4. . . . . . . Деградация. . . . . . . 206 5. . . . . . Знайте. . 209 6. . . . . 211 6. . . . .5. . . . . . . . . . . . . . . . . . . . . . .10. . . . . . 218 . . .5. . . . . . . . . .4. . . .6. . . . . . . . .12 Оглавление 5. . . . . .1. . . . . . . . . . . . .5. . . . . . . . . . . . . .2.1. . . 207 Глава 6. . . . . . . . . . . . . . . . . . . . 213 6. . . .1. . . . . . .1. . Сокращенные формы записи кодов операций . . . . . . . . . . . . . . . . Коды операций для работы с платформой. . . . .4. . . . . . . . . . . . . .7. . . 200 5. . . . . . . . . . . . . . .1. . . . . . . . . . . . . . . . . . . . . . . . . Пропускная способность . . . . Пример: дизассемблирование invokedynamic-вызова . . . . . . . . 202 5. . . 213 6. 205 5. . . . . . . Мощность . . . . . . . . . . . . .2. . .3. . . . . . . . . . Терминологическое описание производительности — базовые определения . . . . . . . . . . . . . . . . . . . . что именно вы измеряете . . . . . . Коэффициент использования. . . . . . . . . . . . . 197 5. 213 6. . . . .4. . . . . 202 5. . . . . . Масштабируемость . . Знайте.1. . . . . . . . . . . . . .1. . . . . . . .2. какого уровня производительности вы хотите достичь . . . Понятие о повышении производительности .7. . . . Ожидание. 204 5. . . . . . . . Знайте. . . . . . . . . .8.2.4. . . . . . . . . . . . Арифметические коды операций . invokedynamic . . 211 6.4. . . . . . . . 201 5.6. . . . . . 198 5. Резюме. . . . . Прагматический подход к анализу производительности. . . . .1. . Как работает invokedynamic. . . . . . . . .

. . . . . .6. Понятие об иерархии латентности памяти. . . . 254 6.1. . Практический пример: понятие о кэш-промахах. . . . . . . .1. . . . . . . . . . . . . . . . . . . . Знакомство с HotSpot. .6. . . . . . Резюме . . . . . . . . . 218 6. . . . . . . . . . . . . . . . . . . . . . . . .3. . . . Аппаратные часы . . Основы. . . . 225 6. . . . . . . . . . .Оглавление 13 6. . . . . . . . . . . . Параллельное отслеживание и очистка. . . . . . . . . . . . . . . . . . .4. . . . .3.1. 248 6. . 243 6. .2.3. . . 221 6. . . . . . . . . .5. . . . . . . . . . . Отслеживание и очистка. . . . . Что пошло не так? И почему нас это должно волновать?. . . . . . . 224 6. .3. . . . . . . . . . . . . . . . . . .4. . . . .4. . . . . . 222 6. 255 6. . . . . . . Динамическая компиляция и мономорфные вызовы. . . . . . 234 6. .6. 255 6. . . . . .5.9. . . . . . . . . . Знайте об опасности поспешной оптимизации.3. . . .4. 226 6. . 252 6. . . . . . . . . . . . . . .2. . . . .3. . . . . . .5. . Почему так сложно выполнять оптимизацию производительности в Java. . . . . . . . . . 250 6. . . Чтение журналов сборщика мусора. . . . . . . . . 232 6. . .4. . . . . . . . . . . . . . . . . .5. . . . . . . . . Визуализация использования памяти с помощью VisualVM. . . . . . .5. . . . . .1.4. . . . 257 . . . . . . . . . . . . . . . . . . . . . . . . . . . .6. . . какой ценой дается повышение производительности. Полезные переключатели виртуальной машины Java. . . . .6. . . . . . .5. 242 6. . .5. . . 220 6. . . . . . . . . . . . . . . . . . . . .4. . . . . . .5. . . . . . . . G1 — новый сборщик мусора для Java. . . . . . . . .6. . . . . . . . . .2. . . . . . Знайте. . . . . . . 236 6. . . . .4.5. Анализ локальности. .6.2. . . Закон Мура: прошлые и будущие тенденции изменения производительности. . .5. . . . 241 6. . . . .8. . . . . .7. Встраиваемая подстановка методов. . . . . . 219 6. . . . 249 6. . . . . . . . . . . . .7. . . . . . . . .5. . . . Роль времени при повышении производительности. . . . . . . . Чтение журналов компиляции. . . . . Проблема с nanoTime() . . . . . . . . . . jmap . . . .5. . . . .3. Вопрос времени — от железа и вверх . Сборка мусора. . . . . . . . . . Динамическая компиляция с применением HotSpot . . . 246 6. . . . . . . . . . . . 229 6. . . . . . . . . . 225 6. . 230 6. . . . . . . . . . . . . . . . . .2. . . . .2. . . . . . . . . . . . . . . . 233 6. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3. . . . . .

. . . . 280 7.2. . . . . . . . . . . . . Имеется ли хороший инструментарий и поддержка данного языка на уровне тестов. . . . . . . . . . . . . .2. . . . . . . .4. . . . . . . . . . . . . . . . . . . Высоки ли риски в области проекта. . . . Система согласования. . . .5. . . . Фикции компилятора. . 279 7. . . .1. . . . . . . . . . . . . . . .1. . . . . . . 278 7. . . . . . . . . . . . . . . . . . . .1. . . . . . . . . . . . . . Сравнение интерпретируемых и компилируемых языков. Насколько хорошо язык взаимодействует с Java. . . . . Идиомы словаря и фильтра. . 276 7. . . . 266 7.4. . . Многообещающие языки . . . . . . . . . . . . . . Как виртуальная машина Java поддерживает альтернативные языки . .1. . . . . . . .2. . . . .3. . . . . . . . . Сравнение динамической и статической типизации . . . . . . . . . . . . . . . . . . . . . . . . 284 . . . . . 279 7. . . . . . . . . . . . . . . .5. . . . Среда времени исполнения для не Java-языков . . . .5. . . . . . . . . . . . Зачем использовать другой язык вместо Java.3. . . . . .2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 7. . . . Языковой зоопарк. Концептуальные основы функционального программирования. . . . Сравнение повторной реализации и оригинала .1. . . . 272 7. . .14 Оглавление Часть 3.2. . . . . .4. . . .3. . . . . . . . . . . . . . . . . . . 263 7. Резюме . . . . . . 277 7. . . . Сравнение императивных и функциональных языков. 275 7. . . . . . . . . . . . .3. . . . . . Языку Java недостает гибкости? Это провокация!. .4. . .4. . . 274 7. . .6. . . . . . . . . . . . . Насколько много разработчиков использует данный язык.3. . . . . . . .2. . . . . . . . 262 7. . . . . . . . 281 7. . . . . . 268 7. . . . 270 7. . . .4. . . . . . . . . . .2.1. . . 263 7. . . . . . . .2. . . . Многоязычное программирование на виртуальной машине Java. . . . . . . . . 281 7. . . . . . . . .4. . . . . . . . .3. . . . . . . . . . . . Многоязычное программирование на виртуальной машине Java Глава 7. . . . . . . . . . . . . . . . . . . . . .1. . . Насколько сложно выучить данный язык . . . . . . . . . . 265 7. . . .1. . . . . . . . . . . . . . . . . . . . . . . .2. . .4. . . . . . . . . . . . . . . Как подобрать для проекта другой язык вместо Java . . . 269 7. . . . . . . Альтернативные языки для виртуальной машины Java . .2. .1.5. . . . . 269 7. . . . . . . . . . . . 278 7.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . Оператор безопасного разыменования . . . . . . . . . .1. . . . сравнение динамических и статических типов. . . . . 300 8. . . . . . . . 310 8. . .4. . . . . . . . . . . Обработка исключений. . 308 8. . .4. . . . 291 8. . . . . . . . . . . . . . . . . . . . .3. . . . . 299 8. . . . . . . . 302 8. . . . . . . . Числовая обработка . . . . . Опциональные точки с запятой и операторы возврата.4. . . . . . . 292 8. . . . . . . . . . . . . .1. . . .3.1. . . . . . . . . . . . . . . 304 8. . . 290 8. . 299 8. . . . . . . . . . . . . . . . . . Компиляция и запуск. . . . . . . . . . . . . .3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8. . .2. . . . . . Консоль Groovy. . . . . . . Функции Groovy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5. . . Знакомство с Groovy . . . . . . . . .6. . . . 301 8. . . . 297 8. . . . 293 8. . . . . . . . . . . . . . 311 8. . . . .3. .3. Синтаксис списков и словарей. . . . . . . . . . . . . . . . . . . . . Вызов Groovy из Java. . . . . . . . .3. . . . . . . .2. . . . . . . . . .2. Первоклассная поддержка для операций с коллекциями . . Оператор равенства в Groovy . . . . . . 307 8. . . . . . . . 288 8. .5. . . Переменные. . . . . .Оглавление 15 Глава 8.4. . . . . . . . . . . . . . . . . .4.6. . Groovy — динамический приятель Java . . . . . Первоклассная поддержка работы с регулярными выражениями. . . . . . . . Улучшенные строки. . . . . . Оператор Элвис. . . . . . . . . . .2. . . Вызов Java из Groovy.2. . . . . . . . . а также контекст . . . . . . . . . . . . . . . . . . . . Отличия от Java — ловушки для новичков. . . . . . . . . . . .1. .4. . . . . . . .1. . . . Взаимодействие между Groovy и Java. .2. . . . . . .6.2. Внутренние классы . . . . . 303 8. .3. . . . . . . GroovyBeans . . . . . 285 8. . . . . . . .2. . . . . . 289 8. . . 287 8. . . . . . . . . . .4. . . . . .4. . . . . . . . . . . . . Резюме. .5. . . . . . . . . . . . .5. 315 . . .5.4. . Опциональные скобки для параметров методов. . . . . . пока отсутствующие в Java . . 296 8. .1. . . . . . . . . .1. . . . . . . . . . . . . .4. 305 8. . . 298 8. . . . . . . . . . . Модификаторы доступа. .4. . . .3. . . . . . . . . . . . . . 295 8. . . .2. . . . . . . . . . . . . . . . . . . . . . . . . . . .2. . . . . . . . . . . . . . . . . . . . 311 8. . . . . Groovy 101 — синтаксис и семантика. . . . . . . . . . . . . . . . . 298 8. . . 300 8. Стандартный импорт. . . . . . . . . . . . .3. . . . . . . .3. . . . .1. . . . Простая XML-обработка. . . . . . . . . . . . . . . 297 8. . . . .4. . . . . . . .7. . . . . Функциональные литералы .

. . . . . . . . . . . . . . . Конструкторы .1. .4. . . . . . .2. . . . . . . . . . Case-классы . . .4. . . . . . . . . . . . . . . 345 9.4.2. . . . . . . .2. .3. . . . . . . . . . . . . 335 9. . . . . . . . . . . . . . . . . . . . . . . . .5.1. . . . . . . . . . . . что Scala может не подойти для вашего проекта . . . . Структуры данных и коллекции. . . . . . .4. . . . . . . . . . . . . Акторы . . . . . . . . . . . . . . . . . . . . . Типажи. 317 9. Одиночка и объект-спутник. . . 327 9. Список . . . . . . . .5. . . . .1. . . . . . .3. .4. . . .2. . 330 9. 317 9. . . . . . Любая сущность — это объект. . . . . . . .3. . . . . . Выведение типов. . . . . . . . . . .3. . . . 329 9. . .4. . . . . . 324 9. Методы. . . . . . . . . . . . . . . . . . . . . . . . . .1. . . . . . . . . . . . . . . .2. . . . . . Подходит ли Scala для моего проекта?. . . . . 320 9. . . . 322 9. . . . . . 333 9. . . .2. . . . . . .6. . . . . .4. . . . . .16 Оглавление Глава 9. . 350 9. . . . . . . . . . . . . . . . . . .1. . . .4. . . . . . . .2. . . . . . . . . . . . .5. . . . . . . . . . . .6. . . . . . . . Сравнение Scala и Java . . . . . . . . . . . . . . . . 334 9. . . . . .5. . . . . . . . . . . . . . . . . . . . . Scala — лаконичный язык . . . . . . . 328 9. . . . . .4. . . . . . . . . . . . . . . . .1. Импорт . . . . . . . . . . . Словарь . . . . 327 9. . . . . . . . . . . . . . . . . . . 332 9. . . . . Обобщенные типы. . . . . . . . . . . . . . . . . . . . . . . 331 9. . . . . . . . . . .2. . . указывающие. . . . . . Язык Scala — мощный и лаконичный . . . . . . . . . . . . . . Case-классы и сопоставимые выражения. .2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 9. . . . . . 325 9. . . . . . . . . . . . . . . . . . . . . . . . . . но своеобразная . . .3. . . . . . . . . . . . . . . Быстрый обзор Scala. . . . . . . . . . . . . . . . . Когда и каким образом приступать к использованию Scala. 336 9. . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 9. .3. . . . . .1. . . . . Признаки. . . . 325 9. . . . . . . . . . . . . . . 344 9. . . . . . . . . . . . . . . . . . . . . . . 339 9. . . . . . . . . . . . . . . . . . . . . . . . . . . . 326 9. . .3. . . . . . . . . . . . . . . . . Предостережение . . . . . .1. . . . . .1. 316 9.3. . Циклы и управляющие структуры . . . . . . Объектная модель Scala — знакомая.3. . Сопоставимые выражения. . . . . . . . . . . . . . .5. .1. . . . . .5. . . . .4.3. . . . . . Функциональное программирование на Scala. . . 337 9. . . . . . . . . . . . . . . .3. . . . . . . Как вновь сделать код красивым с помощью Scala. . . Использование компилятора и REPL. . . 351 . .3. . .

. . . 373 10. . . . . .1.2.5. . Резюме . .2. Взаимодействие между Clojure и Java. . . . . . .1. . . . . . . . . . . . Функциональное программирование и замыкания . . . . 363 10. . .6. . . словари и множества . . . . . . . . . . . .2. . . . . . 380 10. . Тип Java у значений Clojure.2.1. . . . . . . . . . . . . . . . . . . . . . .1. . . . . . . 361 10. . . . . . . . 385 10. . . . . . . . . . 375 10. . . . . 362 10. . . . .2. . . . . . 379 10. . . . . Арифметика. . . . . . . . . . . . . . .2.Оглавление 17 9. . . . . . . . . Макросы чтения и диспетчеризация.4. . . . . . . . . . . . .1. . .6. . . . . . . . . . 366 10. . . Вызов Java из Clojure. Работа с функциями и циклами в Clojure . Обмен информацией с акторами через почтовый ящик . . . . 386 . .1. . . . . . . . . . . . . . . . . . . . . . . . .2. . . . . . . . . .2. . . . . . . . . . . . . . . . . . . . . .1. 369 10. . . 382 10. . . . . . . . . Использование посредников Clojure. . . . Последовательности и функции с переменным количеством аргументов. . . . 354 9. . . . . . .5. . . . . . .1. . . . . . . . . . . . . .5. . . . Знакомство с акторами . . .2. 355 9. . . . . . . . . . . . . . . . .5. . . . . . . . . . . . . . Как делаются ошибки . . . векторы. 383 10. . . . . . . . . . . . Базовый курс по работе со специальными формами. . . Знакомство с REPL. . . . . . Использование Clojure из Java. . . Hello World на языке Clojure. Списки. . 359 10. . . . . . . . 363 10. . . . .3. . . . .7. . . . . . . . . . . . . . . .1. . . . . . . .3. . . . . . . . . . . . . . Введение в последовательности Clojure. . . . . . . . . . . . . 356 9. . . . . . . . 370 10. . 360 10. . . . . Введение в Clojure. . . . . . . . 358 Глава 10. . 382 10. . . . . . . . . . . . . Исследовательское программирование в среде REPL .3. . . . . . . . Ленивые последовательности . . .1. . . .5. . . . . . . . .3. . Clojure: программирование повышенной надежности . . . . . . . . . . . . 370 10. . .6. . .1. . . . . Поиск Clojure — синтаксис и семантика . .4. . . . . . . . . 377 10. .5. . . . . . . . . . .3. . . . . . . . . . . . Учимся любить скобки. . . . . . . .3.4. . .4. . .3. . . . . . . . . . 364 10. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2. . . . . . . . . . .5. . . . . . . . . . . проверка на равенство и другие операции .4. . . 365 10. . . . . Весь код — театр. . . . . . . . . . . . . Простые функции Clojure. . . . .3. . . . . . . . . . . . . . . . . . . . . . 384 10. .

. 438 12. . . . . . . . . . . . Подставной объект . . . . . . . . . .6. . . . . . . . . . . . . . 400 11. . .2. . 434 12. . . . . . . . . . . . . . . . . .1. . . Знакомство со ScalaTest. . . . 386 10. . . . . . . . . . . . . 425 11. . . . . . . . . 405 11. . . . . . . . .4. . .3. . .4. . . .1. . . 394 Часть 4. . . . . . . . . . . .2. . . . . . . . . . . . . . Дальнейшие размышления о цикле «красный — зеленый — рефакторинг». . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429 Глава 12. . .1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Образец разработки через тестирование с несколькими случаями использования. . . . . . . . .7. . . . JUnit . 413 11. . . . . .2. . . . . . . . . . . . . . . . . . .2. . . . . . . Суть разработки через тестирование. . . . . .2. . . . . . . . . . 410 11. . . . . . . . . . . . Разработка через тестирование. 389 10. . . . . . . . . . . . . . . . . . . . . Образец разработки через тестирование с одним случаем использования. . . . . . . . .3. . . . . .3. . . . . . . . . . . . . . . . . . . . . . .1. . . . . .2. 415 11. . . . . . . . . . . . . Поддельный объект. Резюме . . . . . Экспресс-проект с Maven 3. . . . . . . . . . . Сборка и непрерывная интеграция. 431 12. . . . . . Знакомство с Maven 3 . . . . . .2. . . . . . . . . . . . . Функции future и pcall . . . . . . . .3. . . . Тестовые двойники . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438 12. . . . . . . . . . . 412 11. . . . . . . Ссылки . Пустой объект. . . . . . . . . . . . 387 10. . . . . . . . . . . . . . . . . . . . Резюме .1. . . . . . . . 427 11. . . . . 393 10. .6. . . . . . . .2. . . . . . . . . . . .1. . . . . . . . . . . . . . 445 . . . . . . .1. . . . . . . . . . . . . .3. . . . . . . . . . Агенты . Параллелизм в Clojure. . . . . . . . . . . . . . . .6. . . . . . . . . . . . . . . . . . . . . . .2. . . . . . . . . . . . . . . . . .6. . . . . . . . . . . . . . . . . . . . . . . 419 11. . Файл POM.18 Оглавление 10. . . . . . . . . . . . . . . . . Объект-заглушка. . . . . . . . . . . .1. . . . . . . . . . . . . . . . . . . . . . . . . Запуск примеров. . 408 11. . . . . . . . . . . . . . . . . . . .2. . . . . . . . 399 11.3. . . . . 435 12. . . . . . . 397 11. . . . . . . . . . . . .1. . . . . . . . . . . .3. . Создание многоязычного проекта Глава 11. . . . . . . Maven 3 — сборка java7developer. . . . . . . . . . . . . . . . . . . . . . . . . .1. . . . . . . . . . . . . . . . .4. . . .

. . Leiningen. . . Контроллеры. 483 13.1.2. . . . . . . . . 477 13. . . .2. . . . . . . . 466 12. . . . 471 12. . . . . . . Почему компиляция Java не подходит для быстрой веб-разработки. 448 12. . .1. . . . . . . .2. . . . . . . . . . . . . . . . . . . . Обеспечение согласованности кода с помощью плагина Checkstyle . REPL-ориентированная разработка через тестирование с применением Leiningen. . . . . . . . . . . 452 12. . . . . . . . . . .1. . . . . . . . Почему статическая типизация не подходит для быстрой веб-разработки. . . . . . . . . . . . . . . .1. . . . . . . . . . . .5. . .4. . . . . . . . 476 13. . . . . . . . . . . . Создание тестовых данных .5. . . . . 478 13. . . . Знакомство с Grails . . . . Проблема с веб-фреймворками на основе Java. .1. . . . . . . . . . . . . . .Оглавление 19 12. . . . . . . Настройка задачи . . . . . . . . . 458 12. . . . . . . . . . . .1. . . . . 465 12. . . . . . . Быстрая веб-разработка . . . . . . .1. . . . 475 13. . . . . . . . . . . . . . .4. . . . . . . .2. 487 13. . . . . . . . . . . . . . . . . . 461 12. . . . . . . 486 13. . . . . . . . . . . . . . . . . . Пример: Hello Lein. . . . . Создание объекта предметной области . . . .7. . . . . . . . . . . . . . . . . . . . . . . . . . . .4. Знакомство с Leiningen. . . . . . 465 12. .4. .4. . . 469 12. . . . . . . . .6. . . . . .6. .3. . . . . . . . . . . . . . . . . . . . . 457 12. . .4. Разработка через тестирование. . Экспресс-проект с Grails. . . . . . . . . . . . Упаковка и развертывание кода с помощью Leiningen . . . . . . . . . .3. . .6. . . . . . . . . .5. . . . . . . 472 Глава 13. . . . . . . . . . . . . . . . . . . . . . .4. . 481 13. . . . . .2. . . .3. Обеспечение качества кода с помощью FindBugs. . . . . . 488 . . . . . . . . . . . . . . . . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Установка плагинов Jenkins. .5. Базовая конфигурация. . . .5. . . . . . . . . .4. . . 480 13. . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Выполнение задачи. . . . Jenkins — обеспечение непрерывной интеграции. . . . . . . Критерии при выборе веб-фреймворка .6. .6. . . . 464 12. . . . . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4. . . . Сохраняемость объектов предметной области . .5. . . . . . . . . . . . . . . . . . . . . 450 12. .1. . . . . . . .2. . . 455 12. . . 474 13. . . . . . . . . . . . . .3. . . . Параметры кода в Maven и Jenkins. . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . . . . . .3. 483 13. . 459 12. . Архитектура Leiningen. . . . . .6. . . . . . . .

. . . . . . . . . . . . управляемая во время исполнения . . . . . . . . . . .2. . . . . . . . . . . . . . .1. . . . Знакомство с Compojure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492 13. . . . . . . .3. . . Чего ожидать в Java 8 . . . . . . . . . . . . . . .6. . . . . . . . . . . . . . . .7. . . . . . . . . . . . . . .5. . . . . . . . . . .6. . Дальнейшее исследование Grails . . . .5. . . . . . 504 13. . . . .1. . 516 14. . . . . . . . Hiccup. . . . . Основные функции в программе «А не выдра ли я?» . . . . . . . . . . . . . . . Виды GSP/JSP . . . . 500 13. . . . . . . . . . . . . . 493 13. . О сохранении основательности .5. . . . . . . . . . . . . . . . . . . . . . . . . . . .2. . . . 491 13. . . . . . . . Быстрая циклическая разработка. . . . . . . . . .1. . . 492 13. Лямбда-выражения (замыкания) . . . 502 13. . . . . . . 509 14. . . . . Hello World с Compojure. . . . . . 512 14. . . . . . . . . . .2. . . . . . . . . . .7. . . . .7. . . Многоязычная модуляризация.1.6. . . . . . . Скаффолдинг и автоматическое создание пользовательского интерфейса.1. . . . . Пример проекта с Compojure: «А не выдра ли я?» . . . . . . . . . Многоязычное программирование . Модуляризация (проект Jigsaw). . . . . . . . 514 14. . . . . . . . . . . . .3. . . . . 510 14. . . . . . . . .8. . . 517 14. . . . . . . . . . 515 14. GORM — объектно-реляционное отображение. . . . . . . . . . . 494 13. . .1. . . .7. . .8. . . . . . . . . . . . .3. . . . . . . . . . . . . . . . . . . . . . . . . .4. . . . . . . . . . . 489 13. . .2. . . . . . . . . . . . .2.1. . 495 13. . Резюме . Логирование.2. . . . . . . Многоядерный мир . . . 509 14. . . . . .6. . . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . Ring и маршруты. . . 496 13. . . . . . . . . . . . . . . . . . . . 493 13. . . . Плагины Grails. Межъязыковые взаимодействия и метаобъектные протоколы . . . . .3. . . . . . . . . . . . .2. .6. . . . . . . . .1. . . . Настройка программы «А не выдра ли я?». . . . . . . . . . . . . . . . .2. . . . . . . . . 518 . . . . . . . . . 500 13. . . Будущие тенденции параллелизма. . . . . . 507 Глава 14. . . .2. . . . . . . .4. . . . . . . . . . . .20 Оглавление 13. . . . . . . . . . . . . .5. . . . . . . . . . . . . . . . . . . . . . . 517 14. . . . . . . . .3. . . . . . . . . . . . 499 13. . .1. Параллельная обработка. . . . . . . . . . . . . . . . . . . . . . . .

. .3. . . . 532 А. . . . . . . . . . . . . . . . . . . . . . . . Установка исходного кода java7developer. . . . . . . . . . . . Тестирование . . . . . .2. . . . . . . . 539 C. . . . . . . . . . . . .2. . . . . . . . . . . .1. . 528 А. . . . . . . . . . . . . . . . . . . . . Groovy. . . . . . .2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521 14. . Новые направления в развитии виртуальной машины Java. . . . . . 539 C. . . . . . . Скачивание Groovy . . . . . . . . . . . . . 528 А. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3. . . . . . . . . . . . .3. . . . . . . . . . . . Примеры паттернов подстановки. . . . . . . . 535 А. Резюме . . . . . . . . . . . . . . . . . . . . Синтаксис и примеры паттернов подстановки . . . . . . . . . . . . . . . .1. . . . . . . . . .2. . . . . . . . . 533 А. . . . . . . . . 530 А. . . . . . . . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Структура исходного кода java7developer. . . . . . . . . . . . . . . . . . . . . . .3. .4. . . . . . . . . . . . . . Конвергенция виртуальных машин. . . . . . . .3. 519 14. . Установка Groovy . . . . . . . . . 537 B. . . . . . . . . . . . . . . Кортежи . . . . . . . . . . . . . . . . . . . . . .4. . . . . . . . . . . . . . . . Скачивание и установка Maven . . . . . . . . . . . . . . . . . . 540 . . Синтаксис паттернов подстановки. . . . . . . . . . . . . . . . . . Запуск сборки java7developer. 537 Приложение C. . . . . . . . . . . . . . . . . . . . . . . . . . Сопрограммы.4. 534 А. . . . . . . . Очистка . . . . . . 532 А.21 Оглавление 14. . . . . 525 Приложения Приложение A. . . . Компиляция . . . . . . . . . . . . . . . . .1. . . . . . . . 522 14. . . . . . . .3. . . Однократная подготовка сборки . . . . . . . . . . . . . .5. . .2. . . .1. . . . . . Резюме. . . 520 14. . .1.1. . . . . . 536 Приложение B. . . . . . . . . . 539 C. . . . . . . .4. . . . . . . Установка альтернативных языков для виртуальной машины Java . . . . . . .4. . . . . . . .1. .3. . .1. 537 B. . . . . . . . . . . . . . . . . . . .

. . . .1. . . . . . . . Scala. . . . . . Clojure. . . . . . Установка WAR-файла. . . . . . . . . . . . . Конфигурация сборки . . . . . . . . . . . . . . . . . . . . 548 D. . . . Установка Grails . 547 D. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550 E. Первый запуск Jenkins. Grails. . . . . . . . . . . .2.4. . . . . .2. . . . . . . . Установка Jenkins . . . . . .4. . . . . . . . .2. . . . . . . . . . . . . . . . . . . . . .1. . . . . . . .3. . . . . . . . . .2. . 543 C. . . . . . 548 D. . . . . . . . . . . . . . . . . . . . . . .1. .22 Оглавление C. . . . . . . . . . . . . . . java7developer — Maven POM . . . . . . . . . .2. . . . . . . . . . . . . . . . . . . . . . .4. . . . . . . . . 548 D. . . . . . . . . . . . . . . . . . . . . 550 E. .2. . . . . . Запуск WAR-файла . . . . . . . . . . 543 C. . . . . . . . 543 C. . . . 554 . . . . . Установка специализированного пакета .1. . . . . . . . 547 D.2. . . . . . . . . 547 D. . . . . . 548 Приложение E. . . . Скачивание Grails . . . .2. . . . . . . . . .4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2. . Скачивание и установка Jenkins . . . . . . . .3. . . . . . . . . . Управление зависимостями. . . . . . . . . . . Загрузка Jenkins . . . . . . . . 542 C. . . . . . . . . 544 Приложение D. . . . . . . . . . . . . . . . . . . . . . . .

Итак. Для изменения формата файла класса или дополнения вирту� альной машины новой функцией требуется немало потрудиться. и механизм «ветвление/слияние».2. Кроме того. Когда я прочел главу о загрузке классов. то самое время подучиться. которые я услышал от Бена Эванса (Ben Evans). Кроме того. Он приехал на Крит. где торгуют пивом. правиль� но? Если ваши знания о многопоточности ограничиваются этим фактом. касающиеся синтаксического сахара. совершенно не меняя вашего кода. а не MergeSort. помога­ ющего заглянуть в сущность байт-кода. Я считаю себя одним из основателей языка Java. о которых даже не задумывался. цель которых заключалась в изменении отдельных частей экосисте� мы Java. Книга начинается с рассказа о разработках. Действительно. выступал с докладами на самых разных конференциях и вел сложные курсы по Java��������������������������������������������������������� ������������������������������������������������������������� . а за углом есть магазин. как обходить определенные бреши в области безопасности. Читай себе и учись! Авторы объясняют принципы работы javap — маленького инструмента. постоянно появляются новые идеи. Здесь вы узнаете обо всех нововведениях. генерируемого компилятором Java. и тогда производительность при работе с определенными разновидностями входных дан� ных действительно возрастает. Каждый день в поч� товой рассылке. В методе Arrays. Бен это знает. они рассказывают о новом механизме invokedynamic и объясняют. что здесь на автозаправке продают пиво» — это были первые слова. когда я читал эту книгу. Здесь были выложены на всеобщее обозрение приемы. написал сотни статей. Примерно такое же чувство меня одолевало. то можете заметить небольшой рост производительности. я пять лет прожил на этом греческом острове и ни разу даже не подумал купить пиво на местной заправке «Бритиш Петролеум». Это и улучшения. разворачиваются дискус� сии. чтобы поучаствовать в конференции Open Spaces Java. эта книга — о языке Java 7. Как указывают авторы. Эта книга научит вас мыслить в духе «разделяй и властвуй». читая книгу Бена и Мартина. и Java NIO. Бен выглядел разочарованным. чем он от� личается от обычной рефлексии. мне показалось. . Вот уже 15 лет я программирую на Java.Вступление «Кёрк мне сказал. вы узнаете. что авторы немного перегнули палку. ведь он участвует в работе исполнительного комитета JCP. Если вы сортируете частично упорядоченный массив.sort() теперь используется алгоритм TimSort. «в настоящее время в области парал� лельной обработки ведется огромное множество исследований». Я ответил. А теперь. и switch на строках. которыми обогатился язык. Изменить внутреннюю структуру библиотеки не так сложно. которые мы с друзьями применяли для создания «волшебного» кода. я не перестаю по� ражаться их идеям. как раньше. посвященной проблемам параллелизма. что на автозаправке обычно покупаю бензин. Параллельная обработка? Это что-то связанное с Thread и synchronized.

Возможно. — это класс CacheTester. учиться и еще раз учиться! Через какое-то время я снова ехал мимо автозаправки. а не гадайте».24 Вступление Одна из наиболее понравившихся мне глав — глава 6 под названием «Понятие о повышении производительности». Прочитав эту книгу. описываемый в главе 6. проблема про� изводительности рассматривается на аппаратном уровне. при этом авторы не слишком углубляются в технические детали. другие — декларативные. Разрабатываются новые языки. Существует много причин. В любом случае эти инструменты очень пригодятся вам для управления проектом. Третья часть книги посвящена многоязычному программированию на вирту� альной машине Java (JVM). На мой взгляд. что она закрылась. что старый добрый «дворецкий» Hudson так быстро уступил место Jenkins. вам просто подскажут. Человек не в состоянии угадать. чтобы вы могли выбрать. Scala и Clojure. Jython и JRuby) являются «портами». Более того. в следующей версии мы увидим лямбда-выражения и модуляризацию. Бен и Мартин представляют нам три альтернативных языка: Groovy. на которой могут работать другие языки. прежде чем определяться с выбором. Одни из них функциональные. какой язык вам лучше всего подходит. вы избавитесь от необходимости написания большого количества шаблонного кода. как в вашей системе можно организовать разработку через тестирование и непрерывную интеграцию. рассматривают харак� терные черты. Java — это далеко не только язык программирования. какой код будет работать медленно. Эту главу можно охарактеризовать одной фразой: «Измеряйте. какой язык использовать. Главы. отображающий затраты на кэш-промахи. пред� назначенные для параллельной обработки. Интересный небольшой контроль� ный инструмент. Так. эти языки развиваются сейчас особенно динамично. Далее вы узнаете. как заставить вашу систему работать быстрее. Оказалось. Кроме того. Я так и не узнал. Это еще и платформа. Например. Доктор Хайнц Кабуц (Heinz Kabutz). Многие вещи. по которым может потребоваться использовать на JVM не Java. как и многие другие предприятия в кри� зисной Греции. здесь расска� зано. Scala и Clojure. Мне было интересно узнать. обновляются конструкции. через которые другие языки могут работать на виртуальной машине Java. Начиная новый проект. рассмотрите все имеющиеся варианты. К этому и сводится качественная оптимизация производительности. равно как и Checkstyle вместе с Findbugs. на которой Бен хотел купить пива. Новостная расылка для специалистов по Java . Вывод один: учиться. посвященные Groovy. которые верны сегодня. а иной язык. В этой главе не предлагается единственный программерский трюк — напротив. вы станете по-настоящему основательным разработчиком Java. Языки могут быть динамическими (Groovy) или стабиль� ными (Java и Scala). вы узнаете. что делать для сохранения этой основательности. Это первая книга со времен выхода труда Джека Ширази (Jack Shirazi) Java Performance Tuning («Настройка производитель� ности в Java»). Авто� ры описывают разницу между ними. могут быть ошибочны в будущем. Мы уже были свидетелями взрывного распространения языков различных типов. Отдельные языки (например. Язык Java постоянно изменяется. в которой по существу рассказывается. вы не найдете здесь спра� вочника по Groovy. продавали ли они пиво. достаточно содержа� тельны. сравнивают их с Java. как нужно измерять производительность.

просмотревший имевшиеся в продаже книги. но вся наша работа проникнута данной темой. а руководством для всех разработчиков Java. поговорим о наилучших совре� менных методах разработки программ. Мы не преуменьшаем значения тех� нической стороны этого искусства. чтобы почерпнуть идеи для работы над дополнитель� ными темами. в книге и непросто рассказать об этих гранях. Для того чтобы карьера разработчика могла состояться. Новое поколение разработки». Но мы хотели именно заинтересовать вас и помочь сделать первые шаги. заметил. которые вас наи� более заинтересуют. принятых в IT-отдел по обслуживанию операций с ино� странной валютой в составе «Дойче Банк». которому суждено было стать книгой «Java. В результате он решил написать эту недостающую книгу. интересующихся: «А что изучить дальше? В каком направлении развиваться?» Покой нам только снится. Мы расскажем вам о новых функциях Java 7. Мы на� деемся. Там он познакомился с тремя программистами из IBM (это были Роб Николсон (Rob Nicholson). С одобрения менеджеров IT-отдела «Дойче Банк». организованная LJC. В кни� ге мы подчеркнем несколько моментов. имеющих особое значение для нас с вами . Надеемся. техническим тусовкам и пиву они решили взяться за со� вместный проект. что на рынке не хватает актуального мате� риала для начинающих Java-разработчиков. которые помогли ему влиться в лондонское сообщество Java (LJC — London Java User Group). А потом вы сможете продолжить изучение тех тем. написанных для новой группы молодых специалистов. В следующую субботу после этого состоялась ежегодная Открытая конферен� ция. Бен отправился на конфе� ренцию Devoxx в Бельгию. что не менее важны более тонкие проблемы. К вечеру этого дня на волне взаимной любви к учительству. В ходе работы над проектом акцент книги немного изменился. Один из нас (Бен). что в этой книге нам удалось убедительно представить разработку программ как вид социальной деятельности. Зоя Слэттери (Zoe Slattery) и Холли Камминс (Holly Cummins)). что в этой книге нам удалось подчеркнуть некоторые моменты. Может быть. ему необходимо непре� станно интересоваться новыми технологиями и с воодушевлением учиться.Предисловие Эта книга началась как сборник учебных заметок. способные разжечь в человеке такую страсть. чем энциклопе� дическое исследование. Именно на ней Бен повстречал одного из лидеров LJC. Она стала не просто вводным курсом для выпускников (хотя и эта задача в ней решена хорошо). связанные с межличностной коммуникацией и другими гранями человеческого общения. Это скорее экскурсионный тур. а также о перспективах платформы. Мартина Вербурга (Martijn Verburg). но вместе с тем считаем.

что квинтэссенция опыта. вы сможете подробнее изучить изменяющийся мир Java и экосистему. Таким образом. но работают на виртуальной машине Java (JVM). окружающую этот язык. мы поговорим о новых языках. В первую очередь мы пытались учесть ваши интересы. которую вы держите в руках. которые мы считаем наиболее интересными.26 Предисловие как для Java-разработчиков. это путешествие с заделом на будущее. производительность. то не потеряете интереса к собственным проектам и сможете их контролировать. чем нам было работать над ней. Эти темы мы обсудим потому. . будет вам полезна и интересна. что эта книга даст вам пищу для размышле� ний и читать ее будет не менее интересно. что если вы станете хорошими Javaразработчиками. что в ближайшие годы они станут очень важны для многих разработчиков. Параллельная обработка. Надеемся также. Такой принцип работы называется многоязычным программированием (polyglot programming). Мы считаем. Мы верим. байткод и загрузка классов — вот основные технологии. которые не являются Java. Прежде всего. Кроме того.

которые по� могли нам с рецензированием материала: Питера Бьюдо (Peter Budo). коллег. Мартина Скурлу (Martin Skurla). примите наши извинения! Без особого порядка мы хотели бы поблагодарить всех перечисленных ниже людей за то. Ника Хар� кина (Nick Harkin). Луи� са Мурбину (Luis Murbina). Тима Мюррея Брауна (Tim Murray Brown). Криса Рида (Chris Reed). Если мы кого-то забыли (возможно. Сложно вспомнить имена всех людей. Аманду Уэйт (Amanda Waite). Адама Джея Мэркхама (Adam J. посодействовавших нам в этом предпри� ятии. что эта книга смогла увидеть свет. Мы хотели бы поблагодарить следующих людей. . были пробелы в наших записях) — пожа� луйста. И даже оппонентов. Ричарда Догерти (Richard Doherty). Там есть большой список благодарностей. Можете посетить сайт http://www. П. перев. Vanderwildt).com и найти там пост. Джона Стивенсона (John Stevenson). где мы встретились. Розаллин (Rozallin). Энтони Стаббза (Antony Stubbs). Майкла Джойса (Michael Joyce). Франка (P. Колина Випурса (Colin Vipurs). Франка Аппиа (Frank Appiah). Мы никогда бы не завершили этот труд без множества наших друзей.meetup. анон� сировавший выход оригинальной книги. Сандро Манкьюзо (Sandro Mancuso) и Арула Дхезиазеелана (Arul Dhesiaseelan). что большинство самых суровых из наших критиков также являются нашими друзьями. Джоэла Глю� са (Joel Gluth). Адриана Смита (Adrian Smith). Franc). Не стоит забывать ни одного имени.Благодарности Согласно одной африканской пословице. Марка Хиндес� са (Mark Hindess). Джемму Силверс (Gemma Silvers). 1 Эта пословица стала широко известна в Америке после выхода книги Хиллари Клинтон (Hillary Clinton) «Нужна деревня» (It Takes A Village). То же можно с полным правом сказать и о написании книги. партнеров. Лондонское Java-сообщество Лондонское Java-сообщество. Дэниела Лемона (Daniel Lemon). место. — это место.java7developer. Markham). Иоанниса Маврукакиса (Ioannis Mavroukakis). «Себкома» Правина (“Sebkom” Praveen). Джона Поултона (Jon Poulton). Ричарда Пола (Richard Paul). Кевина Райта (Kevin Wright). Динука Вирасингха (Dinuk Weerasinghe). Вандервильдта (N. ставшее огромной ча� стью нашей жизни.com/ londonjavacommunity. Нам исключительно повезло. Джодева Девасси (Jodev Devassy). това� рищей. Н. Рашула Хусейна (Rashul Hussain). Нуно (Nuno). — Примеч. Крейга Силка (Craig Silk). «чтобы вырастить ребенка — нужна де� ревня»1. находящееся в Интернете по адресу www.

Мэри Пирджис (Mary Piergies). Алекса Андерсона (Alex Anderson). несомненно. У него было всего несколько отважных соратников и мечта. Колина Хоува (Colin Howe). Ричарда Уорбертона (Richard Warburton). Джанет Вейл (Janet Vail) и. Спасибо Джону Райану III (John Ryan III). Энди Кэрроллу (Andy Carroll). Тришу Джи (Trisha Gee). объединенных сумасшедшей идеей.coderanch. Леонарда Аксельсона (Leonard Axelsson). Саймона Мейпла (Simon Maple). Брюса Дарлинга (Bruce Durling) и доктора Рассела Уиндера (Dr. Это Азиз Рахман (Aziz Rahman). LJC — настоящий краеугольный камень лондонского технического бомонда. Сегодня LJC объяединяет около двух с половиной тысяч участников. В ходе подго� товки этой книги нам доводилось общаться со многими сотрудниками издатель­ ства. Фреда Розенберга (Fred Rosenberger). Деваку Курея (Devaka Cooray). Джима Гафа (Jim Gough). не можем не поблагодарить Барри Крэнфорда (Barry Cranford). Наконец. осно� вателя LJC. Черил Джерозал (Cheryl Jerozal). Большое спасибо за неутомимую работу Рене Грегуару (Renae Gregoire). Дипака Балу (Deepak Balu).com С удовольствием благодарим Маниша Годбоула (Maneesh Godbole). Им — наши особые благодарности. что он услышал двух молодых авторов. отдельно благодарим Комитет сертифицированных разработчиков Java при LJC — Майка Баркера (Mike Barker). Кристофер Хаупт (Christopher Haupt). Вутера Эта (Wouter Oet). Карен Дж. а также Кристине Рудлофф (Christina Rudloff) и Морин Спенсер (Maureen Spencer) — за их поддержку по ходу дела. Manning publications Благодарим Марджана Бейса (Marjan Bace) из издательства Manning за то. Марка Сприцлера (Mark Spritzler) и Роэла де Нийса (Roel De Nijs) за их подробные ком� ментарии и ценнные отзывы. Деннису Далиннику (Dennis Dalinnik). Грега Чарльза (Greg Charles).28 Благодарности Подробные консультации по другим языкам мы получили от Джеймса Кука (James Cook). Элизабет Мартин (Elizabeth Martin). Кроме того. Сомая Нахала (Somay Nakhal) и Дэвида Иллсли (David Illsley). Это общество породило множество других технических сообществ. Чед Дэвис (Chad Davis). Спасибо следующим рецензентам. всем остальным. Дэвида О’Миру (David O’Meara). Без всех вас этот труд никогда бы не увен� чался успехом! Спасибо Кэндис Гиллхули (Candace Gillhoolley) за ее маркетинговую работу. кто остался за кадром и кого мы позабыли. Miller). Ульфа Дит� мера (Ulf Ditmer). который сделал подробную оконча� тельную техническую рецензию рукописи незадолго до передачи книги в печать. читавшим рукопись на разных этапах ее подготовки и оставившим ценные отзывы нашим редаторам и нам. www. Миллер (Karen G. который начал это большое дело несколько лет назад. Берт Бейтс (Bert Bates). Йеспера де Йонга (Jesper De Jong). Russel Winder). Дэвид Стронг (David .

Спасибо доктору Хайнцу М. оказанное нам на Крите. Патрик Стегер (Patrick Steger). Речь об этом пойдет в главе 13. конечно же. Благодарим Джанну Боярски (Jeanne Boyarsky). Федерико Томассетти (Federico Tomassetti). не хватит места. Благодарности Бена Эванса В написании этой книги поучаствовало такое множество людей и такими разны� ми способами. Именно после консультаций с ним мы решили написать соответствующий раздел в главе 4. Без его консультаций этот великолепный API никогда бы не стал доступен разработчикам. который тестировал образцы кода по мере того. Heinz M. чем рукопись отличается от книги. Спасибо Джейсону Ван Зилу (Jason Van Zyl) за то.javaspecialists. Родни Боллинджер (Rodney Bollinger). Роберт Веннер (Robert Wenner). Мои особые благодарности — следующим людям:  Берту Бейтсу (Bert Bates) и остальным сотрудникам Manning за то. Джефф Шмидт (Jeff Schmidt). ежедневно пишущим на Java. а также Драгоса Догару (Dragos Dogaru). Спасибо. чтобы поблагодарить их всех. нашего невероятно классного стажера. . как и следовало ожидать. Особые благодарности Благодарим Энди Бёрджесса (Andy Burgess) и изумительный сайт www. Кабуцу (Dr. Джереми Андерсон (Jeremy Anderson).com. Джон Гриффин (John Griffin). как мы создавали книгу. что мне. Мацей Крефт (Maciej Kreft). а также за работу над классным ресурсом Java Specialists’ Newsletter (www. — за дружбу. Спасибо Мэтту Рэйблу (Matt Raible) за то.eu/). ничто не ускользнуло от ее острого критического взора. Пол Бенедикт (Paul Benedict). Франко Ломбардо (Franco Lombardo). Kabutz) за его великолепное предисловие и роскошное гостеприимство. и за многое другое. а также за его дружбу и удивительное отношение к нашему ремеслу. Дипак Вохра (Deepak Vohra). за то. руководителю разработки NIO. что расска� зали мне. и. Они приведены в главе 12. Спасибо Кёрку Пеппердайну (Kirk Pepperdine) за его ценные замечания и ком� ментарии по главе 6.  Мартину. которая любезно предложила свои услуги самого лучшего технического редактора.Благодарности 29 Strong). Антти Койвисто (Antti Koivisto) и Стивен Харрисон (Stephen Harrison). Спасибо Алану Бейтмену (Alan Bateman).2 для Java 7. Сантош Шанбхаг (Santosh Shanbhag). что он любезно разрешил воспользоваться некоторыми материалами из книги Maven: The Complete Reference (издательство Sonatype). Джанна! Спасибо Мартину Лингу (Martin Ling) за очень подробный рассказ о хрономет� ражном оборудовании.java7develo­ per. что вдохновлял меня продолжать писать в трудный час. что он любезно позволил нам вос� пользоваться некоторыми материалами о выборе веб-фреймворка. Рик Вагнер (Rick Wagner). конечно.

который возжег во мне страсть ко всему техническому. складывающиеся из таких мелочей. Я до сих пор придерживаюсь этой философии и при программировании. какую дала мне она! 1 2 Классная. что смогли заглянуть в будущее и принесли в дом компьютер Commodore 64. от которых я унаследовал так много личностных качеств. на тот момент настройка производительности не была моей сильной стороной. и вообще в жизни. очень классная игра-платформер! Я смеялся до колик. Я очень люблю их обоих. в комплекте с тем компьютером к нам в дом попал и мануал по программированию. начинают налаживаться сами. . который вдохновлял нас всех (не только потому. а также интереснейшим напарником-докладчиком на конференциях (так вышло. потраченный на книгу. стойко мирившуюся с тем. ты была неповторима. И наконец. Интересно. Я никогда не забуду. когда я и моя сестра были маленькими. я определенно узнал о виртуальной машине Java гораздо больше. если бы кто-то мог познать в жизни такую же удивительную любовь и поддержку. но и потому. Папа научил меня. Было просто прекрасно писать книгу вместе с другом. Ах. в особенности обоим моим дедушкам — Джону Хинтону (John Hinton) и Джону Эвансу (John Evans). наблюдая. с которыми мне доводилось встречаться за годы работы. Его техническая подкованность временами просто оше� ломляет! Для меня было честью писать эту книгу вместе с ним. Кроме того. что очередной романтический вечер отменяется ради написания еще одной главы. как мама налегает на джойстик! Скажем так. что мы еще в детские и юношеские годы писали код вместе. что если правильно справляться с мелкими делами. Бен оказался отличным лидером в LJC. — как всегда.  и наконец. Хотя большая часть компьютерного времени в нашей семье посвяща� лась Джампмену1. Спасибо. И-Джею (E-J) (именно благодаря ему в этой книге так часто упоми� наются выдры). чем какой-нибудь сценический дуэт разговорного жанра). что он классный!).30 Благодарности  моей семье. и великодушно подготовившую все рисунки и скриншоты для книги. чем мог помыслить. благодарю мою восхитительную жену Керри. которые всегда прощали меня за «еще один вечер». Благодарности Мартина Вербурга Спасибо моей маме Яннеке и моему папе Нико. вы сможете ее найти? Бен — просто один из самых потрясающих технарей. что теперь мы пользуемся не меньшей славой. что он астрофизик. то крупные дела. как давным-давно на экране высветилось то пер� вое (очень медленное2) звездное небо — у меня на глазах свершилось волшебство! Спасибо моему свояку Йосу. Спасибо моей сестре Ким за то. Еще в этой книге засветилась моя суперплемяшка Гвинет. а также Лиз.

В статье содержится еще несколько замечательных характеристик. очень рекомендуем полностью прочитать ее на досуге. Она призвана превратить вас в современного Java-разработчика.  простой синтаксис и относительно небольшое количество концепций в основе языка. сами определения довольно меткие.  широкая и открытая экосистема.ru/post/165093/. Новое поколение разра� ботки». 1 Полный русский перевод статьи можно найти по адресу http://habrahabr. неполная и в основном неверная исто� рия языков программирования»1: «1996 — Джеймс Гослинг изобретает Java. где Java сосуществует с другими языками. вновь наполнить вас тягой к изучению этого языка и платформы. предложенному Джеймсом Ири (James Iry) в его чудесной статье «Краткая. что ниже автор дает такое же описание языку C# (за исключением последнего предложения).  добавление нового функционала и усложнений в библиотеках. таких как внедрение зависимостей. перев. разработка через тестирование и непрерывная интеграция.  консервативный подход к развитию языка. Sun громогласно возвещает о новизне Java». закрепите (или приобретете и закрепите) знания современных технологий. Хотя вся хохма с языком Java в этой статье заключается в том. Это относительно читабельный. но с множественным наследованием интерфейсов. и приступите к исследованию дивного нового мира JVM. динамическая компиляция). Но возникает довольно серьезный вопрос. основанный на классах. поддерживающий сборку мусора.Об этой книге Мы рады приветствовать вас на страницах книги «Java. которому уже 16 лет? Разумеется. которые зарекомендовали себя как очень успешные:  автоматическое управление средой времени управления (например. — Примеч. Для начала обратимся к описанию языка Java. сборка мусора. так как одной из самых сильных сторон этого языка была способность работать на немногочисленных основных дизайнерских реше� ниях. статически типизированный. Неужели можно сказать о нем достаточно много нового и интересного? Если бы все так и было. то у нас получилась бы очень короткая книга. он уже стабилен. Но мы продолжаем говорить о Java. В ходе повествования вы узнаете новые функции Java 7. Зачем же мы пишем новую книгу о язы� ке. . объектно-ориентированный язык без множественного наследования реализаций.

как Groovy. что исторически язык менялся медленно. Эти черты позволили языку и платформе ������������������������������ Java�������������������������� сохранить силу и динамич� ность — даже притом.32 Об этой книге Такие дизайнерские решения позволили сохранить в мире Java инновационные процессы. Книга «Java. Во второй части собраны четыре главы (3–6). файлы классов/ байт-код и настройка производительности. Во всей книге используется син� таксис и семантика Java 7.  «Создание многоязычного проекта». запла� нированных для Java 8. Еще одним крупным изменением последних лет стал бурный рост языков. а не рево� люционными. В настоящее время создается множество проектов (со временем их становится все больше). что некоторые читатели сразу захотят перейти к конкрет� ным темам. Мы убеждены в полезности практического обучения. современная параллельная обработка. включающего. как лучше читать книгу. работа­ ющим с файлами. Изменения в языке являются эволюционными.  «Необходимые технологии». по мере чтения текста. Разработка многоязычного проекта. что Java 7 еще до выпуска создавался как плацдарм для разработки новой версии. Серьезное отличие от предыдущих версий заключается в том. Далее в этом разделе мы объясним. очень важна в контексте современной экосистемы Java. поэтому главу 1 прочитать необходимо. приведенный здесь материал рассчитан для изучения от начала до конца. Этой теме посвящена последняя часть нашей книги. не являющихся Java. и частично адаптировали книгу и к такому способу чтения. . Но мы понимаем. В ре� зультате началось перекрестное опыление между Java и другими языками JVM. как: внедрение зависимостей. Scala и Clojure. поэтому рекомендуем вам обязательно опробовать код. В Java 7 выполнена основная работа по подготовке крупных языковых изменений. а широкая экосистема дала новичкам возмож� ность легко находить готовые компоненты.  «Многоязычное программирование на виртуальной машине Java». работающих полностью на JVM и включающих Java как один из исполь� зуемых языков. если вы предпочита­ ете изучить лишь избранные главы. Как работать с этой книгой В принципе. но работающих на виртуальной машине Java (JVM). файловыми системами и сетевым вводом-выводом. Благодаря сравнительно простому ядру порог для вступления в сообще� ство разработчиков остался низким. в частности. подходящие под их нужды. так как в ходе релизов компания Oracle придерживается стратегии «План B». посвященные таким темам. сопровождающий материал. Глава 2 под названием «Новый ввод-вывод» будет особенно интересна специалистам. Такая тенденция сохранилась и в Java ������������������������������������������������������� ������������������������������������������������������������ 7. Новое поколение разработки» разделена на четыре части:  «Разработка на Java 7». такие языки. В первой части содержится две главы о Java 7.

кто желает пойти по этой дороге. наконец. предлагаемыми в Java 7. похожий на Java). Другие главы можно читать по порядку или в отдельности друг от друга. Для кого предназначена книга Основной целевой аудиторией этой книги являются разработчики на Java. однако. базируется на темах. в главе 3 мы подробно рассказываем. то в нашей книге вы приобретете хо� рошие базовые знания по этим темам. В части 2 содержатся четыре главы о необходимых на практике технологиях. новыми возможностями асинхронного ввода-вывода и многими другими темами. В главе 2 мы знакомим вас с новыми API ввода-вывода. Так. рассмотренных ранее. совмещающий объектно-ориентированные и функ� циональные черты. так как в ней обсуждается категоризация и использование альтернативных языков на JVM. если вы хотите обучиться функциональному программированию. можно читать отдельно друг от друга. полностью функциональный язык (Clojure). Хотя эти главы можно читать и в отрыве от остальной книги. в некоторых из них мы предполагали. Глава 7 нужна для понимания части 3. Первая глава представляет собой введение в язык Java 7 со всем множеством его мелких функций. то лучше прочитать их по порядку. глава 1 необходима для понимания всей книги. Главы. тех. Если вы хотите освежить свои знания по определенным технологиям и разо� браться в таких темах. Сумма всех этих расширений известна под названием «Монета» (или «проект “Монета”»). предполага­ ющие знание материала. жела­ ющие усовершенствовать свои знания как о языке. хотя если вы не знакомы с функциональным программированием.Об этой книге 33 В третьей части (главы 7–10) мы поговорим о многоязычном программиро� вании на виртуальной машине Java. Главу 7 прочитать необходимо. то эта книга — для вас. так и о платформе. то наши главы о языках (особенно о Java и Clojure) очень вам пригодятся. изложенного выше. который. как в промышленных масштабах стала . как внедрение зависимостей. Если вы хотите идти в ногу со всеми новинками. посвященные этим языкам. в частности с полностью модернизированной системой поддержки файловой системы. параллельная (конкурентная) обработка и разработка через тестирование. далее — гибридный язык Scala. Но в более поздних главах будут попадаться разделы. Одним словом. Дорожная карта В первой части — всего две главы. В следу­ющих трех главах мы рассмотрим сначала Groovy (язык. издание ориентировано на тех разработчиков. В частности. Кроме того. и. оптимизирующих производитель� ность. что вы уже знакомы с вышеизложенным материалом и/или ориентируетесь в определенных темах. В четвертой части (последние четыре главы) вводится новый материал. которых интересует развитие многоязычного программирования.

com/technetwork/java/javase/downloads/index. Соглашения в коде и материал для скачивания Для того чтобы начать работу. В главе 4 объясняется. В главе 7 начинается рассказ о многоязыч� ном программировании и приводится контекст. Мы срываем завесу таинственности. Просто следуй� те инструкциям по скачиванию и установке двоичного файла (выберите файл. а не иначе. Рас� сматриваемый здесь язык Clojure широко известен как «Lisp с человеческим лицом». Вся прочая необходимая информация изложена в приложении А. показывающий. В главе 8 мы знакомим вас с Groovy — динамическим языком.html. как производство аппаратного обеспечения будет уверенно двигаться в направлении многоядерных процессоров. Scala — это очень мощный и лаконичный язык. и рас� сматриваем многоязычные технологии в нескольких областях разработки про� граммного обеспечения. Здесь мы предложим пару новых технологий для прототипирования (Grails и Compojure). Эта глава помогает разобраться и в таких конкретных темах. нужно скачать и установить Java 7. как правильно организовать в Java современную параллельную обработку. похожим на Java. окружающую эту тему.oracle. В главе 12 делается обзор двух рас� пространенных инструментов. Groovy доказывает. В главе 6 дается вводная ин� формация о настройке производительности в ваших приложениях на языке Java. В главе 13 мы поговорим об ускоренной веб-разработке и обсудим. изложенный в предыдущих главах. Часть 3 посвящена многоязычному программированию на виртуальной ма� шине Java и состоит из четырех глав. В главе 5 рассмотрены файлы классов и работа с байт-кодом на виртуальной ма� шине Java. которая появится в Java 8. а также даем некоторые практические советы. В этой главе вы сможете в полной мере оценить потенциал функционального язы� ка на виртуальной машине Java. как сборка мусора.34 Об этой книге использоваться технология внедрения зависимостей. почему бывает уместно и важно задействовать в проекте другой язык. В частности. Эта тема начнет вызывать все больший интерес по мере того. отчасти функциональный. В главе 9 мы окунемся в гибридный мир Scala — отчасти объектно-ориентированный. может значительно повысить продуктивность Java-разработчика. Двоичные файлы и соответству­ ющие инструкции находятся на сайте Oracle. почему Java работает именно так. В главе 11 мы затрагиваем разработку через тестирование и рассказываем о методологии работы с подставными объектами (mock objects). и помогаем понять. что динамический язык. синтаксически схожий с Java. где подробно рассказано. почему язык Java традицион� но отстает в этой области. как устанавливать и запускать исходный код. . предназначенных для организации конвейерной сборки (Maven 3) и непрерывной интеграции (Jenkins/Hudson). Далее показано стандарти� зированное решение на языке Java с применением Guice 3. В главе 14 подводится итог и делаются предположения о будущем ���������������������������������������������������� Java������������������������������������������������ . Глава 10 рассчитана на любителей языка Lisp. посвященном стандартной версии Java (Java SE): www. В части 4 мы опираемся на материал. подходящий для вашей операционной системы). мы поговорим о поддержке функцио� нальных подходов.

При создании и запуске примеров мы пользовались NetBeans 7.0. Для этого мы пользовались разбиением длинных строк и аккуратно добавляли отступы.6 и выше.  новые версии *nix.  NetBeans 7.7.1 и выше.1 и Eclipse 3.  Mac OS X 10. Требования к программному обеспечению Java 7 работает практически на всех современных платформах.Об этой книге 35 Весь исходный код в книге записан примерно таким моноширинным шрифтом. Во многих листингах мы аннотируем код. Сравни� тельно длинные листинги имеют заголовки.  IntelliJ 10. Вы сможете запус� кать примеры из книги. Исходный код для всех примеров из книги доступен по адресу www. . попробуют использовать примеры кода в IDE (интегрированной среде разработки). в которых сообщается дополнительная информация о коде. Этим он отличается от окружающего текста. Scala и Clojure хорошо поддерживаются в следующих версиях основных IDE:  Eclipse 3. чтобы он укладывался в рамки страницы. если работаете в одной из следующих операционных систем:  MS Windows XP или выше. Мы старались форматировать код так.1 и выше.2 и выше.com/ TheWell-GroundedJavaDeveloper. Java 7 и последние версии Groovy.1. Но ино� гда все же встречаются слишком длинные строки — в них мы используем символы продолжения строки. краткие листинги идут прямо в тексте.manning.5. Большинство читателей. чтобы подчеркнуть ключевые моменты. Примеры кода встречаются по всей книге. вероятно. В тексте используются нумерованные списки.7.

). FOSDEM и т.Об авторах Бен Эванс — организатор в LJC (Лондонское сообщество Java-разработчиков. В этой среде он известен как «Разработчик от Дьявола» за постоянную конструктивную критику статус-кво. в рамках программы JSR. его доклады и презентации пользуются успехом на крупных кон� ференциях (JavaOne. Бен часто читает лекции по таким темам. занимающихся подготовкой докумен� тов JSR (запросов на спецификацию Java). д. а в настоящее время занимает пост CEO (главного исполнительного дирек� тора) в софтверной компании. OSCON. . а также участвует в деятельности OpenJDK (программа OpenJDK). Лон� донская группа пользователей Java) и член Java Community Process Executive Committee. сложившегося в современной софтверной индустрии. Он координирует глобальные раз� работки членов «Групп пользователей ���������������������������������������� Java������������������������������������ ». jClarity���������������������������������������������� ������������������������������������������������������ ) более 10 лет профессионально работает в тех� нологической сфере и является консультантом по свободному ПО в самых разных компаниях — от стартапов до крупных предприятий. На про� тяжении «интересных времен» Бен много и активно работал в технологической сфере. Поскольку Мартин — признанный эксперт по оптимизации работы технологи� ческих команд. Он участвует в определении стандартов для экосистемы Java. специализирующейся на программировании на Java и работающей в основном в финансовой сфере. Мартин Вербург (����������������������������������������������������������� CTO�������������������������������������������������������� . Devoxx. Мартин — один из руководи� телей Лондонской группы пользователей ������������������������������������� Java��������������������������������� . производительность и параллельная обработка. как платформа Java.

говорили на разных диалектах и языках. не говоря уже об уроженцах различных городов и регионов. мы обменяли культурное разнообразие на более глубокую и интересную личную жизнь — и.Иллюстрация на обложке Рисунок на обложке книги называется «Цветочница». . Люди. когда одну книгу по программированию сложно отличить от другой. Обо всем рассказывало платье. В этом нам помогает собрание Марешаля. Цве� тистая коллекция Марешаля живо напоминает нам. Каждая из иллюстраций аккуратно отрисована и раскрашена вручную. В век. Иллюстрация взята из вы� шедшего в XIX веке четырехтомного компендиума национальных костюмов под авторством Сильвена Марешаля (Sylvain Mare´chal). мы приветствуем инициативность и изобретательность в софтверной индустрии. царив­ шее в те времена. С тех пор дресс-коды изменились. На ули� цах города и в сельской местности было легко понять. Сейчас сложно отличить друг от друга даже жителей разных континентов. насколько разными и разоб� щенными были города и регионы нашего мира еще каких-то 200 лет назад. Вероятно. каковы род его занятий и положение в жизни. Этот сборник вышел во Фран� ции. откуда родом прохожий. жившие изолированными группами. по� казывая на обложках наших изданий разнообразные региональные костюмы двух� вековой давности. сейчас стерлось. несомненно. Региональное разнообразие в одежде. гораздо более быстрый и разносто� ронний технологический прогресс.

piter.com (издательство «Питер». компьютерная редакция).com вы найдете подробную информацию о наших книгах. предложения и вопросы отправляйте по адресу электронной поч� ты vinitski@minsk. .piter. Мы будем рады узнать ваше мнение! На сайте издательства http://www.От издательства Ваши замечания.

Новый ввод-вывод .ЧАСТЬ 1 Разработка на Java 7 Глава 1. Введение в Java 7 Глава 2.

значительно облегчающих жизнь разработчика-практика. вооружившись Java 7. которые помогут вам работать более продуктивно. чем язык Java отличается от платформы Java.  вспомогательный класс Files. упрощающий создание. Эти новые знания будут закреплены по ходу чтения книги.40 Часть 1. которому посвящена вторая глава. не оказывая значительного влияния на базовую платформу. да дорог». вы сможете естественно думать и писать на языке Java ������������������������������������������������������������������� ������������������������������������������������������������������������ 7. Все эти изменения могут показаться незначительными. Изменения Java 7 делятся на два больших множества: проект «Монета» (Project Coin) и NIO. должен знать о новейших функци� ях. Разработка на Java 7 В первых двух главах мы поговорим о том. копирование. а также предостав� ляет новые мощные возможности асинхронной обработки. перемеще� ние и удаление файлов. появляющихся в языке. Первое множество — проект «Монета» — это несколько небольших изменений на уровне языка.2. несомненно. будет непросто. как можно вырасти профессионально. В Java 7 есть несколько таких функций. К таким изменениям относятся:  конструкция try-with-resources (автоматически закрывающая ресурсы). Закончив изучение первой части. Второе множество изменений — это новый ��������������������������������� API������������������������������ ввода-вывода (��������������� NIO������������ .  усовершенствованные числовые литералы.  строки в switch. В ходе ра� боты мы подготовим почву для обсуждения более крупной темы из этой части — нового механизма ввода-вывода в Java. Чтобы быстро писать эффективный и надежный код. Они призваны повысить продуктивность труда разработчика. позволяет осуществлять в фоновом режиме крупные опе� рации ввода-вывода. . Основательный Java��������������������������������������������������������� ������������������������������������������������������������� -разработчик. как и почему были реализованы эти новые функции. основанный на обратных вызовах и использовании конструкций future.  ромбовидный синтаксис (требующий меньше шаблонного кода при работе с обобщенными типами). Он кар� динально модернизирует поддержку файловой системы ������������������������ Java�������������������� . нуж� но глубоко понимать.  множественный catch (объявление нескольких исключений в блоке catch). посколь� ку все функции Java 7 используются и в последующих главах. К важнейшим измене� ниям относятся:  новая конструкция Path для ссылки на файлы и файлоподобные сущности.  асинхронный ввод-вывод. В разминочной первой главе мы рассмотрим небольшие синтаксические изменения. можно лучше понять.  встроенная навигация по дереву каталогов. возможно. Но если исследовать семантику.2). которая скрывается за изменениями синтаксиса. О каждом из этих изменений можно сказать: «мал золотник. Но понять синтаксис этих изменений.

внедрения и выпуска изменений на платформе Java. работающем с файлами или другими ресурсами. но мощные синтаксические изменения. мы перейдем к рассмотрению шести основных новых функций. когда шумиха вокруг новой версии утихла и Java 7 раскочегарился на пол� ную мощность. Положение дел в этом языке может показаться вам немного непривычным. к кото� рому мы многократно будем возвращаться на протяжении всей книги. В качестве разминки мы плавно введем вас в курс Java 7. .  оператор try-with-resources. появившихся в Java 7. Добро пожаловать в Java 7.  усовершенствованная обработка исключений. Эта конструкция помогает избегать ошибок в коде. Итак. Дочитав эту книгу. приступим к делу и поговорим о дуализме языка и платформы — пожалуй. которое зачастую неправильно понимается: поговорим об отличиях язы� ка и платформы. И это хорошо — так много всего предстоит исследовать теперь. ожидающих впереди. как построен процесс одо­ брения. Дочитав эту главу. Для начала объясним отличие. в частности новый способ обработки исключений (множественный catch). но уже на этом этапе познакомим вас с некоторыми мощными возможностями. а также работу с конструкцией try-with-resources (исполь� зование ресурсов в блоке try). а также в мир других языков. но эффективных нововведений. Мы расскажем. поэтому с ним просто необходимо разобраться. действующих на виртуальной машине Java (JVM). Вы изучите новый синтаксис. вы сделаете только первые шаги в огромный мир — мир новых функций. Обсудив этот процесс.1 Введение в Java 7 В этой главе:  Java как платформа и как язык. Затем мы рассмотрим проект «Монета» — набор небольших. вы сможете по-новому писать на Java и будете в полной боевой готовности для изучения мас� штабных тем.  небольшие. это центральный аспект современного языка Java������������������������������� ����������������������������������� . Это важнейший вопрос. появившихся в рамках проекта «Монета». софтверного искусства.

 — это разница между язы� ком �������������������������������������������������������������������������� Java���������������������������������������������������������������������� и платформой �������������������������������������������������������� Java���������������������������������������������������� . Одно из самых очевидных качеств языка Java заключается в том. Это виртуальная машина Java (JVM). Эти сведения вам точно не помешают. в каком он ей предоставляется. и в том. с которой мы начнем наш разговор. Из-за этого может возникать неясность и некоторая путаница и в том. то как они стыкуются и образуют общую систему Java? Связь между языком и платформой заключается в совместном использовании файлов классов (файлов в формате . Существует несколько отдельных спецификаций. Стандартизация позволяет различным производителям и участникам разнообразных проектов создавать реализации. Это означает. Разработка на Java 7 1.1. но вполне гарантируют правильность результатов. Язык и платформа Важнейшая проблема. но разные авторы по-разному определя� ют и феномен языка. Четко очертим эти различия прямо сейчас. описывающие. Одна из основных причин успеха Java�������������������������������������� ������������������������������������������ как системы ПО заключается в ее стан� дартизации. предоставляющее нам среду времени исполнения. Иными словами. что вы уже довольно хорошо знакомы с ним. должен таким быть!). насколько высока будет производительность конкретной реализации одной конкретной за� дачи. как долж� на работать платформа.class). и феномен платформы. На рис. не являющимися Java. Надеемся. Ниже мы подробнее обсудим разницу между двумя этими спецификациями. а знание этой темы — один из способов. Итак. линкующая и вы� полняющая ваш код в том виде. Такие спецификации не гарантируют.  Платформа Java — это программное обеспечение. Рекомендуем серьезно изучить опре� деление файлов классов. .42 Часть 1. управляющих системой Java. позволяющих хорошему Java����������������������� ��������������������������� -программисту стать вы� дающимся. вот эти определения. а требует предварительного преобразования этого кода в файлы классов. 1. что он пригоден для чтения человеком (или. над которым мы немного пошутили в разделе «Об этой книге». которые теоретически должны работать одинаково. непригодных для чтения человеком. что ����������������������������������������������� Java������������������������������������������� имеет спецификации. Самые важные из них — это спецификация языка ������������������������������ Java�������������������������� (������������������������ JLS��������������������� ) и спецификация вир� туальной машины Java (VMSpec). насколько серьезно в Java 7 поставлена работа с исходными языками. машина не интерпретирует непосредственно файлы с исходным кодом на языке Java. используемые в коде приложения. Если вы усматриваете в этом признак того.1 показан полный процесс создания и использования кода Java. Удивительно. то нам нравится ход ваших мыслей. Код предостав� ляется в виде файлов классов. В версии Java 7 это разделение соблюдается очень строго. продолжайте в том же духе. на самом деле VMSpec уже нигде не ссылается на JLS. по крайней мере. к чему относятся те или иные программные функции. При внимательном изучении такого дуализма напрашивается вопрос: «А како� ва же связь между языком и платформой?» Если в Java 7 они настолько разделены.  Язык Java — это статически типизированный объектно-ориентированный язык. чем отличаются язык и платформа. так как эта разница затрагивает суть самых разных тем из нашей книги.

код Java начинается в виде исходного кода. что работа байт-кода начинается с интерпретации виртуальной машиной JVM. многие разработчики также объясняют. как gcc. . что истинная компиляция — это динамическая компиляция во время исполнения. Введение в Java 7 Рис. 1.43 Глава 1. после чего он подвергается динамической компиляции Как понятно из рисунка. Обратите внимание. 1. В свою очередь. Настоящим компилятором в экосистеме Java является динамический компилятор (JIT) (см. рис. это генератор файлов классов для обработки исходного кода Java. пригодным для чтения человеком. Исходный код Java преобразуется в файлы . в сущности. затем с ним производятся манипуляции в период загрузки. JAVA — ЭТО КОМПИЛИРУЕМЫЙ ИЛИ ИНТЕРПРЕТИРУЕМЫЙ ЯЗЫК? Обычно Java видится разработчику как язык. но несколько позже код также проходит динамическую компиляцию (JIT). что байт-код. Затем эта информация загружается в виртуальную машину Java. что процесс преобразования исходного кода Java в байт-код не явля­ ется компиляцией в том смысле. Призадумавшись. Это означает. В техническом отношении с точки зрения теории компиляции байт-код — это действительно своеобразный промежуточный язык (intermediate language). Но на этом многие специалисты начинают «плыть». В сущности. После этого javac ком� пилирует его в файл . а не настоящий машинный код.class. написан� ного программистом и пригодного для чтения человеком. и машинным кодом. javac не назовешь таким же компилятором. При этом акцентируется тот факт. как код будет использоваться на JVM.1). Поэтому верным ответом на вопрос «Java — это компилируемый или интерпретируемый язык?» будет: «И такой и такой». является машинным кодом для воображаемого или упрощенного процессора.class. а не создание файла класса во время исполнения. Многие популярные фрейм� ворки (особенно те. в каком она понимается в языках C и С++.1. Некоторые специалисты характеризуют систему Java как «динамически компилируемую». что манипуляции с классами и их изменение зачастую осуществляются в ходе процесса загрузки. выстраивая несколько надуманную концепцию о том. компилируемый в файлы . На самом деле байт-код виртуальной машины Java можно считать переходной формой между исходным кодом.class до того. в названии которых присутствует слово Enterprise) преобра� зуют классы в ходе загрузки.

чтобы их можно было показать людям. поговорим о некоторых заметных изменениях синтаксиса языка. можете переходить к разделу 1. вероятно. объединенных в рамках проекта «Монета». лучше идти по пути наименьшего сопротивления. ПРИЧЕМ ТУТ «МОНЕТА» Цель проекта «Монета» — постепенно вносить в язык Java небольшие изменения. 1. как подбира� лись функции и как процесс эволюции языка происходит на уровне небольших изменений. Это означает. фантазии и вездесущие несносные каламбуры просто наводнили техническую культуру. к этому уже привыкли.2. Начнем с небольших синтаксических перемен. так как существительное coin означает «монета». Но если вы не очень интересуетесь тем. Малое прекрасно — расширения языка Java. или Проект «Монета» Проект «Монета» (������������������������������������������������������������ Project����������������������������������������������������� ���������������������������������������������������� Coin������������������������������������������������ ) — это свободный проект. появившихся в Java 7. В изменении языка Java�������������������������������������������������� ������������������������������������������������������ прослеживается определенная кривая усилий. сколько работы потребуется на полную разработку изменений — до такой степени готовности. 1. объединенных проектом «Монета». разрабатываемый в рам� ках подготовки Java ������������������������������������������������������������� ������������������������������������������������������������������ 7 (и 8) с 2009 года. Это название в английском языке связано с игрой слов. Разработка на Java 7 Итак.3 — там мы непосредственно обсу­ждаем конкретные изменения в языке. В этом разделе мы объясним. Но не все функции настолько просты. требуемое в каждом конкретном случае. На рис. когда мы немного разъяснили разницу между языком и платформой. . Этот раздел будет построен в фор� ме ситуационного исследования (case study). чтобы их можно было реализовывать в виде библиотек либо новых возможностей интегрированной сре� ды разработки. что сможем пролить немного света на эту проблему. Хотя вы. Но члены сообщества не все� гда и не вполне представляли. то так и стоит поступить. Именно в таком значении «Монета» понимается здесь — проект «чеканит» новые выражения. Мы расположили эти линии в порядке возрастания необходи� мых усилий. а также развеять некоторые мифы. но и о том. Мы считаем. Это же слово может означать «выдумать». Подобные языковые игры. В принципе. Реали� зация некоторых языковых деталей требует значительно меньшего количества разработок.44 Часть 1. например «выдумать фразу». что важно рассказать не только о том. Мы надеемся. В ходе разработки Java 7 вокруг новой версии языка творилось множество всего интересного. почему появилось такое изменение.2 мы попытались представить раз� личные линии разработки и относительное количество усилий. При реализации некоторые функции должны быть глубже укоре� нены в платформе. чем реализация других. как развивается язык Java. добавляя их в язык. что изменилось в языке. а глагол to coin — «чеканить». что если существует возможность реализовать новую функцию как библиотеку.

которые бывают смешными или немного издевательскими.  изменение формата файлов классов — аннотации (Java 5). что такая синтаксическая форма избыточна — она уже есть в языке. — но синтаксический сахар существует потому. Первая серия предложений по изменениям в рамках «Монеты» высказывалась на рассылке разработчиков «Монеты» в период с февраля по март 2009 года. Принято говорить об «обессахаривании» кода для получения базового представления конкретной функции. СИНТАКСИЧЕСКИЙ САХАР Некоторые функции языка называются выражением «синтаксический сахар» (syntactic sugar). являющиеся синтаксическим сахаром. Изменения вносятся только на уровне компилятора (в случае с Java это javac). Проект «Монета» (а также оставшаяся часть этой главы) посвящен переменам. 1. расположенным в диапазоне от синтаксического сахара до небольших изменений языка. Действует общее правило. Было внесено почти 70 предложений. демонстрирующих. что человеку с ним легче работать.com/. согласно которому функции. — см. Относительная сложность усилий при реализации новой функциональности различными способами Ниже перечислены функции (в основном из Java 7). Это означает.45 Глава 1.  небольшая новая языковая функция — конструкция try-with-resources (Java 7).2. удаляются из представления программы для компилятора на ранней стадии процесса компиляции. http://icanhascheezburger. так как для этого требуется выполнить небольшую работу. которые иллюстрируют элементы приведенной выше шкалы:  синтаксический сахар — символы подчеркивания в числах (Java 7). Введение в Java 7 Рис. насколько широк диапазон возможных оптимизаций. Внедрять в язык изменения на уровне синтаксического сахара сравнительно легко.  новая функция виртуальной машины Java — invokedynamic (Java 7). Было даже сделано шутливое предложение добавить многострочные строки в стиле «лолкотов» — юмористических надписей на фото� графиях с котами. .

сделанного на конференции JavaOne в 2006 году. которые потом сказываются на всей структуре языка. работа ведется в коллективном режиме. чтобы сориентировать других разработчиков. как язык и платформа могут развиваться в будущем: изменения обсуждаются открыто. велись развернутые дискуссии о функциях. исходного кода которых у Sun не было). В рассылках.  открыто обсуждать сделанное предложение в рассылке и учитывать конструк� тивную критику от других участников. исходный код языка Java был выпущен по лицензии GPLv2 (за исключением отдельных фрагментов. При создании платформы Java в свободном режиме основное внимание было приковано к проекту OpenJDK.11 спе� цификации JLS. Разработка на Java 7 Предложения «Монеты» оценивались в соответствии с совершенно простым набором правил. таких как coin-dev. Сложно представить себе более мелкое изменение. может быть. Автор предложения должен был сделать три вещи:  подать подробное правильно оформленное предложение. которые могут быть добавлены в будущем. КОТОРАЯ РАЗРАБАТЫВАЛАСЬ В СТИЛЕ СВОБОДНОГО ПО Java не всегда был «свободным» языком программирования.  написать тесты и примеры.  реализовать прототип в компиляторе исходного кода. JAVA 7 — ПЕРВАЯ ВЕРСИЯ. Изменения всегда приводят к последствиям. которые позволили бы реализовать предложенное изменение. Посетите страницу http://java. происходит раннее про� тотипирование функций. поэтому Java 7 — первая версия языка. которое мы рассмотрим ниже. Таким образом.  добавить библиотечную поддержку. На примере проекта «Монета» хорошо видно. Но после соответствующего объявления. Это случилось примерно на этапе выхода Java 6.  быть готовым предоставить прототипный набор заплаток (патчей). но даже оно затрагивает сразу несколько частей спецификации. — так мы помогаем улучшать и сам язык Java. необходимую для данного изменения. lambda-dev и mlvm-dev. описывающее пред� лагаемое изменение (принципиально это должно быть изменение языка Java. которая разрабатывалась по лицензии свободного программного обеспечения (OSS). связано с добавлением единственного слова — String — в раздел 14. широкое сообщество разработчиков могло совместно заниматься созданием Java 7.net/projects/jugs/pages/AdoptOpenJDK.  обновить документацию. Вот действия. На самом деле мы помогаем вести программу Adopt OpenJDK. На данном этапе вы можете задать вопрос: «А как выглядит такое маленькое изменение в спецификации?» Одно из таких изменений. которые следует осуществить (или как минимум проверить их необходимость) при любом изменении:  обновить JLS. пока мало знакомых с OpenJDK.46 Часть 1. а не изменение виртуальной машины Java). . вы захотите к нам присоединиться.

мы немного познакомились с основами проекта «Монета».  реализовать изменения виртуальной машины. Введение в Java 7 47 Кроме того. применение ресурсов в блоке try (конструкция try-with-resources). которые были отобраны для включения в язык. Поэтому любые изменения в системе типов могут приводить к неожиданным изменениям. Дело в том. что языки с богатой системой статических типов обычно имеют множество воз� можных точек взаимодействия между различными компонентами таких систем типов. то там можете подробно изучить все предложения. Кроме того. Мы не будем приводить полные формальные обоснования этих предложений. теперь рас� смотрим отдельные функции. такой подход оказался прагматичным. какой объем работы требуется для внесе� ния даже малейших изменений. поговорим о первой из новых функций Java 7 — строковых значениях String в конструкции switch. сделанные в рамках проек� та «Монета». Как видите. насколько это возможно. если вы молодой и перспективный разработчик языков программирования. по кото� рым были приняты те или иные новые функции. Не потому. что система типов в Java плоха. . как будет учтено воздействие изменения на всю спецификацию языка! Когда дело доходит до внесения изменений. Это строки (String) в конструкции switch. 1. работы немало. и это уже после того. объяснить мотивы.  добавить поддержку в файл класса и инструментарий виртуальной машины. такие как нативный интерфейс Java (JNI).3. работающие с нативным кодом.  обдумать любые воздействия на компоненты. то следует:  обновить спецификацию VMSpec. Итак.  учесть воздействие изменения на сериализацию. возникавшими при использовании функций с пере� менным количеством аргументов. Нам предстоит поговорить о синтаксисе и о значении новых функций.  учесть воздействие изменения на отражение (reflection). но все эти материалы доступны в ар� хиве расылки coin-dev. усовершенствованная обработка исключений. особенно щекотливые ситуации возникают с системой типов. ромбовидный синтаксис и исправление си� туации с предупреждениями. Итак.Глава 1. новые формы числовых лите� ралов. мы попытаемся. Изменения в рамках проекта «Монета» В рамках проекта «Монета» в язык ������������������������������������������ Java ������������������������������������� 7 было добавлено шесть основных ново� введений. Учитывая. Мы собираемся подробно обсудить эти изменения. если изменение касается виртуальной машины или платформы. Поэтому. В рамках проекта «Монета» была избрана очень разумная стратегия решения таких проблем: участникам проекта рекомендуется не затрагивать систему типов при предложении изменений.

касающихся нового син� таксиса целочисленных типов. break.1. break. Усовершенствованный синтаксис для числовых литералов Было сделано несколько самостоятельных предложений. break.println("Error!"). Short.out. break.out. Character. case 2: System. В Java ������������������������������������� ������������������������������������������ 7 спецификация была расширена и к ис� пользованию был допущен тип String.out. это совсем простые нововведения.3.println("Thursday"). case "Saturday": System.out. break.println("Dimanche"). default: System.out. это же константы. case "Wednesday": System.println("Lundi").out. break.println("Vendredi"). break. break. Строки в конструкции switch Оператор switch в Java позволяет писать эффективные многократно ветвящиеся инструкции без многих и многих уродливо вложенных друг в друга операторов if — вот так: public void printDay(int dayOfWeek) { switch (dayOfWeek) { case 0: System. short. В конце концов. default: System. break.println("Saturday"). case "Tuesday": System. Integer) либо константы enum.out.println("Wednesday").out. case "Friday": System. break.println("Mardi"). break. break. case 3: System.println("Samedi").println("Tuesday"). Разработка на Java 7 1.err. Как и многие другие изменения в рамках проекта «Монета».out.out. break.out.println("Jeudi"). case "Monday": System. . } } Во всех других отношениях конструкция switch не изменилась.println("Monday").out.3. case "Thursday": System. значительно упрощают жизнь Java-программистам.out.println("Error: '"+ dayOfWeek +"' is not a day of the week"). case 1: System.48 Часть 1. case 5: System. В итоге приняты следующие положения:  числовые константы (то есть любой из примитивных целочисленных типов) теперь могут выражаться в виде двоичных литералов.out. int (или технически их эквивалентные ссылочные типы Byte. break. break.println("Sunday"). однако.println("Mercredi"). public void printDay(String dayOfWeek) { switch (dayOfWeek) { case "Sunday": System. char. case 4: System. case 6: System.out. которые. } } В Java 6 и ниже значениями для условий могли быть только константы типов byte. break.println("Friday"). 1.2.

 Необходимо знать о двухаргументной форме parseInt(). Введение в Java 7 49  в целочисленных константах могут использоваться нижние подчеркивания — они повышают удобочитаемость кода. Еще одно небольшое. если допустите опечатку в двоичном значении. чтобы int x правильно представляло комбинацию битов для десятичного значения 102.parseInt("1100110". Это слишком большое количество кода. Если у вас будут причины для работы с двоичными файлами. Итак. 2). На первый взгляд оба изменения кажутся довольно неброскими. Но в таком случае не возникает ни одной из перечисленных выше проблем. при выполнении низкоуровневой об� работки байтов вы можете оперировать комбинациями битов как двоичными константами в операторах switch. если вы хотите гарантировать. Например.  Очень много текста. если требуется просто гарантировать. Но оба ново� введения вызвали небольшое раздражение у Java-программистов. напишите такое выражение: int x = Integer.  Нужно помнить детали работы parseInt() при наличии двух аргументов. Например. кто работает с «сырыми» сетевыми протоколами. Несмотря на краси� вый вид этой записи. шифрованием или занимается другими делами. Однако эти усовершенствования очень интересны для низкоуровневых про� граммистов — тех.  Значительно осложняется работа динамического компилятора. Двоичные литералы До выхода Java ������������������������������������������������������������� ������������������������������������������������������������������ 7. являющаяся константой во время компиляции. но полезное нововведение для представления групп битов или других длинных числовых представлений — применение нижних подчеркива� ний в числах. то эта маленькая функция вам очень пригодится. прихо� дилось либо пользоваться неуклюжим (и чреватым множеством ошибок) преоб� разованием к исходному типу (base conversion). представляется как выражение времени исполнения. Мы не утверждаем. после появления Java 7 мы можем поступить так: int x = 0b1100110. начнем с рассмотрения двоичных литералов. такой подход связан с некоторыми проблемами. К счастью. что эта конструкция позволяет совершить что-то ранее невозможное.  Вы получите исключение RuntimeException (но не исключение времени компи� ляции). либо задействовать методы parseX. предполагающими операции с битами.  При вызове такого метода заметно страдает производительность. что x в конечном итоге имеет правильную комбинацию битов.Глава 1.  Сущность. что константа не сможет исполь� зоваться в качестве значения в операторе switch. если вы хотели манипулировать двоичными значениями. Это означает. .

что человеческий мозг принципиально отличается от компьютерного процессора. насколько проще стало читать значение. поясним: номера в диапазоне 555-01xx специально зарезервированы для такого «художественного» использования. телефонный номер в США обычно имеет следующую форму: 404-555-0122. Разработка на Java 7 Нижние подчеркивания в числах Вы. которым люди научились справляться с длинными последовательностями чисел.  08-92-96 (номера отделений банков в Великобритании). Обратите внимание: здесь показан всего лишь фрагмент удобного для чтения синтаксиса. например:  $100. не сомневаетесь. ПРИМЕЧАНИЕ Если любознательные читатели из Европы задумывались о том. как наш мозг обрабатывает числа. — разбиение их на части. как минимум один из них должен показаться вам знакомым: long anotherLong = 2_147_483_648L. Обязательно используйте L в верхнем регистре. Рассмотрим пару примеров. Поэтому оба символа не подходят нам в ка� честве разделительных знаков. Это — одна из причин. скорее всего. Вообще. применяемого во время компиляции. воспринимающих голливудское кино чересчур серь­езно. нам будет проще работать с 1c372ba3. люди плохо умеют обращаться с длинными последовательностями чисел. В других длинных последовательностях чисел применяются разделительные знаки. чтобы люди. чем длинными последовательностями. по которым по­ явилась шестнадцатеричная система счисления. Например. не перепу� таете это число с 10_000_000. Характерным примером такого отличия является то. хотя процессор всегда будет иметь дело со второй формой. чтобы никто не получал телефонных звонков от зрителей. Вместо этого разработчики проекта «Монета» по� заимствовали идею из языка �������������������������������������������������� Ruby���������������������������������������������� и ввели в качестве разделительного знака ниж� нее подчеркивание (_). не путали букву l и число 1. наверное. Запись 1010100L гораздо понятнее.000. int bitPattern = 0b0001_1100__0011_0111__0010_1011__1010_0011. и запятая и дефис — слишком многозначные символы в сфере обработки чисел в программировании. Так. тогда как перепутать 100000000 с 10000000 очень легко. чем с 00011100001101110010101110 100011. ведь наш разум лучше оперирует более короткими строками. что вы можете записать 100_000_000 и. К сожалению. Обратите внимание. Это означает. ВНИМАНИЕ В Java по-прежнему допустимо использовать строчную букву l для обозначения типа long.50 Часть 1. . каждый символ в которых сравнительно малоинформа� тивен.000 (большие суммы денег в США). присвоенное anotherLong. почему номера американских телефонов в фильмах и книгах всегда начинаются с 555. Так. занимающиеся поддержкой кода. сильнее насыщенными информацией. 1010100l. Компилятор извлекает из кода нижние подчеркивания и сохраняет обычные цифры. Один из спосо� бов.

 вероятно. Попробуем свести все варианты к этим двум случаям — тогда мы будем обра� батывать все исключения вида «файл отсутствует или каким-то образом поврежден» в одном условии catch (листинг 1. 1.2). рассмотрим код на Java 6.err. файл конфигурации не существует. Введение в Java 7 Надеемся.println("Config file '" + fileName + } catch (ParseException e) { System. что вы уже убедились в пользе этих дополнений при обработке це� лых чисел.err. } В этом методе возникает несколько различных ситуаций.  файл конфигурации может иметь неправильное синтаксическое оформление. Усовершенствованная обработка исключений Усовершенствования связаны с двумя основными изменениями: множествен� ным catch (multicatch) и переброской неизменяемого исключения (final rethrow). } catch (FileNotFoundException fnfx) { System. открыть файл конфигурации. чем полезны эти изменения. Функционально эти исключительные ситуации относятся к двум разным груп� пам: либо файл отсутствует или каким-то образом поврежден. Обработка нескольких различных исключений в Java 6 public Configuration getConfig(String fileName) { Configuration cfg = null. Далее мы поговорим об усовершенствованной обработке исключений в Java 7. return cfg.3. либо файл присут� ствует и не содержит ошибок. приводящих к выбро� су исключения:  возможно. Листинг 1. файл конфигурации исчезнет как раз в то время. В Java 7 это возможно. "' is not consistent").1). '" + fileName + "'").  в файле конфигурации может содержаться неверная информация. но его не удается правильно получить (например.err. произвести его синтаксический раз� бор и обработать несколько различных исключений (листинг 1.err. .println("Config file '" + fileName + } "' is missing"). который пытается найти.1.3.println("Error while processing file } catch (ConfigurationException e) { System. try { String fileText = getFile(fileName). Чтобы разобраться.println("Config file '" + fileName + } catch (IOException e) { System. "' is malformed"). из-за ошибки оборудования или отказа сети). cfg = verifyConfig(parseConfig(fileText)). когда вы считыва­ ете из него информацию.51 Глава 1.

} catch (final Exception e) { . вы вынуждены объявить сигнатуру исключения в этом коде как Exception — реальный динамический тип исключения оказывается поглощен.2. к кото� рым оно могло бы относиться (на практике это обычно бывает Exception или Throwable). } Соответственно. doSomethingElseWhichMightThrowSQLException(). throw e. Обработка нескольких различных исключений в Java 7 public Configuration getConfig(String fileName) { Configuration cfg = null. понятно.. что фактически выбрасывается тип времени исполнения для найденного исключения — в данном . Это означает.println("Error while processing file '" + fileName + "'"). охватывающему все типы исключений. а если вы это видите. throw e.err. Еще один пример нового синтаксиса помогает разобраться с переброской ис� ключений. cfg = verifyConfig(parseConfig(fileText)). } catch (FileNotFoundException|ParseException|ConfigurationException e) { System. что исключение в этом коде может относить� ся только к типу IOException или SQLException. что в более ранних версиях Java часто можно было встретить такой код: try { doSomethingWhichMightThrowIOException().52 Часть 1..println("Config file '" + fileName + "' is missing or malformed"). try { String fileText = getFile(fileName). Разработка на Java 7 Листинг 1. } return cfg. Проблема заключается в том. Во многих случаях разработчику может потребоваться произвести какие-то манипуляции с полученным исключением до его переброски.err. } catch (IOException iox) { System. doSomethingElseWhichMightThrowSQLException(). } Поскольку здесь присутствует ключевое слово final. В синтаксисе Java 7 этот фрагмент изменится всего на одно слово: try { doSomethingWhichMightThrowIOException(). который не удается точно распознать во время компиляции. } catch (Exception e) { . что оно должно обрабатываться в блоке catch как относящееся к общему супертипу.. } Исключение e относится к типу. то и компилятор тоже видит.. Тем не менее несложно заметить.

со� держит ошеломляющее утверждение: якобы две трети случаев закрытия close() в JDK выполнены с ошибками! К счастью. в котором человек часто допускает ошибки. как бы вы напи� сали в Java 6 блок кода. Введение в Java 7 53 случае это может быть лишь IOException или SQLException. Листинг 1. а потом записывает информацию в файл (out). Действительно.3. Чтобы оценить. насколько она полезна. с помощью сильно обобщенной конструкции catch. который потом можно было бы перехва� тить лишь на очень высоком уровне.3). Предло� жение. Использование ресурсов в блоке try (try-with-resources) Объяснить это изменение довольно легко.4. Этот прием и называется переброской неизменяемого исключения (final rethrow). Вот один из вариантов ре� шения (листинг 1. Наряду с этими общими усовершенствованиями обработки исключений в Java 7 было улучшено и управление ресурсами. поданное в рамках проекта «Монета» и касающееся этого изменения. чем ожидалось. который считывает информацию из потока.Глава 1. чтобы ресурс автоматически закрывался после выхода управления за пределы блока. из-за которых его реализация происходит значительно сложнее. что практически никто не уме� ет вручную закрывать ресурсы на 100 % правильно. Основная идея заключается в том. До недавнего времени даже в справочных руководствах от ����������������������������������������������� Sun�������������������������������������������� этот процесс был описан с ошибками. файл или тому подобную сущность) в область видимости блока таким образом. Как раз такой подход и был выбран при реализации этого улучшения. 1. Но прак� тика показывает. Об этом мы поговорим далее. Синтаксис для управления ресурсами в Java 6 . чтобы поместить ре� сурс (например. Она защищает нас от полу� чения слишком общего типа исключения. но оказалось. компилятор можно хорошо приспособить к созданию именно тако� го «педантичного» шаблонного кода.3. просто представьте себе. Это важное изменение по той простой причине. новая конструкция очень помогает писать безошибочный код. В предыдущем примере ключевое слово final является опциональным. что такой механизм обла� дает скрытыми нюансами. что использовать его полезно — это помогает при корректировке новой семантики конструкций catch и rethrow. идущего с URL (url).

url — это объект URL (универсальный локатор ресурса). InputStream is = url.write(buf. пытающийся написать код такого типа вручную.3. — очень сложно правильно разобраться с комбинацией нескольких исключений. } } .  файл File. обусловленная несколькими факторами.openStream() ) { byte[] buf = new byte[4096]. как и код из лис� тинга 1. Последняя проблема.4). указывающий на сущность. Синтаксис Java 7 для управления ресурсами try (OutputStream out = new FileOutputStream(file). Именно по этой причине в основном предпочтение отдается новому синтакси� су — в нем просто сложнее допустить ошибку. len). int len. которую вы хотите загрузить. осуществляющий такую же задачу.4. может не открыться (и в него нельзя будет записать информацию) либо может закрыться неправильно. в котором вы хотите сохранить загружаемую информацию.read(buf)) > 0) { out. что при обработке внешних ресурсов действует один из законов Мерфи: «Что угодно может сорваться в любой момент»:  поток InputStream. Разработка на Java 7 А у вас получилось похоже? Основной момент здесь заключается в том. while ((len = is. Компилятор не допускает таких ошибок. соответствующий потоку OutputStream. Листинг 1. которые легко совершает любой разработчик. 0.54 Часть 1. Вот что получится в Java 7(листинг 1.  может возникнуть проблема. Как и выше. file — это объект File. Рассмотрим код ��������������������������������������������������������� Java ���������������������������������������������������� 7. доставляющая сильную головную боль. может не открыть� ся по URL либо может закрыться неправильно. из которого нужно считывать информацию.

ScratchSuprExcep.run(ScratchSuprExcep..ch01.ScratchSuprExcep. Теперь вы можете просматривать в ис� ключениях и информацию о типах. если была допущена ошибка при создании ObjectInputStream из файла (someFile. как вы закончите работу с ними.. Ресурсы используются в блоке и автоматически высвобождаются после того. в котором мы увидим заблоки� рованное исключение NullPointerException (сокращенно — NPE): Exception in thread "main" java. в котором нулевой InputStream возвращается из метода: try(InputStream i = getNullStream()) { i.NullPointerException at wgjd. это будет не файл ObjectInput.java:39) Suppressed: java. рассмотрим следующий фрагмент кода.. try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("someFile. } В результате получится улучшенный стектрейс. } Один из аспектов конструкции TWR — появление улучшенных стектрейсов и заблокированных исключений.java:23) at wgjd.bin")) ) { . следующий код не закроет FileInputStream правильно.ch01. А файл другого формата может не открыться правильно. поэтому стековые следы были усовершенствованы. Программисту. име­ ющему опыт работы с языком C#.bin).lang. До Java 7 информация об исключении могла быть поглощена при обработке ресурсов. Введение в Java 7 55 В этом простейшем примере показан синтаксис блока для автоматического управления — операция try с ресурсом в круглых скобках.java:24) 1 more .main(ScratchSuprExcep.run(ScratchSuprExcep. эта конструкция может напомнить условие using.Глава 1. а поток FileInputStream не будет закрыт! Наилучший способ гарантировать правильность работы try-with-resources — раз� бивать ресурсы на отдельные переменные. При работе с конструкцией try-with-resources нужно оставаться начеку. которая ранее просто терялась. try (FileInputStream fin = new FileInputStream("someFile. так как в некоторых случаях ресурс может остаться незакрытым. ObjectInputStream in = new ObjectInputStream(fin) ) { . } Предположим. но. поток ObjectInputStream не будет создан. Например.available(). возможно. что файл (someFile. Такой вариант не исключен и с TWR.bin").bin) существует.. Следовательно. Например.lang.ScratchSuprExcep.ch01.NullPointerException at wgjd. Концептуально это хороший отправной пункт для работы с данной новой функци� ей.

5. у вас есть несколько пользователей. называемого AutoCloseable. что эта новая технология задействована еще не во всех местах платформы. Допустим. Map<String. String>> usersLists = new HashMap<>(). что при работе с этой функцией компилятор исполь� зует новую форму выведения типов. которых вы идентифицируете по userid (этот идентификатор представляет собой целое число). Map<String. которое частично избавляет вас от набора кода при работе с обобщенными сущностями (дженериками). специ� фичных именно для него. Проект «Монета» предоставляет вам такую возможность. а не просто подставляет текст. А информацию о типах в правой части выражения пускай обрабатывает компи� лятор. Запись получится короче на несколько пикселов. Но необходимо учитывать. многовато текста. такая форма обладает обратной совместимостью. String>> usersLists = new HashMap<Integer. Эта функция. И каждый пользователь имеет одну или несколько таблиц поиска. Ромбовидный синтаксис В Java 7 есть и такое изменение.56 Часть 1. Как все это будет выглядеть в коде? Map<Integer. Многие классы платформы Java 7 были преобразованы так. лучше написать что-то в таком роде: Map<Integer. Поэтому.3. определяющий полный тип. правда. чтобы работать в рамках нового условия try конструкции TWR как ресурс. В вашем собственном коде вам определенно следует использовать TWR всегда. Необходимо отметить. что определение и настройка экземпляров бы� вают очень пространными. была включена в состав JDBC 4. Кроме того. Map<String. В Java 7 подобная сокращенная форма объявлений абсолютно допустима. причем почти половина информации дублиру� ется. Согласитесь. Этот механизм позволит вам избежать ошибок при обработке исключений. Тогда вы сможете легко избавиться от ошибок в вашей базе кода. Может быть. AutoCloseable был сделан суперинтерфейсом Closeable). Разработка на Java 7 TWR И AUTOCLOSEABLE На уровне системы функция TWR реализуется с помощью нового интерфейса. чтобы могли реализовывать интерфейс AutoCloseable (в свою очередь. 1. String>>(). когда возникает необходимость работать с ресурсами. Одна из проблем при работе с дженериками заключается в том. если вам доведется пересматривать старый код. как только будет возможность. Класс должен реализовывать этот интерфейс. В правой части выражения он выводит правильный тип для выражения. Рекомендуем приступать к использованию конструкции try-with-resources. то можете сократить старые пространные объявления и перейти к ис� пользованию нового синтаксиса с выведением типов. .1.

а потом осуществляете операцию. String>[] arrayHm = new HashMap<>[2].. читаем дальше? Хорошо. entries) { . Но вы. касающееся информации о типе. появившуюся в рамках проекта «Монета». Введение в Java 7 57 ПОЧЕМУ ТАКОЙ СИНТАКСИС НАЗЫВАЕТСЯ РОМБОВИДНЫМ Такой синтаксис называется ромбовидным потому. — «Усовершенствованное выведение типов при создании экземпляров обобщенных сущностей» (Improved Type Inference for Generic Instance Creation). что сокращенная информация о типе имеет форму ромба. Наконец. Как правило. наверное. Звучит довольно глупо. то можете пропустить этот раздел и переходить к следующему.. все находящиеся в конце списка переменные параметры помещаются в массив (автоматически создаваемый для вас компилятором) и передаются как единый параметр.3. вы не можете создать массив элементов уже известного обобщенного типа. возможно. если вы не имеете привычки писать код. который принимает переменное количество параметров (все эти па� раметры относятся к одному типу) в конце списка аргументов. Иными словами. . Если же фрагмент ниже напоминает что-то из вашего творчества. Все это. фигурирующее в предложении. Разберемся. требующую сделать выборку из этих ссылок. в чем же проблема. что метод с переменным количеством аргументов — это такой метод. сокращаемое до аббревиатуры ITIGIC. Упрощенный вызов методов с переменным количеством аргументов Это одно из самых простых изменений.. Официальное название. Такие предупреждения возникают в очень специфичных слу� чаях. Это чересчур длинное выражение. Вы. Например. но тут мы сталкиваемся с одним из признанных сла� бых мест дженериков Java — как правило.Глава 1. возникающих при исполь� зовании переменного количества аргументов. рассмотрим еще одну новую функцию. не знаете. 1. так что остановимся на термине «ромбовидный синтаксис». новый ромбовидный синтаксис позволяет печатать меньше кода. хорошо. принимающий в ка� честве аргументов переменное количество ссылок типа T. } Итак. как реализуются такие vararg-методы. то читайте дальше. Несомненно. Оно удаляет предупреждение. Речь пойдет об удалении предупреждений. public static <T> Collection<T> doSomething(T.6. конечно.. следующий код не скомпилиру� ется: HashMap<String. когда переменное количество аргументов комбинируется с дженериками в сиг� натуре метода. знаете.

реализующего API. Кому действительно важно получить такое предупреждение — так это товарищу. В кон� це концов.58 Часть 1.lang. Раньше предупреждение инициировалось при компиляции кода. String>> coll = doSomething(hm1. компилятор жульничает и нарушает собственное пра� вило о запрещенном массиве обобщенного типа. во-вторых. Два этих феномена — во-первых. и.SafeVarargs. Код в doSomething() либо нарушает безопасность типов. и кого-то об этом лучше предупредить. обрабатывающие сгенерированные компилятором массивы. String>[] warnHm = new HashMap[2]. которое следует проигнорировать. то есть производителю API. выдавая невразумительное предупреждение вида «непрове� ренные или небезопасные операции». в конструкциях таких типов есть потенциал для нарушения безопасно� сти типов. которые. но тип этого массива строго должен быть одним из запрещенных типов массивов. В любом случае от пользо� вателя API ничего не зависит. которая казалась совершенно нор� мальным API����������������������������������������������������������������� �������������������������������������������������������������������� . когда пишется API. Collection<HashMap<String. могут сделать с их помощью не так много. String> — но просто не можете создать ни одного экземпляра такого типа. Туда и отправляется предупреждение. Он создает экземпляр массива. методы с переменным количеством аргументов. HashMap<String. Обратите вни� мание: вы можете определить тип warnHm как массив HashMap<String. невозможно инстанцировать — вме� сте становятся очередной головной болью. Java 7 также предоставляет новый тип аннотаций: java. Вместо этого приходится сделать так: HashMap<String. hm2). Но несчастный раз� работчик всего-то хотел использовать штуку. где используется API. а не его потребителю. Чтобы упростить жизнь и разработчикам API. содержащий как hm1. использова­ вшего данный API. С точки зрения системы типов тут все вполне правильно. так и hm2. Компи� лятор предупреждает программиста. а туда. String> hm2 = new HashMap<>(). обладающий потенциалом для подобного нарушения безопасности типов. Компилятор попытается создать массив. Стал� киваясь с такой дилеммой. либо не нарушает. Такая аннотация может применяться к методу . однако. Выдается предупреждение. Теперь же оно выдается в том случае. String> hm1 = new HashMap<>(). массивы известных обобщенных типов. Но программисты. Разработка на Java 7 Нельзя создать массив указанного обобщенного типа. — и обратить внимание на систему типов должен именно его разработчик. Рассмотрим следующий код: HashMap<String. Приходится «наступить себе на горло» (или как минимум заблокировать предупреждение) и принудительно преобразовать экземпляр необработанного типа (представляющий собой массив HashMap) в warnHm. а ему выдают какие-то грозные предупреждения по каким-то необъ� ясненным причинам. пользующие� ся такими типами API. написавшему doSomething(). но при этом «ругается». Оно попадает не туда. где этот API был определен. Куда деваются предупреждения в Java 7? Рассматриваемая новая функция в Java 7 изменяет акцент в предупреждении.

сколько всего требу� ется сделать. как именно взаимодействуют различные компоненты системы типов и как изменятся эти взаимодействия вслед за внесением изменений в структуру языка. насколько заковыристым может быть эффект от самых небольших изменений. как ввод-вывод был органи� зован в более ранних версиях Java.4. который в противном случае выдал бы предупреждение рассмотренного выше вида. конечно. гарантирует. да дорог. так как классы Java 7 (иногда называемые NIO. какие из основных библиотек поменялись в Java 7. которые были во многом пересмотрены. что этот метод не будет совершать никаких небезопасных операций. насколько они полезны в создаваемых программах. намного более сложны и потенциально могут разветвляться на десятки несхо� жих ситуаций. различия между которыми. чтобы рассказать о совсем маленьком измене� нии! Итак. 1. Но рассмотренный пример помогает проиллюстрировать один очень важный момент. .2) построены на базе уже имеющегося фреймворка. хотелось бы. о которых нам еще предстоит поговорить. Как только вы начнете работать с этими измене� ниями.Глава 1. возможность переместить предупреждение из одного места в другое — это. возможно. то перево� рачивайте страницу — обо всем этом рассказано в следующей главе. не революционное нововведение в языке. возможно — некоторые функции нереали� зуемы без внесения изменений в язык). Вам совсем не помешают знания о том. вы убедитесь. в сущности. Теперь перейдем к более обширным изменениям. Для начала рассмотрим. Данное изменение даже не назовешь особенно сложным — в от� личие от тех. чтобы понять. Хотя здесь мы обсудили в основном мел� кие синтаксические изменения. Сле� дующая большая тема — это библиотеки ввода-вывода. чем им. предлагая изменения. Новую функцию всегда легче реализовать в виде библиотеки (если это. они могут значительно улучшить ваш код. Аннотируя метод с помощью @SafeVarargs. конечно. Выше в этой главе мы упоминали. рассмотренные ниже. появившимся в этом релизе. что в рамках проекта «Монета» разработчикам рекомендуется не трогать систему типов. Связанные с этим сложности вынуждают разработчиков языка вносить мелкие и сравнительно консервативные изменения. Если вы хотите изучить дополнительные примеры синтаксиса TWR в действии либо узнать о новых высокопроизводительных классах ввода-вывода. Если это произойдет. однако. Введение в Java 7 59 API (или конструктору). Резюме Вносить изменения в язык непросто. Этот последний пример демонстрирует. Изменения в системе типов Как много слов пришлось написать. Изменения. А на этом примере видно. разработчик. Действи� тельно. то компилятор заблокирует предупреждение. мал золотник. не такие явные.

2 (описан в запросе на спецификацию JSR-203).2).  Path — новая основа для файлового ввода-вывода и ввода-вывода каталогов.  введение в асинхронный ввод-вывод.2 значительно упрощает решение этой программной зада� чи. В Java 6 (и ниже) решить такую задачу практически невозможно в силу нескольких причин:  на уровне классов и методов отсутствует прямая поддержка навигации по де� реву каталогов. Расмотрим пример практического использования.io. содержащие заданный набор при� вилегий на чтение/запись и владение файлом.  вспомогательный класс Files и его различные вспомогательные методы. Именно ему посвящена основная часть данной главы. так как появляется новая конструкция Network-Channel. При этом не придется вручную конфигурировать пулы потоков и другие низкоуров� невые конструкции. с помощью которых вы сможете вы� полнять файловые и сетевые операции ввода-вывода в фоновом потоке. Предположим.  Он содержит новые асинхронные классы. предназначенные для параллельной обработки.  Пакет упрощает написание кода с использованием сокетов и каналов.File. большинство из которых относится к пакету java.  Этот пакет является полнофункциональной заменой java.  отсутствует простая операция для считывания атрибутов файла (таких как readable. NIO.  решение типичных практических сложностей с вводом-выводом. начальник поручил вам написать процедуру Java. . Пакет пред� назначен для написания кода. writable или executable). Новый API Java 7 NIO.  нет способа поиска символьных ссылок и манипулирования ими. Одно из самых значительных изменений API в языке Java — крупные обновле� ния в наборе API для ввода-вывода.2 Новый ввод-вывод В этой главе:  новые API ввода-вывода в Java 7 (также называемые NIO. так как непосредственно поддерживает навигацию по дереву каталогов (Files.2 — это набор новых классов и ме� тодов.nio. которая просматривает все каталоги на рабочем сервере и находит все файлы свойств. взаимодействующего с файловой системой. Новый набор называется «Обновленный вводвывод» или NIO.

каковы основные тенденции в нашем деле. с которыми может столкнуться программист при реализации параллелизма. но NIO. см. что Java 7 действительно умеет пользоваться встроенными в него механизмами! 1 2 Символьная ссылка — это особый тип файла. все активнее исследуются альтернативные методы хранения данных. Параллелизм — искусство. Уф! Эти новые API — не панацея (хотя функции очень симпатичные). считывание этого файла наверняка помешает основному рабочему потоку программы! В Java 5/6 пришлось бы использовать классы из пакета java. опираясь при этом на базовые функции операционной системы.4). с которыми вы сталкиваетесь в ходе раз� работки. не на� рушая основного рабочего потока программы. Как будет показано в главе 4. то всегда можно вернуться к разделам этой главы. Это означает. предлагая простые абстракции для использования много� поточного доступа к файлам и сокетам.2 — это механизм.util. чем может быть полезен NIO.5). учитывая. Это всего несколько примеров. что NIO. пользуясь новым AsynchronousFileChannel (см. В главе 4 рассмотрены те сложные нюансы. Вторая тенденция — распространение многоядерных процессоров. подраздел 2. тем не менее они вам очень пригодятся. .3.3). подраздел 2.1). помогающий решить некоторые типичные проблемы. а также простые однострочные операции для считывания атрибу� тов файлов (Files. Во-первых. описывающим задачи программи� рования ввода-вывода на Java. не указывая собственных рабочих потоков и очередей. Текущая глава позволяет в достаточной мере познакомиться с возможностями ввода-вывода в Java 7. Такая ссылка напоминает ярлык для доступа к файлу. чтобы эти файлы свойств считывались. символьные ссылки1 (Files. см. открывающих возможности для по-настоящему параллельного (а значит. быстрого) ввода-выво� да. чтобы считывание этого файла происходило в отдельном фоновом потоке. разброс возможных ошибок очень велик.2 значитель� но облегчает эту задачу. Но в Java 7 с помощью API NIO. большие файлы отчетов с сервиса микроблогов). что некоторые из рассматриваемых здесь функций уже затрагивались в первой главе.2. Даже если вы не используете эти функции непосредственно. они все равно будут все сильнее влиять на вашу практику про� граммирования по мере их распространения в IDE. Вам поможет и то.isSymbolicLink(). Новый ввод-вывод 61 walkFileTree(). то эта глава — для вас! Если описанные ситуации вам не встречались.2 позволяет считывать и записывать крупные файлы в эффективном асинхрон� ном режиме. что в ближайшем будущем предстоит много работать со считыванием и за� писью крупных файлов (например. особенно в случаях с нереляционными и очень крупными наборами данных. серверах приложений и в по� пулярных фреймворках. современная параллельная обработка в Java остается довольно сложной.Глава 2.2 и уве� ренно исследовать новые API. Вы знаете.2 вы сможете считать большой файл в фоновом режиме. который указывает на другой файл или точку в файловой системе. демонстрирующих. см.4. Кроме того.concurrent для создания пулов потоков и рабочих оче� редей. раздел 2. чтобы приступить к написанию кода на основе NIO. листинг 2. NIO. Это еще раз доказывает. начальник требует.readAttributes(). которое не так просто освоить2. что один из файлов свойств имеет размер не менее 1 Мбайт. Если у вас сложилось впечатление.

Одна из причин огромной популярности Java заключается в обширной библио� течной поддержке.2 с точки зрения создателей API. Поэтому. скорее всего. Сначала мы поговорим о новой абстракции файловой системы — Path. чтобы понять. как появился NIO.0–1. а другие. мы поговорим о взаимопроник� новении функционала Socket и Channel. Поэтому мы рассмотрим эту тему очень подроб� но. требующих поддержки ввода-вывода. Но опытным Javaразработчикам известно. поддерживающих эту абстракцию.  Отсутствовали концепции буферов или абстракций каналов для данных. Одной из основных про� блем для разработчиков долгое время были API для ввода-вывода (I/O). История ввода-вывода в Java Чтобы в полной мере оценить структуру API NIO. впервые в истории Java! Полагаем. планируете воспользоваться новыми возмож� ностями файлового ввода-вывода.1. которые были реализованы в ранних версиях Java практически на пещерном уровне. Наконец. как с ними нужно работать).1. мы обсу� дим обычные операции. Но начнем с того.  Существовала лишь ограниченная поддержка различных символьных кодиро� вок. как появился NIO.62 Часть 1. что есть несколько областей. что вам уже не терпится перейти к коду! Если совсем не терпится — можете переходить к разделу 2. Кроме того. обратимся к истории поддержки ввода-вывода в Java. главу 1) и новых API из NIO. а также о значении этих изменений для разработчиков сетевых приложений. немного странными. осуществляемые в файловой системе. возможно.2.2. В частности. В языке предоставляются мощные и лаконичные API.1.2 (и понять. . Если какие-то примеры использования API покажутся вам особенно красивы� ми. Поэтому имелось множество запрограммированных вручную решений для поддержки определенных типов оборудования. Это третья крупная реализация вводавывода в Java. а также о классах. что вы. 2. осно� ванный на свойствах файловой системы. что ограничивало масштабиру­ емость. мы обсудим асинхронный ввод-вывод и рассмотрим пример.  Операции ввода-вывода часто блокировались. Опираясь на знания о Path. 2.0–3. Но мы отлично понимаем. Поэтому разработчикам приходилось много и кропотливо заниматься низко� уровневым программированием. разработчики сталкивались со следующими проблема� ми при создании приложений. позво� ляющие решать разнообразные задачи при программировании.2. такие как переме� щение и копирование файлов. то этот раздел поможет вам взглянуть на NIO. основательный Java-разработчик должен знать историю ввода-вывода в Java.2 обеспечивает исключительно надежное программирование ввода-вывода — пожалуй. Java 1.3 В самых ранних версиях Java (1.0) отсутствует полномасштабная поддержка ввода-вывода. Разработка на Java 7 СОВЕТ Комбинация конструкции try-with-resources (см.

2. основанная на популярной реализа� ции на языке Perl. Вообще. На самом деле его дизайн и реализация настолько хороши.  новая библиотека регулярных выражений. PERL — КОРОЛЬ РЕГУЛЯРНЫХ ВЫРАЖЕНИЙ Язык программирования Perl — бесспорный лидер в области поддержки регулярных выражений.4 был реализован неблокирующий ввод-вывод.2. Java 1. В частности.4 и неблокирующий ввод-вывод Для того чтобы решить эти проблемы. а также других функций.Глава 2. На тот момент класс java. в Java отсутствовала поддержка неблокирующего ввода-вывода. что осложняло манипуляции с данными.io. В этой области в разное время были достигнуты два крупных улучшения:  в рамках Java 1.4.4. Поэтому разработчикам с большим трудом удавалось создавать масштабируемые решения для ввода-вывода. но Java-разработчики по-прежнему сталкивались со сложностями при программировании ввода-вывода.  возможность кодировать и декодировать наборы символов. использовать везде». На основании запроса на спецификацию JSR-51 неблокирующий ввод-вывод (NIO) был добавлен в Java в 2002 году. Java приступила к реализации поддержки неблокирующего ввода-вывода. Мы считаем. благодаря которым Java превра� тился в привлекательный язык для серверной разработки:  абстрактные уровни (уровень буфера и уровень канала) для операций вводавывода. На этом этапе до� бавился следующий обширный набор функций. хранящимися в па� мяти. Если вас интересует история этого языка. позволяющий ассоциировать файлы с данными. почему язык Java почти не использовался в разработках для серверной стороны до выхода Java 1. . 1 Некоторые критики Java могли бы сказать. Неблокирующий ввод-вывод был настоящим прорывом.org/. позволяющих разработ� чикам создавать более быстрые и надежные решения для реализации ввода-выво� да.1.File содержал несколько досад� ных недоработок:  отсутствовала непротиворечивая обработка имен файлов на разных платфор� мах1. поддержка обработки файлов и каталогов в файловой системе остав� ляла желать лучшего. что несколько языков программирования (в том числе Java) во многом скопировали синтаксис и семантику Perl.  в Java 7 механизм неблокирующего вывода был коренным образом переработан.perl. Новый ввод-вывод 63  Отсутствовала поддержка регулярных выражений.  интерфейс. что именно в этой области язык Java нарушает известный принцип «Написать однажды. когда вышла версия 1. то любую информацию о нем можно прочитать на сайте http://www. что эти неудобства частично объясняют.

приме� нявшихся в современном программном и аппаратном обеспечении. специфичным для данной файловой системы.  Разработка API для асинхронных операций ввода-вывода (которые. Обычно Path означает путь к эле� менту файловой системы. например к C:\workspace\java7developer (каталог в фай� ловой системе Microsoft Windows) или /usr/bin/zip (местоположение утилиты ZIP в файловой системе *nix).1 (http://jcp.3.  не удавалось использовать функции. так и с файлами. чтобы освоить файловый ввод-вывод в рамках NIO.1.  Создание нового интерфейса файловой системы. конфи� гурации параметров и мультикаст-датаграмм. специфичные для платформы или опера� ционной системы1. как создавать пути и мани� пулировать ими. в отличие от опроса. 2. поддерживающего групповой доступ к атрибутам файлов и обеспечивающего доступ к API. определенного в запросе на спецификацию JSR-51. Если вы подробно изучите.2. Введение в NIO. 1 2 В данном случае обычно подразумеваются сложности с доступом к механизму символьных ссылок в операционных системах Linux/UNIX. как ZIP-архив. которые подробно описаны в самом документе JSR-203. то сможете выполнять навигацию в файловой системе любого типа. В результате JSR-203 появилась система.2 Запрос на спецификацию JSR-203 (составленную под руководством Алана Бейт� мена) был подготовлен. с которым придется научиться работать.2 в Java 7.4 присутствовала. Начнем изучение основ новой поддержки файловых систем с обсуждения Path и сопутствующих тем. включая добавление связывания. чтобы избавиться от описанных выше ограничений. при моделировании доступа для чтения/для внесения измене� ний). а также интерфейса для предоставления сервисов для реализаций подключаемых файловых систем. раздел 2.  Окончательная доработка сокетно-канального функционала. известная нам как API NIO. являются неблокирующими) как с сокетами. Path — основа файлового ввода-вывода Path — один из основных классов. 2.  в файловых системах не поддерживались неблокирующие операции2. . Поддержка неблокирующих операций с сетевыми сокетами в Java 1. Разработка на Java 7  не удавалось создать унифицированную модель работы с атрибутами файлов (например. а так� же обеспечить поддержку для некоторых новых парадигм ввода-вывода.64 Часть 1. в том числе по таким системам. Она предназна� чалась для достижения трех основных целей.2.  возникали сложности с обходом каталогов.org/en/jsr/detail?id=203).

1.  корень пути.1 (в его основе — структура исходного кода для этой книги) мы крат� ко напоминаем о нескольких концепциях файловой системы:  дерево каталогов. Рис. 2.  относительный путь.  абсолютный путь. абсолютный путь и относительный путь . Новый ввод-вывод 65 На рис. 2. на котором показаны корень.Глава 2. Дерево каталогов.

специфичного для файловой системы. Всегда следите за тем. а потом попытаться считать информацию с этого адреса. Верно и обратное: ваше приложение может неизменно запускаться из одного и того же места (допустим. в файловой системе которых действительно существует местоположение C:\workspace\java7developer. Подобные манипуляции обычно выполняются с помощью вспомогательного класса Files. Но корень дерева каталогов может измениться (например. что в NIO. Чтобы перейти в /java7developer/src/ main. приложение может запус� каться из каталога /java7developer/src/test и у вас есть код. считывающий имена файлов из каталога /java7developer/src/main. из целевого каталога на рис. даже если он не связан с каким-то конкретным физическим местоположением. то получили бы исключение IOException. до того. в таком случае следует использовать абсолютный путь. Этот файл не существует. когда вы указыва­ ете несуществующий Path и пытаетесь считать из него информацию с помощью метода. . Действительно. мы поговорим об этом методе. Одним словом. представляющий собой указание на новый файл.2 четко разграничивается концепция местоположе� ния (представленного Path) и операции с физической файловой системой (такие. Разработка на Java 7 Мы говорим об абсолютных и относительных путях. В таком случае вы не можете полагаться на абсолютный путь — придется исполь� зовать относительные пути..1.4.. а также в файловой системе. Например. Та же логика действует и в случае. чтобы ваша логика и порядок обработки исключений охватывали и случаи выполнения вашего кода в другой файловой системе. из-за чего отказала целая серия жестких дисков на факультете информатики в его университете!2 Еще раз отметим. Не будем никого называть по именам. так как важно знать. Если создать путь Path со значением C:\workspace\java7developer. виртуальная машина Java связывает Path с конкретным физическим местоположением только во время исполнения. может понадобиться создать путь. структура которой может изменяться. пока вы не задей­ ствуете метод Files.1). чтобы надежно переходить в желаемые точки./main вы не попадете в желаемый каталог (вместо этого вы окажетесь в несуществующем каталоге /java7developer/src/test/java/com/main). Хотя это и может показаться нелогичным. Путь (Path) в системе NIO. в неко� торых случаях такая возможность очень полезна. когда вы еще толь� ко собираетесь создать этот файл. ВНИМАНИЕ Будьте осторожны при написании кода. где находится конкретное местоположение в файловой системе относительно того места. например /java7developer/src/main. 1 2 Чуть ниже.2 — это абстрактная конструкция. откуда запускается наше приложение. в подразделе 2. Например. то это сработает лишь на компьютерах. Files.readAllBytes(Path). если ваше приложение запускается из /java7developer/src/test/java/com/ java7developer? В таком случае при использовании относительного пути от . с /java7developer на D:\workspace\j7d). например.createFile(Path target)1. Один из авторов книги как-то раз об этом забыл. представленного через Path. но можете сами попытаться распутать эту таинствен­ ную историю. 2. как копирование файла).66 Часть 1. Если бы вы попытались считать со� держимое файла. как он создан. Можно создать путь Path и манипулировать им. А что. можно воспользоваться относительным путем ./main.

Создание пути Создать путь совсем просто.  объединение двух Path. 2. если вам понадобится решать проблемы.Глава 2. здесь есть методы для сопоставления строки пути с другими строками и для удаления избыточности из пути Paths Вспомогательный класс. но и в таких. Кроме того. предоставляющий различные методы — например. Вы можете манипулировать путем Path как вам угодно и использовать функционал Files.  получение информации о Path.. как ZIP или JAR. что в разных API NIO. Новый ввод-вывод 67 Класс Path подробно описан в табл. Мы полагаем.  удаление избыточности из Path. так и альтернативная файловая система.getDefault(). что Path не обязательно должен представлять реальный файл или каталог. Исследуем класс Path. она просто позволяет прикреплять дополнительные строки. — это IOException. Вторая переменная обычно не используется. Это может быть как файловая система.1. Таблица 2. преобразования пути в другие формы или для извлечения фрагментов пути. попытавшись решить пару простых задач:  создание Path. String.2. например get(String first. которые могут использоваться для получения информации о пути.1.2 единственное проверяемое исключение. заданная по умолчанию. more). . но иногда единственный вид исключения может затемнять суть проблемы. вызвавшей исключение. выдаваемое различными методами в Path или Paths. метод FileSystems. сравнение двух Path. 2... Основные классы для изучения ввода-вывода файлов Класс Описание Path Класс Path содержит методы. создание Path. существует ли файл на самом деле. В таком случае придется писать дополнительный код для обработки исключений. которые встретятся нам в этом разделе). из которых образуется строка Path. more) и get(URI uri) FileSystem Класс. чтобы проверять. Начнем с создания пути Path для представления местоположения в файловой системе. возвращающий файловую систему.. представляющего собой путь между дву� мя Path. служащий интерфейсом для файловой системы. заданную по умолчанию Не забывайте. Самый быстрый способ — вызвать метод Paths. String.get(String first. узнаваемая по уникальному идентификатору ресурса (URI) FileSystems Вспомогательный класс. предоставляющий вспомогательные методы для возвращения пути. СОВЕТ Path можно использовать не только в традиционных файловых системах. Если он существует. СОВЕТ Вы заметите. касающиеся конкретных разновидностей (подклассов) IOException.1 (как и кое-какие другие классы. это сделано ради простоты. доступа к элементам пути. то с ним можно выполнять определенные операции.

get("/usr/bin/zip") эквивалентен вызову следующей более длинной последовательности: Path listing = FileSystems. Например. с какой именно операционной системой вы работа­ ете и откуда запускается листинг с кодом (листинг 2.toAbsolutePath(). вот так: listing. ваша программа может работать из каталога /opt. возвращающих полезную информацию о пу� ти.1). Получение информации о пути . и. Если вы рабо� таете с операционной системой. СОВЕТ При создании Path можно использовать относительный путь. расположенной в /usr/bin.1. что оно существует) и пр.. Вызов Paths./usr/ bin/zip. Например. расположенный на один уровень выше /opt (то есть на уровень /). Разработка на Java 7 Воспользуемся методом Paths. находящейся в каталоге /usr/bin/: Path listing = Paths. Не составляет труда превратить этот относительный путь в абсолютный. с которым вы работаете. Так вы попадаете в каталог. 2 elements deep [usr/bin] Запустив этот листинг на своем компьютере. а также выводится другая важная инфор� мация: корневой путь этого Path и родительский путь этого Path. 2. его имя файла (при условии. то должны получить примерно следующий вывод: File Name [zip] Number of Name Elements in the Path [3] Parent Path [/usr/bin] Root of Path [/] Subpath from Root. допустимо использовать . утилита ZIP в которой расположена в /usr/bin. Листинг 2.2.getDefault().2. Можно запросить у Path определенную информацию: родительский компонент этого Path. В следующем листинге создается Path для полезной утилиты ZIP. чтобы создать путь Path к /usr/bin/zip.68 Часть 1.get(String first) для создания абсолютного пути Path к полезной программе-архиватору ZIP. Получение информации о пути В классе Path имеется группа методов. а потом — в /usr/bin/zip. вызвав метод toAbsolutePath(). Все зависит от того.get("/usr/bin/zip"). вы можете получить разные ре� зультаты.getPath("/usr/bin/zip").

то есть на каталог. что ваше приложение запускается из /java7developer/src/main/ java/com/java7developer/chapter2/ (см. Допустим. что и Listing_2_1. При интерпретации пути могут возникнуть и другие случаи избыточности. В данном случае вы находите подпуть. вы работаете в операционной системе *nix и ищете информацию о логе (файле журнала). то часть . Все эти типы избыточности могут вызвать ситуацию. Следующий фрагмент . Во-первых. в которой Path не ведет к тому местоположению файла. Предположим. анализатора файлов свойств) вы. который может содержать такие элементы:  . возможно.Глава 2. из которого вы запускаетесь) значения не имеет.java. Кроме того. так как вы сможете пользоваться ими для логирования результатов манипуляций с путями. 2. Вы находитесь в том же каталоге. начиная от корня (0) до второго элемента в Path (2) . Но на самом деле этот каталог /usr/logs — просто символьная ссылка на /application/logs. который называется log1. В данном случае достаточно указать более краткий путь — Listing_2_1.4. Посколь� ку вас интересует именно реальное местоположение. Поэтому.txt и находится в каталоге /usr/logs.java. Избавление от избыточности При написании служебных программ (например. Эти методы окажутся крайне полезными на первом этапе изучения различных API NIO. получите Path. можно отсечь от Path избыточную инфор� мацию.  . должен указывать. если вы получили Path от .1). где ваш путь Path расположен относительно роди� тельского Path и корня. например символьные ссылки (о них мы поговорим в подразделе 2. в котором фактически находится этот файл журнала. из какого количества элементов состоит этот Path (в данном случае нас интересует количество катало� гов) . указав начальный и конечный индекс. В Java 7 имеется пара вспомогательных методов. вы можете выбрать подпуть./Listing_2. куда именно направлен ваш Path. — означает текущий каталог. помогающих выяснить. Новый ввод-вывод 69 После создания Path к /usr/bin/zip можно проверить. рис. 2. куда.3. воспользовавшись методом normalize() этого пути.2.2. — означает родительский каталог. Всегда полезно знать.3). по вашим расчетам.java. от избыточной символьной информации лучше избавиться..1./ (фактически это тот самый каталог.

Path pathToConfDir = logDir. позволяет манипу� лировать с несколькими Path. создавать путь между двумя дру� гими путями и сравнивать пути друг с другом. Это делается с помощью метода resolve. В следующем фрагменте кода рассчитывается путь к конфигура� ционному каталогу из каталога логирования.get(".get("/usr/logs/log1. которую мы здесь обсудим. который был написан до перехода на Java 7? Команда разработчиков . Разработка на Java 7 кода возвращает Path от Listing_2_1.properties.java"). И эти аргументы потребуется преобразовать в правильный путь Path.properties./). отсекая избыточную запись.java.get("/uat/"). Или вы можете получить ряд аргументов Path. 2.2 можно без труда объединять пути. С помощью метода toRealPath() вы получаете реальный путь Path к /application/ logs/log1. Path prefix = Paths. В NIO. Еще существует мощный метод toRealPath(). находить пути между ними и т.70 Часть 1. Последняя функция API Path. д. Вернемся к примеру. Path confDir = Paths. находящимся в каталоге /usr/logs. указывающую на местоположение в одном и том же каталоге (часть . Преобразование путей Преобразование путей обычно требуется выполнять при работе со вспомогатель� ными программами./Listing_2_1. Он обнаруживает символьные ссылки и следует по ним. — чтобы удостовериться. где мы работали с операционной системой *nix и имели дело с файлом log1. может понадобиться узнать. в котором объединяется функцио� нал методов toAbsolutePath() и normalize().4.get(logging).properties"). Path completePath = prefix. Path logDir = Paths. что структура каталогов с вашим исходным кодом соответствует принятым стандартам. В ходе таких манипуляций мы сможем сравнивать пути.txt. выполняющим вашу программу.resolve("conf/application. как файлы располо� жены друг относительно друга.2.toRealPath().get(configuration). Мы собираемся представить полный путь вида /uat/conf/application. Чтобы получить путь между двумя другими путями. В следующем фрагменте кода показано объединение двух путей: uat и conf/ application. что делать с уже имеющим� ся кодом. Как и ожидалось. String logging = args[0].txt. когда вы научились работать с классом Path. переданных сценарием оболочки.relativize(confDir). а также полный сравнительный метод equals(Path path) для сравнения путей. Этот каталог на са� мом деле представляет собой символьную ссылку на каталог /application/logs. Path normalizedPath = Paths. Теперь. пользуйтесь методом relativize(Path). String configuration = args[1]. можно использовать startsWith(Path prefix) и endsWith(Path suffix).normalize(). Path realPath = Paths.txt"). Например.

основан� ный на java. как в Java 7 поддерживается работа с каталогами. каталог — это просто путь Path со специфическими атрибутами.File.  Новый метод toPath() для класса java. listing. Пути NIO. Path listing = file.toPath(). вероятно. что здесь обеспечивается навигация по каталогам.Глава 2. обеспечивающих взаимодействие между новым вводом-выводом на основе Path и предыдущими версиями Java. в частности с деревьями каталогов. использующего прежний ввод-вывод. пользуясь выражениями-масками (например. Потрясающая но� вая возможность Java 7 заключается в том. 2. File file = new File(". ..toAbsolutePath(). Начнем с простейшего случая — нахо� ждения произвольных файлов в каталоге. копирования и удаления с по� мощью метода walkFileTree.io. Эта возможность кратко продемонстрирована в следующем фрагменте кода.nio. В этом разделе мы рассмотрим две наиболее распространенные разновидности задач.java"). Благодаря появлению нового интерфейса java. моментально преобразующий имеющийся путь Path в файл File. Но вам зачастую придется иметь дело с боль� шими объемами устаревшего кода.DirectoryStream<T> и реализующих его классов вы можете выполнять следующие функции широкого применения:  перебирать записи в каталоге.5.2 и класс File. Новый ввод-вывод 71 NIO.io.3. 2.toFile().io. файлы text/xml). уже поняли из рассказа о путях.File. На этом мы заканчиваем исследование класса Path. построенный на базе java. file = listing. *Foobar*) и обнаружением контента по типам MIME (например. например находить в нем файлы.2 позаботилась и об обратной совместимости и добавила пару специальных функций API.  выполнять рекурсивные операции перемещения.file. В Java 7 появляются два новых метода. а потом выпол� ним ту же задачу во всем дереве каталогов. Далее рассмотрим. существующий в Java Классы в новом API файловой системы позволяют полностью заменить старый API. связанных с такими операциями. мгновенно преобразующий име­ ющийся файл File в новую конструкцию Path.2. Работа с каталогами и деревьями каталогов Как вы./Listing_2_1.File.  Метод toFile() для класса Path. Сначала поговорим о нахождении файлов в одном каталоге.2. приведенного в разделе 2.  находить записи в каталоге.

что вы с легкостью сможете искать файлы в дереве каталогов (то есть выполнять поиск в подкаталогах). Перечисление файлов свойств в каталоге Начинаем с объявления знакомого вызова Paths.properties. Применяемый здесь механизм сопоставления с образцом называется сопоставлением с маской (glob pattern match).2).walkFileTree(Path startingDir.3. String patternMatch) . Для пра� вильной работы с ней нужно знать несколько интерфейсов и подробности ее реа� лизации.2. в результате чего возвращается поток DirectoryStream. Движение по дереву каталогов — довольно новая функция в Java 7. FileVisitor<? super Path> visitor).2. в котором отфильтрованы файлы. Поиск файлов в каталоге Во-первых. Листинг 2. вы выводите все записи .72 Часть 1.newDirectoryStream(Path directory.2 продемонстрирован потенциал нового метода при работе с един� ственным каталогом. Но что. вы захотите заполучить вспомогательный класс. Разберем следующий листинг (листинг 2. а также осуществлять над ними желаемые действия. но и отличается от такого сопоставления.1.properties) из каталога с проектом java7developer. . В листинге 2. если нам потребуется выполнить рекурсивную фильт� рацию по нескольким каталогам? 2. которое вы могли встречать в Perl. Подробное описание сопоставления с образцом по маске и его применения дано в приложении B. оканчивающиеся на . используемый при движении по дереву каталогов: Files. Основные со� бытия происходят при вызове Files. Это означа� ет.class в каталоге /opt/workspace/java вашего пространства для разработки.get(String) . который удаляет все файлы .3. Основной метод. Разработка на Java 7 2. Это может происходить на этапе очистки (cleanup step) при подготовке сборки. Воз� можно. Он чем-то похож на привычное сопоставление с использованием регулярных выражений. Движение по дереву каталогов В Java 7 появилась поддержка навигации по целому дереву каталогов. Наконец. рассмотрим простой пример использования фильтра для сопоставления по образцу (pattern matching) для перечисления всех файлов свойств (.

представленное в листинге 2. расположенных не выше C:\workspace\java7developer\src. правда? К счастью. что интерфейс FileVisitor вынуждает вас реализовать как минимум следующие пять методов (где T — это обычно Path):  FileVisitResult preVisitDirectory(T dir).  FileVisitResult visitFileFailed(T file. IOException exc).  FileVisitResult postVisitDirectory(T dir.walkFileTree со стандартной реализацией SimpleFileVisitor. Мы дополним и изменим поведение. Выглядит как большой кусок работы. создатели API Java 7 предоставили стандартную реализацию — класс SimpleFileVisitor<T>.  FileVisitResult preVisitDirectoryFailed(T dir. Листинг 2. В следующем листинге перечисляются файлы . В этом листинге продемонстрировано использование метода Files.3.properties из каталога C:\workspace\ java7developer. Дело в том.java с исходным кодом из всех каталогов. но предоставить реализацию интерфейса FileVisitor (это довольно затейливый параметр FileVisitor<? super Path> visitor) — уже не� сколько сложнее. IOException exc). содержащимся в подкаталогах .2. улучшенной с помощью специфической реализации метода visitFile (листинг 2. BasicFileAttributes attrs). там мы выводили список всех файлов .3). IOException exc). Новый ввод-вывод 73 Указать startingDir легко.  FileVisitResult visitFile(T file. Как вы помните.Глава 2. Перечисление файлов с исходным кодом Java.

то нужно обнаруживать такой атрибут (о том. что� бы проверить. Благодаря этому такие операции.4. то пишем этот файл в стандартный поток вывода1. работы с содержимым файлов — в NIO. Если вам требуются подобные переходы по символьным ссылкам. 2.3) и принимать необходимые меры. каталогов. оканчивается ли название файла на . Класс Files подробнее описан в табл. копировании. пишется при переопределении метода visitFile(Path. Здесь мы пишем простой код на Java. Вы хотите. обеспечивающие легкость копирования. Для этого мы будем использовать новый класс Files и сопутствующие элементы.74 Часть 1.4. о ко� тором мы поговорим в этом разделе (WatchService). до� полняющий стандартную реализацию SimpleFileVisitor .java. Если оканчивается. Основные классы для работы с файлами Класс Описание Files Главный вспомогательный класс содержит все методы. д. чтобы SimpleFileVisitor выполнил для вас основную работу по обходу каталогов и т. рассказано в подразделе 2. .4. Итак. но API обладает достаточной гибкостью и может работать с вашим собственным FileVisitor. заключается в том. который здесь нужно отметить. В большинстве случаев понадобится дополнять SimpleFileVisitor.2 была улучшена. 2. называется Files. Основной момент. так что пока отложим этот вопрос. удалении или ином изменении.2. который вам требуется написать. ПРИМЕЧАНИЕ Метод walkFileTree не переходит по символьным ссылкам автоматически. как рекурсия. как это делается.2 Поддержка выполнения операций над файловой системой — перемещения фай� лов. Весь код. Основной класс. вы научились обращаться с путями и деревьями каталогов. Таблица 2. BasicFileAttributes) . Разработка на Java 7 Начинаем с вызова метода Files. Перейдем от манипуляций с местоположением к осуществлению операций с самой файловой системой. что вы передаете FindJavaVisitor. становятся безопаснее.walkFileTree . если вы захотите реализовать его с нуля. перемещения. удаления файлов.2 вместе с другим важным классом. предоставляющий такую поддержку. а также другие манипуляции WatchService Основной класс для отслеживания файлов. изменения атрибутов файлов. Другой пример практического использования может заключаться в рекурсивном изменении файлов — перемещении. Ввод-вывод файловой системы при работе с NIO. а также наличия или отсутствия изменений в них 1 Мы поговорим о BasicFileAttributes в разделе 2.

СОВЕТ Если вы будете запускать фрагменты кода из этого раздела. как:  создание и удаление файлов. переименование и удаление файлов. при работе с файловой системой по-прежнему требуется относиться к коду с повышенной осторожностью. . что актуальны для вашей файловой системы! Следующий фрагмент кода демонстрирует простейшую операцию создания файла с помощью метода Files. Масштаб этих изменений может показаться ошеломительным. начиная с самых простых — копирования и удаления файлов.createFile(target). 2. может сорваться сетевое соединение.1.  считывание и запись атрибутов файлов.txt"). кто-то может пролить кофе на сервер (такой трагический случай был в практике одного из авторов книги).  считывание информации из файлов и запись информации в них.  использование WatchService для уведомления об изменении файлов. копирование. Создадим в нем файл MyStuff.txt. как в стандартном случае. создание и удаление файлов не всегда оказывается таким простым.  использование SeekableByteChannel — усовершенствованного байтового канала. Даже если операция совершается «на автопилоте».createFile(Path target).2 стали значительно лучше поддерживать атомарные операции. скрывающих уровни абстрагирования и позволяющих легко и быстро работать с файловыми систе� мами.get("D:\\Backup\\MyStuff.4. Рассмотрим несколько практических случаев.exists(Path). Поэтому обсудим и допол� нительные варианты. Разумеется. поговорим об установке прав доступа для чте� ния/записи к новоиспеченному файлу. ВНИМАНИЕ Хотя различные API NIO. но API хорошо сработан и содержит множество вспомогательных методов. например Files. Создание и удаление файлов С помощью простых вспомогательных методов из класса Files можно без труда и создавать файлы. команда shutdown now может быть выполнена не в том окне UNIX. Path target = Paths. Допустим. API действительно выдает исключение RuntimeException от некоторых своих методов. Новый ввод-вывод 75 В этом разделе мы рассмотрим выполнение таких операций над файловой сис� темой. Отличный способ познакомиться с новым API — почитать написанный для него код и самому попытаться написать такой код.  перемещение. то заменяйте приведенные пути файлов теми. но определенные исключительные случаи можно убрать с помощью вспомогательных методов. В частности.Глава 2. в вашей опера� ционной системе есть каталог D:\\Backup.  работа с символьными ссылками. и удалять их. позволяющего указывать и положение и размер. Path file = Files.

Files. Следующий фрагмент кода удаляет только что созданный вами файл по адресу D:\\Backup\\MyStuff. ВНИМАНИЕ При создании файлов с конкретными правами доступа учитывайте любые ограничения umask или «ограничения прав».76 Часть 1.fromString("rw-rw-rw-"). В пакете java.txt").get("D:\\Backup\\MyStuff. В качестве примера установки прав чтения/записи для владельца файла.delete(Path).createFile(target. Set<PosixFilePermission> perms = PosixFilePermissions.get("D:\\Backup\\MyStuff. Path target = Paths. пользователь. он на самом деле создается как rw-r--r-из-за маскировки каталогов. создавался файл для чтения.file. накладываемые родительским каталогом на этот файл.asFileAttribute(perms). рекомендуется использовать специфичный для файловой системы класс с правами доступа к файлам.txt.txt"). Удаление файла — более простая операция. Этот файл еще предстоит создать. атрибуты опре� деляют. от имени которого работает ваш процесс Java.txt"). это означает.attribute.get("D:\\Backup\\MyStuff. которая выполняется с помощью метода Files.attribute содержится список имеющихся классов java.copy(Path source. что мы разрешаем всем пользователям читать информацию из файла D:\\Backup\\MyStuff. должен иметь право на такое удаление! Path target = Paths.3.4. Разумеется. Далее поговорим о перемещении и копировании файлов в файловой системе.txt"). записи.nio.2. Кроме того. nio. поль� зователей из группы владельца и всех остальных пользователей файловой системы POSIX1 рассмотрим следующий код. В принципе. Более подробно о поддержке атрибутов файлов мы поговорим далее. Разработка на Java 7 Довольно часто вам понадобится указывать определенные атрибуты FileAttributes для этого файла — это делается в целях безопасности. Path target = Paths. поддержива­ емый во многих операционных системах. Path source = Paths. Например. может оказаться.4. Files. Поскольку эта характеристика зависит от файловой систе� мы. Path target). 2. В следующем фрагменте кода показана простейшая операция копирования с при� менением метода Files. attr).txt и запи� сывать туда новую информацию. 1 Переносимый интерфейс операционных систем UNIX — базовый стандарт. Files.file.copy(source.get("C:\\My Documents\\Stuff.delete(target). выполнения или какой-либо комби� нации трех вариантов. . что. Копирование и перемещение файлов С помощью простых вспомогательных методов из класса Files вы можете с легко� стью совершать операции перемещения и копирования. FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions. target). хотя вы и задали для нового файла rw-rw-rw. в подразделе 2.

move(Path source. поскольку в различных файловых систе� мах присутствуют свои наборы атрибутов и собственные интерпретации значений этих атрибутов. В следующем примере мы используем параметр. Начнем этот подраздел с рассмотрения некоторых базовых атрибутов файла. есть группа атрибутов. если он уже существует.) (обратите внимание. позво� ляющий перезаписать файл. CopyOptions.move(Path source.get("D:\\Backup\\MyStuff. копировать и перемещать файлы. Опять же. в частности указывающих время последнего обращения к файлу. Path source = Paths.3. REPLACE_EXISTING).txt"). Среди других параметров копирования следует назвать COPY_ATTRIBUTES (копи� рование файла вместе с его атрибутами) и ATOMIC_MOVE (гарантирует успешное за� вершение обеих сторон операции перемещения — в противном случае происходит откат операции). можно ли совершать с файлом одно или более из следующих действий: считывание файла. import static java.*. при перемещении файла вы хотите задать параметры копирования. д. Интерфейс .get("C:\\My Documents\\Stuff.StandardCopyOption. Path target. Теперь подробнее рассмотрим.file. В данном случае мы хотим сохранить при перемещении атрибуты исходного файла. Поддержка базовых атрибутов файлов Хотя существует мало по-настоящему универсальных атрибутов файлов. либо группу владельца.*.get("D:\\Backup\\MyStuff. Files. 2. а также перезаписать целевой файл (если он существует).txt"). является этот файл каталогом или символьной ссылкой. Path target = Paths. поддерживаемых в большинстве файловых систем. Классические права кто указывают владельца фай� ла. Path target). Files. Операция перемещения очень похожа на операцию копирования и выполняет� ся с помощью атомарного метода Files.nio.file. и т. Вот вы и научились создавать. запись информации в этот файл либо исполнение файла. что можно делать с файлом и кто может это делать. COPY_ATTRIBUTES).. Классические права типа что регламентируют. target.Глава 2. удалять. Path source = Paths. Во второй части этого подраздела поговорим о поддержке атрибутов файлов в конкретных файло� вых системах. Атрибуты файлов Атрибуты файла определяют.. Новый ввод-вывод 77 Довольно часто вам будет требоваться снабдить операцию копирования допол� нительными параметрами.get("C:\\My Documents\\Stuff.nio. Это не такая простая тема.4. Поэтому можно воспользоваться методом Files. как в Java 7 поддерживаются атрибуты файлов. REPLACE_EXISTING.move(source. либо всех пользователей. как правило. target. import static java.StandardCopyOption. определяющих.copy(source.txt"). как происходит работа с переменным количе� ством аргументов).txt"). Path target = Paths.

Листинг 2. где дан полный список этих вспомогательных методов. К такой информации относятся. что все атрибуты показаны при помощи вызова к Files. в частности. isSymbolicLink=false.  Когда файл в последний раз был изменен?  Каков размер файла?  Является ли файл символьной ссылкой?  Не каталог ли это? В листинге 2. в частности такие. size=351872} Обратите внимание. isRegularFile=true.4. Универсальные файловые атрибуты Есть и другая информация. предназначенные для сбора этих базовых атрибутов. и др.. options). о том.78 Часть 1. fileKey=(dev=e000002. Stringattributes. creationTime=2011-07-20T16:50:18Z. lastAccessTime=2011-06-13T23:31:11Z. Java 7 также обеспечивает поддержку просмотра атрибутов файлов и манипу� лирования этими атрибутами в конкретных файловых системах. readAttributes(Path path. которая содержится в общих файловых атрибутах и которую можно собрать с помощью методов класса Files.. Разработка на Java 7 BasicFileAttributes определяет это общее множество. .ino=30871217). isDirectory=false. isOther=false. вы должны получить примерно следующий вывод: /usr/bin/zip 2011-07-20T16:50:18Z 351872 false false {lastModifiedTime=2011-07-20T16:50:18Z.4 приведены методы класса Files. Можете посмотреть документацию Java (Javadoc) о классе Files. В этом листинге приведена информация о /usr/bin/zip. является ли файл сим� вольной ссылкой. но вам нужно использовать вспомогательный класс Files для получения ответов на различные вопросы о фай� ле. LinkOption. данные о владельце файла.

2. мы можем обеспечить пра� вильность прав доступа (rw-r--r--). обеспечиваемые с помощью интерфейса FileAttribute и класса PosixFilePermissions. Здесь же задано право чтения файла для всех остальных пользователей. ВНИМАНИЕ Мы уже упоминали об этом.2. Допустим. Новый ввод-вывод 79 Поддержка специфичных атрибутов файлов Мы уже рассмотрели возможности поддержки атрибутов в Java 7. специфичных для определен� ных файловых систем. Обратите вни� мание на особый скрытый файл . Поддержка атрибутов файлов в Java 7 . когда создавали файл в подразделе 2.5. Обязательно удостоверяйтесь.4. Java позволяет производителям этих файловых систем реа� лизовывать интерфейсы FileAttributeView и BasicFileAttributes.Глава 2. Пользователь (Admin) намерен предоставить всем пользователям право чтения этого файла. На рис. что предложенная вами логика и механизм обработки исключений охватывают и те случаи. в котором задано разрешение на запись информации в файл. Здесь показаны права доступа к файлу . Рассмотрим следующий пример. требуется задать на Java 7 правиль� ные права доступа к определенному файлу. Содержимое домашнего каталога пользователя Admin.profile. Воспользовавшись специальными классами для POSIX. который является домашним каталогом пользователя Admin.profile Листинг 2. которые называются PosixFilePermission и PosixFileAttributes.5 мы собираемся обеспечить соблюдение этих прав доступа к фай� лу . но вносить в него изменения может только Admin.profile в соответствии с рис.1.2 показано содержимое ката� лога. 2. в которых ваш код может запускаться в другой файловой системе. но считаем необходимым вновь подчеркнуть: будьте внимательны при написании кода. В листинге 2. Для поддержки атрибутов. 2. действующее для пользователя Admin (но ни для кого другого). специфичного для отдельных файловых систем. Рис. 2.

Кроме базовых атрибутов. не показанных здесь). которые специфичны для файловой системы. В классе Files есть полезный вспомогательный метод. если обозначить каталог через символьную ссылку. Символьные ссылки Символьную ссылку можно считать указателем на другой файл или каталог. заметили. Затем вы можете получить доступ к PosixFilePermission . К сожалению. но мы рассмотрим один при� мер работы с данной системой — поддержку символьных ссылок в Java 7. что PosixFilePermission относится к типу enum и поэтому не реализует интерфейс FileAttributeView. Разработка на Java 7 Начинаем работу с импорта констант PosixFilePermission (а также других опе� раций импорта. на который указывает эта ссылка. то нужно иметь возможность принимать оправданные решения . вероятно. позволяя непо� средственно считывать атрибуты файла (с помощью метода readAttributes) и на� прямую задавать права доступа (с помощью метода setPosixFilePermissions). утилиту для резервного копирования или сценарий для развертывания. позволяющий считывать те атрибуты. то при щелчке на ссылке вы попадете в каталог. в данном случае — PosixFileAttributes . в рамках этой главы мы не можем обсудить все конкретные случаи. Java 7 предоставляет расширяемую систему поддерж� ки специальных функций операционной системы. Итак.80 Часть 1. потом получаем путь Path к файлу . Но если вы пише� те программу — скажем.profile. Вы. В большинстве случаев работа с символьными ссылками происходит прозрачно. почему же здесь не используется реализация PosixFileAttributeView? Так ведь на самом деле она используется! Но вспомогательный класс Files скрывает от вас эту абстракцию. Например. После очистки имеющихся прав доступа вы добавляете к файлу новые права доступа — опять же с помощью полезного вспомогательного метода Files .

Глава 2. Мы собираемся считывать атрибуты имен� но из того места. Linux. Исследование символьных ссылок Класс Files предоставляет метод isSymbolicLink(Path) для проверки символь� ных ссылок.NOFOLLOW_LINKS). установ� ленной в каталоге /opt/platform. в част� ности (но не только) в UNIX. как в Java 7 поддер� живаются функции конкретных файловых систем. Этот параметр применим для нескольких вызовов методов. BasicFileAttributes. Но на самом деле каталог /usr/logs является ссылкой (указателем) на каталог /application/logs. Windows 7 и Mac OS X.2. которая применя� ется в операционной системе UNIX. являющегося целью символьной ссылки . Поддержка символь� ных ссылок в Java 7 основывается на семантике той реализации. Новый ввод-вывод 81 о том.3.NOFOLLOW_LINKS. LinkOption. Листинг 2. переходить ли по символьной ссылке. У него есть вспомогательный метод для возвращения реального пути Path. Так вы можете считывать пра� вильные атрибуты файла . В листинге 2.txt и находящемся в каталоге /usr/logs. Символьные ссылки — наиболее популярный пример того. Чтобы отключить это. Структура API позволяет в бу� дущем добавлять и поддержку других функций.2 выполняется следование по символьным ссылкам. называемом log1.readAttributes(target. где действительно находится файл. По умолчанию в API NIO. Уже после этого мы пыта� емся считать базовые атрибуты файла. Символьные ссылки применяются во множестве операционных систем. что вы ра� ботаете в операционной системе *nix и ищете информацию о файле журнала (лога). нужно применить LinkOption. Именно там в действительности находится интересующий нас файл журнала. специфичных для определенной . символьной ссылкой. Вновь воспользуемся примером из подраздела 2. является ли путь Path к версии Java.2 такая возможность предо­ ставляется.6.class. В NIO.6 мы проверяем. то нужно вы� звать: Files. Предположим. соответствующие самой символьной ссылке. Если вы хотите прочитать базовые атрибуты файла.

В этом разделе мы рассмотрим процесс открытия файлов (со всеми сопут­ ствующими параметрами). while ((line = reader. Так вы гарантируете наличие верных прав доступа. StandardCharsets. а также группу небольших примеров. 2.WRITE)) { writer.. try (BufferedReader reader = Files. который можно добавить в вызов с переменным количеством аргументов. Теперь. . приступим к ма� нипуляциям с их содержимым. над созданием которой вы тайком трудитесь по ночам).UTF_8)) { String line.newBufferedReader(logFile. Разработка на Java 7 файловой системы (даже для той суперсекретной квантовой файловой системы. описывающих обычные случаи считывания и записи информации. Быстрое считывание и запись данных Одна из целей Java 7 — предоставить максимально возможное количество вспомо� гательных методов для считывания файлов и записи информации в них. Разуме� ется. как в Java 7 можно (с помощью метода Files.WRITE.82 Часть 1.io. в частности.get("/tmp/app.. как считывание всех строк (всех байтов) из файла.UTF_8. какими различными способами можно открывать файл для обработки.newBufferedReader) открыть файл для считывания строк. READ и APPEND. try (BufferedWriter writer = Files. Для начала обсудим.log"). такие новые методы используют местоположения Path . К другим распространенным параметрам относятся.. Path logFile = Paths. } Обратите внимание на использование StandardOpenOption.get("/tmp/app. когда вы научились практике обращения с файлами. } } Открыть файл для записи не сложнее: Path logFile = Paths.4. Это один из параметров OpenOption. StandardOpenOption. StandardCharsets. Открытие файлов Java 7 позволяет напрямую открывать файлы для обработки с помощью буферизо� ванных считывателей и записывателей либо (для совместимости со старым кодом Java для ввода-вывода) с помощью потоков для ввода и вывода. но разработчики позаботились и о взаимодействии со старыми потоковыми классами из пакета java. В следу­ющем фраг� менте кода показано. регламентирующих запись информации в файл. выполняются путем вызова всего одного метода.log").4.. . что такие задачи. В сухом остатке имеем.newBufferedWrite(logFile.write("Hello World!").readLine()) != null) { .

Глава 2. OpenOption. и новый файловый ввод-вывод. связанные именно с ней. например.readAllBytes(logFile). Все это — довольно сложный низкоуровневый код. Особенно это касается файлов свойств или файлов журнала (логов).UTF_8). Именно здесь может очень пригодиться система уведомлений о внесении новых изменений в файл. что теперь вы можете обойтись без написания шаблонного кода. Как и в других случаях. как вызывать вспомогательные методы. Этот класс использует клиентские потоки для отслеживания изменений в зарегистрированных файлах или каталогах. в осно� ве которого лежит пакет java. В предыдущих примерах показан тривиальный код для считывания файлов и за� писи информации в них. а Java 7 предлагает красивые высокоуровневые абстракции. Упрощение записи и считывания Во вспомогательном классе Files есть несколько полезных методов. Подобные уведомления идеально подходят для замены сравнительно более медленных меха� низмов опроса. когда выполнять считывание и когда — запись.4.io.file. newOutputStream(Path. а также во многих других случаях. Это озна� чает. в которых .) и Files. В листинге 2. Так удается удачно объединить старый вводвывод.. StandardCharsets .5. Уведомление об изменении файлов Java 7 позволяет отслеживать внесение изменений в файл или каталог.get("/tmp/app.newInputStream(Path. OpenOption. позволяющие избавиться от множества ненужного шаблонного кода. так: new String(byte[]. СОВЕТ Не забывайте. Это дела� ется с помощью класса java.). При обнаружении изменения класс возвращает соответствующее событие.nio..log"). Сегодня он часто используется в Java 6 и более старых версиях. Path logFile = Paths.nio.. List<String> lines = Files.WatchService. не считывать в буфер байтовые массивы данных с применением цикла while. выполняющих обычные задачи считывания всех строк кода или всех байтов из файла. используемых в некоторых современных приложениях. Подобное уведомление о событиях может пригодиться при мониторинге безопасности. что при работе со строками String всегда требуется знать их кодировку. Новый ввод-вывод 83 Взаимодействие с потоками InputStream и OutputStream обеспечивается с помо� щью специальных методов Files. При программировании некоторых ситуаций требуется знать. StandardCharsets. обнов� лении информации из файла свойств.7 служба WatchService используется для обнаружения любых изменений в домашнем каталоге пользователя karianna и для вывода информа� ции о таком событии изменения на консоль. Если вы забудете указать кодировку (это делается с помощью класса StandardCharsets.. byte[] bytes = Files.UTF_8)).readAllLines(logFile. В следующем фраг� менте кода показано. то позже могут возникнуть неожиданные проблемы. 2. построенный вокруг пакета java.

пока не изменится флаг shutdown) к службе WatchService применяется метод take(). код опрашивает этот ключ WatchKey на наличие событий WatchEvents . например ENTRY_CREATE.84 Часть 1. Далее рассмотрим очень важный новый API абстрагирования. Здесь задействуется асинхронный ввод-вывод с применением интерфейса SeekableByteChannel. то вы сообщаете об этом «во всеуслышание». чтобы он был готов к приему следующего события. Вы можете отслеживать и события других видов. Листинг 2. Использование WatchService Получив задаваемую по умолчанию службу WatchService.7. . остается еще сбросить ключ . ENTRY_DELETE и OVERFLOW (последний случай может означать. Как только WatchKey оказывается доступен. вы регистрируете отслеживание изменений для домашнего каталога пользователя karianna . что событие было потеряно или отменено). Затем в бесконечном цикле (он продолжается до тех пор. пока будет доступен ключ WatchKey. дожидающийся. Если обнару� живается событие WatchEvent рода Kind ENTRY_MODIFY . Наконец. всегда стоит добавлять в код легковесный механизм для отключения такого цикла. предназначенный для считывания и записи данных. Разработка на Java 7 используется непрерывный опрашивающий цикл.

из которого считываете информацию или записываете ее в файл. Следующее крупное изменение.channels. Этот сервер получает доступ к байтовому каналу. не исключено. записывающий определенную ценовую информацию в кон� кретное место в текстовом файле.size() .SeekableByteChannel в JDK есть один реализу­ ющий класс — java. channel. так и с файлами. обеспечивающие возможность поиска (так называемые seekable-свойства) для считывания последних 1000 символов из файла журнала. как можно использовать новые свойства FileChannel. SeekableByteChannel В Java 7 появился интерфейс SeekableByteChannel. а также при записи информации в них. что у разработчиков значительно расширяется поле для ма� невра при работе с содержимым файлов. что вы можете пользоваться преимуществами новейших наработок в об� ласти аппаратного и програмного обеспечения. В нем вы можете содержать инфор� мацию о точной позиции того места. использующих такой парал� лельный доступ к крупным файлам. мно� гоядерные процессоры. Асинхронные операции ввода-вывода Еще одно значительное нововведение в NIO.open(logFile.get("c:\\temp.FileChannel. что появится и возможность работы с непрерывными потоками сетевых данных. появившееся в API NIO. Кроме того.log"). После такого доступа сервер может выполнять синтаксический анализ журнала на наличие конкретного кода ошибки. К ним относятся. у вас может быть сервер приложений с мно� жеством потоков. потре� буется написать код. Новый ввод-вывод 85 2. ByteBuffer buffer = ByteBuffer. считывающий последние 1000 символов из файла журнала. StandardOpenOption. 2.nio.6. поддерживается обработка файлов и сокетов на уровне операционной системы. позволяет вам использовать множественные фоновые потоки при считывании файлов. при которой еще до завершения записи или считывания может выполняться другое действие.2 — возможность асинхронного вводавывода при работе как с сокетами. Ожидаемо.1000). Асинхронный ввод-вывод — это просто разновидность обработки ввода-вывода.channels.Глава 2. появившаяся в классе FileChannel. Path logFile = Paths.nio.read(buffer. Другой пример — возможно.allocate(1024). Например. что в ближайшем будущем появится несколько интересных открытых проектов.READ). прикреплен� ному к большому файлу журналов. тео� ретически означает. FileChannel channel = FileChannel. Он предназначен для дополнения такими реализациями. сокетов и каналов. Асинхронный ввод-вывод — это важнейшая . channel. На практике это означает. Например. В следующем фрагменте показано. У интерфейса java. которые позволяют разработчикам изменять положение и размер байтового канала. вам может понадобиться код.5.4. в частности. Учитывая возможности расширения данного интерфейса. Новая возможность поддержки поиска.2 и связанное с появ� лением асинхронного ввода-вывода.

не существовало простой возможности одновременно считывать информацию из нескольких частей файла. но если нет — не отчаивайтесь. Так мы смогли бы одновременно записывать информацию в несколько областей этого файла или сокета.1. которые мы рассмотрим в главе 4. Интересно отметить. приходилось дожидаться завершения операции ввода-вывода. В предыдущих версиях Java вам пришлось бы вручную писать многопоточный код (с использованием конструкций java. Опять же. Кроме того. Мы считаем. На эту тему давно не выходило значительных работ. В следующем разделе эта методика описана достаточно подробно. чтобы интересующийся разработчик мог с ней разобраться.86 Часть 1. и лишь потом возвращаться к выполнению основной работы. В Java 7 появилось три новых асинхронных канала. которая могла протекать очень долго. то самое время освежить знания по предмету и лишь потом возвращаться к чтению этой главы. что вам уже доводилось применять такую технологию параллельной обработки. что сейчас мы устраиваем небольшой предварительный сеанс! Для начала поговорим о Future-стиле доступа к файлам. если вы только не написали вручную какой-то умный код. что эти новые асинхронные API исполь� зуют некоторые современные технологии параллельной обработки. который пытается сохранить позиции в области серверного и системного про� граммирования. Нужно записать 100 Гбайт дан� ных в файловую систему или сетевой сокет.5. Он указывает на использование интерфейса java. Надеемся. на которые следует обратить внимание:  AsynchronousFileChannel — для файлового ввода-вывода.Future. Существуют две основные парадигмы (стиля) использования новых API асин� хронного ввода-вывода: парадигма Future (с ожиданием) и парадигма Callback (обратный вызов). поддерживает за� держки.util.concurrent. 2.util. предложенный самими разработчиками API NIO.2. который назовем «с ожиданием». Таким образом. СОВЕТ Если в последнее время вам не доводилось работать с каналами нового ввода-вывода. Рассмотрим простой практический пример. В качестве вводного пособия рекомендуем использовать немного устаревшую книгу Рона Хитченса (Ron Hitchens) Java NIO (издательство O'Reilly. Стиль с ожиданием Стиль с ожиданием (Future style) — термин. основной поток блокировался при операциях ввода-вывода. принимающих соединения.  AsynchronousSocketChannel — для сокетного ввода-вывода. что этот фактор сыграет одну из важнейших ролей в сохранении популярности Java как серверного языка. 2002).concurrent). Поэтому считайте. Разработка на Java 7 предпосылка для масштабируемости и производительности системы в любом язы� ке. стиль асинхронной обработки с ожиданием применяется в тех слу� .  AsynchronousServerSocketChannel — для асинхронных сокетов. Как правило.

если это необ� ходимо. Рис. когда вы хотите.3. Допустим. 2.Глава 2. На рис. а потом собирал результаты ввода-вывода в ходе опроса. вы хотите считать 100 000 байт из файла на диске (это достаточно медленная операция) в рамках выполнения другой задачи. используемый в такой ситуации). По завершении операции возвращается ее результат. вы будете пользоваться методом Future get() (с параметром за� держки или без него) для получения результата после завершения такой асин� хронной операции ввода-вывода. основной поток дождется завершения операции ввода-вывода и потом продолжит работу. Асинхронное считывание в стиле с ожиданием Как правило. Самое важное — это означает. чтобы главный поток управления инициировал ввод-вывод. Напротив.3 проде� монстрировано. как этот процесс применяется при считывании крупного файла (в листинге 2. в котором будет храниться результат вашей асинхронной операции. Новый ввод-вывод 87 чаях. В таком случае объявляется объект Future. Тем временем ваш основ� ной поток может и далее заниматься выполнением других задач. 2.8 показан код. операция ввода-вывода инициируется в отдельном пото� ке. При стиле с ожиданием используется известная техника java. Когда эти прочие задачи будут завершены.util. В предыдущих версиях Java пришлось бы дожидаться. что работа вашего актуального потока не будет тормозиться из-за потенциально медленной операции ввода-вывода.concurrent. пока .

На практике конечный результат либо будет получен (и основной поток продолжит работу). Пока это происхо� дит. как весь этот процесс организован «за кулисами». В Java 7 вы можете продол� жать заниматься полезной работой в вашем основном потоке.88 Часть 1. Результаты считывания будут сохраняться в объекте Future . Это показано в лис� тинге 2. Далее ввод-вывод будет проходить параллельно с потоком. Листинг 2. Детали процесса требуют дополнительного разъяснения. после завершения работы вы проверяете резуль� тат такого считывания . . ваш основной поток продолжает выполнять полезную работу (например. что уже является нетривиальной задачей). Отметим. если вы не применяете пул по� токов и рабочие потоки — для этого требуется использовать блоки java. поскольку вы используете AsynchronousFileChannel.8. Разработка на Java 7 эта операция считывания не закончится (конечно. API/JVM предоставляет пулы потоков и группы каналов для выполнения задачи.util.txt. пока фоновый ввод-вывод завершится. Возможно. который его инициировал. Если говорить вкратце. Такой параллельный процесс ввода-вывода происходит автоматически. вас интересует. Такое считывание или запись будет выполняться в фоновом потоке . Наконец. рассчитывать налог) . Вы также можете предоставить и сконфигурировать такие элементы самостоятельно. либо придется дожидаться. что мы искусственно гарантировали обязательное получение конеч� ного результата (добавив в код isDone()).8. Асинхронный ввод-вывод в стиле с ожиданием Начинаем работу с открытия канала AsynchronousFileChannel для считывания или записи информации в файл foobar. concurrent.

Обработчик завершения для операции ввода-вывода. Такая техника может быть вам знакома из программирования пользовательских интерфейсов с помощью Swing. Процесс показан на рис. И вдруг это считывание срывается. с ко� торыми приходилось иметь дело в Swing. Обычно такой стиль применяется в тех случаях. Интерфейс java. Задаваемый по умолчанию пул потоков конфигурируется с помощью системных свойств. Новый ввод-вывод 89 но все они подробно описаны в официальной документации. заданным по умолчанию (который. CompletionHandler<V. в который подаются задачи для обработки событий ввода-вывода и диспетчеризации результатов к обработчикам завершения (обработчики завершения принимают на канале результаты операций ввода-вывода).5.nio. стиль с применением обратных вызовов техно� логически похож на применение обработчиков событий. A> (где V — тип результата. При создании AsynchronousFileChannel без указания пула потоков этот канал будет ассоциирован с системозависимым пулом потоков. Методы completed(V. связанном с расчетом прибыли. В более строгой форме ситуация описывается так. Поэтому просто про� цитируем фрагмент Javadoc по AsynchronousFileChannel. В таком случае вам немедленно потребуется выполнить возврат или обработать исключение. будет использоваться совместно с другими каналами). . возможно. которая совершенно необходима для использования в бизнеспроцессе. а также в ходе использования других API Java. Стиль с применением обратных вызовов В отличие от стиля с ожиданием. так����������������������������������������������������  ��������������������������������������������������� как он напоминает методики обработки событий. реализующий этот процесс). После этого разведчик вернется к основному потоку. Главная идея заключается в том. Существует и альтернативная техника. относящихся к пулу потоков. Канал AsynchronousFileChannel ассоциирован с пулом потоков. а A — прикрепляемый объект. Разведчик получит результат операции ввода-вывода.channels. вы считываете финансо� вую информацию.Глава 2. на основании которого будет запущен собственный метод этого разведчика completed или failed (переопределяемый вами). называемая Callback (обратный вызов). A) и failed(V. 2. чем стиль с ожиданием. при обмене сообщениями.9 приведен код. Некоторые разработчики считают стиль с обратными вызовами более удобным. гарантированно активизируется одним из потоков. связанная с вводом-выводом. от которого вы получаете результат) активизируется после того. инициированной на канале. A) этого интерфейса должны быть реализованы так. 2. когда необходимо немедленно реагировать на изменение ситуации после успешности или неуспешности выпол� нения того или иного асинхронного события. Например. чтобы ваша про� грамма четко различала случаи успешного и неуспешного завершения ввода-вы� вода и правильно на них реагировала. выполняющий операцию ввода-вывода.4 (в листинге 2. как будет выполне� на асинхронная операция. что основной поток отправит «разведчика» (обработчик завершения CompletionHandler) в отдельный поток.2. определяемых в классе AsynchronousChannelGroup.

Асинхронное считывание с применением обратного вызова В следующем примере мы вновь считываем 100 000 байт из foobar.4. Для объ� явления успешности или неуспешности операции используем CompletionHandler <Integer.txt.9. Листинг 2. 2.90 Часть 1. ByteBuffer>. Разработка на Java 7 Рис. Асинхронный ввод-вывод — стиль с применением обратных вызовов .

Но если эта сфера как раз относится к вашим профессиональным интере� сам. способными выполнять операции ввода-вывода. Кажется. приведенные в этом разделе. разработчики могут писать приложения. Для начала вспомним. такие утюги уже есть!). то этот раздел определенно можете про� пустить. 2. — например. Новый ввод-вывод 91 Два листинга.Глава 2. какую роль сокеты и каналы играют в Java. так как вводит сущность NetworkChannel. сочетающую черты Socket и Channel. Класс java. Пакет java. Сокет — это конечная точка для обмена информацией между двумя машинами. В прежних версиях Java программные конструк� ции Socket и Channel не очень хорошо взаимодействовали друг с другом — любые попытки их объединить были неудачными.channels. представляющие собой соединения с сущностями. например записывать . Реализует клиентские сокеты (также называемые просто сокетами). что в недалеком будущем любой утюг сможет подключаться к ней (а мо� жет быть. для IP-телефонии) и создавать более производительные клиентские и серверные программы. с файлами и сокетами. Написание низкоуровневого сетевого кода — достаточно узкоспециализирован� ная область. Возьмем определения из Javadoc. объединяющих сокеты и ка� налы и позволяющих вам иметь в API единую точку контакта для управления сокетными и канальными взаимодействиями. работающие с сетевыми сокетами (например.nio. чтобы выполнять ту или иную операцию ввода-вывода. описывали работу с файлами. Далее обсудим несколько небольших изменений. продолжайте читать — здесь мы сделаем обзор новых возможностей. Если вы не работаете в ней. В старых версиях Java вы действительно пытались привязать канал к реализации Socket. но стили асинхронного доступа с ожиданием и с обратным вызовом также могут при� меняться к AsynchronousServerSocketChannel и AsynchronousSocketChannel.Socket. Определяет каналы.6. Java 7 несколько упрощает для разра� ботчиков обращение с каналами и сокетами. определяет селекторы для мультиплексированных операций неблокирующего ввода-вывода. Таким об� разом.net. Окончательная шлифовка технологии сокет — канал В наше время компьютерные программы как никогда нуждаются в доступе к сети.

а также его подинтерфейса — MulticastChannel. Но при объединении Channel и Socket появлялись серьезные проблемы. 2. NetworkChannel Новый интерфейс java. обеспечиваемое функцией NetworkChannel. Кроме того.nio. мы устанавливаем IP «Условия обслуживания» и идентифицируем параметр SO_KEEPALIVE на соответ­ ствующем сокетном канале. отображая параметры.1. которые под� держивает интернет-адрес сокета на порте 3080. В следующем листинге показаны эти вспо� могательные методы. Разработка на Java 7 данные через TCP-порт. какие работы были проведены в рамках окончательной шлифовки нового интерфейса NetworkChannel.NetworkChannel представляет собой ассоцииро� вание канала с сетевым сокетом.92 Часть 1.channels. которые. Листинг 2. чтобы управлять параметрами сокетов и выполнять связывание на сокетах. Параметры NetworkChannel Еще одно дополнение. Рассмотрим. — это много� адресные операции. Мы демонстрируем их.6. в частности. .  В старых версиях Java не было возможности пользоваться платформоспеци� фичным сокетным поведением. Он определяет группу полезных методов.10.  В старых версиях Java приходилось смешивать сокетные и канальные API. позволяют просмотреть доступные параметры сокета и устанавливать его новые параметры на данном канале.

12 (листинг 2. Пример многоадресной в этой многоадресной группе. Листинг 2. например к BitTorrent.Глава 2.90. Новый ввод-вывод 93 2.2. появился новый интерфейс java. По умолчанию его реализует класс под названием DatagramChannel. что вы отправляете пакет на групповой адрес и прика� зываете сети размножать этот пакет столько раз. Таким образом.5. В более ранних версиях Java можно было кое-как сварганить многоадресную реализацию.MulticastChannel. присо­ единяясь к ней на IP-адресе 180. но среди API Java не было красивой абстракции. Для того чтобы новые NetworkChannel могли поддерживать присоединение к мно� гоадресным группам.11).6.channels. Суть процесса заключается в том.5. В следующем гипотетическом примере мы отсылаем к многоадресной группе системные статусные сообщения и получаем сообщения от этой группы. MulticastChannel Возможность многоадресной передачи — обычное практическое требование.4. сколько необходимо для предоставления его копии всем получателям. зарегистрированным Рис. вы с легкостью можете выполнять многоадресную передачу информации и получать информацию от многоадресных групп. В Java 7 для решения такой проблемы вводится новый интерфейс MulticastChannel. предназначенной специально для этого. Термин «многоадресный» описывает передачу информации по сети от одного узла ко многим. Параметры NetworkChannel . Пример проиллю� передачи стрирован на рис. предъ� являемое к приложениям для одноранговых сетей. Часто это делается на основе интернет-протоко� ла (IP).nio. 2. 2.11.

значительно ускоряющих написание кода для файлового вво� да-вывода. Язык Java 7 позволяет с максимальной пользой задействовать современные возмож� ности благодаря новым API. такой новый ввод-вывод удобен при работе с приложениями. новый служебный класс Files предоставляет множество вспомо� гательных методов. Новые библиотеки Java 7 позволяют оперировать местоположениями (Path) и выполнять операции в файло� вой системе — в частности. чтобы предоставить вам универсальные методы для вы� полнения задач. управлять файлами. Разработка на Java 7 На этом завершается наше первичное исследование новых API NIO. вам понравился этот летучий рейд! 2. Дюк — это талисман Java! http://kenai. каталогами. символьными ссылками и пр. современной параллельной обра� ботке. появившимся в рамках NIO. Здесь нам придется покорпеть над некоторыми продвинутыми функциями современного язы� ка Java. чем раньше. Теперь прибавим оборотов и перейдем ко второй части нашей книги. В частности. Мы поговорим о внедрении зависимостей. Гораздо проще стало работать с крупными структурами.io. Например. главу 1).2 создавался так.File. и вы можете писать менее пространный код. Ранее для их решения пришлось бы писать довольно объемный код. сущим пустяком теперь стала навигация по файловым системам с полной поддержкой платформоспецифичных поведений. а также о настройке программной системы. Надеем� ся. Резюме Аппаратный и программный ввод-вывод в наше время стремительно развивается. весь процесс упрощается по сравнению со старым вводомвыводом на основе java.2 также использует наработки Java. NIO.94 Часть 1. Кроме того. NIO. Асинхронный ввод-вывод — это мощная новая возможность для работы с боль� шими файлами без резкого снижения производительности.2.2.com/projects/duke/pages/Home . объединенные проектом «Монета» (см. Советуем заварить любимого кофе в кружке Дюка1. пропускающими большие объемы трафика через сетевые сокеты и каналы. подзаправиться и двигаться дальше! 1 Кстати. основанной на Java. модифицированные для операций ввода-вывода. состоящими из множества каталогов. Благодаря этому операции ввода-вывода становятся в Java 7 значительно надежнее. Кроме того.7.

Современная параллельная обработка Глава 5.ЧАСТЬ 2 Необходимые технологии Глава 3. Внедрение зависимостей Глава 4. Файлы классов и байт-код Глава 6. Понятие о повышении производительности .

util. а не как наука. Необходимые технологии В этой части книги (главы 3–6) мы углубимся в изучение важнейших тем и техно� логий. Вам предстоит изучить действующую в Java модель памяти (Java Memory Model). а по� том — и во фреймворк (и даже в стандарт Java). Поэтому мы посвятим данной теме целую главу. мы займемся исследованием возможностей пакета java. В частности. как измерять производительность. мы расскажем. но и рассмотрим. В главе 6. как оптимальные практики разработки превратились в паттерн (шаблон) проектирования. и параллельная обработка задействуется довольно неэффективно. и научим вас понимать зна� чение байт-кода. как именно виртуальная машина Java загружает. связывает и проверяет классы. вы приобретаете замечательные навыки отладки.concurrent (и не только). что «танцы с бубном» — это неверный метод. Это распро� страненная технология. мы поговорим о сущностях Java 7 MethodHandle. завершающей эту часть. позволяющая уменьшить связанность кода. оказывающие влияние на производительность. Далее нам предстоит разобраться с проблемой многоядерной революции в про� изводстве процессоров — это важнейшее явление в производстве аппаратного обеспечения. уметь их применять для мак� симально эффективного использования современных процессоров. Из-за этого растет раздражение и впустую тратится время. Отслеживание и устранение проблем с производительностью часто требуют от команды разработчиков исключительных усилий. Основательный Java-разработчик обязательно должен разбираться в возможностях параллельной обработки языка Java. Умея внимательно читать и глубоко понимать содержимое файлов классов Java и содержащегося в них байт-кода. а также немало времени. привыкшим пользоваться рефлексией. мы обратим особое внимание на сборку мусора (garbage collection) и динамическую компиляцию (JIT) — две основные составляющие виртуальной машины Java. Многие разработчики Java не вполне по� нимают. как развивалась эта технология. Исследуя проблемы . оптимизировать его тестируемость и восприятие. Мы докажем. Начнем с главы о внедрении зависимостей (dependency injection). Мы покажем. Мы не только расскажем об основах внедрения зависимостей. как использовать javap для навигации. который позволит быстро заглянуть в суть ваших проблем с производительностью. Отладка производительности часто воспринимается как искусство. как можно решать знакомые задачи более эффективными и безопасными способами. после чего станем нарабатывать практические навы� ки параллельной обработки на Java. а также реализацию потоков и параллелизма в этой модели. Мы продемонстрируем научный подход. необходимых для работы с современным языком Java. Несмотря на то что серьезная поддержка параллельной обработки существует в Java уже с 2006 года (Java 5).96 Часть 2. Вооружившись этим теоретическим материалом. Наконец. эта область недостаточно хорошо изучена. а не гадать о ней. так как из-за какого-то конфликта загрузчиков классов может выполниться «неправильная» версия класса. Затем поговорим о загрузке классов. MethodType и invokedynamic и покажем разработчикам.

Вы будете знать. как именно функциони� руют язык Java и виртуальная машина Java. помогающим анализировать использование памяти. Дочитав эту часть.Часть 2. Необходимые технологии 97 производительности. который сидит в IDE и думает только об исходном коде. пожалуй. вы уже не будете обычным разработчиком. . самую мощную универсальную виртуальную машину. мы научимся читать журналы сборщика мусора и работать с бесплатным инструментом Java VisualVM (jvisualvm). а также сможете в полную силу ис� пользовать. суще� ствующую на нашей планете.

3 Внедрение зависимостей В этой главе:  инверсия управления (IoC) и внедрение зависимостей (DI). а не иначе. который поможет вам глубже понять сущность внедрения зависимостей.  Guice 3. Начнем эту главу с введения в теорию внедрения зависимостей и расскажем. если вы (как многие из нас) начали работать с фреймворками внедрения зависимостей еще до того. А также уточним. как сложилась эта популярная парадигма. а не конструирует их сам. Вы познакомитесь с документом JSR-330 — официальным стандартом внедре� ния зависимостей в Java. при применении которой ваш объект получает необходимые ему зависимости в го� товом виде. какую пользу эта технология принесет вашей базе кода. более удобным для чтения и тестирования. Это особенно важно. эталонная реализация для JSR-330. по которым использу� ется такой подход. как у вас появилась возможность подробно изучить причины. Внедрение зависимостей очень положитель� но отражается на всем коде: он становится слабо связанным.  как документ JSR-330 объединил внедрение зависимостей для Java. использующим инверсию управления/внедрение зависи� мостей. Вкратце: внедрение зависимостей — это технология. Внедрение зависимостей (форма инверсии управления) — важная парадигма программирования. Многие считают его очень красивым и лег� ким методом внедрения зависимостей в Java.  почему так важно освоить технику внедрения зависимостей. которая примерно с 2004 года относится к основному направ� лению разработки для Java. Даже если вы уже рабо� таете с фреймворком. например @Inject. Далее мы познакомим вас с фреймворком Guice 3. в этой главе найдется материал.  типичные аннотации JSR-330. . Так вам будет проще понять причины. по которым стан� дартный набор аннотаций для внедрения зависимостей в Java сложился именно так. начнем с теории и обсудим. который явля� ется эталонной реализацией для JSR-330. Итак. почему вам может быть интересно освоить ее.

Пользователь может вводить описания . Если программа спроектирована качественно. содержа� щий первичный управляющий поток. 3. сводится к тому. что в системе обязательно есть еще один фрагмент кода. Это древняя видеоигра. которая сама находит собственные зависимости. Начнем с инверсии управления — феномена. Поэтому именно он будет вызывать ваш код. Инверсия управления. Но. Инверсия управления Если вы применяете парадигму программирования. то получили бы множество разнообразных ответов! Можно просто приступить к использованию различных фреймворков для вне� дрения зависимостей и научиться работать с ними на примерах из Интернета.1. Начнем с версии. При использовании инверсии управления этот принцип «централизованного контроля» как будто выворачивается наизнанку. лежащих в основе двух важнейших концепций (инверсия управления и внедрение заисимо� стей). то ход программной логики обычно контролируется центральным функционалом. Этот раздел мы начнем с изучения некоторых теоретических вопросов.1.com Q&A. в которую необходимые зависимости внедряются. ко� торые выполняют конкретные функции. и поговорим о пользе применения данной парадигмы. не связанную с инверсией управления. Чтобы дополнительно прояснить эти концепции. а не ваш код — его.wikipedia. который зачастую ошибочно сме� шивают с внедрением зависимостей. если бы вы обратились с этим вопросом на популярный сайт stackexchange. а как — графическая. но сама логика программы инкапсулиру� ется в вызываемых подпроцедурах. и преобразуем ее в вер� сию. внедрении зависимостей и их базовых принципах? На этот вопрос можно ответить по-разному. получаемого от пользователя. Код вызывающей стороны управ� ляет порядком выполнения программы. зачем же нужно знать об инверсии управления. то центральный функционал вызывает методы применительно к переиспользуемым объектам.org/wiki/Zork_I:_The_Great_Underground_Empire). мы рассмотрим пример с HollywoodService. Взглянем на инверсию управления с другой стороны и разберем пользовательский интерфейс игры Zork (http://ru. если бу� дете понимать внутреннюю структуру происходящего процесса. В текстовой версии игры мы увидим в интерфейсе просто пустое поле для вво� да текста.Глава 3. Действительно. также иногда называемая «принципом Голливуда». как и в области фреймворков для объектно-реляционного отображения (таких как Hibernate). вы сможете стать гораздо более сильным разработчиком. Дополнительные знания: понятие об инверсии управления и внедрении зависимостей Итак. Посмотрим.1. Внедрение зависимостей 99 3. как управляется текстовая версия этой игры.

конеч� но же.2. Фреймворк гра� фического пользовательского интерфейса определяет. ПРИМЕЧАНИЕ Контейнер инверсии управления можно считать средой времени выполнения.100 Часть 2. какой обработчик события будет выполняться. контроль передается от логики приложения к самому фреймворку графического пользовательского интерфейса. Среди контейнеров для внедрения зависимостей в Java следует назвать Guice. но это совершенно не обязательно! Явное инстанцирование и передача объектов (то есть зависимостей) к вашему объекту могут быть не менее удобными. где мы рас� смотрим Guice. Здесь важно отметить.3. например Guice. они позволяют гарантировать. будет сконфигурирована как Singleton («Одиноч� ка»). логика приложения больше этим не занимается. который перерабатывает действие и возвращает результат. Для этого можно использовать специализированные фреймворки.html Спасибо за этот совет Тьяго Аррайсу (Thiago Arrais) (http://stackoverflow. предназначенная для многократного использования. «Внедрение зависимостей» (Dependency Injection. Необходимые технологии действий. какой обработчик события будет активизирован. Если обратиться к другой версии этой игры. «Локатор Сервисов» (Service Locator) и. это паттерны «Фабрика» (Factory). Некоторые такие возможности будут исследованы в разделе 3. Основное управление программой инвертируется.com/2010/07/ inversion-of-control-containers-and_21. то здесь уже задействуется инверсия управления. что зависимость.1. но большинство разработчиков предпочитают пользоваться сторонним фреймворком со встроенным контейнером для инверсии управления. СОВЕТ Существует несколько способов внедрения зависимостей в объекты. Если пользо� ватель щелкает кнопкой мыши на варианте действия. Spring и PicoContainer.com/users/17801/ thiago-arrais)! . Внедрение зависимостей Внедрение зависимостей — это частный случай инверсии управления. Этот термин был популяризован Мартином Фаулером (Martin Fowler) в его статье «Инверсия управления и паттерн внедрения зависимостей»1. Контейнеры для инверсии управления предоставляют полезные возможности. иду на восток. то происходит непосредственная активизация обработчика события. например go east (иду на восток) или run from Grue (бегу от монстра). выполняемого в настоящий момент.blogspot. В таком случае процесс нахождения ваших зависимостей не находится под непосредствен� ным управлением кода. чем внедрение посредством фреймворка2. После этого основная логика приложения активизирует соответствующий обра� ботчик события. например. другое название — «Инъек� ция зависимостей»). с графическим пользовательским интерфейсом. что именно логика приложения управляет тем. а логика прило� жения сосредотачивается на обработке действия. Например. Вы можете написать собственный механизм внедрения зависимостей. Существует несколько вариантов реализации инверсии управления. В частности. 3. 1 2 Русский перевод статьи доступен по адресу http://yugeon-dev.

101 Глава 3. Преимущества внедрения зависимостей Преимущество Описание Пример Слабое связывание Ваш код перестает быть жестко связан с созданием нужных ему зависимостей. важно понимать.1. Внедрение зависимостей Как и при работе со многими другими парадигмами программирования. этим и займемся. классу HollywoodService можно передать AgentFinder любого типа Тестируемость В дополнение к слабому связыванию необходимо оговорить еще один случай. При необходимости тестирования вы можете внедрить в код тестовый двойник именно в качестве зависимости Можно внедрить «фиктивную» талонную систему оплаты. Пользователи могут предоставлять собственные специфичные реализации Предприимчивый разработчик может продать вам инструмент для поиска агентов в Linkedin Облегченный код В коде больше не требуется передавать зависимости между уровнями. Этим она отличается от «реальной» платежной системы.1. зачем используется внедрение зависимостей. Вместо этого зависимости можно внедрять именно в тех точках. на наш взгляд. преимущества внедрения зависимостей мы обобщили в табл. — наилучший способ усвоить теорию. где ему и место По нашему опыту. а не поиском подробной информации о драйверах JDBC Многоразовые компоненты В дополнение к слабому связыванию достигается еще одно преимущество: ваш код может задействоваться более широкой пользовательской аудиторией. так как ему не требуется заниматься загрузкой и конфигурированием зависимостей. часто встречающийся на практике. вы также можете добиться того. которая всегда будет возвращать одно и то же значение стоимости. 3. что код перестанет быть сильно связан с конкретными вариантами реализации зависимости Если вашему объекту HollywoodService требуется создать собственный SpreadsheetAgentFinder. При программировании. основанном на интерфейсах. которая является внешней относительно вашего кода и может быть недо­ ступна Более высокая слаженность кода Ваш код сосредотачивается на решении основной задачи. . Таблица 3. то ему можно просто передать такой SpreadsheetAgentFinder. основанного на интерфейсах». Итак. Самые значительные. Применяя технику «программирования. содержащий зависи� мости. преобразование обычного кода в код. Побочный положительный эффект заключается в повышении удобочитаемости кода Ваш объект доступа к данным (DAO) занимается именно выполнением запросов. где они нужны Вместо передачи подробной информации о драйвере JDBC от служебного класса можно внедрить драйвер непосредственно в объект для доступа данных — туда.

Интерфейс AgentFinder и реализующие его классы Чтобы использовать инструменты для поиска агентов. поэтому специально опустили некоторый шаблонный код. Листинг 3. который получает список агентов от SpreadsheetAgentFinder. HollywoodService с жестко закодированным AgentFinder . как перевести код.1. в проекте предусмот� рен стандартный класс HollywoodService. занимающихся подбором Java-разработ� чиков (назовем их «профессиональными»).1. фильтрует их по показателю профессиональной заинтере� сованности и возвращает такой список интересующих вас агентов. к применению реализации в стиле «Фабрика» или «Локатор серви� сов».3.2.1 есть интерфейс AgentFinder с двумя реализациями — SpreadsheetAgentFinder и WebServiceAgentFinder. вам передали работу над небольшим проектом. Эта практика обес� печивает подстановку объектов во время исполнения. В основе большинства этих операций лежит техника «программирования на основе интерфейсов». не использующий инверсию управления. Допустим. Листинг 3. Переход к внедрению зависимостей В этом разделе будет показано. ПРИМЕЧАНИЕ В этом разделе мы планируем закрепить ваше базовое представление о внедрении зависимостей.102 Часть 2.2. В листинге 3. Необходимые технологии 3. как показано в листинге 3. то есть внедрить в код зависимости. который будет возвра� щать всех трудовых агентов из Голливуда.

2001). «Фабричный метод» или «Локатор сервисов» либо их комбинация обычно использовались для решения проблемы «блокирования» при применении зависимости. приме� няющая AgentFinderFactory для динамического выбора AgentFinder. ПРИМЕЧАНИЕ Паттерны «Фабричный метод» и «Абстрактная фабрика» рассмотрены в книге Эриха Гаммы (Erich Gamma).3 продемонстрирована версия класса HollywoodService. почему код завязан на использовании всего лишь одной реализации AgentFinder  — SpreadsheetAgentFinder . . Подобное ограничение реализации представляло проблему для многих Javaразработчиков. Ричарда Хелма (Richard Helm). Ральфа Джонсона (Ralph Johnson) и Джона Влиссидеса (John Vlissides) «Приемы объектно-ориентированного проектирования. Класс Hollywood с паттернами «Фабрика» и/или «Локатор сервисов» Один из следующих паттернов — «Абстрактная фабрика». Паттерн «Локатор сервисов» рассмотрен в книге Дипака Алура (Deepak Alur). В листинге 3.Глава 3. специально ориентированные на ее решение. Джона Крупи (John Crupi) и Дэна Малкса (Dan Malks) «Образцы J2EE. Лучшие решения и стратегии проектирования». Паттерны проектирования» (СПб. Сначала многие разработчики использовали варианты паттернов «Фабрика» и «Локатор сервисов». который следу� ет использовать. Как и в случаях с другими распространенныими проблемами. Все эти варианты представляли собой разные типы инверсии управ� ления. Внедрение зависимостей 103 Снова вернемся к HollywoodService в этом листинге и разберемся.: Питер. развивались паттерны.

что в идеале не должно быть его основной задачей. а не реальную реализацию объекта AgentFinder.  Метод getFriendlyAgents содержит код для нахождения нужной зависимости. Зачастую она даже заменяет реализации абстрактной фабрики и локатора сервисов.  Код внедряет справочную ссылку (agentFinderType).4. мы уже почти справились с внедрением зависимости. но пока остается две проблемы. HollywoodService с внедренной вручную зависимостью для AgentFinder .4.104 Часть 2. Необходимые технологии Листинг 3. допускающей использование лишь одной конкретной реализации AgentFinder. удалось избежать блокировки. каков будет наш следующий шаг рефакторинга. Листинг 3. вы уже догадываетесь. HollywoodService с внедрением зависимостей Вероятно. Итак. Далее мы представим метод getFriendlyAgents уже с тем AgentFinder. Эта операция показана в листинге 3. техника внедрения зависимостей распространяется все шире.3. который ему нужен. затем приказываете AgentFinderFactory получить AgentFinder на основании этого типа . Вы внедряете agentFinderType . HollywoodService с фабричной подстановкой AgentFinder Как видите. По мере того как разработчики начинают писать все более чистый код.

Для того чтобы вам было легче понять. которую делала фабрика AgentFinderFactory. как мог бы выглядеть класс HollywoodService при применении стан� дартной аннотации JSR-330. AgentFinder был внедрен в метод getFriendlyAgents . Преимущество фреймворков для внедрения зависимостей заключается в том. Внедрение зависимостей 105 Теперь у вас фактически есть написанное вручную решение. Фреймворк внедряет нужные вам зависимости именно в тот момент. которую могут использовать любые совместимые фреймворки. Рассмотрим. Это показано в лис� тинге 3. насколько чист стал метод getFriendlyAgents. HollywoodService с внедрением зависимостей и JSR-330 Перейдем к последнему примеру кода в этом разделе. включающее вне� дрение зависимости. Контейнер содержит уже готовые зависимости. Но если разработчик вручную внедряет собственные зависимости примерно таким образом. Здесь мы собираемся пере� поручить внедрение зависимостей специальному фреймворку. . где может понадобиться зависимость. Вы уже видите. что они могут выполнять такие операции практически в любой точке кода. должна и сей� час быть где-то выполнена. Та работа. По-прежнему не решена проблема с выбором того. можно привести простую аналогию: фреймворк внедрения зависимостей — это обертка вокруг вашего кода. действующая во время исполнения. Именно здесь может очень пригодиться фреймворк внедрения зависимостей с контейнером для инверсии управления. Фреймворк может выполнять такую работу. у него остается еще одна серьезная головная боль.5. поскольку у него есть контейнер для инверсии управления. какую именно реализацию AgentFinder вы хотите использовать.Глава 3. он сосредоточен именно на бизнес-логике . когда они вам требу� ются. используя стандартную аннотацию JSR-330. В данном случае фреймворк для внедрения зависимостей инжектирует зависимость прямо в метод getFriendlyAgents. готовые для применения в вашем коде во время испол� нения.

Вновь рассмотрим некоторые преимущества внедрения зависимостей на при� мере класса HollywoodServiceJSR330 в листинге 3. внедрять любую специ� фичную реализацию AgentFinder. можно внедрить базовый класс Java (например. СОВЕТ Хотя аннотации JSR-330 и позволяют внедрять зависимости для метода. Для того чтобы протестировать класс HollywoodService. это обычно делается лишь для методов-конструкторов или методов-установщиков. .  Многоразовые компоненты. Код класса HollywoodServiceJSR330 существенно сократился по сравнению с исходной версией HollywoodService. так как позволяет обойтись без веб-службы. Необходимые технологии Листинг 3.  Тестируемость. Данное соглашение более подробно обсуждается в следующем разделе.  Более высокая слаженность кода. WebServiceAgentFinder) теперь инжектируется во время исполнения фреймворком для внедрения зависимостей. Код больше не работает с фабриками и сопут� ствующими им поисковыми операциями. HollywoodService. Он отлично подходит для модульного тестирования. возвращающий фик� сированное количество агентов. внедрение с JSR-330 для AgentFinder Конкретная реализация AgentFinder (например. POJOAgentFinder). насколько просто теперь будет другому разработчику. В терминологии разработки через тестирование такой элемент называется «класс-заглушка» (stub class).106 Часть 2.  Облегченный код. HollywoodService больше не требует конкретного типа AgentFinder при выполнении работы.5.5. который поддерживает аннотацию JSR-330 @Inject . а выполняет лишь бизнес-логику. использующему ваш API.  Слабое связывание. таблицы либо других сторонних реализаций. JDBCAgentFinder. Только представьте. какая ему только потребуется. — например.

что собираются вместе приступить к разработке стандартного набора аннотаций для подобных взаимодействий1. реализуемые в основанных на Java фреймворках для внедрения зависимостей. Далее мы рассмотрим этот подход несколько подробнее. работающих в этой области. 3. Подробнее этот документ можно изучить на сайте http://jcp.Inject от 8 мая 2009 года. как Guice. Вкратце: JSR-299 строится на основе JSR-330 и призван обеспечить стандартизированную конфигурацию для корпоративных сценариев. этот подход красиво обобщает основные возможности. все равно было сложно подо­ брать общие аннотации или конфигурацию.inject) для регламентации стандартизированного внедрения зависимостей в версии Java SE.tss?thread_id=54499 . Стандартизированное внедрение зависимостей в Java C 2004 года мы могли познакомиться с несколькими широко распространившими� ся контейнерами с инверсией управления. Данная инициатива встретила практически стопроцентную под� держку со стороны всех основных игроков. Новый стандартизированный подход к внедрению зависимостей в Java (в соот� ветствии с запросом на спецификацию JSR-330) позволяет решить подобные проблемы.com/news/ thread. Внедрение зависимостей 107 Использование внедрения зависимостей все отчетливее становится стандартной практикой для основательного Java-разработчика. После этого был подан запрос на спецификацию JSR-330 (javax. применявшимися для внедрения зави� симостей (стоит назвать хотя бы Guice. В нескольких популярных кон� тейнерах предоставляются отличные возможности для внедрения зависимостей. www. До недавнего времени все реализации использовали различные подходы к конфигурированию внедрения зависимостей для кода. анонс @javax. так как он позволяет со� ставить красивое и надежное представление о «внутрисистемной» основе работы такого фреймворка для внедрения зависимостей.Глава 3. Но еще совсем недавно различные фреймворки для внедрения зависимостей определяли довольно несхожие стандарты. А КАК НАСЧЕТ JAVA ДЛЯ ПРЕДПРИЯТИЙ? В корпоративной версии Java (Java EE) собственный механизм для внедрения зависимостей появился уже в версии JEE 6 (он назывался CDI). Поэтому разработчикам было сложно перехо� дить от одного фреймворка к другому. чтобы получить все преимущества инверсии управления.org/.inject. когда два ведущих члена сообщества. которым должен следовать код. занимающегося внедрением зависимостей. это касалось использования XML или аннотаций Java). Этот механизм был описан в запросе на спецификацию JSR-299 («Контексты и внедрение зависимостей на платформе Java EE»).2. Spring и PicoContainer).theserverside. Даже если в различных фрейм� ворках и наблюдалось некоторое сходство в стилях конфигурации (например. Кроме того. Боб Ли (Bob Lee) (из Guice) и Род Джонсон (Rod Johnson) (из SpringSource) объявили. Более или менее приемлемое решение было найдено примерно в мае 2009 года. 1 Боб Ли.

предполагающими применение конструкторов.inject состоит из пяти типов аннотаций (@Inject. называемый “внедрением зависимостей”.inject. http://atinject. целесообразен в большинстве нетривиальных приложений». вам не придется реализовывать их самостоятельно. Этот процесс. что данный пакет просто предоставляет интерфейс и несколько типов аннотаций. таинственно выпадающих из области видимости. В пакете javax. @Qualifier. то при работе вы будете ограничены лишь этим фреймворком. как новейшие фреймворки для внедрения зависимостей исполь� зуют новый стандарт. . реализуемых фреймворками внедрения зависимостей. позволяющий избежать блокировки в рамках единственного фреймворка. «В этом пакете перечислены средства для получения объектов такими способами. (А если вы его разрабатываете — снимаем перед вами шляпы!) ЗАЧЕМ МНЕ ЗНАТЬ. где предлагается более широкий набор возможностей.com/svn/trunk/javadoc/ javax/inject/packagesummary. Как правило. Например.html. совместимый с JSR-330. если вы не создаете собственный контейнер для инверсии управления в Java. JNDI). Если ваш код использует функцию.108 Часть 2. если требуется легковесное решение для внедрения зависимостей. вы можете запускать свой код во фреймворке Guice. Пакет javax.googlecode. 6 и 7). которые позволяют максимизировать возможности переиспользования. которая поддерживается только в конкретном фреймворке внедрения зависимостей. Чтобы понять. которые разделяться не должны. а что — нет. Об этих компонентах мы поговорим в следующих нескольких подразделах. В документации Javadoc по javax. После этого вы можете перей� ти к фреймворку Spring. доументация по Java.inject отлично рассказано о назначении это� го пакета. но вам могут потребоваться и более сложные функции. @Named.inject1. но теперь хотя бы вырисовывается способ. так что процитируем дословно: Пакет javax. к неожиданным срывам пошаговой отладки и таинственному возникновению других гнусных проблем. @Scope и @Singleton) и из одного интерфейса Provider<T>. ВНИМАНИЕ На практике все сложнее.inject (поддерживаются версии Java SE 5. какие жаркие дебаты развернулись по поводу того. Можете себе представить. тестируемость и удобство поддержки по сравнению с традиционными подходами. Начнем с аннотации @Inject. фабрик и локаторов сервисов (например. КАК ВСЕ ЭТО РАБОТАЕТ? Основательный Java-разработчик не ограничивается простым применением библиотек и фреймворков. Необходимые технологии После того как Java обогатилась javax. что должно войти в общий стандарт. разделяемых зависимостей. Необходимо помнить. чем кажется.inject предоставляется подмножество общих функций для внедрения зависимостей. При работе с внедрением зависимостей недостаточное понимание этих деталей может приводить к появлению неправильно сконфигурированных зависимостей. 1 «Пакет javax. зависимостей. появилась возможность использовать стандартизированное внедрение зависимостей и при необходимости переходить от одного фреймворка к другому. стоит внимательно изучить пакет javax. Ситуация пока далека от совершенства. не понимая при этом принципов их работы (хотя бы в общих чертах).inject».

Content content) { this. Например. Это обычная практика при внедрении зависимостей для задания опциональных полей. необходимые для выполнения их задач.html . как аннотация @Inject используется с методом-установщиком. В следующем кратком примере кода показано. когда приходится предоставлять служебным методам ресурсы. так как в противном случае среда времени исполнения Java (JRE) не могла бы определить. сводящиеся к тому. Существуют некоторые ограничения. Можно аннотировать конструктор с помощью @Inject и быть уверенным. С ее помощью вы можете указывать.Глава 3.  методы. Можно аннотировать метод с помощью @Inject. где хотели бы внедрить зависимость. } Этот способ внедрения параметров методов становится особенно мощным. как и в случае с конструкто� ром. @Inject public void setContent(Content content) { this. а также не могут сами объ� являть параметры типа 1.  поля. таковы:  конструкторы.1. которая должна най� ти определенные данные. что вы не сможете использовать уловку «Обобщенные методы». } Такая спецификация допускает внедрение нуля и более параметров для кон� структоров.content = content. ко­ торая описана в Руководстве по Java на сайте Oracle по адресу http://download. 1 Здесь мы имеем в виду. ВНИМАНИЕ В соответствии со спецификацией в классе может быть только один конструктор с аннотацией @Inject. кото� рые могут внедряться для последующей обработки во время исполнения. что его параметры будут предоставляться во время исполнения сконфигурированным вами контейнером инверсии управления. и. Например. И это правильно. объекты Header и Content вне� дряются в MurmurMessage при вызове конструктора. какой из внедренных конструкторов имеет приоритет.header = header.content = content.2. Внедрение зависимостей 109 3.com/ javase/tutorial/extra/generics/methods. можно передать аргумент. внедрение конструктора без параметров — вполне до� пустимая операция. этот метод может получить во время исполнения нуль или более параметров путем внедрения. @Inject public MurmurMessage(Header header. this. что внедренные методы не могут объявляться как abstract. методу находящей службы. Типы членов классов.oracle. Аннотация @Inject Аннотация @Inject используется с тремя типами членов классов. представляющий собой объект доступа к данным (DAO). Иначе говоря.

Можно внедрять и поля (при условии. 3. public class MurmurMessenger { @Inject private MurmurMessage murmurMessage. тогда как по умолчанию допустимо применение полей и параметров методов. например полей с предустановленными разумными умолчаниями.com/svn/trunk/javadoc/javax/ inject/Inject. Теперь вы уже достаточно хорошо знакомы с аннотацией @Inject. Эта концепция более понятна из схемы. Так мы обеспечим сохранение квалификатора во время исполнения. то вам понадо� бится как-то различать их. использование может быть ограничено полями. 3.2.. В этом доку� менте рассмотрены некоторые нюансы того.html . то в таком случае применяется внедрение метода-установщика. поскольку осложняет модульное тестирование. которые вы собираетесь внедрить в ваш код. показанной на рис. но эта прак� тика не очень распространена. Если вы используете реализацию. Синтаксис опять же довольно прост. } Вы можете подробнее почитать об аннотации @Inject в Javadoc1. Так. что они не являются final). Аннотация @Qualifier Аннотация @Qualifier определяет контракт для реализующих фреймворков. предоставляемую одним из фреймворков.2.  Обычно реализация должна иметь и аннотацию @Documented. как вы можете квалифицировать (более подробно идентифицировать) внедренные таким образом объекты для использования в вашем коде. Например. то необходимо учитывать: существуют правила.  Реализация должна быть аннотирована с помощью @Qualifier и @Retention(RUNTIME). . «Аннотация типа Inject» http://atinject. Необходимые технологии СОВЕТ Уже можно считать стандартными оптимальными подходами использование внедрения конструктора при установке обязательных зависимостей для класса. если придется внедрять в код.1.. Если речь идет о задании необязательных зависимостей. какие типы значений могут внедрять� ся и как работать с циклическими зависимостями. чтобы ее можно было добавить к API как часть общедоступного Javadoc. 1 Javadoc. Он мо� жет использоваться для квалификации (идентификации) объектов. Рассмотрим.  Реализация может быть доступна лишь для ограниченного использования. регламентирующие создание реали� зации аннотации @Qualifier. если в вашем контейнере для инверсии управления сконфигурировано два объекта одного и того же типа.  Реализация может иметь атрибуты. если она аннотирована @Target.googlecode.110 Часть 2.

1. @Documented @Retention(RUNTIME) @Qualifier public @interface MusicGenre { Genre genre() default Genre. Аннотация @Named Аннотационный интерфейс @Named — это специфический @Qualifier.METAL) Genre genre. но важно иметь базовое представление о том. Этот контракт применяется для квали� фикации внедряемых объектов по имени. предоставля­ ющий контракт для реализующих объектов. что внедряемый Genre будет иметь требуемый тип. Фреймворк музыкальной библио� теки может давать квалификатор @MusicGenre. В дальнейшем разработчик может использовать этот квалификатор при создании класса MetalRecordAlbumns. } Маловероятно. 3.TRANCE. ROCK. 3. которая может предостав� ляться в контейнере для инверсии управления.3. public enum GENRE { CLASSICAL. Внедрение зависимостей 111 Рис. который в спецификации определяется как реализу­ емый в контейнерах инверсии управления всех типов. Если скомбинировать аннотацию @Inject .2. представьте себе краткий гипотетический пример реализации @Qualifier.Глава 3. TRANCE } } public class MetalRecordAlbumns { @Inject @MusicGenre(GENRE. — это аннотационный ин� терфейс @Named. как работают различные реализации контейнеров для инверсии управления. Аннотация @Qualifier используется для различения двух объектов типа MusicGenre Чтобы увидеть вышеизложенный список «в перспективе». что вам придется создавать собственные аннотации @Qualifier. Квали� фикатор гарантирует. METAL. Один из типов @Qualifier.

как инъектор (то есть контейнер для внедрения зависимостей) будет переиспользовать экземпляры внедренного объекта. который может применяться для опреде� ления того. Некоторые контейнеры инверсии управления поддерживают и собствен� ные реализации @Scope.  Когда объявляется реализация аннотационного интерфейса @Scope. .  Если объявляется реализация аннотационного интерфейса @Scope. Необходимые технологии с квалифицирующей аннотацией @Named. } Хотя существуют и другие достаточно распространенные квалификаторы. @Inject @Named("broadcast") private MurmurMessage broadcastMessage. public class MurmurMessenger { @Inject @Named("murmur") private MurmurMessage murmurMessage. имеющий конкретное имя. Еще одна область. Аннотация @Scope Аннотация @Scope определяет контракт.112 Часть 2.. было принято решение. в которой различные составители исходной спецификации пришли к общему мнению — о необходимости наличия стандартизированно� го интерфейса. Это решение озвучено в докумен� те JSR-330. особенно в области обслуживания пользовательских ин� терфейсов в Сети. пока не был повсеместно принят документ JSR-299. 3. если в одном и том же классе будет объявлено более одной аннотации @Scope либо если будет обнаружен неподдерживаемый вариант аннотации @Scope.) Только аннотация @Singleton была признана общей реализацией @Scope в соответствии с JSR-330. то длитель� ность жизненного цикла его внедренного объекта определяется реализацией данной области видимости. то он должен быть потокобезопасным. Такие стандартные поведения задают определенные границы для работы фрейм� ворков внедрения зависимостей.2. . что только квалификатор @Named должен реализовывать� ся всеми фреймворками внедрения зависимостей. но применять этот экземпляр только в целях внедрения.. инъектор должен создать экземпляр объекта для внедрения. Поэтому @Singleton в спецификации также определяется как отдельный тип аннотации.  Если внедряемый объект может использоваться несколькими потоками в реа� лизации @Scope. управляя жизненными циклами внедряемых ими объектов. Подробнее о потоках и их безопасности рассказано в главе 4.  Контейнер с инверсией управления должен генерировать исключение. (Такая ситуация сохранялась как минимум до тех пор. то в код будет внедряться объект правиль� ного типа.4. который управлял бы областями видимости для внедренных объектов. Спецификация определяет несколько стандартных поведений.

2. у вас появятся следующие преимущества при управлении внедренным объектом:  вы можете получать множественные экземпляры данного объекта.6. Будьте осторожны с паттерном «Синглтон» — иногда он оказывается антипаттерном! В большинстве фреймворков для внедрения зависимостей @Singleton считается скрытым умолчанием. 3. Аннотация @Singleton Аннотационный интерфейс @Singleton — это аннотация. если вы не объявляете область видимости. можете запросить фреймворк внедрить реали� зацию интерфейса Provider<T> для объекта (T). T get(). Например. } В данном примере мы предполагаем. Интерфейс Provider<T> Чтобы вы могли приобрести дополнительный контроль над объектом. что позволит вам искать объекты в более узкой области. Он применяется в тех случа� ях. что defaultHeader никогда не изменится (фактически это статические данные). Довольно часто приходится внедрять значение объекта. а не сам этот объект. и только однажды.  можно определять область видимости. Ральфа Джонсона (Ralph Johnson) и Джона Влиссидеса (John Vlissides) «Приемы объектно-ориентированного проектирования. Ричарда Хелма (Richard Helm). этот элемент может быть внедрен как синглтон.  можно разрывать циклические зависимости.5. Если вы явно объявляете синглтон. можно внедрить реализацию интерфейса Provider<T> (Provider<Message>) . то это можно сделать так: public MurmurMessage { @Inject @Singleton MessageHeader defaultHeader. которое не будет изменяться. Таким образом. чем все загруженное приложение. при котором инстанцирование класса может происходить однажды. Внедрение зависимостей 3.  при необходимости можно отложить получение объекта (ленивая загрузка) либо вообще его не получать. который должен предостав� лять полностью сконструированный внедренный экземпляр объекта ( T ). а значит. ПАТТЕРН SINGLETON Singleton («Одиночка») — это просто паттерн проектирования. внедряемым в ваш код с помощью фреймворка. Наконец. то по умолчанию фреймворк предполагает. рассмотрим наиболее гибкий параметр. что вы собираетесь использовать синглтон. Паттерны проектирования» (СПб. Интерфейс содержит всего один метод. Например.2. широко используемая во фреймворках для внедрения зависимостей.113 Глава 3. В таком случае синглтон — доста� точно эффективное решение. Подробнее об этом рассказано в книге Эриха Гаммы (Erich Gamma). 2001). когда недостаточно ни одной из стандартных аннотаций.: Питер.

таком как Guice. который тот ведет примерно с 2006 года. Необходимые технологии в конструктор MurmurMessage.inject. Guice 3 — эталонная реализация внедрения зависимостей в Java Guice (произносится как «Джюс») — проект Боба Ли. как можно получать дополнительные экземпляры внедрен� ного объекта Message от Provider<Message>. чем обычный фрейм� ворк для внедрения зависимостей. их связыванием. вы зани� маетесь конфигурированием ваших зависимостей.google. Мы по� кажем примеры написания кода с внедрением зависимостей. Guice — это нечто большее. В данном случае вы используете вторую копию этого внедренного объекта Message. в которых благодаря Guice могут применяться стандартные аннотации JSR-330. Использование интерфейса Provider<T> Обратите внимание. а не единственный экземпляр Message — при внедрении объекта вручную был бы доступен всего один его экземпляр. как показано в листинге 3.114 Часть 2. Здесь подробно описаны мотивы создания проекта. которая загружается лишь при необходимости . а также опре� деляете. вот мы и изучили теорию и рассмотрели несколько небольших примеров работы с новым пакетом javax.com/p/google-guice/.6.3. и в данном разделе мы будем пользоваться этой версией. . связанных именно с внедрением зависимостей. если ваш код исполь� зует аннотацию @Inject (а также другие аннотации из JSR-330). Здесь же вы можете скачать двоичные JAR-файлы для запуска примеров. Проект расположен по адресу http://code.6. размещена документация. 3. Именно в фреймворке для внедрения зависимостей. Листинг 3. но в рамках текущего раздела мы сосредоточим� ся на возможностях Guice. Теперь освоим на практике все приобретен� ные знания. в какой области видимости они будут связываться. Guice 3 — это полная эталонная реализация для JSR-330. Итак. Он будет получать различные объекты Message на осно� вании произвольных критериев. Для этого исследуем Guice — полнофункциональный фреймворк для внедрения зависимостей.

В терминологии Guice. Листинг 3. Знакомство с Guice Теперь вы понимаете. необходимо обязательно включить в путь к классу JAR-файлы. При рассмотрении последующих образцов кода в нашей книге JAR-файлы Guice 3 также автоматически входят в состав сборки Maven. а сами связи будут объявляться в переопреде� ленном методе configure(). Звучит сложно? Не волнуйтесь.1. которые вы хотите внедрить. Поэтому следует усвоить эту терминологию. Здесь мы будем следовать соглашению по работе с внедрением конструктора. «модуль».google. Чтобы обеспечить полную поддержку контейнеров инверсии управления и внедрения зависимостей. в котором будут содержаться связи. HollywoodService с внедрением зависимостей посредством Guice для AgentFinder . Внедрение зависимостей 3.7. нужно скачать архив Guice и распаковать его в удобную для вас папку.7.115 Глава 3. объявляющие коллекции связей (bindings).com/p/google-guice/downloads/ list . «связь». ГДЕ СКАЧАТЬ GUICE Скачайте новейшую версию Guice по адресу http://code. если вы собираетесь создавать приложения на базе Guice. как работают различные аннотации из JSR-330.com/p/google-guice/wiki/ Motivation?tm=6. В данном конкретном случае класс WebServiceAgentFinder привязывается как объект для внедрения. чтобы приказать инъектору (injector) построить данный граф объекта (object graph). Рассмотрим листинг 3. Фактически так происходит внешняя конфигурация зависимостей. когда мы изучим эти концепции в коде. Соответствующая документация находится по адресу http://code. определяют конкретные реализа� ции. Начнем с создания класса связей AgentFinderModule. СОВЕТ «Граф объекта». все на самом деле станет понятно. которые вы хотите внедрить.google. Этот класс AgentFinderModule должен дополнять AbstractModule. когда HollywoodServiceGuice запрашивает внедрение (@Inject) AgentFinder. в свою очередь. И вы може� те использовать их в своем коде с помощью Guice! Guice позволяет собрать кол� лекцию объектов Java (вместе с их зависимостями). Начнем с создания конфигурационного класса (модуля). В этом разделе мы вновь рассмотрим пример с классом HollywoodService. «инъектор» — общепринятые термины в среде Guice. Чтобы использовать Guice в своем коде Java. нужно создать модули (modules). которы� ми фреймворк Guice будет управлять для вас. Связи.3.

inject.google.List. Построение графа объекта Guice — автономное Java-приложение В стандартном Java-приложении вы можете построить граф объекта Guice с помо� щью метода public static void main(String[] args).Injector. Теперь.116 Часть 2.8. import java. который хотите привязать (AgentFinder).Guice. Необходимые технологии Основная часть операции связывания осуществляется при использовании метода связывания Guice. вы можете получить инъектор для построения графа объекта. Мы рассмотрим.8 показано.util. как это делается. Листинг 3. какая реализация будет внедряться . Ему вы передаете класс. public class HollywoodServiceClient . import com. затем используете метод to для объявления того. как это делается в автономном Java-приложении и в веб-приложении. когда ваша связь объявлена в модуле. HollywoodServiceClient — построение графа объекта с помощью Guice import com.inject.google. В листинге 3.

поскольку вам могут понадобиться более сложные связи. List<Agent> agents = hollywoodService.google. <filter> <filter-name>guiceFilter</filter-name> <filter-class>com. Построение графа объекта Guice — веб-приложение При работе с веб-приложением необходимо добавить к нему файл guice-servlet.class). чем обычная связь WebServiceAgentFinder с AgentFinder. Внедрение зависимостей 117 { public static void main(String[] args) { Injector injector = Guice. применительно к которому можете немедленно вызвать метод getFriendlyAgents. показанная в листинге 3.servlet. добавим следующий код в web.createInjector(new ServletModule())..xml.createInjector(new AgentFinderModule()).getInstance(HollywoodServiceGuice. } } Наконец. а также вставить следующий фрагмент кода в файл web. но все несколько усложняется.Глава 3. public class MyGuiceServletConfig extends GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Далее считается стандартной практикой дополнять ServletContextListener для работы с модулем Guice ServletModule (синонимичен AbstractModule в�������� ��������� листин� ������� ге 3.MyGuiceServletConfig</listener-class> </listener> При создании HollywoodServiceGuice из инъектора вы получаете полностью скон� фигурированный класс.7..java7developer. правда? Да. В таком случае контейнер сервлетов запустит этот класс при развертывании приложения: <listener> <listener-class>com.inject. HollywoodServiceGuice hollywoodService = injector.xml.getFriendlyAgents(). . } } С веб-приложениями ситуация обстоит немного иначе.7). . Довольно просто.jar.

а мы остановимся на использовании стандартной связи JSR-330 @Named.2.3. Мы не будем здесь дословно цитировать документацию. онлайн-документацию по Guice). } Вы уже видели такую связь в действии. чтобы извлечь AgentFinder с кон� кретным именем. Вы конфигурируете такой поименованный (@Named) тип связи в своем модуле AgentModule.  встроенные связи (built-in bindings). с дополнительным идентификатором. а подробно обсудим лишь самые распространенные связи — ссылочные связи. Связи именно такого типа мы использовали при конфигурировании AgentFinderModule в листинге 3.  динамические связи (just-in-time bindings). какой именно объект следует внедрить.  связи поставщиков (provider bindings). Связывающие аннотации Связывающие аннотации используются для комбинирования типа класса. который может точно указывать. связывающие аннотации. Вы можете писать собственные связывающие аннотации (см. который вы хотите внедрить. Ссылочные связи Ссылочная связь — это простейшая форма связи.  связывающие аннотации (binding annotations).to(WebServiceAgentFinder. В данном случае вы также сможете работать с уже знакомой аннотацией @Inject. можно внедрять и классы.  нецелевые связи (untargeted bindings). являющиеся прямыми потомками дан� ного класса) во время исполнения.9. Связь этого типа указывает инъектору. используя метод annotatedWith.class). как показано в листин� ге 3. В официальной документации перечис� лены следующие типы:  ссылочные связи (linked bindings).6. Морские узлы — различные связи в Guice В Guice предлагается множество связей. . Рассмотрим следующий по распростра� ненности тип связей — связывающие аннотации. но ее необходимо дополнить аннотацией @Named. встроенной в Guice.  связи экземпляров (instance bindings).  методы @Provides. @Override protected void configure() { bind(AgentFinder.118 Часть 2. Необходимые технологии 3. что он должен внедрять реализующий или дополня­ ющий класс (да.class). а также связи @Provides и Provider<T>.

HollywoodService с использованием @Named Итак. так и вместе с ней. Листинг 3. какой объект внедрить.Глава 3. Такие связи позволяют передавать пол� нофункциональные зависимости с применением аннотации @Provides и интер� фейса Provider<T>.10. Внедрение зависимостей 119 Листинг 3. вы научились конфигурировать поименованные зависимости. снабженных аннотацией @Provides .10.9. HollywoodService будет использовать AgentFinder . @Provides и Provider<T> — предоставление полностью инстанцированных объектов Можно применять аннотацию @Provides как вместо использования связи в методе configure(). как показано в листинге 3. Например. Теперь перейдем к следующему типу связей. если вы хотите вернуть полностью инстанцирован� ный объект. AgentFinderModule. Например. Инъектор будет просматривать возвращаемый тип всех методов. вам может понадобиться внедрить довольно специфиче� скую реализацию SpreadsheetAgentFinder в формате таблицы Microsoft Excel. использующий @Provides . чтобы определить. предоставленный методом provideAgentFinder с аннотацией @Provides.

Для это� го Guice поддерживает интерфейс JSR-330 Provider<T>. Очень важно иметь понятие об области видимости. использующий интерфейс Provider<T> На этом завершается рассказ о последнем примере связывания. чем нужно. AgentFinderModule.120 Часть 2. Необходимые технологии Количество методов @Provides может быстро вырасти.11. там упоминался метод T get(). Этот метод запускается. в какой области видимости существуют эти зависимости. когда класс AgentFinderModule конфигурирует связь с AgentFinderProvider с помощью метода toProvider. Вспомните раздел о JSR-330. Теперь вы научились использовать Guice для связывания зависимостей. Такая связь продемонстрирована в листинге 3.11. Но еще остается обсудить. тогда их понадобится распределить на отдельные классы (а не переполнять ими классы модулей). так как если объекты оказываются в неверной области видимости. готовых для применения в коде. то они существуют очень долго и потребляют больше памяти. который мы хотели рассмотреть. . Листинг 3.

насколько потокобезопасным должен быть класс. относящиеся к внедренному объекту.9. а еще есть область видимости @Singleton. некоторым разработчикам будет удобнее держать в одном месте все правила. когда привязывали основной AgentFinder? При добавлении . Фактически в последнем случае мы имеем область видимости на уровне приложения.Глава 3. Самая узкая область видимости — @RequestScope. то можете так и делать. Начнем с области видимости.  как часть последовательности связывания (например.  как дополнительная аннотация к контракту @Provides.3. Для этого можно применить к объявлению класса область видимости @Singleton вот так: @Singleton public class SpreadsheetAgentFinder { .to(). который вы хотите внедрить Предположим. Задание области видимости для класса. Внедрение зависимостей 121 3. Поскольку SpreadsheetAgentFinder теоретически может внедряться многократ� но. Задание области видимости для внедренных объектов в Guice Guice предоставляет несколько уровней области видимости для объектов.in()). Список получился немного абстрактным. область видимости @Singleton указывает. как мы использовали свя� зывание в листинге 3. ограниченной классом.3. который вы собираетесь использовать во всем приложении. что разработчик видит на примере.. область @SessionScope шире. который вы хотите внедрить. вам всего лишь однажды понадобился единственный экземпляр SpreadsheetAgentFinder. который вы хотите внедрить.. которые вы собираетесь внедрять. знакомая вам из до� кумента JSR-330. bind(). Область видимости зависимостей может применяться в коде несколькими способами. Помните. что необходимо гарантировать по­токобезопасность класса (подробнее о безопасности потоков мы поговорим в главе 4). Использование последовательности BIND() для задания области видимости Возможно. } Дополнительная польза от применения этого метода заключается в том. Если вы предпочитаете объявлять всю вашу информацию об областях видимо� сти при привязывании зависимости. например:  к классу. Поэтому изучим суть перечисленных случаев на небольших примерах кода.

to(WebServiceAgentFinder. finder. мы можем добавить дополнительную аннотацию @Request. . как Guice рабо� тает с аннотациями JSR-330 при внедрении зависимостей в вашей базе кода.in(Session. } Guice также предоставляет конкретные области видимости на основе веб-при� ложений (например. не относящиеся к JSR-330.xls"). область видимости запросов сервлета). по мере необходимости вы можете писать собственные области видимости.9.setType("Excel 97"). действующей в пределах сеанса (session scope). Так внедренный основной объект AgentFinder станет доступен в области видимости. связанных с обеспечением безопасности и логированием. Необходимые технологии области видимости к этой связи используется схожий подход: вы просто добавля­ ете . чтобы привязывать результирующие экземпляры SpreadsheetAgentFinder к области видимости запроса. добавив в по� следовательность in(Session. public class AgentFinderModule extends AbstractModule { @Override protected void configure() { bind(AgentFinder. Такая функциональность может быть полезна при реализации сквозных возможностей приложения.class) .in(<Scope>. воз� вращаясь к листингу 3. return finder. теперь вы имеете хорошее базовое представление о том.annotatedWith(Names. например поддержка аспектноориентированного программирования (AOP).setPath("C:/temp/agents. Эти вопросы рассмотрены в онлайндокументации по Guice и в примерах кода.class) в последовательность связывания как дополнительный вы� зов метода.class). @Provides @Request AgentFinder provideAgentFinder() { SpreadsheetAgentFinder finder = new SpreadsheetAgentFinder().class) . } } Задание области видимости поставщика объектов @Provides Можно указать область видимости вместе с аннотацией @Provides.9. Например. Итак. Так определя� ется область видимости объектов. finder. В следующем фрагменте кода мы усовершенствуем листинг 3. предоставляемых этим методом. Кроме того. В Guice есть и другие функции.named("primary")) .122 Часть 2.class).

во многих прикладных случаях бывает достаточно использовать Guice и совместимый с JSR-330 набор аннотаций. В дальнейшем вы обязательно должны заботиться о потокобезопасности данного объекта (подробнее мы поговорим об этом в главе 4). унифицирующий общую функциональ� ность внедрения зависимостей. Виртуальная машина Java сможет без труда создавать и уничтожать их по мере необходимости (о виртуальной машине Java и ее производительности мы подробно поговорим в главе 6). . сохраняющих состояние. а также популярный и легкий способ приступить к внедрению зависимостей в вашем коде. могут обходиться и без установленной области видимости. связанные с внедрением зависимостей. данной пользовательской сессии или данному запросу. Внедрение зависимостей 123 БУДЬТЕ ВНИМАТЕЛЬНЫ ПРИ ВЫБОРЕ ОБЛАСТИ ВИДИМОСТИ! К ключевым решениям. Но внимательно изучив концепции паттернов Factory («Фабрика») и Service Locator («Локатор сервисов»). для объектов. удобный в тестировании и легкий для чтения. Напротив. Действительно. Изучая стандартный набор аннотаций для внедрения зависимостей. которую оно может дать вашей базе кода. позанимайтесь чем-нибудь приятным. Оторвитесь от чтения. Guice — это эталонная реализация JSR-330. Даже если вам сложно иметь дело с парадигмой вне� дрения зависимостей. Речь пойдет о параллельной обработке. JSR-330 — не просто важный стандарт. инверсия управления — довольно сложная концепция. о которых вы должны знать. несомненно. в которой тяже� ло разобраться. так как она позволяет писать сла� бо связанный код. В результате вы сможе� те их максимально эффективно использовать. чтобы решать практически любые задачи. Паттерн Factory можно считать промежуточным этапом на пути к пониманию внедрения зависимостей и той пользы. вы сможете понять.4. как различные фрейм� ворки внедрения зависимостей реализуют спецификацию. Резюме Возможно. заслужили небольшой пере� рыв. которые приходится принимать основательному Java-разработчику. не сохраняющие состояния и сравнительно простые в создании. вы можете гораздо полнее понять. будет ли жизненный цикл данного объекта равен всему циклу приложения. стоит ее придерживаться. то. Этот документ описывает неявные правила и огра� ничения. 3. Если вы читаете книгу с самого начала. которую должен в совершенстве знать любой основательный Java-разработчик.Глава 3. пожалуй. область видимости нужно задавать всегда! Необходимо заранее определять. Объекты. а потом с новыми силами приступайте к изучению темы. как работает базовая реализация инверсии управления. относится выбор области видимости для используемых в работе объектов.

но дает достаточно сведений. До появления Java 5 блочный параллелизм был единственно возможным вариантом. в которой должен разбираться любой основательный Java-разработчик — речь пойдет о библиотеке java.4 Современная параллельная обработка В этой главе:  теория параллельной обработки. что нужно. Runnable и языковых примитивов. о которых мы погово� рим далее по ходу книги. Почему-то многие полагают. но и до сих пор не утратил актуальности. Итак. чтобы сори� ентировать вас в том. В конце этой главы мы рассмотрим новый фреймворк fork/join. при обсуждении не Java-языков. которые может совершить разработчик. Далее мы обсудим тему. чтобы полностью понимать различные особенности параллелизма. которые вы должны изучить по теме параллелизма. Кроме того. ЧТО ТАКОЕ ПОТОКИ! Это одна из наиболее распространенных (и наиболее опасных) ошибок. закончив чтение этой главы.  библиотеки java. Эта глава не претендует на звание компендиума по всем вопросам. а качественная многопоточная разработка . и позволить присту� пить к работе.util. Кроме того.  блочный параллелизм. после прочтения этой главы вам будет проще и комфорт� нее писать параллельный код. НО Я УЖЕ ЗНАЮ. что потребуется изучить в дальнейшем. чтобы считать себя компетентным разработчиком параллельного кода. вы будете готовы к применению новых методов параллельной обработки в своем собственном коде. На самом деле тема параллелизма очень обширна.util. название кото� рого можно перевести как «ветвление/слияние». обеспечивающих реализацию параллелизма в Java — все.  модель памяти в Java (JMM). которые в ней предоставляются. вы будете знать достаточно много теории.  легкий параллелизм с применением фреймворка fork/join (ветвление/сли­ яние). Эта глава начинается с изучения базовых концепций и с шапочного знаком� ства с блочным параллелизмом.concurrent. что поверхностное знание Thread.concurrent и о базовых элементах для параллельной обработки.

Затем мы поговорим о влиянии. почему параллельная обработка в Java организована именно так. Определенно они окажут большое влияние на Java и другие языки. (Goetz Brian and others. Есть несколько великолепных книг. Для начала обсудим основы поточной модели в Java. Современная параллельная обработка 125 сложна. которое оказывают «структурные силы» на проектирование и реализацию систем. Цель этой главы — познакомить вас с базовыми механизмами работы платфор� мы. помогающими понять. .concurrent при написании кода. так и сложность. Second Edition. 4. С другой стороны. 4. Рассмотрение модели потоков в Java Модель потоков в Java основана на двух фундаментальных концепциях:  разделяемое. Addison-Wesley Professional. Вы сможете понять как необходимость. Кроме того.1.Глава 4.  диспетчеризация потоков по приоритетам. Две наилучших — «Параллельное программирование на языке Java» Дага Ли (Lea Doug. Но вы должны учитывать. с которыми вы будете сталкиваться в ходе профессиональной карьеры. а так� же разберем причины. что изложенной здесь информации недостаточно.util. что в настоящее время в области многопоточности разворачивается множество актуальных исследований. посвященных исключительно параллельной обработке в Java. На самом деле именно с теории мы и хотели бы начать. Многопоточность по-прежнему продолжает доставлять проблемы даже самым лучшим разработчикам с многолетним опытом. неотделимые от правильной организации параллелизма.1. Concurrent Programming in Java. 2006). Завершим этот раздел рассмотрением примера многопоточной системы и пока� жем. После этого обратимся к изучению сил. как в Java организуется параллельное программирование. связанные с этим видом деятельности. 1999) и «Параллелизм в Java на практике» Брайана Гётца и др. необходимо учитывать. между которыми часто возникают конфликты. мы достаточно глубоко изучим теорию парал� лельной обработки. Теория параллелизма — базовый пример Чтобы понять.1. а не иначе. по которым в работе параллельных систем возникают издержки. мы по� знакомимся с теорией. чтобы стать по-настоящему высококлассным разработчиком многопоточного кода. которая должна радикально измениться в практическом отношении в ближайшие несколько лет. то мы назвали бы именно параллельную обработку. как можно с удобством использовать java. Prentice Hall. Мы обсудим две наиболее важные из этих сил: стремление к безопасности и стремление к жизнеспособности. чтобы вы могли усвоить терминологию и понять проблемы. Если бы нам предложили назвать фундаментальную область вычислительной техники. видимое по умолчанию изменяемое состояние. Java Concurrency in Practice.

 Диспетчер потоков может перераспределять потоки между ядрами процессора.  В ходе обработки объекты могут без проблем разделяться между потоками (совместно ими использоваться). очень важными для параллельных систем. существует риск. Это не радует. частично рассмотрена в главе 6. но сейчас. Правда. . Эти аспекты условно называются структурными силами (design forces).  Объекты могут быть изменены любыми потоками.126 Часть 2. мы рассмотрим в этой главе. сделан� ные в одном потоке. а единственный довольно эффективный способ использования таких ядер — написание параллельного кода. называемый java. что изменения. Кроме того. по природе своей требующих параллельного программирования. будут невидимы в других потоках — в то время как их ви� димость будет необходима. в Java 5 появился набор библиотек для обеспечения парал� лелизма. где мы обсуждаем производительность. чем с классическими блочно-структурирован� ными примитивами для параллелизма. Оказывается.  При оперировании методами необходима возможность их выгрузки прямо в процессе работы (в противном случае метод может войти в бесконечный цикл и будет постоянно впустую расходовать ресурсы процессора). работать с которым зачастую довольно сложно. что принципы параллельного программирования начинают вступать в противоречие с некоторыми аспектами. так как в производстве аппаратного обеспечения все сильнее нарастает тенденция к созданию многоядерных процессоров. принятые уже на первых этапах проектирования Java. основанный на потоках и блокировках. — это очень низко� уровневый механизм. Для снижения этих рисков предусмотрена следующая возможность. некоторые решения. Некоторые сложности. На тот момент это был огромный шаг вперед. то меньше. априори существующие силы. выяснилось. при этом возникает риск непредсказуемой переброски потока. через 15 лет. нагружая эти ядра то больше. Они пред� ставляют собой высокоуровневые. как писать параллельный код. УСВОЕННЫЕ УРОКИ Java был первым широко распространенным языком. из-за которой метод останется «выполненным наполовину». имеющими ссылку на эти объекты. Необходимые технологии Рассмотрим некоторые наиболее важные особенности этих идей. Многим программистам удобнее работать с таким инструментарием. В нем предоставляется несколько инструментов для написания параллельного кода. связанные с параллельным кодом. которые зачастую вступают в противоречие с проектированием реальных параллельных объектноориентированных систем. на практике довольно сложны для большинства программистов. в котором появилась встроенная поддержка многопоточного программирования. а объект окажется в не� согласованном состоянии. мы узнали много нового о том.util. По мере того как разработчики поднаторели в написании параллельного кода.  Объекты могут блокироваться для защиты уязвимых данных Параллелизм Java. Тема современных процессоров. Чтобы упро� стить этот процесс.concurrent .

работающих на разных ядрах процессора. Если скомбинировать такой подход с определенным способом защиты объекта (например. в конце выполнения метода этот объект останется в правильно определенном и непротиворечивом со� стоянии. Как понятно из названия. Структурные концепции Самые важные структурные силы были перечислены Дагом Ли в ходе его эпохаль� ной работы по созданию java.2. которые изменяют состояние объекта только непротиворечивым образом. то система гарантированно останется безопасной. О ДОЛГОВРЕМЕННОЙ БЕЗОПАСНОСТИ Одна из стратегий обеспечения безопасности сводится к тому. Об этом пойдет речь в следующих нескольких разделах. что. либо не удается. как и безопасность типов объекта. с применением блокировки синхронизации или критической секции). в принципе.Глава 4. что она обеспечивает безопасность типов при параллелизме. где одними и теми же объектами потенциально могут оперировать сразу несколько потоков. что экземпляры объекта остаются самодо­ статочными независимо от любых других операций. независимо от того. которые могут протекать в системе в настоящее время. Обычно эта цель достигается при сохранении всей информации о состоя� нии объекта в закрытом виде и предоставлении общедоступного API из методов. чтобы никогда не возвращаться из публичного метода в несогласованное состояние. Подробнее рассмотрим каждую из этих сил. 4. параллелизм можно представить как расширение для традиционных концепций моделирования объектов и безопасности типов. Если система объектов обладает таким свойством. в которой любое предпринятое действие в итоге либо прогрессирует. Безопасность и безопасность типов при параллельной обработке Безопасность — это обеспечение того. пока объект несогласован. будучи в несогласованном состоянии. а также никогда не вызывать публичный метод (и любой другой метод к любому объекту). Современная параллельная обработка 127 Потратим еще немного времени и рассмотрим некоторые наиболее важные из этих сил. . В непа� раллельном коде необходимо гарантировать. какие обще� доступные методы вы вызываете применительно к объекту. то говорят. Безопасность типов при параллелизме — это.util.  жизнеспособность.concurrent:  безопасность (также называемая безопасностью типов при параллельной обработке). Жизнеспособность «Живой» называется такая система. но она применима в гораздо более сложной среде. такая же концепция.1.  производительность.  возможность многократного использования.

располагая данным объемом ресурсов. Вот некоторые распространенные причины:  взаимная блокировка. . Необходимые технологии Ключевые слова в этом определении — «в итоге». мы познакомим вас еще с несколькими важными параметрами.128 Часть 2. так как она не затрагивается ничем из вышеперечисленного. Временный отказ может быть обусловлен несколь� кими базовыми проблемами. как много работы может выполнить система. которая была бы спроектирована специально для несложного переиспользования. Производительность Количественную оценку производительности системы можно выполнить не� сколькими способами. не рассчитанный на переиспользование. на базе которого строится код приложения. хотя и не иде� ально) и устойчивым отказом.  недостаточным количеством процессорного времени для запуска потока. и подобное противоречие можно считать основной причиной. а основной смысл жизнеспособности — обеспечить прогресс. java.  временным отказом ресурса. Один из способов — применение многоразового инструмента� рия (например.3.util. Возможность многократного использования Возможность многократного использования (переиспользуемость) — это четвертая структурная сила. возможно. в частности:  блокировкой или ожиданием получения блокировки. Система параллельной обработки. по которой так сложно проектировать каче� ственные системы для параллельной обработки. Есть разница между времен� ной невозможностью прогрессирования (что само по себе не так плохо. что может приводить к проблемам с безопасностью.  необратимая проблема с ресурсами (например.1.  Переиспользуемые системы часто предоставляют свои внутренние ресурсы.  Стандарты безопасности могут противоречить жизнеспособности. Хотя.  ожиданием ввода (например. потеря связи с NFS). при сетевом вводе-выводе). Кроме того. Ниже в этой главе мы поговорим о блокировках.  потерянный сигнал. 4. В главе 6 мы поговорим об анализе производительности и о методах ее настройки. иногда очень желательна. Как и в каких случаях возникает конфликт Часто структурные силы противодействуют друг другу. Пока будем считать. Устойчивый отказ также может возникать по многим причинам. Основная цель безопасности — не допустить патологических явлений. хотя ее и не так просто реализовать. вы уже знакомы со многими из них. а также о некоторых других проблемах.concurrent). что производительность — это мера того.

облегчающие нашу работу в этом направлении.  количество потоков. 4. которого вы в конечном итоге должны попытаться достичь. Например. чтобы быть безопасным. и при этом оставался довольно живым и высокопроизводительным. неизбежно вы� зывающих существенные издержки:  блокировки и мониторы. .  Формулировать рекомендации по разработке. недетерминированном режиме. Это наиболее слабая из альтернатив.  конструкция алгоритмов. разработанное с гру� быми нарушениями правил.  Максимально ограничивать внешнюю коммуникацию каждой из подсистем. что производители пользовательских приложений действитель� но готовы с вами сотрудничать. Баланс. Современная параллельная обработка 129  Нерационально написанная система безопасности обычно не отличается высо� кой производительностью. чтобы обеспечить высокую безопасность. Сокрытие данных — мощный инструмент для обеспечения безопасности. Это достаточно сильный метод. которым необходимо следовать при создании клиентских приложений. Разработчик должен знать о возможности применения всех этих механизмов обеспечения безопасности и использовать самую сильную из потенциально воз� можных техник.1.  диспетчеризация. что все пункты этого перечня были учтены. чтобы код был достаточно гибок и полезен для решения разнообразных проблем. При разработке параллельного кода необходимо гарантировать.  Документировать требуемые поведения.  количество контекстных переключений.  Делать внутреннюю структуру каждой из подсистем максимально детермини� рованной. При этом необходимо учитывать. но. что код готов. Могут возникнуть большие проблемы с отлад� кой. но иногда прибегать к ней необходимо. Соответствовать сразу всем этим требованиям непро� сто.  локализация памяти. нужно статически проектировать в каждой подсистеме знания о потоках и объектах.Глава 4. есть кое-какие практичные методы. требует. Только потом можно считать. Вот наиболее распространенные из методов. Источники издержек В системе параллельной обработки есть множество особенностей.4. если придется отлаживать сломавшееся приложение. так как активно задействует блокировки. если код приходится развертывать в очень общем контексте. но он предполагает. даже если взаимодействия в этой подсистеме будут протекать в параллельном. Запомните этот список как основной контрольный перечень. что в отдельных случаях могут быть приемлемы лишь самые слабые механизмы. достаточно закрыт. которые мы по� старались расположить в порядке полезности. к счастью.

посвященной производительности). а потом передавать элемент следующему пулу потоков. так и параллельных алгоритмов. применим изученную теорию на практике и спроектируем образец приложения. которая относится к конкретной функциональной области. Построение и анализ» Томаса Кормена (Thomas H. то станете высококлассным программистом независимо от того. Вообще. при качественной организации приложения каждый пул потоков должен концентри� роваться на такой обработке. В таком случае каждая фаза будет представлена в виде пула потоков. Рассмотрим в обобщенном виде. Пример приложения продемонстрирован на рис. Приложе� ние будет брать элементы по очереди.1. второе издание. Простой и стандартный способ создания такого приложения — обеспечить соответствие различных элементов приложения различным этапам интересующего нас процес� са. относящиеся к числу лучших работ по этой теме. 4. Пример обработчика транзакций Завершая этот теоретический раздел. Пример многопоточного приложения . с каким языком работаете. Необходимые технологии КОНСТРУКЦИЯ АЛГОРИТМОВ Именно в этой области разработчики могут по-настоящему отличаться.5. по которым можно научиться построению как однопоточных. Многие из этих источников издержек будут упоминаться и далее в этой главе (а также в главе 6. Рис. 4. Две книги.util. как это можно сделать с помощью классов из биб� лиотеки java. Если вы научитесь дизайну алгоритмов. выполняющего параллельную обработку.130 Часть 2. Представьте себе несложную систему для обработки транзакций. Руководство по разработке» Стивена Скиены (Steven Skiena). выполнять над каждым из них определенные манипуляции.1. Corman) и «Алгоритмы. Это отличные книги.concurrent. 4. — «Алгоритмы.1.

позволяющие распределять работу между пулами. что сегодня находятся в java. Начнем с обзора синхронизации.2. д. здесь есть парал� лельные структуры данных (для создания разделяемых кэшей и других практиче� ских нужд). перейдем к изучению следующей темы — «классического» параллелизма Java. а как решались подобные проблемы до появления Java 5. В пакете содержатся пулы потоков. опираясь на приведенный пример. возникающие при параллелизме. Отдельно остановимся на том. а потом обсудим распро� страненные техники (и подводные камни) параллельного кода — полностью син� хронизированные объекты. Для этого мы обсудим первоначальный. плохо протестированные образцы). очень удобные для реализации с помощью классов. о чем пойдет речь в следующих разделах. то разработчикам приложений пришлось бы заново изобретать многие из таких компонентов самостоятельно (возможно. Один элемент может заниматься обработкой фазы «Проверка кредито� способности».concurrent не было. Затем мы кратко рассмотрим жизненный цикл потока. представля­ ющих альтернативу блочно-синхронизационному методу обеспечения паралле� лизма. что хорошего и что плохого в традиционном подходе к параллельной обработке. входящих в состав java. когда у нас еще не было этих классов?» Зачастую группы при� ложений выпускались с собственными наборами библиотек для параллельного программирования — и получались составные компоненты. volatile и т.util. поскольку можно одновременно задействовать несколько элементов. а также многие другие полезные низкоуровневые инструменты. При подобном проектировании получаются приложения. предназначенные для исполнения (а в классе Executors есть хороший набор фабричных методов для создания таких пулов) и очереди. Итак. Но. Кроме того. ключевое слово volatile и не� изменяемость. важно четко понимать. напоминающие по назначению те. В зависимости от тонкостей приложения в фазе «Проверка состояния сче� та» могут даже одновременно обрабатываться различные команды. . а также тонкие (и не слишком тонкие) ошибки.util. то можете увеличить их про­ пускную способность.Глава 4.util. при котором применялись ключевые слова Java для параллельной обработки — synchronized. получались бы непричесанные. Мы поговорим об этом в контексте структурных сил и с учетом того. взаимные блокировки. почему может быть сложно программировать с применением такого подхода. Но может возникнуть вопрос: «Хорошо. Если бы java. чтобы такое обсуждение альтернатив получилось максимально полез� ным. довольно низкоуровневый подход к многопоточному программированию.concurrent.concurrent. Современная параллельная обработка 131 Если вы проектируете приложения именно так. а другому элементу поручим обработку фазы «Проверка состояния счета». Параллельная обработка с блочной структурой (до Java 5) Значительная часть этой главы посвящена обсуждению подходов. 4. Но многие такие спе� циальные компоненты могли вызывать проблемы при проектировании.

беседуя о синхронизации. В теории параллелизма такая конструкция именуется критической секцией. не блокируются. Лишь один поток может одновременно проходить через все синхронизирован� ные блоки или методы объекта..  Синхронизация во внутреннем классе не зависит от внешнего класса (чтобы понять. ПРИМЕЧАНИЕ А вы задумывались. Несинхронизированные методы могут работать параллельно с синхронизированными методами.. стоит ли делать это явно либо путем использования getClass(). относящейся к экземпляру объекта (либо блокировки. почему для работы с критической секцией в Java используется ключевое слово synchronized? Почему не critical или locked? Что значит быть синхронизированным? Мы вернемся к этой теме в разделе 4. либо к методу. касающихся синхронизации и блокировок в Java.  Если вы хотите заблокировать объект класса.5. хорошо взвесьте. пытается ли сторонний поток вмешаться в тот же либо в иной синхронизированный блок конкретного объекта. Таким обра� зом. Это правило соблюдается независимо от того. }. На самом деле в этой главе мы говорим о сравнительно новых технологиях параллельной обработки. Надеемся. поскольку на уровне подклассов два этих подхода отразятся по-разному. почему так происходит.2.2. Но. ключевое слово synchronized может применяться либо к блоку.  Несинхронизированные методы игнорируют и не проверяют состояния какихлибо блокировок. Необходимые технологии 4. блокировка какого объекта должна быть получена.  Синхронизированный метод можно считать аналогом блока synchronized (this) { . поскольку отсутствует какойлибо экземпляр объекта для блокировки. то можете пару минут подумать. а потом читать дальше. — для методов static synchronized).  При блокировании массива объектов отдельные объекты. вспомните. . оно не может появляться в интерфейсе при объявлении метода. Синхронизация и блокировки Как вы уже знаете. охватывающего целый код (правда. Для метода это означает получение блокировки. Применительно к блоку про� граммист обязан указать. Если в это пытаются вмешаться другие потоки. входящие в его состав. в байт-коде синхронизированный метод и такой блок представляются по-разному). относящейся к объекту Class. то они приостанавливаются виртуальной машиной Java. остановимся на некоторых базовых фактах.  Блокировать можно только объекты — но не примитивы.  Ключевое слово synchronized не входит в состав сигнатуры метода.132 Часть 2. как реализуются внутренние классы). но если вы не знаете ответа на вопрос либо никогда об этом не задумывались. что перед входом в блок или метод поток должен получить соответствующую блокировку.  Метод static synchronized блокирует объект Class.1. Оно указывает. что вы помните все следующие факты (или большинство из них).

4. После этого планиров� щик находит ядро. Модель состояния для потока На рис. синхронизированный метод вызывает другой синхро� низированный метод в этом же классе).Глава 4. встречает точку синхронизации для этой же блокировки (например. не допускающие повторного вхождения (называемые также нереентерабельными) существуют в других языках. то ему разрешается продолжить работу. наконец.2. Если машина сильно загружена. а когда доступное . с такими схемами обычно неудобно работать и их лучше избегать. возможной приостановке. если вы только не знаете абсолютно точно. Теперь поговорим о со� стояниях. 4. Затем поток обычно переходит к потреблению выделенного ему времени.util. что делаете. Современная параллельная обработка 133  Блокировки Java допускают многократное вхождение. Правда. блокированию на ресурсе или возобновлению рабо� ты и.locks. то перед началом работы возможен небольшой период ожидания. Рис. На этом обзор синхронизации в Java можно закончить.concurrent. 4. в котором можно запустить данный поток. почитайте в Javadoc раздел о ReentrantLock в java.2. ВНИМАНИЕ Схемы блокировки. что если поток. к завершению. Если вас интересуют подробности.2 показано. Модель состояний потока Java Изначально поток создается в состоянии Ready (Готов). удерживающий блокировку.2. Это означает. и такие схемы можно синтезировать в Java. как протекает жизненный цикл потока — от создания к запус� ку. в которых поток оказывается на протяжении своего жизненного цикла.

что на момент вызова метода состояние было согласованным). что пока не может начать работу на данном ядре. «Разбуженный» поток вновь переходит в состояние Ready. Для по� вторного запуска он должен быть «разбужен» (после того как «проспит» заданное количество времени либо получит соответствующий сигнал).134 Часть 2. Если соблюдаются все приведенные ниже правила.  все методы синхронизированы. В таком случае поток не выгружается из ядра. но остается занятым. о которой мы говорили в подраз� деле 4. врезку «О долговременной безопасности»). он перейдет в состояние Ready. то класс опре� деленно является потокобезопасным и «живым».  экземпляры объектов гарантированно являются согласованными после возвра� та из любого незакрытого (non-private) метода (при условии. . удовлетворяющий следующим условиям:  все поля всегда инициализируются в согласованное состояние в каждом кон� структоре. чтобы дождаться поступления новых временных квантов от процессора. пока удерживаемую другим потоком. Как только это произойдет. Обратимся к более подробному описанию этой стратегии. но и указать. Именно так организует� ся принудительная диспетчеризация потоков. дожидаясь. 4. пока не будет израсходован выделенный ему квант времени. Речь пойдет о полностью синхронизи� рованных объектах. Ее центральный феномен — полностью синхронизиро� ванные объекты.2.3. Это может быть связа� но с тем.1. Теперь поговорим об одном широко известном способе решения проблем. пока данные или блокировка станут доступны.  все методы доказуемо завершаются в ограниченное время. Поток может быть заблокирован. В такой ситуации поток удаляется из ядра и высвобождает все свои блокировки. ко� торые могут возникать при синхронизации.  при нахождении в несогласованном состоянии не выполняются вызовы к мето� дам другого экземпляра. Необходимые технологии время будет израсходовано. выполнение потока продол� жится до тех пор. что код программы приказывает данному потоку приостановить работу перед продолжением (посредством Thread. полностью синхронизированный класс — это класс.  отсутствуют общедоступные поля. Итак. что выполнено какое-то внешнее условие). если он ожидает операции ввода-вывода или должен получить блокировку. Поток может не только приступить к действию по команде планировщика.sleep()) или дожидается определенного уведомления (обычно о том.1. Полностью синхронизированные объекты Выше в этой главе мы затрагивали концепцию безопасности типов при параллель� ной обработке и упоминали об одной из стратегий обеспечения такой безопасности (см.

и put) в контейнере arrivalTime. В конечном итоге именно такая блокировка вас и замедляет. чтобы координировать все операции доступа (и get. Проблемы возникают только с производительностью — ведь безопасный и «живой» класс совсем не обязательно должен быть очень быст� рым.Глава 4. Кроме того. Современная параллельная обработка 135  при нахождении в несогласованном состоянии не выполняются вызовы к какомулибо незакрытому методу. Приходится использовать synchronized. Поэтому синхронизация применяется именно для того. получал ли он конкретное уведомление. просто фантастика — класс получился одновременно безопасным и «живым». Данная ситуа� ция демонстрирует классический конфликт между операциями чтения и записи. . у это� го класса можно запросить. чтобы избежать несогла� сованности.1. В листинге 4. Это — центральная проблема при организации парал� лелизма таким способом.1 показан пример такого класса как элемента внутреннего интер� фейса воображаемого распределенного сервиса микроблогов. Листинг 4. Класс ExampleTimingNode будет получать обновления с помощью метода propagateUpdate(). Полностью синхронизированный класс На первый взгляд.

В этой версии не только записывается время каждого обновления. Как видите. использующие такой подход. весь доступ ограничивается операциями get и put). Пример специально составлен для демонстрации взаимного блокирова� ния — не используйте его в качестве основы для реального кода. 4. но и каждый узел. Листинг 4. но и характеризуется достаточной хрупкостью.2. получающий обновление.4. Взаимные блокировки Следующая классическая проблема параллелизма (над которой бьется не только Java) — это взаимная блокировка (deadlock). вы не затрагиваете arrivalTime вне метода synchronized (действительно.2. инфор� мирует об этом другой узел.2. Рассмотрим листинг 4. что у нас здесь совсем немного кода. Это еще одна причина. по которой сообщество Java занялось поисками более надежных подходов. но это возможно лишь потому. В реальных крупных системах это будет невозможно именно из-за большого объема работающего кода. в котором мы немного расширили предыдущий пример. Ошибки очень легко просачиваются в огромные базы кода. Пример взаимных блокировок . Необходимые технологии ХРУПКОСТЬ КОДА Код из листинга 4.136 Часть 2.1 не только имеет проблемы с производительностью. Это упрощенная попытка построить мнгогопоточную систему обработки об� новлений.

 4. который может подхватить его работу. Потоки. что каждый поток требует от второго освободить удерживаемую блокировку.Глава 4.3. отправ� ляемые к отдельным потокам.3. A. то взаимных блокировок можно было бы . предпо� лагает всегда получать блокировки для каждого потока в одном и том же порядке. В предыдущем примере первый поток. код вполне нормальный. позволяющий не попадать во взаимные блокировки. Рис. 4. возникнет взаимная блокиров� ка — оба потока доложат о получении сообщения. оказавшиеся во взаимной блокировке Один из методов. Каждое обновление должно подтверждаться в ре� зервном потоке. Причина в том. а второй поток — в порядке B. Современная параллельная обработка 137 На первый взгляд. Ситуация проиллюстрирована на рис. прежде чем метод подтверждения сможет сработать. то. B. Если вы запустите этот код. то у нас предусмотрен второй поток. получает блокировки в порядке A. У вас есть два обновления. B. скорее всего. Если бы оба потока «настаивали» на получении блокировок в порядке A. для которого он является резервным потоком. Такая структура не кажется безумной — если какой-то поток не сработает. но ни один не получит обновле� ния. чтобы начать работу.

произошедших в области параллельного программирования в последние годы. Большие группы потоков могут работать на разных ядрах в один и тот же физический момент времени (потенциально они могут одновременно оперировать разделяемыми данными). взаимной бло� кировки удается избежать за счет того. Необходимые технологии избежать. почему слово synchronized было выбра� но для обозначения заблокированной секции или метода. пока обработка сообщения еще продолжается. то есть при совершении такого вы� зова состояние является несогласованным. если первый завершит работу и высвободит все свои блокировки. так как второй поток вообще не сработает. 4. 4. По прибытии сообщения получающий узел вызывает другой объект. что код нарушает правило согласованности состояния. в крайнем случае.5. Говоря в терминах подхода с полной синхронизацией объектов. предста� вим себе такую картину и поговорим о том. Итак. Ради обеспечения эффективности каждый из одновременно работающих потоков может иметь соб� ственную копию кэшированных данных. относится к производству оборудования. Теперь вернемся к загадке. встретить систему с двумя ядрами.4.4. сформулированной ранее: почему для критической секции в Java используется ключевое слово synchronized.138 Часть 2.1? Ответ таков: синхронизируется представление памяти в различных потоках бло- . в основе которой лежит разделение процессорного времени. Старый и новый способ представления параллелизма и потоков Выше мы задавали вопрос: а что же синхронизируется в коде в листинге 4. Поэтому па� раллельное программирование вполне логично понималось как работа.2. Сегодня практически все устройства крупнее мобильного телефона имеют по нескольку ядер. Потоки загружались и выгру� жались в единственном ядре. Рис. Эта ситуация показана на рис. Почему synchronized? Одно из самых значительных изменений. которыми он и оперирует. Еще не так давно практикующий программист мог годами работать на одноядерных системах или. Так мы подойдем к теме неизменяемости и обсудим ключевое слово volatile. поэтому изменяется и представление о многоядерном программи� ровании. 4.

Современная параллельная обработка 139 кируемого объекта. что переменная volatile не вызывает никаких блокировок. касающиеся заблокированного объекта. как завершится выполнение инструкции. чем блокировка будет снята. Обратите также внимание на то. перед использованием всегда повторно счи� тывается из основной памяти. в том числе при работе с примитивами. Оно применяется как простой инструмент для синхрони� зации полей объектов. Это означает. .5. всегда нужно применять блокиров� ку для обеспечения полной безопасности. всегда пробрасывается в основную память до того. считываются из основ� ной памяти.0). когда важно учитывать актуальное состояние. что при обеспечении истинной безопасности потоков перемен� ная volatile должна использоваться только для моделирования такой переменной. запись в которую не зависит от текущего (считываемого) состояния.5. сбра� сываются в основную память прежде. Ключевое слово volatile Ключевое слово volatile существует в Java с первых дней существования языка (появилось в Java 1. Изменения объекта проникают между потоками через основную память Кроме того.  любое значение. 4. 4.6.2. видимое потоком. Несколько менее очевидное следствие использования переменных volatile заключается в том. Благодаря подобной конструкции программист может писать упрощенный код. но за счет дополнительных перебросок данных при каждом доступе. что после завершения работы блока (или метода) synchronized абсолютно все изменения. Таким образом. 4. записываемое потоком. Можно представить. Рис. Поэтому при ее использовании вы гарантированно не попадете во взаимную бло� кировку. — как показано на рис. как начнется исполнение кода в заблокированной секции. поток с блокировкой синхронизируется с представле� нием объекта в основной памяти еще до того. при входе в блок synchronized и последующем получении блокиров� ки все изменения. сделанные в блокированном объекте.Глава 4. Изменчивое (volatile) поле регулируется следующими правилами:  любое значение. что такая операция обернута в крошечный блок synchronized. В тех случаях.

Работа с ним может сводиться к вызову статического (static) класса вместо конструктора для создания новых объектов. Статический внутренний класс выступает как построитель неизменяемого класса и предоставляет разработчику единственную возможность получать контроль над новыми экземплярами неизменяемого типа. Необходимые технологии 4. Таким образом. следовательно. В качестве альтернативы многие программисты пользуются методом FactoryMethod. Это объекты.3 показано. особенно если требуется аккумулировать состояние из не� скольких источников. либо содержащие только поля final (которые. Это не всегда бывает удобно. Они всегда остаются безопасными и «живыми». как такая конструкция может использоваться для моделирования обновлений в микроблогах (опять же мы опираемся на предыдущие листинги из этой главы). либо вообще не имеющие состояния. так как их состояние не может быть изменено. Листинг 4. должны передаваться в конструктор. реализующего обобщенный интерфейс строителя. требуемые для инициа� лизации конкретного объекта.3. ин� станцирование может быть выполнено лишь с помощью статических методов FactoryMethod. позволяющая исполь� зовать неизменяемые объекты. Он представляет собой комбинацию двух конструкций: статического внутреннего класса. и закрытого конструк� тора для неизменяемого класса как такового. но будет разрешено изменение полей. прежде чем создать новый неизменяемый объект. Одна из проблем заключается в том. Соответственно. что все значения. Это может приводить к неудобным вызовам конструктора с применением многочисленных параметров. как и у неизменяемого класса. Для решения этой проблемы можно использовать паттерн Builder («Строитель»). что у этого класса будут точно такие же поля.140 Часть 2. Неизменяемость В данном контексте очень ценной представляется техника.7. связанная с потенциальной необ� ходимостью передачи многочисленных параметров методу FactoryMethod.2. Один из распространенных вари� антов реализации класса Builder предполагает. они никогда не мо� гут оказаться в несогласованном состоянии. В листинге 4. Однако по-прежнему сохраняется проблема. Конструкторы обычно дела� ются защищенными или закрытыми (protected или private). Неизменяемые объекты и строители . должны заполнять� ся в конструкторах объектов).

6. На са� мом деле мы уже пользовались свойствами неизменяемых объектов в листин� гах 4.Builder ub = new Update. — ключевое слово final применяется только к тому объекту.author(myAuthor). на который оно указывает.1 и 4. Как показано на рис. Но внутри объекта ссылка на объект 1 может быть переброшена так. ссылка на главный объект не может быть при� своена для указания на объект 3. Современная параллельная обработка 141 Имея этот код. 4. Рис.updateText("Hello"). 4. которое хотелось бы сделать о неизменяемых объек� тах. Последнее замечание.Глава 4. Этот паттерн очень распространен не только в теории. Update u = ub.Builder(). Неизменяемость значения по сравнению со ссылкой .build(). чтобы она стала указывать на объект 2. вы затем можете создать новый объект Update следующим об� разом: Update. Иными словами — ссыл� ка final может указывать на объект.2. имеющий нефинальные поля. но и на практике.6.

util. По нашему опыту. по-прежнему работают без изменений. Иногда бывает невозможно добиться эффективной разработки с применением одних лишь неизменяемых объектов. иногда бывает необхо� димо работать именно с изменяемыми объектами. названия ко� торых начинаются с Atomic. ее всегда следует задействовать. содержащий атомарные методы (дей� . в котором содержится богатый но� вый инструментарий для работы с многопоточным кодом. появи­ вшиеся в Java 5. как и volatile. Итак. если это осуществимо. но классы и пакеты. Мы поможем вам начать работать с классами и покажем примеры их использования. Мы сделаем экспресс-тур по некоторым основным классам java. чем мы можем рассказать в этой книге. нужно прочитать гораздо больше.util. Считайте это обсуждение вводным курсом по работе с конкурентным кодом.util.concurrent и связанным с ними пакетам. как можно приступить к их использованию в вашем собственном коде.util.atomic В пакете java.142 Часть 2. Необходимые технологии Неизменяемость — очень мощная техника. Но они обернуты в класс-API. они обеспечивают такую же семантику. поскольку каждое изменение состояния объекта требует создания нового объекта. Далее мы перейдем к одной из важнейших тем данной главы — поговорим о со� временных и вместе с тем простых API для обеспечения параллелизма.util.3. Чтобы максимально эффектив� но использовать java. Составные элементы современных параллельных приложений После выхода Java 5 в этом языке появился новый способ понимания параллелиз� ма. а это практически всегда стоит усилий. а не полномасштабным тематическим семинаром. который был написан с применением старых подходов (до Java 5). 4. если сознательно работать над его портированием на новые API.concurrent. код улучшается. Они содер� жатся в библиотеке java. Атомарные классы — java.concurrent.concurrent. В сущности. Этот инструментарий был усовершенствован в более новых версиях Java.concurrent.concurrent. МИГРАЦИЯ КОДА Если у вас есть работающий многопоточный код. Все началось с пакета java.util.concurrent. 4. то нужно выполнить его рефакторинг с помощью java. В частности. затрачиваемых на обеспечение миграции. вам сле� дует прочитать соответствующие разделы Javadoc.3. Повысится ясность и надежность кода.util. чтобы получить целостное впечатление обо всех пакетах. Кроме того. Рассмотрим.1. — с ними программирование классов для параллель� ной обработки значительно упрощается. Они до сих пор очень цен� ны для практикующего разработчика. поговорим о пакете для работы с ато� марными объектами и пакете для работы с блокировками.atomic содержится несколько классов.

2. } ПРЕДОСТЕРЕЖЕНИЕ Атомарные классы не наследуют от классов. Чтобы представлять номер последовательности. Такие процессоры и операционные системы используются на большинстве современных компьютеров. либо поток блокируется — третьего не дано. для чтения и для записи).getAndIncrement(). Такой подход имеет несколько недостатков:  существует всего один тип блокировки. . который есть у AtomicInteger или AtomicLong. который будет возвращать гарантированно уникальный (причем обяза� тельно возрастающий) номер при каждом вызове. осуществляемым над заблокированным объектом. Далее поговорим о том. public long nextId() { return sequenceNumber. Примерно такая же концепция последовательных номеров присутствует и в базах данных (отсюда и название переменной).  не ограничивать блокировки в рамках блоков (скажем. чтобы максимально эффективно использовать ха� рактеристики современных процессоров. как в java. Современная параллельная обработка 143 ствующие по принципу «все или ничего») для подходящих операций.concurrent.  либо приобретается блокировка. Для это� го предназначен атомарный метод getAndIncrement(). который реализует номер последова� тельности: private final AtomicLong sequenceNumber = new AtomicLong(0).  блокировка приобретается в самом начале синхронизированного блока или метода.concurrent моделируется основной ком� понент системы синхронизации — интерфейс Lock.util. простая концеп� ция блокировки. в сущности. 4.3. Блокировки — java.Глава 4. Так. класс должен иметь метод nextId().  блокировка одинаково применяется ко всем синхронизированным операциям. а AtomicInteger — вместо Integer (хотя AtomicInteger дополняет класс Number). Для разра� ботчика это может быть очень простой способ избежать условий гонки при опера� циях над совместно применяемыми данными. если необходимая поддержка доступна на уровне оборудования и операционной системы. Реализации написаны так. то можно попробовать изменить к лучшему несколько следующих моментов:  добавить различные типы блокировок (например.util. имеющих схожие названия. Если вы собираетесь переработать поддержку блокировок. Рассмотрим небольшой фрагмент кода. а подобные классы обычно применяются для реализации последовательных номеров. AtomicBoolean нельзя использовать вместо Boolean. обеспечить возможность блокировки в одном методе и разблокировки — в другом).locks В основе блочного подхода к синхронизации лежит. Поэтому операции могут быть неблоки� рующими (свободными от блокировок).

нужно позволить ему восстановить предыдущее со� стояние либо приняться за выполнение какой-то другой задачи (с помощью метода tryLock()). применяемой с синхронизированными блоками Java.concurrent. скорее всего. что блокировку уже имеет другой поток). . Такая блокировка немно� го гибче обычной.  ReentrantReadWriteLock — эта блокировка обеспечивает повышение производи� тельности в тех случаях.  ReentrantLock — в сущности. поскольку он уже заблокирован (см. Необходимые технологии  если поток не может получить блокировку (например.144 Часть 2.  позволить потоку попытаться получить блокировку и прекратить такие попыт� ки по истечении определенного периода времени.4. Листинг 4. 4. Вариант примера с взаимной блокировкой и использованием ReentrantLock Попытка заблокировать другой поток . Он предлагается вместе с парой реализаций. теперь в нем используется вариант ReentrantLock. это эквивалент уже известной нам блокировки. Так и возникает взаимная блокировка.3). рис. В лис� тинге 4. Интерфейс Lock может использоваться для полного копирования любого функ� ционала.util. не удастся.locks. когда код содержит много считывающих и сравнитель� но мало записывающих элементов. который доступен при блочно-структурированном параллелизме.4 приведен переработанный пример с взаимной блокировкой. Основной компонент для воплощения всех этих возможностей — интерфейс Lock из пакета java. потому.

чем применение блочно-структурированного подхода. при возврате этого объекта от метода). Такая конструкция очень удобна. Но в действительности это происходит лишь иногда. Листинг 4. Далеко не всегда вы увидите текст received confirm of update (получено подтвержде� ние об обновлении).5. позволяющих справляться с взаимными бло� кировками. что мы рассматривали при изучении блочно-структурированного параллелизма. Но все же использовать такие объекты для разработки надежной стратегии блокировок бывает довольно затруднительно. Существует несколько стратегий. то такой вариант метода использовать не получится.5 (и представьте себе. — хорошее дополнение к вашему инструментарию. дав другим по� токам возможность получить блокировку. . Работа с объектами Lock может быть гораздо более многообещающей. что вза� имная блокировка снимается. если вам требуется пересылать объекты Lock (например. напоминающую ту. finally. Некорректный вариант кода для устранения взаимной блокировки Если запустить код из листинга 4. Ниже мы попытаемся устранить взаимную блокировку. которая обычно не работает. то может создаться впечатление. Но есть среди них и такая. — и об этом необходимо знать. Рассмотрим вариант метода propagateUpdate(). С другой стороны. Современная параллельная обработка 145 ИСПОЛЬЗОВАНИЕ TRY … FINALLY С БЛОКИРОВКАМИ Вариант метода lock() с блоком try .5. что такие же изменения внесены в код confirmUpdate()). В данном примере мы поменяли безусловную блокировку на метод tryLock() с за� держкой. показанный в лис� тинге 4.Глава 4... где и снимается блокировка. если воспроизвести ситуацию.

как показано в листинге 4. чтобы гарантировать следующее: если попытка получить вторую блокировку не увенчается успехом. необходимый для продолжения работы. В таком случае у остальных потоков будет возможность получить полный набор блокировок. Необходимые технологии На самом деле взаимная блокировка не снимается. Устранение взаимной блокировки . как любой из них вызовет confirmUpdate(). Если обоим потокам удастся получить первую блокировку до того.6. Ведь если исходная блоки� ровка получена (в методе propagateUpdate()). Настоящее решение заключается в том. Листинг 4. то эти потоки окажутся во взаимной блокировке.146 Часть 2. то поток должен высвободить удерживаемую им блокировку и немного переждать. то поток вызывает confirmUpdate() и так и не освобождает первую блокировку до завершения.6.

Вы убедитесь. напоминающие блокировки. Этот простой механизм обес� печивает легкое развертывание паттерна минимальных приготовлений. неверную попыт� ку откорректировать оригинал и второй вариант коррекции. содержащими взаимные блокировки. Упражняясь с кодом. Мы лишь слегка коснулись возможностей Lock — есть и множество других спо� собов создавать более сложные структуры. которая поможет справляться с проблемой взаимных блокировок. как сможет начать работу второй поток. CountDownLatch CountDownLatch — это простой паттерн синхронизации. в принципе. Это означает. ПОЧЕМУ СОЗДАЕТСЯ ВПЕЧАТЛЕНИЕ. Запустите этот код несколько раз. Можете поэкс� периментировать с какими-нибудь другими приведенными выше вариантами кода. Для этого предоставляется целочисленное значение int (счет) при создании нового экземпляра CountDownLatch. позволяющий множествен� ным потокам регламентировать между собой минимальные приготовления. Поток ненадолго приоста� новит работу. в некорректном варианте кода (см. что потенциально должно позволить другому потоку получить бло� кировку. вы можете лучше понять.3. что происходит с блокировками. Почему же это решение иногда срабатывает? Дело в том. чем любой из них сможет преодолеть барьер синхронизации. 4. что один из них (обычно первый поток) оказывается способен перейти в confirmUpdate() и получить вторую блокировку до того. ЧТО ПОКАЗАННЫЙ ВЫШЕ НЕКОРРЕКТНЫЙ ВАРИАНТ ИНОГДА РАБОТАЕТ? Как мы убедились. что код дополнительно усложнен. Об одной из подобных концепций — защелке — мы и поговорим далее.5) взаимная блокировка никуда не девается. листинг 4. что оба потока. Такая ситуация возможна и в исходном варианте кода. кото� рые должны быть выполнены прежде. но там она гораздо менее вероятна. всегда срабатывают — от взаимной блокировки удалось избавиться. . а второй заставляет вызывающий поток выжидать. Современная параллельная обработка 147 В данной версии вы проверяете наличие кода возврата от tryConfirmUpdate().Глава 4. Он влияет на работу планировщика потоков виртуальной машины Java. если счет уже достиг 0 или менее).3. При получении false исходная блокировка будет снята. Первый уменьшает счет на 1. что иногда диспетчеризация потоков будет складываться так. Затем для управления защелкой (latch) приме� няется два метода: countDown() и await(). пока счет не будет равен 0 (второй метод ничего не делает. делая его менее предсказуемым. а также выработать ин� туицию. Мы имеем в виду оригинал.

хотя мы и опустили эту часть кода). работающих внутри единого процесса. Листинг 4. Использование защелок для упрощения инициализации В этом коде мы задаем защелку с «кворумным значением». как только за� вершит инициализацию. равное этому значению. При этом предполагается. была ли хотя бы половина из этих потоков правильно инициализиро� вана. «пытает� ся узнать». и можно начинать работу (в том числе от� правлять уведомления. Необходимые технологии В листинге 4. что на инициализацию потока уходит определенное время. Как только инициа� лизируется количество потоков. можно приступать к об� работке. Лишь если такая проверка закончится успешно. Каждый поток будет вызывать срабатывание countDown().148 Часть 2. пока уровень кворума будет достигнут. система начинает посылать уведомления этим потокам.7 группа потоков. .7. Поэтому основному потоку остается только дождаться.

пре� доставляемый в классе Collections. В новом варианте контейнера оптимизирован функционал synchronizedMap().4. содержащий некоторые новые методы. Как показано на рис. Теперь методы из этого класса возвращают коллекции. которыми не любит заниматься большинство разработчиков. Java позволяет решать такие задачи.7. ConcurrentHashMap Класс ConcurrentHashMap предоставляет параллельную версию стандартного HashMap. 4. Классический вид HashMap СОВЕТ Хорошо написанная реализация параллельного HashMap на самом деле не будет блокироваться при считывании. в который будут вноситься изменения. Рис. а значение соответствует текущему состоянию. Современная параллельная обработка 149 Далее мы поговорим об одном из самых полезных классов. В принципе. 4. в каком сегменте памяти будет сохраняться пара ключ/ значение.util.Глава 4. При записи будет блокироваться только соответствующий сегмент.7. если такой ключ там пока отсутствует. Класс ConcurrentHashMap также реализует интерфейс ConcurrentMap. 4.  replace() — API предоставляет различные формы этого метода для внесения атомарных изменений в HashMap. .  remove() — атомарно удаляет пару ключ/значение.concurrent. имеющихся в арсе� нале разработчика многопоточных приложений: ConcurrentHashMap из библиотеки java. обладающие избыточным количеством блокировок. Именно отсюда берется «хешевая» часть имени класса. чтобы определить. но есть еще низкоуровневые детали. Напрашивается достаточно прямолинейное обобщение многопоточности: при внесении изменений требуется не блокировать всю структуру. лишь если ключ присутству� ет.3. обеспечивающие по-настоящему атомарную функцио� нальность:  putIfAbsent() — добавляет пару ключ/значение в HashMap. классический HashMap использует функцию (хеш-функ� цию). а заблокировать лишь тот участок памя� ти.

150

Часть 2. Необходимые технологии

В качестве примера попробуем заменить методы synchronized в листинге 4.1
обычным, несинхронизированным доступом в тех случаях, когда вы изменяете
HashMap под названием arrivalTime так, чтобы этот контейнер одновременно являл�
ся ConcurrentHashMap . Обратите внимание на отсутствие блокировок в листин�
ге 4.8 — явная синхронизация здесь вообще не наблюдается.
Листинг 4.8. Использование ConcurrentHashMap
public class ExampleMicroBlogTimingNode implements SimpleMicroBlogNode {
...
private final Map<Update, Long> arrivalTime =
new ConcurrentHashMap <>();
...
public void propagateUpdate(Update upd_) {
arrivalTime.putIfAbsent(upd_, System.currentTimeMillis());
}
public boolean confirmUpdateReceived(Update upd_) {
return arrivalTime.get(upd_) != null;
}
}
ConcurrentHashMap — один из наиболее полезных классов в библиотеке java.util.
concurrent. Он обеспечивает дополнительную безопасность при многопоточности

и более высокую производительность, а при обычном использовании не имеет
серьезных недостатков. Его аналог для работы с List называется CopyOnWriteArrayList,
о нем мы поговорим далее.

4.3.5. CopyOnWriteArrayList
Как понятно из названия, класс CopyOnWriteArrayList заменяет стандартный класс
ArrayList. CopyOnWriteArrayList был создан потокобезопасным путем с добавлением
семантики копирования при записи. Это значит, что любые операции, изменяющие
список, создают новую копию массива, копируя список (как показано на рис. 4.8).
Это также означает, что любые сформированные итераторы могут «не беспокоить�
ся» о любых модификациях, которых они «не ожидали».

Рис. 4.8. Массив Copy-on-write

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

Глава 4. Современная параллельная обработка

151

значительный спад производительности и небезупречную синхронизацию. Подоб�
ная ситуация часто складывается с данными, которые не являются критически
важными.
Рассмотрим, как копирование при записи работает на практике. Допустим, у нас
есть лента сообщений микроблогов. Это классический пример данных, не являющих�
ся критически важными. Кроме того, при работе с такими данными работоспособный
и самодостаточный их снимок для каждого читателя обычно более важен, чем полная
глобальная согласованность всех данных. В листинге 4.9 показан класс-контейнер,
демонстрирующий ленту сообщений в таком виде, в каком ее просматривает отдель�
ный пользователь. (Далее мы воспользуемся этим классом в листинге 4.10, чтобы
подробно продемонстрировать, как осуществляется копирование при записи.)
Листинг 4.9. Пример копирования при записи

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

152

Часть 2. Необходимые технологии

Можно вызвать класс MicroBlogTimeline из кода, показанного ниже.
Листинг 4.10. Демонстрация поведения, действующего во время копирования
при записи

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

Глава 4. Современная параллельная обработка

153

 Если заменить CopyOnWriteArrayList на обычный List (1), то в результате получим
исключение ConcurrentModificationException.
 Здесь также есть пример объекта Lock, совместно используемого двумя потоками
для управления доступа к разделяемому ресурсу (в данном случае STDOUT). В блоч�
но-структурированном виде этот код был бы гораздо более запутанным.
Вывод данного кода выглядит так:
TL2: Update [author=Author [name=Ben], updateText=I like pie, createTime=0],
Update [author=Author [name=Charles], updateText=I like ham on rye,
createTime=0], Update [author=Author [name=Jeffrey], updateText=I like a
lot of things, createTime=0], Update [author=Author [name=Gavin],
updateText=I like otters, createTime=0],
TL1: Update [author=Author [name=Ben], updateText=I like pie, createTime=0],
Update [author=Author [name=Charles], updateText=I like ham on rye,
createTime=0], Update [author=Author [name=Jeffrey], updateText=I like a
lot of things, createTime=0],

Как видите, во второй строке вывода, помеченной как TL1, отсутствует последнее
уведомление, в котором упоминаются выдры (otters). И это несмотря на тот факт,
что доступ к mbex1 произошел уже после того, как список был изменен. Здесь мы
видим, что Iterator, содержащийся в mbex1, был скопирован в mbex2 и добавление
последнего обновления произошло незаметно для mbex1. Это и есть свойство копи�
рования при записи, которое мы хотим обеспечить для этих объектов.
ПРОИЗВОДИТЕЛЬНОСТЬ COPYONWRITEARRAYLIST
Работа с классом CopyOnWriteArrayList требует более тщательного продумывания, чем использование
ConcurrentHashMap, — последний, в сущности, является ситуативной конкурентной заменой для HashMap.
Дело в проблемах с производительностью, возникающих при использовании копирования при записи. В таком
случае, если список изменяется в процессе считывания или обхода, должен быть скопирован весь массив.
Это означает, что если вам часто приходится вносить изменения в список, количество таких обращений сравнимо
с количеством обращений для считывания, а подобный подход не гарантирует увеличения производительности.
Но, как мы неоднократно указываем в главе 6, единственный способ получить надежный и производительный
код — тестировать, снова тестировать и измерять результаты.

Следующий крупный общий компонент параллельного кода в java.util.
concurrent — это сущность Queue (очередь). Очередь используется для распределения
рабочих элементов между потоками и может служить основой для разнообразных
гибких и надежных вариантов многопоточного дизайна.

4.3.6. Очереди
Очередь — чудесная абстракция (поверьте, мы так считаем не только потому, что
живем в Лондоне, всемирной столице очередей). Очередь обеспечивает простой
и надежный способ распределения вычислительных ресурсов между рабочими
единицами (либо, с другой стороны, присвоения рабочих единиц вычислительным
ресурсам, если так понятнее).

154

Часть 2. Необходимые технологии

Существует несколько паттернов многопоточного программирования на Java,
которые в значительной степени опираются на потокобезопасные реализации Queue.
Поэтому очень важно хорошо разбираться в очередях. Базовый интерфейс Queue
расположен в java.util, поскольку данный паттерн может быть очень важен и при
однопоточном программировании. Но мы сосредоточимся именно на многопоточ�
ных практических ситуациях. Предполагаем, что вы уже сталкивались с очередями
на практике.
Один из самых распространенных случаев, на котором мы и собираемся сосре�
доточиться, — использование очереди для передачи рабочих единиц между пото�
ками. Такой паттерн часто идеально подходит для простейшего параллельного
расширения Queue — BlockingQueue.

BlockingQueues
BlockingQueue — это очередь, имеющая два дополнительных особых свойства.

 При попытке выполнить с очередью действие put() поток, ставящий элемент
в очередь, должен будет подождать, пока не освободится место, если очередь
уже заполнена.
 При попытке выполнить с очередью действие take() забирающий поток должен
будет блокироваться, если очередь пуста.
Два этих свойства очень полезны, поскольку если один поток (или пул потоков)
при работе слишком вырывается вперед — так, что другому потоку или пулу за ним
не угнаться, — то более быстрому потоку приходится подождать. Так регулируется
вся система. Эта ситуация проиллюстрирована на рис. 4.9.

Рис. 4.9. BlockingQueue

Глава 4. Современная параллельная обработка

155

ДВЕ РЕАЛИЗАЦИИ BLOCKINGQUEUE
В языке Java присутствуют две простейшие реализации интерфейса BlockingQueue: LinkedBlockingQueue
и ArrayBlockingQueue. Их свойства немного различаются. Например, реализация массива очень эффективна,
если известен точный предел, которого может достигать размер очереди. Первая, ссылочная реализация в некоторых ситуациях может работать несколько быстрее.

Работа с WorkUnit
Все интерфейсы Queue являются обобщенными — это Queue<E>, BlockingQueue<E>
и т. д. Хотя это и может показаться странным, но иногда бывает целесообразно
опираться на это свойство и создавать искусственные контейнеры, в которые обер�
тываются рабочие элементы.
Допустим, у вас есть класс MyAwesomeClass, представляющий рабочие единицы,
которые вы хотите обработать многопоточным образом. В таком случае, чтобы
не делать вот так:
BlockingQueue<MyAwesomeClass>

лучше поступить так:
BlockingQueue<WorkUnit<MyAwesomeClass>>

Здесь WorkUnit (или QueueObject, смотря как вы хотите назвать класс-контей�
нер) — это интерфейс или класс, который играет роль обертки и может выглядеть
примерно так:
public class WorkUnit<T> {
private final T workUnit;
public T getWork(){ return workUnit; }

}

public WorkUnit(T workUnit_) {
workUnit = workUnit_;
}

Мы поступаем так потому, что на данном уровне косвенности появляется воз�
можность добавить новые метаданные, не нарушая при этом концептуальную це�
лостность содержащегося типа (в данном случае MyAwesomeClass).
Оказывается, это удивительно полезно. Не счесть практических ситуаций, в ко�
торых пригодятся дополнительные метаданные. Вот несколько примеров:
 тестирование (например, отображение истории изменений для объекта);
 индикаторы производительности (допустим, время прибытия или качество
обслуживания);
 информация о системе времени исполнения (например, как именно была вы�
полнена маршрутизация MyAwesomeClass).
Добавить такую косвенность постфактум бывает довольно сложно. Если вы обна�
ружите, что в определенных обстоятельствах нужны дополнительные метаданные, то,
возможно, для решения проблемы потребуется крупный рефакторинг. А ведь анало�
гичного результата можно достичь, внеся простейшие изменения в классе WorkUnit.

156

Часть 2. Необходимые технологии

Пример BlockingQueue
Рассмотрим BlockingQueue в действии на простом примере: домашние питомцы ожи�
дают приема у ветеринара. В примере представлена коллекция зверушек, которых
можно встретить в ветпункте (листинг 4.1).
Листинг 4.11. Моделирование домашних питомцев на языке Java
public abstract class Pet {
protected final String name;
public Pet(String name) {
this.name = name;
}
public abstract void examine();
}
public class Cat extends Pet {
public Cat(String name) {
super(name);
}
public void examine(){
System.out.println("Meow!");
}
}
public class Dog extends Pet {
public Dog(String name) {
super(name);
}
public void examine(){
System.out.println("Woof!");
}
}
public class Appointment<T> {
private final T toBeSeen;
public T getPatient(){ return toBeSeen; }
public Appointment(T incoming) {
toBeSeen = incoming;
}
}

Этот простой пример показывает, как можно смоделировать очередь на прием
к ветеринару с помощью LinkedBlockingQueue<Appointment<Pet>>. Здесь класс Appointment
играет роль WorkUnit.
Объект ветеринара создается с очередью (в которой будут делаться назначения,
за них отвечает объект, моделирующий администратора из приемной) и со време�
нем паузы. Пауза — это длительность перерывов для ветеринара между приемами
пациентов.

Глава 4. Современная параллельная обработка

157

Можно смоделировать ветеринара так, как показано в листинге 4.12. Пока поток
работает, он многократно вызывает seePatient() в бесконечном цикле. Разумеется,
в реальном мире такая ситуация невозможна, поскольку ветеринар захочет уходить
с работы по вечерам и по выходным, а не торчать в кабинете дни и ночи напролет,
как доктор Айболит.
Листинг 4.12. Моделирование ветеринара

В методе seePatient() поток будет извлекать назначения из очереди и по очере�
ди проверять зверюшек, соответствующих каждому назначению. Если в настоящий
момент в очереди не окажется назначений, то поток будет блокирован.

158

Часть 2. Необходимые технологии

Филигранный контроль BlockingQueue
Кроме простых API take() и offer(), BlockingQueue предлагает и другой способ взаи�
модействия с очередью. Такой способ обеспечивает еще более полный контроль
над процессом за счет небольшого усложнения кода. Речь идет о возможности по�
мещения элемента в очередь и забора элемента из очереди с задержкой. Таким
образом, поток, столкнувшийся с проблемами, может отказаться от взаимодей­
ствия с очередью и выполнить какую-то другую задачу.
На практике такой вариант используется нечасто, но иногда бывает очень по�
лезен, поэтому продемонстрируем и его для полноты картины. Рассмотрим сле�
дующий пример из нашего сценария, описывающего работу с микроблогами (лис�
тинг 4.13).
Листинг 4.13. Пример поведения BlockingQueue

MicroBlogExampleThread t1 = new MicroBlogExampleThread(lbq, 10) {
public void doAction(){
text = text + "X";

Глава 4. Современная параллельная обработка

159

Update u = ub.author(new Author("Tallulah")).updateText(text).build();
boolean handed = false;
try {
handed = updates.offer(u, 100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
if (!handed) System.out.println(
"Unable to hand off Update to Queue due to timeout");

}
};
MicroBlogExampleThread t2 = new MicroBlogExampleThread(lbq, 1000) {
public void doAction(){
Update u = null;
try {
u = updates.take();
} catch (InterruptedException e) {
return;
}
}
};
t1.start();
t2.start();

Запустив пример в такой форме, вы увидите, как быстро заполнится очередь.
Это означает, что предлагающий поток обгоняет принимающий поток. Очень ско�
ро начнет появляться сообщение Unable to hand off Update to Queue due to timeout
(Невозможно записать обновление в очередь из-за задержки).
Здесь мы сталкиваемся с одной из крайностей модели «связанного пула пото�
ков». В данном случае восходящий пул потоков работает быстрее нисходящего.
Такая ситуация бывает проблематичной. В частности, она приводит к переполнению
LinkedBlockingQueue. В противном случае, если количество потребителей превы�
шает количество производителей, очередь может опустошиться. К счастью,
в Java 7 есть новый вариант BlockingQueue , выручающий в такой ситуации, —
TransferQueue.

TransferQueue — новинка в Java 7
В Java 7 появилась очередь TransferQueue. В принципе, это очередь BlockingQueue,
способная выполнять дополнительную операцию — transfer(). Такая операция
немедленно передает рабочий элемент потоку-получателю, если он находится в со�
стоянии ожидания. В противном случае очередь блокируется до тех пор, пока не
появится поток, способный принять элемент. Такую систему можно сравнить с «за�
казной корреспонденцией» — поток, обрабатывавший элемент, не приступит к об�
работке следующего элемента, пока не отдаст уже имеющийся. Соответственно,
система может регулировать скорость, с которой входящий пул потоков принима�
ется за выполнение новых задач.
Регулировать эту скорость можно было бы и с помощью блокирующей очереди
фиксированного размера, но TransferQueue имеет более гибкий интерфейс. Кроме
того, производительность вашего кода может повыситься после замены BlockingQueue

160

Часть 2. Необходимые технологии

на TransferQueue. Дело в том, что реализация TransferQueue была написана с учетом
возможностей современных компиляторов и процессоров, поэтому она работает
очень эффективно. Но если уж говорить о производительности, то ее потенциаль�
ный рост необходимо измерить и убедиться, что он существует, а не просто
предполагать. Учитывайте также, что в составе Java 7 есть только одна версия
TransferQueue — ссылочная (linked).
В следующем примере кода мы рассмотрим, насколько просто заменить очередь
BlockingQueue очередью TransferQueue. Небольшие изменения, показанные в листин�
ге 4.13, совершенствуют код до реализации TransferQueue (листинг 4.14).
Листинг 4.14. Замена BlockingQueue на TransferQueue
public abstract class MicroBlogExampleThread extends Thread {
protected final TransferQueue<Update> updates;
...

}

public MicroBlogExampleThread(TransferQueue<Update> lbq_, int pause_) {
updates = lbq_;
pauseTime = pause_;
}
...

final TransferQueue<Update> lbq = new LinkedTransferQueue<Update>(100);
MicroBlogExampleThread t1 = new MicroBlogExampleThread(lbq, 10) {
public void doAction(){
...
try {
handed = updates.tryTransfer(u, 100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
...
}
};

На этом мы завершаем обзор основных компонентов, предоставляющих нам
сырье для создания надежных многопоточных приложений. Далее нужно скомби�
нировать эти компоненты с движками, позволяющими эксплуатировать параллель�
ный код, — их еще называют фреймворками-исполнителями (executor framework).
Они позволяют планировать и контролировать выполнение задач. Вы, в свою оче�
редь, можете собирать с их помощью эффективные параллельные потоки задач для
обращения с рабочими элементами и выстраивать крупные многопоточные при�
ложения.

4.4. Контроль исполнения
В этой главе мы уже некоторое время обсуждаем фрагменты работы как абстракт�
ные единицы. Но здесь есть одна тонкость. Мы пока не говорили о том, что все
упомянутые выше единицы мельче, чем Thread (поток). Они позволяют выполнять

который можно вызвать и который возвращает результат. String s = cb. За счет небольшого усложнения кода вы можете обращаться к таким абстрак� циям как к пулам потоков. в частности о ScheduledThreadPoolExecutor. Несмо­тря на то что идея этой конструкции достаточно прямолинейна. где столкнемся с ними при изучении других языков. Интерфейс Callable Интерфейс Callable представляет собой очень распространенную абстракцию. Callable<String> cb = new Callable<String>() { public String call() throws Exception { return out. что они должны моделироваться как код.1. в последующих главах.toString(): final MyObject out = getSampleObject(). } }. которые можно планиро� вать. В послед� ней строке следующего фрагмента s устанавливается в значение out. поскольку в этом случае ресурсы на запуск потока Thread не приходится тратить при операции с каждой единицей. а не как непосредственно запускаемые потоки. не создавая отдельного нового потока для каждого фрагмента. Рассмотрим три различных способа моделирования задач — с помощью интер� фейсов Callable и Future и с помощью класса FutureTask. сама кон� цепция довольно тонкая и одновременно мощная. паттернам рабочих и управляющих потоков и исполни� телям — это одни из самых многофункциональных паттернов в словаре разработ� чика. Можно считать анонимную реализацию Callable отложенным вызовом един� ственного абстрактного метода call(). Именно здесь язык Java������������������� ����������������������� ближе всего подхо� дит к реализации функций в виде типов первого класса. Типичный пример использования Callable — анонимная реализация. содержащиеся во фрагменте работы. Мы подробнее поговорим о концепции функций как значений. Мы также поговорим о классах-исполнителях. Callable.Глава 4. не порождая отдельного потока для каждой из них. По окончании обработки одной задачи поток возвращается за новым фрагментом работы. Она позволяет получить исклю� чительно полезные паттерны. они же — функции как типы первого класса.call().toString(). Вместо этого потоки. Это фрагмент кода. 4. который обязательно должен предоставлять� ся в этой реализации. В конечном итоге это означает. Часто такой способ работы с многопоточным кодом наиболее эффективен. который можно вызывать (обычно это делает исполнитель). ко� торые фактически занимаются выполнением кода. которыми мы займемся наиболее плотно. переиспользуются. Современная параллельная обработка 161 вычисления. Future и FutureTask — отвечающие за моделирование задач классы и интерфейсы. . Моделирование задач Наша конечная цель — иметь задачи (рабочие единицы). Callable — пример так называемого SAM-типа (SAM расшифровывается как «единственный абстрактный метод»).4.

поскольку FutureTask по сути явля� ется Runnable. Если результата пока нет. что в этом фрагменте кода getNthPrime() возвращает Future.longValue()). Как вы увидите. которая пока может быть еще не завершена. например. который уже можно назначить исполнителю (и при необходимости отменить). TimeUnit. а не непосредственно вашим кодом. Класс FutureTask Класс FutureTask — это распространенная реализация интерфейса Future. Связь между этими двумя классами подсказывает очень гибкий подход к решению задач.get(60.println("Still not found the billionth prime!"). Long result = null. В принципе. while (result == null) { try { result = fut. где говорили о NIO. Это озна� чает. Задача может быть написана как Callable. испол� няемый в каком-нибудь фоновом потоке (или даже в нескольких потоках).out. cancel(). принимающая задерж� ку и поэтому не попадающая в вечную блокировку. этот интерфейс совмещает ме� тоды Future и Runnable: get(). также реализующая Runnable. что он ожидает результата выполнения задачи. . то get() блокируется до тех пор. } System. Для работы с FutureTask также предоставляются два вспомогательных конструк� тора: принимающий Callable и принимающий Runnable. Мы кратко коснулись таких интерфейсов в главе 2. Даже на современном оборудовании такие вычисления могут длиться довольно долго — в итоге может понадобиться вызвать метод cancel() интерфейса Future. пока результат не появится.2 и асинхронном вводе-выводе. Необходимые технологии Интерфейс Future Интерфейс Future используется для представления асинхронной задачи.  isDone() — позволяет вызывающей стороне определить. что FutureTask можно отдавать исполнителям — и это самое важное. isCancelled() и run(). завершены ли вычис� ления. как Future используется в программе для нахождения простых чисел. В следующем фрагменте кода показано. Есть также версия.out.println("Found it: "+ result. Future<Long> fut = getNthPrime(1_000_000_000). а затем обернута в FutureTask. решаться в одном из фреймворков-исполнителей. Эта задача может. isDone().  cancel() — позволяет отменить вычисление до завершения. Представьте. о кото� рых мы поговорим в следующем подразделе.162 Часть 2. применяемые с Future:  get() — получает результат. это означает.SECONDS). Вот основные методы. } catch (TimeoutException tox) { } System. хотя последний метод вызывается исполнителем.

а потом печатает эту информацию.util. они облегчают разработчику доступ к типичной конфигурации.15 показан пример периодического считывания. предоставляемым в классе Executors из java. CopyOnWriteArrayList. При этом используются исполняющие потоки. которые направляет к пулу потоков:  пулы потоков могут иметь заранее определенный либо адаптивный размер. Периодическое считывание с применением STPE . Это обычный пример использования newScheduledThreadPool(): объект msgReader назначается для опроса (poll()) очереди.15.Глава 4.  задачи могут назначаться для периодического выполнения либо однократно.4. STPE — это просто один из многих родственных исполнителей.util. в то же время предоставляя полнофункциональный интерфейс.2. которые можно с легкостью получать благодаря фабричным методам. получает кусок работы от объекта WorkUnit и ставит его в очередь. ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor (STPE) — это основной из классов пулов потоков. Один из наиболее распространенных паттернов для многопоточных приложений среднего и крупного размера связан с применением пулов потоков STPE. BlockingQueue). Листинг 4.concurrent. соединенные вспомогательными классами из java.  STPE дополняет класс ThreadPoolExecutor (который похож на STPE. о которых мы уже говорили (речь.concurrent. В листинге 4. о ConcurrentHashMap. STPE принимает работу в форме задач. Современная параллельная обработка 163 4. но в нем отсутствует возможность периодического назначения). в частности. Эти фабричные методы являются в основном вспомогательными. Он универсален и поэтому очень широко применяется.

задачи должны легко члениться на составляющие таким образом. Чтобы этого добиться. поверьте.5. Он предназначен для обеспечения легковес� ного параллелизма. что Callable при всей полезности выдвигает довольно много ограничений при создании общего фреймворка для выполнения моделирования. что мы могли бы более рационально использовать вычислительные мощности наших компьютеров. не принимающих аргументы. Теперь перейдем к одному из самых замечательных фрагментов Java 7 — фрейм� ворку fork/join (ветвление/слияние). если специально указывать. точнее. Новый фреймворк позволяет решать широкий набор проблем еще более эффективно. ПРОБЛЕМЫ. Фреймворк fork/join (ветвление/слияние) В главе 6 мы расскажем о том. динамические языки не разделяют такого статического взгляда на мир. ВОЗНИКАЮЩИЕ ПРИ ВЫПОЛНЕНИИ С ПОМОЩЬЮ CALLABLE Существует множество проблем при работе с простыми формами Callable. чем это удается делать с помощью исполнителей. нелегко). то поток печатает ее содержимое.164 Часть 2. 4. вам понадобится множество различных вариантов Callable. необходимо отметить. как . к обсуждению которой мы еще вернемся. Данные наработки также связаны с самыми крупными нововведениями в области параллельной обработки в Java 7. Если рабочая единица была получена. Но. И это означает. Пока просто отметим. какие сигнатуры методов могут существовать в моделируемых вами системах. насколько увеличилась скорость процессоров (или. Подобное несовпадение между системами типов — крупная проблема. Callable предоставляет лишь модель методов. как будет показано в третьей части этой книги. что система типов осложняет такую работу. то больше ничего не происходит и поток возвращается в спящее состояние. Рассмотрим случай. Если при считывании возвращается null (поскольку очередь пуста). причем этот процесс протекает незаметно для пользователя. В Java эту проблему можно обойти. FutureTask и родственными им сущностями. Побочный эффект такого ускорения заключается в том. Необходимые технологии В данном примере STPE пробуждает поток один раз в 10 мс и приказывает ему попытаться опросить очередь (poll()). рассмот� ренных в этом разделе (а это. Вся суть fork/join заключается в автоматическом назначении задач для пула потоков. количество транзисторов на микросхеме) в последние годы. что теперь ожидание ввода-вывода стало довольно распространенной ситуацией. в котором мы попытаемся учесть все возможные сигнатуры. Фреймворк fork/join призван достичь именно этого. которые мог бы иметь неизвестный метод. В частности. Для того чтобы учесть все возможности.

yy большими называются такие задачи. чем Thread. которая является вариантом MergeSort.5. Мы хотим отсорти� ровать их по времени прибытия. рассмот� рим следующий случай. Простой пример fork/join В качестве простого примера. что очень органично вписывается в данный фреймворк. Начнем с простого примера использования фреймворка fork/join. Кратко рассмотрим некоторые основные факты и фундаментальные моменты. кото� рая меньше по размеру.  Обычно в fork/join используются задачи двух видов (правда.16 использован специализированный подкласс ForkJoinTask — RecursiveAction. оба вида чаще всего представлены как экземпляры ForkJoinTask): yy малыми называются такие задачи.9. В листинге 4. прежде чем можно будет перейти к их выполнению.Глава 4. называемую «захватом работы» (work stealing). иллюстрирующего возможности fork/join. Современная параллельная обработка 165 укажет пользователь. Чтобы достичь этого. Познакомиться с fork/join удобнее всего на примере. Класс MicroBlogUpdateSorter предоставляет способ упорядочения списка обнов� лений путем применения метода compareTo() к объектам Update. связанные с fork/join. Во многих прикладных ситуациях fork/join различает боль� шие и малые задачи. .  Служба ForkJoinPool обрабатывает единицу параллелизма (ForkJoinTask).  Во фреймворке fork/join появляется новый тип исполняющей службы. Эти новые экземпляры будут исполняться тем же пулом потоков. Допустим. Метод compute() (который вам придется реализовать. чтобы такие легковесные задачи без особых проблем порождали другие экземпляры ForkJoinTask. а также оснащен механизмом автоматического назначения и переназна� чения задач. не по� требляя значительного количества процессорного времени. которые хорошо подходят для параллельной обработки. который испол� нял их родителя. Он проще обычного ForkJoinTask. чтобы сгенерировать ленты сообщений для пользователей. так как явно не предусматривает получения какого-либо общего результата (обновления переупо� рядочиваются на месте) и акцентирует рекурсивную природу задач. затем затро� нем возможность. Такую ленту мы создали в листинге 4.  Фреймворк предоставляет базовые методы для поддержки разбиения больших задач. которые можно выполнить сразу. поговорим о свойствах тех проблем. 4. так как в суперклассе RecursiveAction он явля� ется абстрактным) просто упорядочивает массив обновлений микроблога по вре� мени создания обновления. Одна из основных черт этого фреймворка — он рассчитан на то. которые требуется разбивать на части (иногда на довольно большое количество частей). применим многопоточную сортировку. В конце концов. которая называется ForkJoinPool. у нас есть массив обновлений для сервиса ми­ кроблогов и эти обновления могут приходить в разное время.1. Такой паттерн иногда называется «разделяй и властвуй».

16.166 Часть 2. Сортировка с применением RecursiveAction . Необходимые технологии Листинг 4.

Листинг 4. Изначально TimSort был разработан Тимом Петерсом (Tim Peters) для языка Python.17. перемешает их. а потом передаст сортировщику (лис� тинг 4.3 (2002). В Python этот алгоритм сортировки используется по умолчанию. Этот код сгенерирует несколько обновлений (каждое состоит из строки иксов). В качестве вывода получим переупорядоченные обновления. Операции сравнения в сортировочной процедуре массива Arrays. что TimSort присутствует в Java 7? Просто передайте массив null объектов Update в листинг 4. В стектрейсе вы заметите классы TimSort. . являющийся гибридом обычной сортировки и сортировки методом вставок. что показан ниже.17).16. но в Java 7 на смену QuickSort пришел TimSort — вариант MergeSort. Современная параллельная обработка 167 Чтобы задействовать сортировщик. его можно запустить с помощью кода напо� добие того.sort() будут срываться изза исключения. Ранее он имел форму QuickSort. Хотите сами убедиться в том. начиная с версии 2. происходящего в результате обращения по нулевому адресу. Использование рекурсивного сортировщика TIMSORT С появлением Java 7 применяемый по умолчанию алгоритм для сортировки массивов изменился.Глава 4.

Иными словами. Он дол� жен обеспечить использование всех потоков из пула на протяжении всего жизнен� ного цикла задачи fork/join. Если маленькая задача выполняется в пять раз быстрее. Задачи ForkJoinTask направляются к классу ForkJoinPool. Это обобщенный класс возвращаемого типа action (действие). В принципе.  анализ файлов журналов. Параллелизация проблем Перспективы использования fork/join выглядят соблазнительно. 4. Причина применения такого алгоритма «захвата работы» заключается в том. Служба поддерживает список задач для каждого потока. Как только одна из задач завершится. ForkJoinTask и захват работы ForkJoinTask — суперкласс для RecursiveAction. Для того чтобы на практике определить. которые могут члениться на составные части. как среда времени исполнения помогает разработчику управлять параллелизмом. а у другого потока — только из больших. так как задачи бывают большими и малыми. Перед нами еще один пример того. fork/join хорошо подходит для решения проблем.  операции над данными. как это показано на рис. то поток. как многопоточный сортировщик MergeSort из подраздела 4. избавляя программиста от не� обходимости решать эту задачу полностью вручную. вам не нужно предпринимать никаких специальных действий для пользования преимуществами захвата работы.  Могут ли подзадачи. требуется проверить ее и ее подзадачи на соответствие пунктам следующего списка.168 Часть 2. как закончит работу поток. RecursiveAction дополняет ForkJoinTask<Void>. представляющему собой службу-исполнитель нового типа. Вот несколько примеров проблем. вполне может остаться без дела до того. очередь выполнения у одно� го потока может состоять только из маленьких задач.2. где количество вычисляется на основании ввода (уже упоминавшиеся выше операции map-reduce).5. Процесс полностью автоматический. но на практике не всякая проблема легко сводится к такой простой форме. Таким образом. Необходимые технологии 4. 4. Соответственно. Захват работы был реализован именно для устранения такой проблемы. Например. что без него могут возникать проблемы диспетчеризации. выполняться без явного взаимо� действия и синхронизации друг с другом? . разработанную специально для решения таких легковесных задач.3.5. которые удобно решать методом fork/join:  моделирование движения множества простых объектов (например.5. задачи двух разных размеров требуют на выпол� нение абсолютно разные отрезки времени. нагруженный лишь маленькими задачами. образующие проблему.1. эффекты частиц).10. насколько легко редуцируется кон� кретная проблема. когда возвращаемое значение представляет собой «сухой остаток» от множества данных. чем большая. ForkJoinTask очень удобен при работе по принципу map-reduce. служба может перебросить часть задач от полностью загруженного потока к бездействующему. нагруженный большими задачами.

что станет базисом для уже имеющегося интуитивного понимания. работающие с Java. Многие программисты. то fork/join в вашем случае. вы ответили на эти вопросы «Может быть» или «Скорее нет». но без изменения этих данных (то есть можно ли назвать их «чистыми функциями» с точки зрения функционального программирования)?  Хорошо ли подходит принцип «разделяй и властвуй» для решения таких под� задач? Возникает ли в итоге выполнения одной подзадачи еще несколько подзадач (которые. Ветвление и слияние  Сводятся ли подзадачи к вычислению каких-либо значений из предоставленных данных. как сами понимают эту модель. Hadoop или NoSQL). знакомы с JMM и пишут код в соответствии с тем. то изложенный ниже новый материал будет строиться на вашем интуитивном пони� мании этой модели. будет хорошо решаться с применением fork/join. будет работать плохо и лучше попробовать другой способ синхронизации. вероятно. могут быть более тонкими. без всякого систематического ее изучения. ПРИМЕЧАНИЕ Приведенный выше список удобен для проверки того. Он очень полезен в своей области применения. 4. Если же. Модель памяти языка Java (JMM) Модель памяти Java (JMM) представлена в разделе 17.concurrent. называемого . вероятно. в свою очередь. так что можете пропустить ее. JMM — довольно сложная тема.10. и подход fork/join уме� стен не в любой ситуации. насколько хорошо проблема может быть обработана с помощью fork/join (подобные проблемы часто встречаются при работе с базами данных. напротив. чем породившая их задача)? Если вы ответили на предыдущие вопросы «Да!» или «В основном да. 4. подходит ли такой фреймворк для устранения вашей проблемы.util. которое можно построить на базе превосходного инстру� ментария java. Вы основательно уясните проблему. но есть некоторые пограничные случаи».Глава 4. В следующем разделе мы обсудим детали модели памяти Java (JMM). Это довольно сухая часть спецификации. Если вы именно такой специалист. то проблема. Если не подходит — будьте готовы к разработке собственного решения. но в ко� нечном итоге именно вам придется решать. Современная параллельная обработка 169 Рис. если вам не терпится перейти к следующей главе. Создавать хорошие многопоточные алгоритмы сложно.6. Она описывает JMM с точки зрения операций синхронизации и в контексте математического конструкта. например.4 спецификации языка Java (JLS). которые зачастую понимаются неправильно.

170 Часть 2. Необходимые технологии частичным порядком (partial order).11 показан пример записи в изменчивую (volatile) переменную. Некоторым разработчикам удобно представлять Happens-Before и Synchronizes-With как базовые блоки. . Это делается по аналогии с Has-A и Is-A. Это отношения между блоками кода. которым требуется детально понимать. а просто перечислим важней� шие правила. 4. что один блок кода должен полностью завершить� ся. которые мы назовем Synchronizes-With (синхронизируется с) и Happens-Before (происходит до). сформулировав их в виде двух базовых концепций.  Synchronizes-With — означает. вам знакомы выражения Has-A и Is-A. 4. Рис. которая синхронизируется с происходящим позже доступом для чтения (для println). но между двумя вышеупомянутыми парами концепций нет прямой технической связи.  если действие А синхронизируется с действием B. прежде чем выполнение другого блока кода сможет начаться. Не будем повторять здесь теоретические детали. Этот раздел великолепен с точки зрения язы� ковых теоретиков и воплотителей спецификации языка Java (авторов компиляторов и виртуальной машины). помогающие понять параллелизм в Java. но гораздо менее интересен для практикующих разработ� чиков.11. используемые для описания компонентов ориентации объекта.  Happens-Before — указывает. Пример Synchronizes-With В JMM действуют следующие основные правила:  операция разблокировки на мониторе синхронизируется с более поздними операциями блокировки. что действие будет синхронизировать свое пред� ставление об объекте с представлением об объекте в основной памяти и лишь потом сможет продолжить работу.  запись во временную переменную синхронизируется с последующими считы� ваниями переменной. то действие A происходит до действия B. как именно будет выполняться их многопоточный код. вероятно. На рис. Если вы изучали формальные подходы к объектно-ориентированному програм� мированию. то.

join() синхронизируется с последним (и всеми другими) действием в подсоединяемом потоке. как на ней работает память и синхронизация.12. Современная параллельная обработка 171  если в программе действует порядок. . вызванный неявной ошибкой в организации параллелизма. высвобождаются до того.12 проиллюстрировано правило транзитивности.Глава 4. как смогут быть получе� ны другими операциями (в том числе операциями считывания). Транзитивность при Happens-Before ПРИМЕЧАНИЕ На практике эти правила обеспечивают минимальные гарантии со стороны JMM.  если X происходит до Y. а Y происходит до Z. Есть и другие правила. запускающее поток. лишь после этого его можно будет финализировать). Рис. как начинает работать финали� затор этого объекта (объект должен быть полностью сконструирован. что «высвобождение происходит до получения блокировки». Эти простые правила полностью определяют представление платформы о том. 4. Правильное поведение конкретной виртуальной машины Java может дать ложное чувство надежности. в сущности. которые. 4. то A происходит до B. удержива­ емые потоком во время записи. при котором A происходит раньше. Общая формулировка первых двух правил сводится к тому. На рис. Реальные виртуальные машины Java в действительности могут работать гораздо лучше. и это совершается в рамках потока.  действие. чем можно предположить на основании этих гарантий. обеспечивают логичное поведение программы:  завершение конструктора происходит до того. синхронизируется с первым действием нового потока. блокировки. Иными словами. то X происходит до Z (транзитив� ность). которое разобьется о первый же «заскок». чем B.  Thread. Для разработчика это может стать настоящей западней.

а также структурные силы. самая ценная часть этой главы — прак� тическая. Необходимые технологии Имея такие минимальные гарантии. 4. применяемые в экоси� стеме. Мы рассказали вам о тонкостях некоторых новейших классов. Для современного Java-разработчика еще важнее то. Это основной навык.util. .util. действующие в многопоточных системах. Даже если вы только начинаете работать с ConcurrentHashMap и атомарны� ми (Atomic) классами.7. вы приобрели необходимые базовые знания и сумеете использовать классы java. связанных с обеспечени� ем видимости изменений для всех потоков. желающий опередить конкурентов. который должен был у вас сформироваться после прочтения этой главы. В главе 5 вы приобретете фундаментальные базовые знания по еще одному важнейшему аспекту работы с платформой — загрузке классов и ис� пользованию байт-кода. Хотя мы рассмотрели не� которые важнейшие теоретические вопросы. Если объекты нельзя изменить. которая поможет вам состояться как Java-разработчику. то будете использовать хорошо протестированные классы. К этому инструментарию нужно обращать� ся в первую очередь. Кроме того. входящие в состав java. появившихся в Java 7. ее должен изучить любой разработчик. Хоро� шему разработчику со временем все больше будет необходимо уверенно разбирать� ся в этой теме. Мы рассмотрели основы параллелизма в Java. Мы обсудили модель памяти. дей� ствующую в Java. если вы создаете многопоточный код для новых версий Java. которые сразу же принесут коду реальную пользу. она подкрепляет многие сложные технологии. легко понять.concurrent в своем коде. А значит.172 Часть 2.concurrent. Надеемся. в частности о LinkedTransferQueue и о фреймворке fork/join. Перейдем к следующей обширной теме. то нет и проблем. почему неизменяемость является настолько важной концепцией в параллельном программировании на Java. что мы обсудили классы. Резюме Параллелизм — это одна из самых важных возможностей платформы Java. и низкоуровневые подробности реализации параллелизма на этой платформе. Эта тема имеет первостепенное значение для реализации многих функций платформы. связанных с производительностью и безопасностью.

здесь мы обсудим и некоторые более сложные возможности — этот материал рассчитан на энтузиастов. что именно это означает. исходного кода вообще не остается. В ходе их загрузки виртуальная машина находит и активизирует новый тип для использования в работающей программе. что необходимо раз� бираться в загрузке классов. которые лежат в основе этих проблем разработки. в котором широко используются возможно� сти внедрения зависимостей. например фреймворк Spring. с которым вы работали. и вы мо� жете его пропустить. Если проблема не сводится к обычной ошибке в конфигу� рации. Как вам исследовать скомпилированный код и понять.  invokedynamic — новая инструкция. Это означает. Один из проверенных и надежных способов стать основательным Java-разра� ботчиком — подробно разобраться в том. В этой главе мы сосредоточимся на тех особенностях платформы. исполнитель. и оно аварийно завершается. в чем разница между такими ошибками и почему они происходят. Допустим.  байт-код виртуальной машины Java и почему он так важен. Начнем с обзора загрузки классов. как именно реа� лизован фреймворк внедрения зависимостей. у вас есть приложение. . таких как загрузка классов и суть байт-кода виртуальной машины Java. отхо� дит от дел — и у вас остается полная версия скомпилированного кода. как именно работает платформа. кроме самых простых. а имеющаяся документация пестрит пробелами. При запуске приложения начинают возникать проблемы. если спешите. Допустим.  дескрипторы методов.5 Файлы классов и байт-код В этой главе:  загрузка классов. то достичь этой цели будет гораздо проще. Кроме того.  строение файлов классов. выдавая непонятное сообщение об ошибке. могут аварийно завершиться с исклю� чением ClassNotFoundException или ошибкой NoClassDefFoundError. Возьмем другой пример. из чего он состоит? Все приложения. но многие разра� ботчики не знают. то для ее отслеживания и решения вы должны понимать. Если хорошо ориентироваться в базовых возможностях платформы.

5. мы познакомимся с новым кодом операции invokedynamic. Разобрав этот урок о строении файлов классов. представ� ляющие в виртуальной машине различные типы.174 Часть 2. преобра� зовать данные в форму. как происходит низкоуровневая работа среды времени исполнения. После этого мы поговорим о различных инструментах для проверки и препари� рования файлов классов. перейдем к изучению байт-кода. Начнем с рассмотрения загрузки классов — в ходе этого процесса новые классы встраиваются в работающий процесс виртуальной машины Java. В этом разделе мы обсудим все эти этапы и познакомимся с загрузчиками клас� сов. После этого новый объект Class. входящий в состав Oracle JDK (инструментария для разработки на Java). информацией о наследовании. Чтобы сделать класс частью исполняемого состояния виртуальной машины Java. пригодную для использования. Обзор — загрузка и связывание Назначение виртуальной машины Java — потреблять файлы классов и выполнять байт-код. потом он должен пройти тщательную проверку (верифи� кацию). и добавить эту информацию к исполняемому состоянию.1. Это классы. чтобы другие языки (не Java) могли максимально эффективно использовать виртуальную ма� шину Java как платформу. Этот код появился в Java 7 для того. При этом связывание подразделяется на несколько более мелких этапов. 5. их должен придерживаться любой язык. . Этот двухэтапный процесс называется загрузкой и связыванием (loading and linking). контролирующие весь описываемый процесс. представляющий тип. Далее мы рассмотрим новый API дескрипторов методов (method handles) и сравним его с известными подходами из Java 6 — например. которую может загрузить платформа. Во-первых. виртуальная машина Java долж� на получить содержимое файла класса в виде байтового потока данных. Мы рассмотрим основ� ные семейства кодов операций виртуальной машины Java и изучим.1. файл класса должен быть загружен и связан. Чтобы это делать. содержащийся в них. который предполагается запускать на виртуальной машине Java. Необходимые технологии Основными проблемами этого обсуждения являются объекты типа Class. Класс — это мельчайшая единица программного кода. В качестве базового инструмента возьмем javap.1. будет доступен работающей системе и можно будет создавать его новые экземпляры. с рефлексией. Загрузка классов и объекты классов Файл с расширением .class определяет тип для виртуальной машины Java — вме� сте с полями. Формат файлов классов хорошо описан в стандартах. Начнем с изучения загрузки и связывания классов. Вооружившись практическими знаниями байт-кода. аннотациями и другими метаданными. нужно выполнить несколько шагов. методами.

Про� цесс начинается с байтового массива (зачастую считываемого из файловой систе� мы). Этот этап делится на три более мелких — верификация. что класс готов к работе.Глава 5. Рис. чтобы можно было перейти к связыванию. Верификация подтвер� ждает. Связывание После завершения загрузки класс должен быть связан. из которых состоит файл класса. Все другие типы. 5. Затем класс проходит подготовку.1. гарантирующая. Соотношение между этапами связывания показано на рис. В ходе этого процесса класс проходит несколько базовых проверок. 5.3). состоящим из нескольких стадий. это часть за� грузки. Создается объект Class. самодоста� точна и подчиняется базовым правилам поведения. будут найдены. гарантирующие. В сущности. что файл класса соответствует ожидаемым параметрам и не будет вызы� вать в работающей системе ошибок времени исполнения либо других проблем. Файлы классов и байт-код 175 Загрузка На первом этапе берется байтовый поток тех данных. действующим для констант.1. . использовать его пока нельзя.3. подготовка и разрешение. Сначала происходит базовая проверка целостности. после чего это застывшее представление класса вновь «оживляется». на которые присутствуют ссылки в файле класса. что символь� ная информация. чтобы гарантировать. содержащаяся в пуле констант (см. что файл класса правильно сформирован. По окончании про� цесса загрузки объект Class еще не является полнофункциональным. Загрузка и связывание (вместе с второстепенными этапами связывания) Верификация Верификация может быть довольно сложным процессом. соответствующий загружаемому вами классу. подраздел 5. На следующем этапе осуществляются проверки.

что перед использованием все переменные правиль� но инициализируются. Эти проверки выполняются ради обеспечения производительности — они по� зволяют пропускать текущие проверки времени исполнения. После того как все дополнительные типы. они упрощают компиляцию байт-кода в машинный код во время исполнения (это и есть динамическая компиляция. то их также может понадобиться загрузить. обеспе� чивающие контроль доступа. уже для любых новых типов. осуществляемых на этом этапе:  проверка того. Но на этом этапе сами переменные не инициализируются. Кроме того. что все методы при работе учитывают ключевые слова. что все методы вызываются с верным количеством параметров верных статических типов. Подготовка На этапе подготовки класса под него выделяется память.  проверка того. позволяющая убедиться. что байт-код работает правильно и не пытается обойти системы управления экосистемой виртуальной машины. При этом необходимо гарантировать. также осуществляются имен� но на этом этапе. что байт-код не пытается манипулировать стеком зловредными способами. будут най� дены и разрешены.176 Часть 2.  проверка.  проверка. класс будет полностью загружен и го� тов к работе. обязательные к загрузке. Затем начинается наиболее сложная часть верификации — проверка байт-кода методов. а байт-код виртуальной машины не выполняется. Необходимые технологии Другие статические проверки. гарантирующая. . Теперь вы используете байт-код из свежезагружен� ного класса. а статические переменные класса подготавливаются к инициализации. проверка того. о которой мы поговорим в разделе 6. не связанные с просматриванием кода (например. на которые проставлены ссылки из нового класса. будут известны во время исполнения. что переменным присваиваются лишь правильно типизированные для них значения. Когда этот шаг будет выполнен. Если какие-то типы неизвестны. поэтому интерпрети� руемый код работает быстрее.6). что методы final не переопределены). Далее перечислены некоторые из важнейших проверок. На этом последнем этапе любые статические переменные могут быть инициализированы и любые блоки статической инициа� лизации могут быть запущены. Разрешение На этапе разрешения виртуальная машина гарантирует. ко� торые теперь стали видны.  проверка. гарантирующая. что все типы. который изначально было приказано загрузить. виртуальная машина может инициализировать класс. Поэтому может вновь начаться процесс загрузки классов.

Эти объекты могут использоваться в Reflection API для предоставления непрямого доступа к возможностям класса.  Системный загрузчик (system classloader. возвращающий объект Class. Никакой верификацией он не занимается. который станет новым загруженным классом. Загрузчики классов В составе платформы присутствует несколько типичных загрузчиков классов. какая часть среды времени исполнения отвечает за нахождение и связывание байтового потока.  Загрузчик расширений (extension classloader) — применяется для загрузки стан� дартных расширений. Именно он занимается загрузкой . объект Class того или иного типа пре� доставляет несколько полезных методов. 5. Теперь ваш код может продолжать работу. соответствующие членам класса. например getSuperClass(). ко� торые применяются для решения различных задач в процессе запуска и обычной эксплуатации платформы. соответствующий базовому типу.2.2. Загрузчики являются подклассами абстрактного класса ClassLoader. Объект Class и ссылки Мы еще не говорили о том.jar. Эта работа выполняется загрузчиками классов. Объект Class имеет ссылки на объекты Method и Field. д. 5.Глава 5. отвечающие за безопасность. действующих в пределах отдельной установки. Рис. использовать новый тип и создавать новые экземпляры. Обычно он используется для загрузки базовых системных архивов JAR — как правило. Теперь он пол� ностью функционален в виртуальной машине. представляющий собой свежезагруженный и связанный тип.1. конструкторам и т.3. полям. 5. Объекты классов могут использоваться с API Reflection для непрямого доступа к методам. это rt. Зачастую его удобнее представлять как часть самой виртуальной машины.2. О них и пойдет речь далее. Кроме того. Обобщенная структура этих взаимосвязей показана на рис. На этом этапе часто устанавливаются расширения. Объекты классов Конечным результатом процесса загрузки и связывания является готовый объект Class. Файлы классов и байт-код 177 5.1. он же — загрузчик приложений) — наи� более часто используемый загрузчик классов. хотя из соображений производи� тельности некоторые свойства объекта Class инициализируются только по тре� бованию.  Базовый загрузчик (bootstrap classloader) — инстанцируется на очень раннем этапе запуска виртуальной машины и обычно реализуется в нативном коде.

о чем мы кратко упоминали в главе 1. Загрузчик классов EMMA изменяет байт-код загружаемых классов по мере их загрузки и добавляет к ним дополнительную информацию об инструментировании. какие именно методы и основные ветви тестируются в ходе конкретных сценариев. специфичные для их собственных приложений. часто используется еще и несколько дополнительных (так называемых специальных) загрузчиков классов. Подробнее о тестах и о покрытии кода тестами мы поговорим в главах 11 и 12. трансформирующегося в ходе загрузки. Загрузка ресурсов осуществляется из JAR или других местоположений. Иерархия загрузчиков классов . не являющихся классами. напри� мер в EE или в сравнительно изощренных фреймворках SE. Они часто трансформируют байт-код по мере его загрузки. насколько полными получаются тесты компонентов для того или иного класса. относящихся к пути класса. Кроме того. 5. EMMA записывает. — загрузчик. На рис.  Специальный загрузчик (custom classloader) — в некоторых окружениях. Здесь разработчик может видеть. инструменте для проверки покрытия программы тестами. EMMA можно скачать по адресу http://emma.3 показана иерархия наследования загрузчиков классов. Некоторые команды даже пишут загрузчики классов. нередко встречаются фреймворки и другой код.178 Часть 2.net/. Наряду с основной областью применения загрузчики классов также использу� ются для загрузки ресурсов (то есть файлов. применяемый в EMMA.3. sourceforge. ПРИМЕР — ИНСТРУМЕНТИРУЮЩИЙ ЗАГРУЗЧИК КЛАССОВ Простой пример загрузчика классов. Рис. Когда тестовые сценарии применяются к трансформированному коду. 5. а также выполняет основную часть работы в большин­ стве SE-окружений. например изображений или конфигурационных файлов). в которых исполь� зуются специализированные (и даже определяемые пользователем) загрузчики классов с дополнительными свойствами. а также взаи� мосвязи между различными загрузчиками. Необходимые технологии классов приложений.

рассмотренных нами выше.1. Служба. Рассмотрим. на которые проставлены ссылки в фай� ле config.xml Путь CLASSPATH должен содержать JAR-файлы для фреймворка с внедрением зависимостей. Листинг 5.Глава 5. Пример — загрузчики классов при внедрении зависимостей Основная идея внедрения зависимостей является двоякой:  между функциональными единицами в составе системы существуют зависимо� сти и конфигурационная информация. действующими во время исполнения (runtime wiring). Правда. как запускалось бы приложение в нашем воображаемом фрейм� ворке с внедрением зависимостей: java -cp <CLASSPATH> org. HollywoodService — альтернативный пример внедрения зависимостей .DIMain /path/to/config. показанная в листинге 3.xml (вместе со всеми другими возможными у них зависимостями). а также для любых классов.  обычно зависимости сложно или неудобно выражать в контексте самих объ� ектов. 5. Именно вторая часть этой модели обычно называется подключениями объектов. которые являются внешними относительно объектов. который мы здесь обсудим.1.7. здесь будет представлена упрощенная версия фреймворка Spring. Файлы классов и байт-код 179 Рассмотрим пример специализированного загрузчика классов и исследуем. В главе 3 мы изучали внедрение зависимостей на примере фреймворка Guice. У вас в голове уже должна сложиться картина из классов.framework. а также из конфигурационной информации и зависимостей. подход. совсем не похож на Guice. для правильного функционирования необходимо опираться на эти зависимости и информацию. Адаптируем под этот стиль один из примеров. как фреймворк может использовать загрузчики классов для реализации внедрения зависимостей. обладающих соб� ственным поведением. В сущности. В этом разделе поговорим о том. очень удобна для этого — результат преобра� зования демонстрируется в листинге 5.4. как загрузка классов может использоваться для реализации внедрения зависимо� стей.1.

какие объекты требуется кон­ струировать. нужна вторая фаза загрузки классов. На первом этапе (об� служиваемом загрузчиком приложений) загружается класс DIMain и все другие классы. специальный загрузчик клас� сов пытается загрузить типы из CLASSPATH.. файл config. Потом. а фреймворк будет вызывать для каждого из них пустой конструктор.WebServiceAgentFinder" .xml проверяется на согласованность. /> <bean id="hwService" class="wgjd. Если все вышепе� речисленное удастся сделать правильно. также тесно связаны с применением загрузчи� . Потом DIMain начинает работать и получает ме� стоположение конфигурационного файла в качестве параметра для main(). какие классы требуется загрузить. а также на безошибочность. Многие другие области технологий.HollywoodServiceDI" p:finder-ref="agentFinder"/> </beans> При таком подходе фреймворк для внедрения зависимостей будет исполь� зовать файл конфигурации. указанных в config. В ходе нее применяется специальный (пользователь� ский) загрузчик классов. В этом примере нам понадобятся бины hwService и agentFinder . На самом деле. то контекст приложения будет полностью настроен и готов к работе. Необходимые технологии Для того чтобы эта конструкция могла управляться благодаря внедрению зави� симостей. На данном этапе фреймворк уже настроен и запущен на виртуальной машине Java. на которые он ссылается. если все хорошо. Мы кратко коснулись Spring-подобного подхода к внедрению зависимостей. то фреймворк для внедрения зависимостей сможет перейти к инстанцированию требуемых объектов и вызывать применитель� но к создаваемым экземплярам нужные методы-установщики. описанной в config.ch05. но мы еще не касались пользовательских классов. за которым следуют методы-установщики (например. Чтобы добиться конфигурации приложения. Во-первых. чтобы определить. Это означает. то будет отменен весь процесс. setFinder() для зависимости AgentFinder из HollywoodServiceDI). Если загрузка какого-либо из типов со� рвется. относящиеся к Java. нужен и конфигурационный файл.xml. что загрузка классов протекает в два этапа.180 Часть 2.xml. фреймворк никак не сможет узнать.. пока DIMain не проверит конфигурационный файл.ch03. Если вся операция пройдет успешно. примерно такой: <beans> <bean id="agentFinder" class="wgjd. при котором широко используется загрузка классов.

в которых приходилось ссылаться на типы аргументов всех интроспективных методов. но с сохранением гораздо более чистого кода. в которых может понадобиться добавить новый неизвест� ный код уже после того. Файлы классов и байт-код 181 ков классов и родственных практик. заинтересует. как процесс виртуальной машины Java начал работу. применяемые в Java 7 для достижения аналогичных целей.). Еще можно вспомнить о необходимости отлавливать гнусные исключения. где поговорим о новом API Java 7. связанных с рефлексией.  получение файлов классов из необычных местоположений (не из файловых систем и не по URL). то мо� жете просто просмотреть или даже пропустить этот раздел. то этот раздел вас. В Java 7 появился новый API для непрямого вызова методов. то вы без труда вспомните все те случаи. ЗАМЕНА РЕФЛЕКСИВНОГО КОДА При рефлексии требуется много шаблонного кода. Для этого его нужно перевести на использование дескрипторов методов. Можно считать работу с этим API более современным подходом к рефлексии. аргументы всех объемлющих методов. Method. Использование дескрипторов методов Если вы не знакомы с API Reflection языка Java (Class. . 5. например Class[]. Существуют довольно веские причины для сокращения шаблонного кода и повышения интуитивной понятности всего кода. который предназначен для удовлетво� рения некоторых нужд. Указатели методов были разработаны в рамках проекта по подготовке invokedynamic для виртуальной машины Java (об этом подробнее — в разделе 5. так как здесь объясняются новые способы. но без излишней пространности. безусловно. а также о сложности интуитивного понимания при чтении рефлексивного кода.  Java EE. так и в обычном пользовательском коде. На этом мы завершаем обсуждение загрузки классов.5). если вы часто используете рефлексию. Field и др.  любые обстоятельства. Вот лишь некоторые наиболее известные примеры:  архитектуры плагинов.Глава 5. так и кустарные). С другой стороны.2. например Object[].  фреймворки (как полученные от производителя. издержек и острых углов. Перейдем к следующему разделу. Если вам доводилось писать больше нескольких строк рефлексивного кода. Но они находят применение как в коде фреймворков.invoke. которые иногда свойственны для API Reflection. Основной его компонент — пакет java. если что-то пойдет не так.lang. содержащий так называемые дескрипторы методов (method handles).

конструктор и т. представляющий сигнатуру типа мето� да. На самом деле Callable — это более ранняя попытка смоделировать возможность вызова метода. Напротив. передав в качестве аргументов arg0 и arg1: MethodHandle mh = getTwoArgMH(). arg0. Это делается с помощью нового класса MethodType. в котором сравним дескрипторы методов с имеющимися аль� тернативами. и они не принимают аргументов.2.182 Часть 2. . } Такая возможность отчасти напоминает рефлексию. Потом рассмотрим рас� ширенный пример. Но он не включает имя метода или «тип получателя» — тот тип. Другими словами.printStackTrace().1. главу 6)). 5. но такая практика быст� ро привела к неконтролируемому росту количества интерфейсов. MethodHandle Что такое MethodHandle? Общепринятый ответ таков: это типизированная ссылка на метод (или поле. предоставля­ ющий возможность безопасного вызова метода. а потом вызовем наш дескриптор применительно к объекту obj. Но с Callable связана одна проблема: этот интерфейс способен лишь моделировать методы. включающий тип возвращаемого значения и типы аргументов. Именно этот подход зачастую использовался в Java 6. потребуется создавать иные интерфейсы с конкретными комбинациями параметров. о котором мы говорили в разделе 4.4. может закончиться память в области для постоянно существующих объектов (PermGen). дескрипторы методов могут моделировать любую сигнатуру метода без необходимости создания множества маленьких классов. } catch (Throwable e) { e.). д. MethodType MethodType �������������������������������������������������������������� — это неизменяемый объект. try { ret = mh. применительно к которому вызывается метод экзем� пляра. Начнем с изучения базовой технологии применения дескрипторов методов. arg1). MyType ret. 5. что может до­ ставлять разработчику существенные проблемы (например. Необходимые технологии далеко не ограничиваясь использованием вместе с invokedynamic. и резюмируем разницу.2. Чтобы учесть все возможные на практике наборы различ� ных комбинаций параметров и возможностей вызова.2. Получим дескриптор на двухаргументный метод (имя которого мы даже можем не знать).invokeExact(obj. где хранятся классы (см. а отчасти — интерфейс Callable. который уже является непосредственно исполняемым. дескриптор метода — это объект. Каждый дескриптор метода имеет экземпляр MethodType.

class).methodType(RetType. что соответствующий экземпляр MethodType является MethodType.class. Если вы хотите узнать. String. 5. Object. String.. Это экземпляры MethodType. Поиск дескрипторов методов В листинге 5. может ли определенный дескриптор метода быть вызван с конкретным набором аргументов.methodType(void.methodType(String. methodType(String. способный предоставлять дескриптор . Поиск дескриптора метода Для получения дескриптора метода нужно использовать объект поиска (lookup object). так вы можете обеспечить максимально возможную безопасность типов.class. методустановщик (для члена типа Object) и метод compareTo()..class.methodType(int. которые указывают на методы наших типов. Кроме того.class). Теперь научимся получать новые де� скрипторы методов. MethodType mtSetter = MethodType. Листинг 5. относящийся к дескриптору. определяемый Comparator<String>.2 показано.3. Обобщенный экземпляр строится по такому же принципу. Вам уже не требуется определять новый тип для каждой сигнатуры. Как видите.class).). указывающий на метод toString() актуального класса. то можете проверить MethodType. Сначала передается возвращаемый тип.class.Глава 5. MethodType mtStringComparator = MethodType.2. Arg1Type. Это объект. вот так: MethodType. представляющие сигнатуры типа toString().class. а потом типы объектов (все — как объекты Class). как получить дескриптор. различные сигнатуры методов теперь могут быть представлены как обычные объекты-экземпляры.class).2. такой как lk в листинге 5. Arg0Type. . которая потребовалась при работе. Вот несколько примеров: MethodType mtToString = MethodType. как объекты MethodType позволяют решать проблему стихийного роста количества интерфейсов. Это означает. мы рассмотрели.2. Обратите внимание: mtToString точно совпадает с сигнатурой toString() — он имеет тип возвращаемого значения String и не принимает аргумен� тов. Итак.class. Файлы классов и байт-код 183 Для получения новых экземпляров MethodType можно использовать фабричные методы в классе MethodType.

ПРИМЕЧАНИЕ Контекст поиска можно применять для получения дескрипторов методов любого типа. В листинге 5. не понаслышке знакомы с проблемами. Необходимые технологии для любого метода. соот� ветствующий желаемой сигнатуре. характерными для рефлексивного кода. если совпадение кажется недостаточ� но полным (например.4. чтобы типы аргумен� тов в точности совпадали с теми типами. требуется пе� редать класс. Разумеется. Кроме того. не существует. Чтобы продемонстрировать разницу между дескрипторами методов и другими приемами. пожалуй. Надеемся. содержащий нужный вам метод. он позволяет отменить выполняемую в данный момент задачу. использования посредников и дескрипторов методов Если вам приходилось подолгу работать с кодом.3 показан адаптированный пример. Это означает. мы демонстрируем два приема в стиле Java 6 — использование рефлек� сии и класса-посредника — и сравниваем их с новым подходом. 5. Теперь. как дескрип� торы методов используются для замены обширного рефлексивного шаблонного кода. то контекст поиска позволит рассмотреть или получать лишь дескрипторы общедоступных (public) методов. мы предложили три различных способа доступа к закрытому методу cancel() извне класса — эти методы выделены в листинге полужирным шрифтом. Мы используем задачу считывания очереди (эта задача называется . но этот метод скрыт для исполь� зования другими классами. в том числе системных типов. В этом подразделе мы продемонстрируем. основанным на MethodHandle. имя метода и тип MethodType. взятый из одной из предыдущих глав. с которым не имеете связи.2. Для того чтобы получить дескриптор метода от объекта поиска. видимого из того контекста исполнения. если вы получаете дескрипторы от класса. при необходимости применяется распаковка или упаковка типов). ThreadPoolManager отвечает за назначение новых задач в пуле потоков. В API Method Handles для этого существует два основных способа: использовать методы invokeExact() и invoke(). что дескрипторы методов всегда безопасны для использования с диспетчерами безопасности — аналога уловки с setAccessible(). В следующем подразделе приведен более обширный пример того. как дескрип� торы методов могут использоваться для замены прежних техник — таких как рефлексия и использование небольших классов-посредников. пытаясь получить типы. в котором широко применяется рефлексия. Метод invokeExact() требует. когда у вас есть дескриптор метода. которые ожидает получить базовый метод. довольно точно совпадающие с ожидаемыми. то вы. логично научиться его вызывать. в котором был создан объект поиска.15.184 Часть 2. Пример: сравнение рефлексии. Метод invoke() совершает некоторые преобразования. он слег� ка изменен по сравнению с примером из листинга 4. что после этого ваша повседневная программистская практика станет немного проще. Кроме того. применяемой при рефлексии.

newScheduledThreadPool(2).MILLISECONDS). public ThreadPoolManager(BlockingQueue<WorkUnit<String>> lbq_) { lbq = lbq_. return stpe. Реализация QueueReaderTask находится в ис� ходном коде.setQueue(lbq).Глава 5. . private final BlockingQueue<WorkUnit<String>> lbq. Предоставление доступа тремя способами public class ThreadPoolManager { private final ScheduledExecutorService stpe = Executors.scheduleAtFixedRate(msgReader.com/TheWell-GroundedJavaDeveloper. } public ScheduledFuture<?> run(QueueReaderTask msgReader) { msgReader. 10. TimeUnit. Файлы классов и байт-код 185 QueueReaderTask и реализует Runnable). ред.manning. Листинг 5.3. сопровождающем эту главу1. — Примеч. 10. } 1 Можно скачать по адресу www.

Листинг 5. чтобы объяснить разницу между ними.4.186 Часть 2. Чтобы посмотреть. обратимся к листин� гу 5. Использование возможностей доступа . Необходимые технологии В этом классе предоставляются возможности для доступа к закрытому методу cancel().4. как использовать эти возможности. На практике обычно предоставляется лишь одна из них — мы демонстри� руем все три лишь для того.

В следующем подразделе мы поговорим о том. Почему стоит выбирать дескрипторы методов В последнем подразделе мы рассмотрели.187 Глава 5. возникает вопрос: а почему стоит выбирать именно дескрипторы методов. Настоятельно рекомендуем использовать дескрипторы во всех новых приложениях. Файлы классов и байт-код Все методы cancelUsing принимают ScheduledFuture в качестве параметра. Таким обра� зом. Таблица 5. В простых практических случаях посредники могут быть проще для понимания. Никаких проблем с менеджерами безопасности Дескриптор метода Продложение  . Сравнение технологий непрямого доступа к методам. 5. что дескрипторы методов совмещают наилучшие черты двух первых подходов. Активный менеджер безопасности может это запретить Внутренние классы могут Из подходящего конполучать доступ к огра. но мы считаем. применяемых в Java Функция Рефлексия Посредник Контроль доступа Необходимо использовать setAccesible(). а не другие альтернативы? В табл. 5. На практике. как именно он реализован.1 показано. где в Java 6 применялись бы рефлексия или посредники.1.5. будучи пользователем API.2. а не альтернативные варианты. как дескрипторы методов используются в ситуациях. поэто� му можете воспользоваться предыдущим кодом и поэкспериментировать с различ� ными способами отмены. что основное достоинство рефлексии — ее привычность. вы можете и не вникать в то.текста разрешен полниченным методам ный доступ ко всем методам. почему разработчику API или фреймворка стоит использовать именно дескрипторы методов.

который допустимо использовать в качестве статического контекста.getLogger(MyClass. помогающие в них разобраться. Дополнительные шаги по пересборке и повторному развер� тыванию не дают никакого эффекта. Допустим. Исследование файлов классов Файлы классов — это двоичные блобы1. Необходимые технологии Таблица 5. Но в Java 7 можно написать так: Logger lgr = LoggerFactory. Если вы когда-либо писали код логирования (например. в которых бывает необходимо подроб� но разобраться в файле класса.1 (продолжение) Функция Рефлексия Посредник Дескриптор метода Проверка типов Отсутствует. Кажется. а также инструменты. с помощью JMX).lookup(). в которых обычно применяется по одному логгеру на пользовательский класс. как и при любом другом вызове метода Стремление достичь такой же скорости. 5.class). что этих методов там нет. ред. Для всех посредников может потребоваться слишком большой объем постоянного поколения памяти Во время исполнения гарантируется безопасность типов. чтобы оптимизировать мониторинг исполнения задач (например. При несовпадении — некрасивое исключение Статические. то есть с ними довольно сложно работать напрямую. как и при других вызовах методов Еще одна дополнительная функция. В этом коде выражение lookupClass() можно считать эквивалентом getClass(). . то окажется.getLogger(MethodHandles. Постоянное поколение памяти не потребляется Производительность Медленная скорость работы по сравнению с альтернативными вариантами Такая же скорость. то при явном использовании имени класса могут возникать проблемы. Могут быть слишком строгими. Имея в арсенале такую новую технологию. в вашем приложении требуется сделать общедоступными дополни� тельные методы.lookupClass()). когда требуется работать с фреймворками логирования.3. 1 Блоб (от англ. как дескрипторы методов. насколько он хрупок. предоставляемая дескрипторами методов. рассмот� рим некоторые низкоуровневые детали файлов классов. Если осуществить его рефакторинг для перемеще� ния в класс-наследник или базовый класс.188 Часть 2. Но существуют обстоятельства. log4j) приблизительно такого рода: Logger lgr = LoggerFactory. Но если проверить управляющий API. binary linked object — «объект двоичной компоновки») — объектный файл без публично доступных исходных кодов. что повторная компиляция и повторное развертывание выполняются без проблем. загружаемый в ядро операционной системы. — Примеч. то знаете. Это особенно полезно в тех ситуациях. — это возможность определить текущий класс из статического контекста.

String getUpdateText(). Начнем с введения в javap и изучения основных переключателей. которыми внутрисистемно пользуется виртуаль� ная машина Java.ch04.ch04. какие методы объявлены в классе. wgjd. где он находится.lang.lang. Или же придется исследовать класс.lang.Object). можете пока пропустить этот раздел.Update). public int hashCode(). который должен был создать. public boolean equals(java. в составе стан� дартной виртуальной машины Java от Oracle поставляется инструмент.String toString(). играющий важную роль в понимании прин� ципов работы байт-кода.1. public java.Object { public wgjd. до вывода байт-кода. Он очень удобен для заглядывания внутрь файлов классов и их дизассемблирования. 5. Файлы классов и байт-код 189 Чтобы исправить такую проблему с развертыванием.lang.ch04. 5. protected и — доступ по умолчанию — защищенные на уровне пакета методы. может понадобиться про� верить. . что знакома нам по человекочитаемой версии кода.Update$Builder. public java.Update(wgjd. который мы обсуждали в главе 4. к которому у вас нет исходного кода или до� кументация которого (на ваш взгляд) неверна.ch04. } По умолчанию javap отображает методы с уровнем видимости public.2.ch04. Далее мы рассмотрим пул констант — в виртуальной машине Java это настоящий «ларчик полезностей». к нему придется обращаться при чтении последующих глав и разделов. $ javap wgjd/ch04/Update. wgjd. Знакомство с javap Инструмент javap может использоваться для решения множества полезных за� дач — от просмотра того. Если вы хотите поскорее отправиться дальше.3. Когда мы подробнее займемся изучением виртуальной машины Java.Update extends java. предоставля­ емых для просмотра характеристик файлов классов. К счастью. Потом мы исследуем пред� ставления имен методов и типов.3.Author getAuthor(). При примене� нии переключателя -p вы также увидите закрытые (private) методы и поля. но не забудьте. эти внутрисистемные имена будут встречаться нам чаще. Внутренняя форма сигнатур методов На внутрисистемном уровне виртуальная машина Java использует несколько иную форму сигнатур методов. которые проверяют содержимое файлов классов.java" public class wgjd. — возможно. чем та.Глава 5. Для решения таких и подобных задач требуется пользоваться инструмента� ми. Рассмотрим простейший случай использования javap применительно к классу Update для обновления микроблогов. на самом ли деле javac создал тот файл класса.class Compiled from "Update. называ­ емый javap . отображаемой в javap .

Необходимые технологии В компактной форме имена типов сокращаются. Такая практика применяется в одной из важных частей файла класса — пуле констант. поэтому их можно напрямую разрешать). javap предоставляет полезный переключатель -s.. но имена дескрипторов типов генерируются полными. чем Object.lang. Ссылочный тип (например.java" public class wgjd..Update extends java. Например.ch04.2. Signature: ()Lwgjd/ch04/Author. выводящий дескрипторы типов сигнатур для некоторых методов.ch04. public java.String getUpdateText(). Signature: (Lwgjd/ch04/Update.2.lang. Их полный список приведен в табл. каждый тип в сигнатуре метода представлен дескриптором типа. чем имя типа. длиннее. Signature: ()I .Object { public wgjd. . Ljava/lang/String.Update). } Как видите. — для строки) S short Z boolean [ Массив В некоторых случаях название дескриптора типа может быть длиннее. Signature: ()Ljava/lang/String.ch04. Дескрипторы типов Дескриптор Тип B byte C char (16-битный символ Unicode) D double F float I int J long L<имя_типа>. Таблица 5.)I public int hashCode().190 Часть 2.class Compiled from "Update. Такие сокращенные формы иногда также называются дескрипторами типов.Author getAuthor(). В следующем примере показан еще один способ использования дескрипторов типов. 5. тип int обознача� ется буквой I. Ljava/lang/Object. рассмотренных нами выше: $ javap -s wgjd/ch04/Update. отображаемое в исходном коде (например. public int compareTo(wgjd.

Файлы классов и байт-код 191 5. Он позволяет без труда протестировать синтаксическую возможность или библио� теку Java. Рассмотрим очень простой пример работы с пулом констант — так мы не за� путаемся в деталях.Глава 5. #12 = Utf8 LineNumberTable .inst:Lwgjd/ch04/ScratchImpl. } } Чтобы просмотреть информацию в пуле констант. Но. = Utf8 <clinit> = Utf8 ()V = Utf8 Code = Fieldref #1. public class ScratchImpl { private static ScratchImpl inst = null. то можете считать пул констант аналогом таких таблиц. Если вы изучали такие языки. inst. где явно используются таблицы символов. Эта команда выводит множество дополнительной информации. Но мы сосредоточимся на тех записях из пула констант. а не только пул констант.run(). Листинг 5.3.5 показан простой «тренировочный» класс. в отличие от других языков. Для этого напишем небольшой фрагмент кода в run(). которые отно� сятся к тренировочному классу. Вот пул констант: #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 = Class #2 // wgjd/ch04/ScratchImpl = Utf8 wgjd/ch04/ScratchImpl = Class #4 // java/lang/Object = Utf8 java/lang/Object = Utf8 inst = Utf8 Lwgjd/ch04/ScratchImpl. можно использовать javap -v.3. содержащейся в пуле констант. в которой предоставляются удобные краткие вариан� ты именования для доступа к другим (константным) элементам файла класса. применяемым в виртуаль� ной машине Java. #11 = NameAndType #5:#6 // instance:Lwgjd/ch04/ScratchImpl.#11 // wgjd/ch04/ScratchImpl. Пул констант Пул констант — это область. Java не предоставляет полного доступа к информации. Простой тренировочный класс package wgjd.5. private ScratchImpl() { } private void run() { } public static void main(String[] args) { inst = new ScratchImpl(). как C или Perl.ch04. В листинге 5.

где содержится дескриптор типа Utf8 Поток байтов.)V #28 = Utf8 SourceFile #29 = Utf8 ScratchImpl. Указывает на имя класса (в виде записи Utf8) Fieldref Определяет поле.run:([Ljava/lang/String. записи в пуле констант типизированы. например CONSTANT_Class. описывает константу MethodType . Указывает на Class и NameAndType этого поля Methodref Определяет метод. будет именем этого класса.3.#16 // java/lang/Object. Таким образом.#16 // wgjd/ch04/ScratchImpl."<init>":()V = Methodref #1.)V = Methodref #1. представляющий символы в кодировке Utf8 InvokeDynamic Новинка Java 7. Необходимые технологии #13 #14 #15 #16 #17 #18 #19 #20 #21 #22 #23 #24 #25 #26 = Utf8 LocalVariableTable = Utf8 <init> = Methodref #3. Кроме того. Иногда записи из пула констант рассматриваются с префиксом CONSTANT_. раздел 5.#21 // wgjd/ch04/ScratchImpl. описывает константу MethodHandle MethodType Новинка Java 7. на которую указывает запись Class.#27 // wgjd/ch04/ScratchImpl. 5. В табл. Записи в пуле констант Имя Описание Class Константа класса.3 показан набор возможных записей при работе с пулом констант.192 Часть 2. запись Utf8."<init>":()V = NameAndType #14:#8 // "<init>":()V = Utf8 this = Utf8 run = Utf8 ([Ljava/lang/String. Таблица 5.java Как видите. запись типа Class ссылается на запись типа Utf8.)V #27 = NameAndType #18:#19 // run:([Ljava/lang/String. Тип указывает на Utf8. они ссылаются друг на друга.5 MethodHandle Новинка Java 7. Указывает на Class и NameAndType этого поля String Строковая константа. Указывает на Class и NameAndType этого поля InterfaceMethodref Определяет метод интерфейса. = Utf8 main = Methodref #1. содержащую символы Integer Целочисленная константа (4 байт) Float Константа с плавающей точкой (4 байт) Long Многоразрядная (длинная) константа (8 байт) Double Константа двойной точности с плавающей точкой (8 байт) NameAndType Описывает пару из имени и типа. Запись Utf8 означает строку. см. Так. Указывает на запись Utf8.run:()V = NameAndType #18:#8 // run:()V = Utf8 args = Utf8 [Ljava/lang/String.

Итак. while и т. Возьмем. В предыдущем примере показан вариант проверки целостности. Сейчас мы рас� смотрим его подробнее — для этого вспомним. Чтобы разрешить ее.). а не «машинный код для вообра� жаемого процессора». как исходный код превращает� ся в байт-код. тип и класс. В байт-коде они показаны в виде команд ветвления (перехода). рассмот� рим относительно простой пример.  Байт-код — это абстрактное представление. 5. обычно это дела� ется динамически. Байт-код До сих пор байт-код оставался в нашем разговоре на заднем плане. а #11 — это #5:#6. Запись #1 ссылается на сам класс ScratchImpl. в которой он работает.  Каждый код операции представлен одним байтом (отсюда и название «байткод»). Возникает своего рода циклическая зависимость. пригодным для чтения человеком. это поможет вам яснее представлять возможности платформы. #10 имеет значение #1.  Байт-код — это непосредственное представление программы. что соответствует константе #11 из класса #1. вам потребуется знать имя. Даже если вы не совсем досконально его .Глава 5. Fieldref из записи #10. Мы рассмотрели некоторые основы структурирования файла класса. скажем. Например. Перейдем к следующей теме — байт-коду. в котором оно находится. применяемого в тренировочном классе. На верификационном этапе загрузки классов есть шаг. но об этом подробнее поговорим в главе 6 и далее. в принципе. что #1 действительно является константой типа Class. Когда вы поймете.  Байт-код создается с помощью javac из файлов исходного кода на Java. а #11 — NameAndType. Чтобы полностью понимать. #10 ссылается на ста� тическую переменную inst в самом классе ScratchImpl (о чем вы и сами могли догадаться из кода. что мы уже о нем знаем. В свою очередь. и ма� шинным кодом. в ходе которого мы убе� ждаемся. п. Он занимает про� межуточное положение между кодом. пере� менная. именуемая inst типа ScratchImpl. так и в среде времени исполнения. При изучении байт-кода мы сталкиваемся с ситуацией. Файлы классов и байт-код 193 Пользуясь этой таблицей. нужно ориентироваться как в байт-коде. что статическая информация в файле класса является непротиворечивой (согласованной).  Некоторые высокоуровневые языковые возможности удалены из байт-кода при компиляции. можете рассмотреть пример разрешения констант из пула. станете лучше разбираться в принципах работы всего нашего кода.5).  Байт-код может быть далее скомпилирован в машинный код. что происходит. которая осуществляется средой времени исполнения при загрузке нового класса. отсутствуют циклические конструкции языка Java (for.4.#11. показанного в листинге 5. Чтобы разрешить поле. немного напоминающей проблему «курицы и яйца». Легко убедиться.

4.class. который показан в листин� ге 5. кратких формах и т. а именно инициализация inst в null.Object { private static wgjd. а байты 2 и 3 представляют 16-разрядный индекс в пуле констант. когда подробнее почита­ ете о байт-коде в последующих разделах.ScratchImpl inst. а потом каталогизируем коды операций виртуальной машины Java. Наконец.java" public class wgjd. В данном случае 16-разрядный индекс имеет значение 10. что putstatic можно представить в виде байт-кода для вставки значения в статическое поле. Ситуация такова.ScratchImpl extends java. Необходимые технологии поймете с первого раза.5. Переходим к конструктору. Code: 0: aconst_null 1: putstatic #10 // Поле inst:Lwgjd/ch04/ScratchImpl. Мы отдельно рассмотрим каждый раздел вывода javap — все они очень инфор� мативны. начнем с заголовка. неожидан� ного или даже интересного: $ javap -c -p wgjd/ch04/ScratchImpl. посвященный конкатенации строк. Итак.194 Часть 2.ch04. private wgjd. static {}. то есть значение (в данном случае null) будет сохранять� ся в поле. Кроме того. опе� раций активизации. Здесь и происходит инициализация переменной. которому соответствует запись #10 в пуле констант.1.ScratchImpl(). в котором нужно рассматривать среду времени исполнения.ch04. Здесь нет ничего страшного. Code: 0: aload_0 1: invokespecial #15 // Метод java/lang/Object. мы изучим еще один пример. 5. который содержится внутри методов. Байт 4 от начала потока байт-кода — это код операции return.lang. После этого примера мы опишем контекст. Далее перейдем к статическому блоку. поговорим о кодах для арифметических операций. Итак. то легко мож� но запутаться. В нашем примере мы воспользуемся тренировочным классом.class Compiled from "ScratchImpl. Наша основная цель — исследовать байт-код.ch04. В частности. то есть конец блока кода. мы будем использовать ключ -p — так мы увидим и тот байткод. Если попытаться вникнуть во всю эту информацию сразу. 4: return Числа в предыдущем коде означают задержку в потоке байт-кода с момента запуска метода. Для начала попробуем исследовать байт-код из файла . Пример: дизассемблирование класса При использовании javap с ключом -c можно дизассемблировать классы. который находится в закрытых методах. байт 1 соответствует коду операции putstatic."<init>":()V 4: return . п. можете вернуться к нему позже.

195 Глава 5. Файлы классов и байт-код Не забывайте. Закрытые методы не могут быть переопределены. Рас� смотрим оставшиеся байт-коды основного метода: 7: 10: 13: 16: putstatic getstatic invokespecial return #10 #10 #22 // поле inst:Lwgjd/ch04/ScratchImpl. // метод run:()V } Команда 7 сохраняет предварительно созданный экземпляр-одиночку (сингл� тон).String[]). Большинство вызовов методов будут преобразовываться в команды invokevirtual. // поле inst:Lwgjd/ch04/ScratchImpl. что в Java пустой конструктор всегда неявно вызывает конструк� тор суперкласса.lang. Здесь вы наблюдаете этот шаг в байт-коде — речь о команде invokespecial. Общая стратегия заключается в том. — это довольно простое представление. Code: 0: return В главном методе вы инициализируете inst и немного занимаетесь созданием объектов. Выражение «байт-код должен быть тупым» отражает общее отношение разработчиков виртуальной машины к байт-коду. Команда 10 возвращает его на верхнюю позицию в стеке. необходимо вызвать тело конструктора. Вообще. байт-код. Code: 0: new #1 // класс wgjd/ch04/ScratchImpl 3: dup 4: invokespecial #21 // метод "<init>":()V Этот паттерн состоит из трех команд байт-кода: new. Код операции dup дублирует элемент. ПРИМЕЧАНИЕ Вообще. поскольку вызываемый метод run() является закрытым (приватным). Обратите внимание: команда 13 — это invokespecial. получаемому из исходных языков. любой вызов метода преобразуется в одну из инструкций вызова виртуальной машины. Чтобы полностью создать объект. так что вы вызываете этот блок кода с помощью invokespecial. по� этому вам не нужно. находящийся на вершине стека. . Код операции new просто выделяет память для нового экземпляра. так что команда 13 может вызвать метод применительно к этому экземпляру. что основную часть оптимизации выполняют динамические компиляторы. В методе <init> содержится код конструктора. создаваемый javac. а начинается работа со сравнительно простой точки. которые нужно научиться узнавать: public static void main(java. В методе run() нет никакого кода. так как мы работаем лишь с пустым трениро� вочным классом: private void run(). чтобы здесь осуществлялся стандартный виртуальный поиск Java. не отличающееся глубокой оптимизацией. Это один из самых простых и распространенных паттернов байт-кода. dup и invokespecial для <init> и всегда представляет собой создание нового экземпляра.

4. при связывании класса с работающей средой ее байт-код будет проверяться. ПРИМЕЧАНИЕ Манипуляции со значениями стека будут возможны лишь в том случае.4 показано. Кроме того. Иногда он именуется стеком операндов или стеком вычислений. применяемая в виртуальной машине Java. ему требуется область памяти. если значения в стеке имеют правильные типы. Необходимые технологии Далее перейдем к обсуждению среды времени исполнения. арифметические операции. если. что методы из загружаемых классов не пытаются злоупо­ треблять стеком.196 Часть 2. необходимо разбираться в том. Одно из самых очевидных свойств. Большая часть верификации сводится к ана� лизу паттерна типов в стеке. Неопределенные или вредные вещи могут произойти. где на лету записывается. К основным опе� рациям относятся: загрузка/хранение. Рис. Таким образом. x64 или ARM-чипа). которую он будет исполь� зовать как стек вычислений для расчета новых значений. Использование стека для операций над числами Как было указано выше в этой главе. не вызывают проблем. активизация методов и платформенные операции. Потом мы обсудим возможные формы сокращенного доступа к кодам операций. которые исполь� зуются для описания основных семейств инструкций байт-кода. как управляется стеко� вая машина. которым виртуальная машина Java отлича� ется от аппаратного процессора (например.2. 5. 5. например. — отсутствие в ней регистров процессора. После этого мы познакомим вас с таблицами. контроль вы� полнения. а потом попытались поступить с ней как с int и выполнить над ней арифметические операции. 5. соответственно. Среда времени исполнения Чтобы понимать байт-код. каждый ра� ботающий поток нуждается в стеке вызовов. Вместо них для всех вычислений и операций используется стек. какие мето� ды в настоящий момент находятся в работе (именно этот стек будет отражаться . мы поместили в стек ссылку на объект. призванные гарантировать.4. как стек операндов может использоваться при операции сложения двух целых чисел. На рис. которая нужна для работы байт-кода. неграмотно оформленные (или изначально зловредные) классы даже не попадают в систему и. Пока метод работает. На этапе верификации при загрузке классов проходят подробные проверки. а далее перейдем к другому примеру.

. вычисляющий. Но как только вы входите в новый метод. В результате управление будет передано вызванному методу. Отдельные простейшие коды операций (например. Файлы классов и байт-код 197 в стектрейсе).3. Введение в коды операций Байт-код виртуальной машины Java состоит из последовательности кодов опера� ций (опкодов). что из него будут удалены аргументы. выполняемых в вызываемом методе. В некоторых случаях два этих стека могут взаимодействовать. чтобы значения. Это заметно в некоторых семействах кодов операций. Каждый код операции обозначается однобайтным значением. но. которые вы хотите передать. Затем метод getNumberOfPets() будет вызван с применением кода операции ак� тивизации. Соответственно. появится в стеке вызовов. За объектом следуют все аргументы. возвращаемое значение помещается в стек операндов вызывающей стороны. Это происходит в рамках того же процес� са. После этого опе� рация сложения может взять два значения и суммировать их. а потом преобра� зовать его — так. Чтобы произвести такие вычисления. Рас� смотрим. а тот метод. требуется перейти к использованию нового стека опе� рандов. на место которых будут вставлены результаты. вы помещаете 3 в стек операндов. Коды операций ожидают увидеть стек в конкретном состоянии. 5. чтобы исследовать его весь в подробностях. В настоящее время используется около 200 таких кодов. множество кодов операций относятся к тому или иному семейству. в который мы только что перешли. Перейдем к исследованию байт-кода.4. для хранения и сложения данных) обязательно должны иметь несколько вариаций. в ходе которого getNumberOfPets() удаляется из стека вызовов. причем за каждой командой может идти по нескольку аргументов. — в данном случае petRecords) в стек вычислений. чтобы вы могли со� ставить о них общее впечатление. но такие коды операций встречаются сравнительно редко. всего может существовать 255 кодов операций. отличающихся в зависимости от примитивного типа. следующий код: return 3 + petRecords. а не доско� нальное исследование. которые уже могут находиться в стеке операндов вызывающего метода. которым они манипулируют. ПРИМЕЧАНИЕ Виртуальная машина Java не является полностью объектно-ориентированной средой времени исполнения — ей знакомы и примитивные типы. Это делается для того. сколько домашних любимцев (pets) у Бена. к счастью. Мы собираемся провести обзор основных возможностей.getNumberOfPets("Ben"). например. не повлияли на результаты вычислений. Мы по очереди обсудим каждое из семейств. Когда метод getNumberOfPets() завершается.Глава 5. Некоторые операции нельзя строго отнести к кон� кретному семейству. Потом нужно вызвать метод. Для этого вы помещаете объект-получатель (тот самый. Это слишком большой список. Это обширная тема с множеством специ� альных случаев. применительно к которому вы вызываете метод.

расположенное по указанному индексу в пуле констант и относящееся к объекту. это означает.198 Часть 2. Рассмотрим пример строки из табл. выполняющих схожие задачи. getfield i1. находимое системой в пуле констант для получения искомого поля (как вы помните.  Компоновка стека — демонстрирует состояние стека до и после того. Если аргументы даны в скобках. используемые как справочный индекс в пуле констант или локальной таблице переменных. занимавшего верхнюю позицию в стеке. а именно строку о коде операции getfield. Вместе эти аргументы составляют 16-разрядное значение.  Имя — означает общее имя типа кода операции. Он используется для считывания значения из поля объекта. Аргументы. это касается кодов операций активизации). начинающиеся с i. как выпол� нится код операции. какова функция кода операции. 5. В табл. то они будут использоваться не во всех кодах операций. осуществляемые в этом семействе. Кроме того. Такой принцип удаления экземпляров объекта в ходе операции — просто один из способов держать байт-код компактным. Необходимые технологии В таблицах с кодами операций по четыре столбца.4 перечислены основные опе� рации.  Описание — рассказывает. то они объединяются. не приходится постоянно помнить о необходимости удаления тех эк� земпляров объектов. i2 [obj] → [val] Получает поле. i2 означает: «сделать 16-раз� рядный индекс из этих двух байт».4. 5.4. В некоторых случаях может быть несколько похожих кодов операций. — это коды операций.  Аргументы — представляют собой те аргументы. расположенного на верхней позиции в стеке. что принимаются кодом опе� рации. Если аргументов больше. что за кодом операции в байтовом потоке следуют два аргумента. 5. Коды операций для загрузки и сохранения Семейство кодов операций для загрузки и сохранения предназначено для за� грузки значений в стек или получения их. объект уда� ляется и заменяется значением этого поля для объекта. Столбец с компоновкой стека показывает. с которыми вы закончили работу.4. что они будут использоваться не во всех кодах операций либо эти элементы опциональны (например. Если элементы даны в скобках. Так. без длительной кропотливой очистки. Во втором столбце записано. . i1. находящемуся на верхней позиции в стеке В первом столбце указано имя кода операции — getfield. что после нахождения индекса в пуле констант класса объекта. индексы в пуле констант всегда 16-разрядные).

i2 [obj] → [val] Получает поле. Такая операция берет аргументы из верхней позиции в стеке и произво� дит над ними требуемые вычисления. Имеет вариантные формы getfield i1.4. 5. которые предназначены для приведения типов друг к другу. В табл. astore. Коды операций приведения имеют очень краткие названия. поэтому в таблице оно дано в скобках. Имеет сокращенные формы и типоспецифические варианты Продложение  . Следует отметить. относящееся к объекту. 5. например i2d для приведения int к double. Файлы классов и байт-код Таблица 5. Арифметические коды операций Эти коды соответствуют операциям. удаляя его из стека в процессе работы. расположенному на верхней позиции в стеке putfield i1. Другой код. i2 [obj. Коды для арифметических операций Имя Аргументы Компоновка стека Описание add — [val1. Аргументы (всегда относящиеся к прими� тивным типам) должны в точности совпадать. Имеет сокращенные формы и типо-специфичные варианты dup [val] → [val. val] → [] Помещает значение в поле объекта по указанному индексу в пуле констант Выше мы упоминали. val2] → [res] Складывает два значения (которые должны относиться к одному и тому же примитивному типу) из верхней позиции в стеке и сохраняет результат в стеке. Таблица 5.199 Глава 5.5. Имеет зависящие от типа варианты и варианты wide store (i1) [val] → [] Сохраняет значение (примитив или ссылку) в локальной переменной. Например. расположенное по указанному индексу в пуле констант. что существует несколько различных форм команд для загрузки и сохранения.5. загружающий в стек из локальной переменной число двойной точности. есть код операции dload. извлекает из стека ссылку на объект и помещает ее в локальную переменную. занимающее верхнюю позицию в стеке. но платформа предлагает и множе� ство таких кодов операций. Коды операций загрузки и сохранения Имя Аргументы Компоновка стека Описание load (i1) [] → [val] Загружает в стек значение (примитив или ссылку) из локальной переменной.4. Имеет сокращенные формы и типоспецифические варианты ldc i1 [] →[val] Загружает в стек константу из пула.5 перечислены базовые арифметические операции. val] Дублирует значение. выполняющим над стеком арифметические действия. что слово cast (приведение) не фигу� рирует в названиях.

Необходимые технологии Таблица 5.6. приведенных в табл. управляющие конструкции высокоуровневых языков от� сутствуют в байт-коде виртуальной машины Java. специфичные для каждого возможного варианта приведения 5.4.6. Вместо этого поток выполнения контролируется небольшим количеством примитивов. расположенные в верхней позиции в стеке. b2 [] → [] Безусловный переход к точке ветвления. Коды операций для контроля исполнения Имя Аргументы Компоновка стека Описание if b1. Команды jsr использу� ются для доступа к небольшим самодостаточным областям байт-кода. Коды операций для контроля исполнения Как было указано выше. val2] → [res] Вычитает два значения (которые должны относиться к одному и тому же примитивному типу). расположенные в верхней позиции в стеке. Имеет широкую форму jsr b1. Имеет широкую форму ret index [] → [] Возврат к смещению. b2 [val1. указанному в локальной переменной по заданному индексу tableswitch {depends} [index] → [] Используется для реализации переключения lookupswitch {depends} [key] → [] Применяется для реализации переключения Подобно индексным байтам. 5. Таблица 5. val2] → [res] Перемножает два значения (которые должны относиться к одному и тому же примитивному типу). аргументы b1. Имеет сокращенные формы и типоспецифические варианты mul — [val1.5 (продолжение) Имя Аргументы Компоновка стека Описание sub — [val1. Имеет сокращенные формы и типоспецифические варианты (cast) — [value] → [res] Приводит значение одного примитивного типа к другому. то необходимо перейти к указанной точке ветвления goto b1. Имеет формы. b2 [] → [ret] Переходит к локальной подпроцедуре и помещает в стек адрес возврата (смещение перед следующим кодом операции). b2 применяются для создания местоположения байт-кода в заданном методе. используемым для поиска констант. в смещениях после окончания . которые могут находиться вне основного потока задач (например.200 Часть 2. расположенные в верхней позиции в стеке. val2] → [res] Делит два значения (которые должны относиться к одному и тому же примитивному типу).6. Позже именно в это местоположение будут выполняться переходы. Имеет сокращенные формы и типоспецифические варианты div — [val1. val2] → [] или [val1] → [] Если конкретное условие выполняется.

которые необходимо учитывать при ра� боте с кодами операций активизации. invokeinterface имеет дополни� тельные параметры. см. i2 [obj.8. Во-первых.)] → [] Вызывает метод интерфейса invokespecial i1. . Широкие формы команд goto и jsr принимают по 4 байта аргументов и создают смещение. Это семейство подроб� но описано в табл. (val1. но в настоящее время не используются. Это означает.)] → [] Вызывает статический метод invokevirtual i1. Файлы классов и байт-код основного байт-кода данного метода). а также коды операций для работы с потоками. поэтому вам и требуется для этого случая иной код операции активизации. .. Это бывает важно в двух случаях — при работе с закрытыми (private) методами и при вызовах методов супер� класса. (val1.. 0. 5. .5 Существует несколько особенностей. 5. Два дополни� тельных нуля в invokedynamic предусмотрены для обеспечения дальнейшей совме­ стимости. 5..)] → [] Вызывает «обычный» метод экземпляра invokeinterface i1. i2.] → [] Динамический вызов.7. . новинка Java 7.. Другой важный момент заключается в различии между обычным и специальным вызовом метода экземпляра. В определенных обстоятельствах (например. Часто без такого смещения можно обойтись. О нем мы подробнее поговорим в разделе 5. а именно monitorenter и monitorexit. 0 [val1... Обычный вызов виртуален. что кон� кретный метод. Они присутствуют по причинам исторического характера и для обратной совместимости. Пять кодов операций для активизации методов приведены в табл. в блоках для обработки исключений) такие команды могут быть полезны. Коды операций для активизации К этой группе относятся четыре кода операций. 5. 0 [obj. В обоих случаях правила переопределения не должны включаться. К ним также относится специальный код операции invokedynamic. . действующих в языке Java. который необходимо вызвать.8.. для создания новых . Коды операций для работы с платформой Семейство кодов операций для работы с платформой содержит: код операции new для выделения памяти для новых экземпляров объектов.5.. i2 [obj. Коды операций активизации Имя Аргументы Компоновка стека Описание invokestatic i1. раздел 5. (val1. count.. При специальных вызовах переопределения не учитываются. которое зачастую бывает больше 64 Кбайт.)] → [] Вызывает «специальный» метод экземпляра invokedynamic i1. i2 [(val1.7. Таблица 5.201 Глава 5..4. Коды операций для работы с платформой используются для управления опре� деленными моментами жизненного цикла объекта — например.4. отыскивается во время исполнения с при� менением стандартных правил переопределения методов. предназначенные для общего управления вызовом методов. i2.7.

чтобы продемонстрировать немного более сложный байт-код — код.8. чем может показаться на первый взгляд. Пример: сцепление (конкатенация) строк Добавим информацию в наш тренировочный класс. if (args. но в самом коде происходит больше операций.10. Необходимые технологии объектов и блокировки их.out. относящиеся к семейству «загрузка и сохранение». Как вы помните. Попробуем применить эту теорию на практике и рассмотрим другой пример. 5. System. } . то там. что код операции new просто выделяет место под объект. 5.9. что к одним локальным переменным требуется обращаться значительно чаще. Так и появились коды операций aload_0 и dstore_2. затрагивающий большинство тех семейств кодов операций.4. Он не может быть вызван из пользовательского кода Java. что же происходит при сцеплении двух строк вместе с применением оператора +? В таком случае создается новый объект String. а после него — invokespecial для вызова метода <init>. Коды операций для работы с платформой Имя Аргументы Компоновка стека Описание new i1. чем к другим.length > 0) str = args[0]. Сокращенные формы записи кодов операций У многих кодов операций есть сокращенные формы. Таблица 5. Высокоуровневая концепция создания объекта также предусмат� ривает выполнение кода внутри конструктора. строки String в Java являются неизменяемыми. непосредственно соответствующий созданию объекта.202 Часть 2. с которыми мы познакомились выше. Важно отметить. указываемому константой по заданному индексу monitorenter — [obj] → [] Блокирует объект monitorexit — [obj] → [] Разблокирует объект На уровне байт-кода конструктор преобразуется в метод с особым именем — <init>. i2 [] → [obj] Выделяет память для нового объекта. не указывая при этом саму локальную переменную в качестве аргумента. Поэтому целесообразно иметь специальный код операции. если требуется со� хранить несколько байтов то тут. Паттерн таков: за new следу� ет dup.4.println("this is my string: " + str). означающий «произвести общую операцию прямо над локальной переменной». откорректированным сле� дующим образом: private void run(String[] args) { String str = "foo". Рассмотрим тренировочный класс с методом run(). удобные. Общий принцип заключается в том. В результате получается характерный паттерн работы с байт-кодом. относящегося к типу. Итак. а вот из байт- кода — может.

как байт-код предоставляет доступ к System. В этих командах демонстрируется создание объединенной строки.println:(Ljava/lang/String. что вы вызываете непустой конструктор — в данном случае StringBuilder(String).out.out:Ljava/io/PrintStream. а потом вызываете применитель� но к ним сначала append(). dup. которую вы желаете получить в качестве вывода. Вы не можете изменить строковый объект путем конкатенации. Code: 0: ldc #17 // Строка foo 2: astore_2 3: aload_1 4: arraylength 5: ifle 12 #A Если размер переданного вами массива меньше или равен нулю. что вы при этом создаете дополнительные объекты StringBuilder. переходите к команде 12. поэтому приходится создавать новый объект. Дело в том.toString:()Ljava/lang/String. 28: invokevirtual #36 // Метод java/lang/StringBuilder."<init>":(Ljava/lang/String. вызовем метод для вывода результатов: 31: invokevirtual #40 // Метод java/io/PrintStream.lang. Вы просто объединяете несколько строк. Результат на первый взгляд может показаться немного странным. 8: aload_1 9: iconst_0 10: aaload 11: astore_2 12: getstatic #19 // поле java/lang/System.)V 34: return . В частности. 15: new #25 // Класс java/lang/StringBuilder 18: dup 19: ldc #27 // Строка this is my string: 21: invokespecial #29 // Метод java/lang/StringBuilder.)Ljava/lang/StringBuilder.)V 24: aload_2 25: invokevirtual #32 // Метод java/lang/StringBuilder. В предыдущей строке показано.class Compiled from "ScratchImpl. а затем toString().Глава 5. Наконец. invokespecial). StringBuilder — просто удобный инструмент для такой операции.append (Ljava/lang/String. Такой паттерн байт-кода указывает.String[]). а байт-код внезапно вам сообщает. что строки в Java неизме� няемые. команды 15–23 демонстрируют создание объекта (new. но в данном случае после dup присут­ ствует ldc (загрузочная константа).java" private void run(java. Файлы классов и байт-код 203 Этому относительно простому методу соответствует следующий байт-код: $ javap -c -p wgjd/ch04/ScratchImpl.

что. 5. здесь детали выбора метода уточняются во время исполнения. Чтобы стать по-настоящему основательным Java-разработчиком. принимающий решение о том. invokedynamic сообщает виртуальной машине. Это команда вызова нового типа. На самом деле invokedynamic — усовершенствование. Такая функция применима лишь в очень сложных практических случаях. а также разберем подробный пример декомпиляции точки вызова с применением ново� . Если же вас заинтересовала данная тема — хорошо. Байткод добавляется в динамические языки для тех случаев. Пока удовлетворимся этим кратким введением в работу байт-кода и перейдем к следующей теме.out. ориентированное как раз не на Java. новый код операции называется invokedynamic. где определена компоновка стека для правильного invokevirtual. какой метод вызвать. которые будут использовать динамические возможности. JAVAC ПРОТИВ INVOKEDYNAMIC В Java 7 отсутствует непосредственная языковая поддержка для invokedynamic. можете прочесть резюме этой главы и переходить к следующей. что в Java 8 появятся новые языковые конструкции (например. какой метод придется вызвать. Напротив. Ситуация полностью соответству� ет табл. как функционирует ядро платформы. а на другие языки. 5. работающих с не Java-языками. Это означает. стандартные методы). Для это� го вызывается вспомогательный метод. Ожидается.204 Часть 2. нужно запус� тить javap применительно к некоторым из собственных классов и научиться рас� познавать распространенные паттерны байт-кода.out. без которой не обойтись любому практикующему разработчи� ку. когда их предполагается использовать на виртуальной машине Java 7 (но в некоторых толковых фреймворках Java были найдены способы для применения invokedynamic). а также для специалистов. что виртуальной машине не требуется прорабатывать все детали на этапе компилирова� ния и связывания — в отличие от случаев работы с другими кодами операций. Но.5. Это означает. Ни одно Java-выражение не компилируется javac непосредственно в байт-код invokedynamic.7. ее не назовешь той. Это новейшая разработка в области байткода Java — в версиях Java до 7-й ничего подобного не встречалось. что требу� ется отложить определение того. Мы расскажем. когда строка вывода собрана. несмотря на исключительный потенциал этой возможности. Она будет интересна для разработчиков фреймворков. invokedynamic В этом разделе мы поговорим об одной из самых технически затейливых возмож� ностей Java 7. если вы не собирались вникать в то. либо детально разбираться в работе нового байт-кода. Необходимые технологии Теперь. Он вызы� вается применительно к System. насколько необычен на практике код invokedynamic. поскольку два верхних элемента в стеке на данном этапе — это [System. можно вызвать метод println(). она применя� ется при вызовах методов. Речь пойдет об одной из важнейших новых возможностей в Java 7 — invokedynamic. <output string>]. В этом разделе мы рассмотрим особенности работы invokedynamic. Итак.

При первом выполнении вызывается загрузочный метод для точки. в пуле констант какого класса нужно искать метод. которая затем связы� вается с командой invokedynamic. сопровождающий команду invokedynamic. Отметим. Загрузочные методы принимают информацию о точке вызова и связывают динамический вызов. дей� ствующих в Java 6. Индекс. Разница лишь в том. Как работает invokedynamic Для поддержки invokedynamic в число определений пула констант было добавлено несколько новых записей. Он возвращает точку CallSite. должен указывать на констан� ту типа CONSTANT_InvokeDynamic. что именно вызывать.5. что при вызове invokedynamic не указыва� ется. Рис. Файлы классов и байт-код 205 го байт-кода.Глава 5. Подобная конструкция означает.5. Для получения ответа на этот вопрос вызывается загрузочный метод. BSM). можно вызвать и сам метод — эту задачу вы� полняет MethodHandle.5. Иногда такие методы именуются загрузочными (bootstrap methods. Они обязательно должны быть статическими и иметь определенную сигнатуру аргументов. . Второй указатель направлен на CONSTANT_NameAndType. что она является несвязанной (unlinked). по кото� рой мы определяем. Первый указатель служит индексом в таблице методов. 5. что вызовы invokedynamic потенциально можно оптимизировать с помощью динамиче� ского компилятора примерно так же. Эта точка вызова удерживает MethodHandle.1. 5. которую невозможно предоставить в условиях технологических ограничений. Команда invokedynamic начинает работать без целевого метода — о ней говорят. что и становится результатом обращения к точке вызова. что необходимо полностью понимать этот механизм. чтобы использовать языки и фреймворки. что CONSTANT_InvokeDynamic напоминает обычный CONSTANT_MethodRef. возвращая экземпляр CallSite. Такие способы оптимизации мы подробнее обсудим в следующей главе. как это делается с вызовами invokevirtual. удерживаемый CallSite. Это показано на рис. 5. в которых активно задействуется invokedynamic. Сравнение виртуального и динамического назначения методов Когда точка CallSite уже связана. Они обеспечивают необходимую поддержку invokedynamic. В данном случае понятно. К нему прикрепляется два 16-разрядных указателя (то есть 4 байта).

Ljava/lang/String.Ljava/lang/Object.org/) — мощная библиотека промышленного масштаба. Пользуясь этой библиотекой. Инструмент был создан в рамках разработки invokedynamic. Code: 0: invokedynamic #22. Реми Форакс (Remi Forax) и команда ASM предоста� вили простой инструмент для создания тестовых классов. полностью поддерживающих новый байт-код. можно создать представление класса. имеющий всего один статический метод с единственной командой invokedynamic. что поддержка invokedynamic на уровне синтаксиса в Java 7 отсутствует.206 Часть 2. которые относятся к динамическому вызову: BootstrapMethods: 0: #17 invokestatic test/invdyn/DynamicIndyMakerMain. а потом превратить его в поток байтов. что отдельные объекты CallSite можно «перенаправить» (то есть изменять целевые методы. которые делают именно это. Эта информация может быть либо записана на диск.math. Пример: дизассемблирование invokedynamic-вызова Выше мы уже упоминали.BigDecimal invokedynamic().5. Необходимые технологии Стоит также отметить. как вызов invokedynamic может быть представлен в байт-коде. Для этой цели хорошо подходит ASM (http:// asm. рассмотрим те записи. либо передана загрузчику классов для вставки в работающую виртуальную машину.) Ljava/lang/invoke/CallSite.ow2. Method arguments: #19 1234567890. В следующем подразделе мы на простом примере разберем. 5. 5: areturn Как видите. В некоторых динамических языках такая функция используется очень активно. Рассмотрим байт-код для такого обертывающего метода: public static java. применяемая в множестве широко известных Java-фреймворков. внутри которого содержится ко� манда для динамического вызова. Итак.2. на которые может быть проставлена ссылка на протяжении жизненного цикла). включающее команду invokedynamic. пока ничего интересного — все сложное действо разворачивается в пуле констант. Поэтому приходится пользоваться библиотекой для манипуляций с байт-кодом. Ljava/lang/invoke/MethodType.bsm: (Ljava/lang/invoke/MethodHandles$Lookup.1234567890 #10 = Utf8 ()Ljava/math/BigDecimal. позволяющей создать файл . ASM была одной из первых библиотек.class. 0 // InvokeDynamic #0:_:()Ljava/math/BigDecimal. Затем этот метод мож� но вызвать из обычного кода Java — он обертывает (скрывает) динамическую природу реального вызова. . В качестве простого примера прикажем ASM создать класс.

как именно работает весь механизм. а также возвращаемый тип. Мы не касались этого момента выше. 3. Как видите. Так или иначе. его следует рассмотреть внимательнее. Загрузочный метод (BSM) #0 — это обычный статический метод bsm(). 1. #0:#21 // #0:_:()Ljava/math/BigDecimal. Надеемся.Глава 5. если угодно — гибкости.6. Чем больше вы будете знать о внутренней организации платформы.  Дескрипторы методов — это важный новый API. на ко� торых построена работа виртуальной машины Java. На этом мы завершаем разговор об invokedynamic. для обеспечения безопасности типов требуется немало манипуляций. но этот механизм обеспечивает значительную безопас� ность. появившийся в Java 7.1234567890 _ #20:#10 // _:()Ljava/math/BigDecimal. а не на сами методы. Резюме В этой главе вы бегло познакомились с байт-кодом и загрузкой классов. что после прочтения этой главы вы усвоили следующие моменты. Мы по­ дробно исследовали формат файлов классов и вкратце рассмотрели среду времени исполнения. остается еще множество ситуаций. В записи #21 указано имя этой конкретной точки динамического связывания. 5. Файлы классов и байт-код #18 #19 #20 #21 #22 = = = = = Utf8 String Utf8 NameAndType InvokeDynamic 207 1234567890.1234567890 #18 // 1234567890. что таблица BootstrapMethods указывает на дескрипторы методов. тем более профессиональным разра� ботчиком станете. . так как он мог только запутать вас и не поспособствовал бы пониманию того. Запись #19 — это статический аргумент. а также о внутренней струк� туре байт-кода и загрузки классов. Код операции invokedynamic указывает на запись #22. переданный в загрузочный метод. приме� няемый к классу DynamicIndyMakerMain. Он заключается в том. Они важны для любого языка. какую и дол� жен иметь BSM. 2. Он явля� ется альтернативой рефлексии. «_». Он имеет верную сигнатуру.  Различные фазы загрузки классов обеспечивают реализацию функций безопас� ности и производительности во время исполнения. сохраняя немалую гибкость. предоставляемую виртуальной машиной Java. Она ссылается на загрузоч� ный метод #0 и NameAndType #21. который предполагается использовать на этой виртуальной машине. 4. BigDecimal (сохраняемый в строке #10). ПРИМЕЧАНИЕ Существует еще один уровень косвенности. в которых что-то может пойти не так во время исполнения.  Формат файлов классов и загрузка классов — это основные концепции. Чтобы разобраться в этом коде.

В частности.208 Часть 2. Необходимые технологии  Байт-код виртуальной машины Java делится на семейства. Вы научитесь измерять и подстраивать производительность системы. вы узнаете. обязательной для каждого основательного Java-разработчика. Прочитав следующую главу. лежащих в основе виртуальной машины Java. вы будете уверен� но разбираться в анализе производительности — той теме. объединяемые по функциональному признаку. как работать с динамическим компилятором. . а также максимально эффективно использовать некоторые из самых мощных тех� нологий. которая часто остается недопонятой. Теперь пора перейти к следующей крупной теме.  В Java 7 появился invokedynamic — новый способ вызова методов. превращающим байт-код в сверхбы� стрый машинный код.

ваши клиенты будут голосовать ногами. где их нет. что человеческий мозг практически всегда ошибается. как извлекать из него пользу. что поведаем вам большой секрет о настройке произ� водительности — необходимы измерения. Почему же? Дело в том. В таком приложении услуги предоставляются множе� ству (пулу) зарегистрированных клиентов. веб-серверы Apache и серверы приложений Java. которые написаны не на Java (в част� ности. Более того. я.  VisualVM — инструмент для визуализации памяти. Очень часто самыми сложными узкими местами системы являются те компоненты. Здесь ошибаются все.6 Понятие о повышении производительности В этой главе:  почему так важна производительность. Джеймс Гослинг (James Gosling) — всем нам не уйти от подсознательной необъективности. Без измерений точная настройка производительности невозможна. Итак. Все это объединено совершенно стандартной сетевой конфигурацией. она часто трактуется неверно. Чтобы плохая производительность не портила весь ваш проект. какие компоненты системы работают наиболее мед� ленно. На самом деле правильный ответ на вопрос «Какая часть моего кода на Java нуждается в оптимизации?» обычно звучит так: «Никакая». когда мы пытаемся угадать. необходимо разбираться в результатах анализа производительности и знать. Если только вы не работаете на совершенно монополизированном рынке. Вы.  новый сборщик мусора G1. начнем с того. то есть ухо� дить от вас к конкурентам. база данных. так что при ее изучении внимание заостряется не на тех вещах.  динамическая компиляция. Она раздражает клиентов и портит репутацию приложения. Представим себе типичное (пусть и довольно консервативное) веб-приложение для электронной торговли. файловая система. Но без измерений Java-разработчик этого так и не узнает. сеть). В приложении есть SQL-база данных. . Вместо того чтобы найти и исправить реальную проблему. Анализ и подстройка производительности — это огромная тема. Мы всегда усматриваем закономерно� сти там. Плохая производительность губит приложения.

 не волшебная пыльца. Таким образом. которые практически не связаны с возникшей проблемой. найдется ли в системе достаточно памяти. так как виртуальная машина Java становится все интеллектуальнее в области оптимизации кода. Работа инженера по настройке производительности заключается в изучении этого вывода и поиске закономерностей. Это:  не набор подсказок и трюков. Кроме того. важнейшие теоретические сведения. насколько эффективно система решает поставленные перед ней задачи. которой нужно осыпать готовый проект. в основе которой лежит эксперимент. Вы должны уверенно отвечать на следующие фундаментальные вопросы. Необходимые технологии разработчик может потратить уйму времени на оптимизацию микрофрагментов кода. Мы постараемся ответить на самые фундаментальные вопросы. а знать.  Почему производительность так важна?  Почему сложно анализировать производительность?  Какие аспекты виртуальной машины Java потенциально вызывают сложности при оптимизации?  Как следует воспринимать настройку производительности и работать с ней?  Каковы наиболее распространенные подоплеки плохой производительности? . который требует ввода и дает выводы — параметры производительности. Поэтому вне контекста большинство таких советов будут бес� полезны (а некоторые из них даже вредны). следует знать. Нужно не гадать. показывающие. Можете относиться к вашему коду как к научному опыту. А чтобы знать наверняка.  Если на сайте начнется распродажа и у вас появится вдесятеро больше посети� телей. Кроме того.210 Часть 2. опирающимся на подсказ� ки и трюки. На самом деле анализ производительности — это наука. чем бывает на ресурсе обычно. чем не является настройка производительности. Особенно осторожно следует относиться к подходам. чтобы справиться с таким наплывом?  Сколько времени вашим пользователям обычно требуется ждать отклика при� ложения?  Каковы эти показатели в сравнении с показателями ваших конкурентов? Для уверенной настройки производительности необходимо перестать гадать о том. ка� сающиеся производительности. Дело в том. а также обозначить основные вехи. Эта глава — своеобразное введение в практику настройки производительности в языке Java. какие элементы замедляют работу всей системы. Но эта тема очень велика. а не набором бабушкиных сказок и устного народного творчества. и мы сможем сообщить лишь азбучные истины. что виртуальная машина Java — очень сложная и отлично настроенная среда.  не секретный соус. настройка производительности может считаться отраслью прикладной статистики. требуются измерения. они быстро устаревают.

1.  масштабируемость. необходимое для обработки отдельно взятой единицы материала при известной загруженности. Понятие о повышении производительности 211 Кроме того. . мы сделаем введение в работу с двумя подсистемами виртуальной машины Java. так и гораздо более крупные системы. можно иметь в виду как единственный многопоточный процесс. на котором ожи� дание отображается как функция от возрастающей загруженности. 6. Некоторые из этих проблем рассмотрены Дугом Ли в контексте написания многопоточного кода. наиболее важными при настройке производительности.  деградация. Зачастую ожидание кво� тируется в расчете лишь на «нормальные» уровни загрузки. в которых необходимо ориентироваться. 6.  мощность. а также очерчивать цели. но при измерении производительности часто бывает полезен специальный график. который по� зволит вам выражать и формулировать возникающие проблемы с производитель� ностью. Начнем с определения нескольких важнейших терминов из лексикона специалистов по производительности:  время отклика.  эффективность. но здесь мы рассмотрим их гораздо более подробно.1. Для начала быстро разберемся с терминологическим аппаратом.  динамический компилятор.  пропускная способность. Говоря о производительности.  коэффициент использования. с которыми можете столкнуться в своем коде. чтобы вы могли познакомиться с темой и нача� ли применять приобретенные знания (достаточно сложные с теоретической точки зрения) при решении реальных проблем. Этого должно быть достаточно. Терминологическое описание производительности — базовые определения Чтобы дискуссия получилась максимально плодотворной. Это:  подсистема сборки мусора. формализуем отдельные концепции производительности. Ожидание Ожидание (latency) — это сквозное время. вплоть до кластерной серверной платформы.1.Глава 6.

 6.1. операционной системой и софтверным стеком).1. Часто говорят о количестве транзакций в секунду на какой-нибудь базовой платформе (например. с заранее указанным аппаратным обеспе� чением. 6. Свеча производительности 6. Рис. Обратите внимание: может существовать очень большая разница между степенью использования различных ресурсов. которые применяются для выполнения полезной работы. 6. демонстрирующий внезапное. Обычно такое явление называется свечой производительности. Коэффициент использования Коэффициент использования (utilization) представляет собой процент доступных ресурсов.1.3. которые система может выполнить за определенный период времени при заданных ресурсах. выделяемой на обработку единиц в нормальном рабочем режиме. что сервер исполь� зуется всего на 10 % — здесь речь идет о проценте вычислительной мощности процессора.212 Часть 2. например процессора и памяти.2.1 показан график. Необходимые технологии На рис. увеличение ожидания при возрастании загруженности. нелинейное резкое ухудшение (деградацию) показателя производительности — например. с сервером известной марки. а не для обслу� живающей работы (и тем более не простаивают). Пропускная способность Пропускная способность (throughput) — это количество единиц. . Часто говорят.

6.6. 6. реализуемый при известном времени отклика или пропускной способности. До определенного момента система может масштабироваться практически линейно.7. а потом вдруг начинается тяжелая деградация. что масштабируемость системы зависит от не� скольких факторов и не является постоянной величиной. Система. . менее эффективна. чем решение B. то решение A в два раза менее эффективно. транзакций). Эффективность Эффективность (efficiency) системы представляет собой результат деления про� пускной способности на количество используемых ресурсов.1. в которой такая производительность достигается мень� шим количеством ресурсов. Понятие о повышении производительности 213 6. В большинстве случаев бывает очень сложно достичь идеального линейного мас� штабирования.4. Если пропускная способность решения A удваивается при удвоении количе­ ства серверов в его пуле. которые могут выполняться в системе в определенный момент времени. Мощность Мощность (capacity) — это количество единиц работы (например. что ресурсы можно рассматривать и в контексте их стоимо� сти — если решение X стоит вдвое больше. Если решение A требует для достижения проектной пропускной способности в два раза больше серверов.5.1. чем B. Необходимо также отметить. Масштабируемость По мере того как в систему добавляются ресурсы. чем та. ее пропускная способность (или время отклика) изменяется. требующая большее количество ресурсов для достижения конкретной производительности.1. Другими словами. чем решение Y (либо если для эксплуа� тации производственной среды требуется в два раза больше персонала). это объем параллельной обработки. Такое изменение времени отклика или пропускной способности называется масштабируемостью (scalability) системы. то реше� ние Y в два раза эффективнее решения X. то мы наблюдаем идеальное линейное масштабирование.Глава 6. 6. Это своеобразное «пике» производительно� сти.1. Деградация При добавлении в действующую сетевую систему все большего количества рабочих единиц или клиентов обычно наблюдается изменение показателей времени откли� ка или пропускной способности. Оно называется деградацией (degradation) систе� мы под дополнительной нагрузкой. мы сравниваем два кластерных решения. Допустим. Не забывайте.

Без измерений как минимум одной наблюдаемой характеристики вы не сможете выполнить анализ производительности. выстроенный с внимательным учетом таких показателей. 6. как мы не раз еще отметим в этой главе. которые могут быть важны в определенных условиях. хотя абсолютное количество выполняемой работы. в начале работы не имеют четкого представления о том. В следующем разделе мы опишем подход. что «код должен работать быстрее». . требуются ясные ответы на следующие вопросы. увеличится. Прагматический подход к анализу производительности Многие разработчики. то вся система будет действовать более эффективно и укорачивать длительность обработки. Но в некоторых случаях деградация может оказывать и положительный эффект. — вот и все. Например. отдельные ее компоненты могут давать как раз такой эффект. Это означает. которым приходится обращаться к проблеме анализа про� изводительности. что настройка производительности завершена?  Какова максимальная приемлемая стоимость (выраженная в рабочем времени программиста. Этот подход в максимальной степени опирается на количественные данные. конечно. Но все должно быть совершенно наоборот. если дополнительная нагрузка позволяет определенной части системы преодолеть порог и начать работать в режиме повышенной производительности.214 Часть 2. понимать. Виртуальная машина Java — это очень динамичная среда времени исполнения. Размытое ощущение того. Чтобы по-настоящему эффективно улучшать производительность. Есть и другие. Необходимые технологии ПОЛОЖИТЕЛЬНАЯ И ОТРИЦАТЕЛЬНАЯ ДЕГРАДАЦИЯ Как правило. возрастает длительность ожидания при обработке). Итак. каких результатов они хотят достичь посредством анализа.2.  Какие доступные для наблюдения характеристики вашего кода вы измеряете?  Как измерять такие характеристики?  Каков смысл наблюдаемых характеристик?  Как вы узнаете. что измерения необходимы. что зачастую есть у разработчиков и менеджеров перед началом работы. деградация — негативное явление. затрачиваемом на оптимизацию и в усложнении кода) такой настройки?  На какие жертвы вы не готовы пойти ради оптимизации? Важнее всего. нужно сразу получить представление о некоторых ключевых областях и лишь потом приступать к технической части работы. Рассмотренные выше термины — наиболее часто используемые характеристики производительности. что добавление новых единиц в систему отрицательно влияет на производительность (например. но они относятся к базовой статистике системы и обычно применяются для справки при настройке производительности.

возмож� но. что именно измеряете. Если эти термины вам не знакомы. Если не измерять какой-либо наблюдаемой характеристики. медианное значение. поищите соответствующую информацию в Интернете и почитайте дополнительные материалы. то не может быть и речи об оптимизации производительности. Принимаясь за анализ производительности. . что ваши цифры точны. Для того чтобы коли� чественными методами определить источник проблемы. что именно вы измеряете При оптимизации производительности вы обязательно что-то измеряете. когда вы приступаете к измерению производительности кода. как по-быстрому решить проблему. Всегда следует привязывать ваши измерения. 6. Понятие о повышении производительности 215 Кроме того. цели и заклю� чения к одной или более базовым наблюдаемым величинам. объем выборки и нормальное распределение. Раздумывая об оптимизации кода. то первый этап пройден. Если вы четко знаете. никогда не забывайте. Измерение производительности в данном отношении — не исключение. важно в точности знать. нужно хорошо знать. какие из наблюдаемых параметров. которые мы пере� числили. Вот кое-какие наблюдаемые параметры. что именно вы измеряете. которые. дисперсия.Глава 6. где вы предпола� гали. уставившись в код. очень часто оказывается. и уверены. Знайте. которыми удобно пользоваться при анализе производительности:  среднее время.1.2. СОВЕТ Чтобы быть хорошим специалистом по анализу производительности. Если вы сидите. Чтобы получить точные и полезные цифры. необходимое методу handleRequest() для выполнения (после разогрева).  деградация времени отклика при повышении нагрузки от 1 до 1000 параллель� но работающих пользователей. Все эти параметры представляют количественные значения. необходимо понимать. стандартное отклонение. что вас осенит. Причина многих проблем производительности может заключаться в про� пущенном индексе в базе данных или объясняться возникновением взаимобло� кировок при захвате блокировок. инженеру понадобится измерить и откорректировать. перцентиль. необходимо обладать базовыми знаниями статистики. для вас наиболее важны. что проблема может быть совсем не в коде. описанных нами в предыдущем разделе. то вы не занимаетесь анализом производительности. что пробуксовка происходит не там.  90-й перцентиль сквозного времени отклика системы при 10 параллельно ра� ботающих клиентах. и наде­ етесь. Но нечеткие или слишком свободно сформулированные цели редко дают хорошие результаты. что такое среднее значение.

А по мере роста количества измерений информации становится так много. который часто недооценивают.2. сколько времени требуется на выполнение метода methodToBeMeasured(). В качестве вывода получим строку. чем на более ранних. которая с точностью до нескольких миллисекунд показывает. как классы собираются в исполняемую про� грамму.  Преобразовывать класс. В простейшей форме код выглядит так: long t0 = System. что эти данные вас просто засыпают. Дело в том. а не результатами прямых измерений. У него есть свои недостатки. которые базируются на одном (или обоих) из этих методов. а выдаваемые им профилировочные показатели являются. Этот этап обладает огромным потенциалом и ле� жит в основе многих современных приемов. что такой код приходится добавлять повсюду в имеющейся кодовой базе. что нужно делать. сколько времени нужно на вы� полнение метода или другого фрагмента кода. статистическими средними данными. Для ра� боты с этим интерфейсом необходимо писать нативный код. methodToBeMeasured(). необходимо упомянуть об инструментальном интерфейсе вирту� альной машины Java (JVMTI). Один из основных шагов. В такой ситуации метод methodToBeMeasured() загружается с помощью специаль� ного загрузчика классов. System.println("methodToBeMeasured took "+ elapsed +" millis"). существуют и некоторые эффекты «холодного старта». Необходимые технологии 6. Неудобство заключается в том. на этапе загрузки класса.2. позволяющих определить. который необходимо оценить. если на выполнение methodToBeMeasured() уходит менее одной миллисекунды? Как будет показано далее в этой главе. Проще всего пользоваться такими способами прямого измерения производи� тельности.currentTimeMillis(). long t1 = System. но ее можно назвать немного инвазивной.216 Часть 2. вставляя измерительный код в исходный класс. Например. который добавляет байт-код в начале и в конце метода . в сущности. long elapsed = t1 . Есть и другие проблемы.  Измерять непосредственно. что на более поздних этапах работы метод вполне может выполняться быстрее. Прямые измерения Прямые измерения — самая простая для понимания практика. используемых на платформе Java. Умейте проводить измерения Есть всего два способа.t0. который часто применяется для создания очень изощренных профилировочных инструментов. Один из простых примеров его применения — автоматическое измерение скорости загрузки методов. Автоматическое измерение скорости загрузки классов В главах 1 и 5 мы уже говорили о том. Кроме того. о которых не стоит забывать.currentTimeMillis(). — преобразова� ние байт-кода на этапе загрузки.out.

как четкая цель. но после начальных иссле� дований всегда лучше сделать паузу и сформулировать цели. какого уровня производительности вы хотите достичь Ничто так не помогает сосредоточиться. Например. Иногда бывает необходимо провести некоторый первичный анализ. Подобная технология лежит в основе многих высокоточных инструментов для отслеживания производительности (таких как OpTier CoreFirst). а потом переходят в компилируемый режим. скажем определить наиболее важные методы.3. методы Java начинают работу в интерпретируемом виде. Знайте. Это хорошо. .  снизить среднее время отклика handleRequest() на 40 %. так как они могут сильно смазать результаты. которые вы измеряете и пытаетесь скорректировать. Слишком часто разработчики впрягаются в аналитические работы. что чем слабее связаны наблюдаемые показатели. Ниже мы более подробно остановимся на том. Оптимизация ради повышения производительности может отри� цательно сказаться на других показателях. а уже потом пытать� ся их достичь. что требуется измерить. Такие потоки выполняют манипуляции над данными — как правило. тем более сложным становится сам процесс определения произ� водительности. переключился ли метод в компилируемый режим. не потрудившись четко поставить цель. как настройка будет завершена? 6. При использовании одного или обоих описанных подходов вы сможете получать количественные данные о том. обрабатывающим сырые данные. В большинстве случаев цель должна быть простой и точно сформулированной:  снизить 90-й перцентиль сквозного ожидания системы при 10 параллельно работающих клиентах на 20 %. и лишь потом ставить цели.Глава 6. они либо записывают вывод в файлы журналов.2. Чтобы получить точные данные о производительности. к которой обра� щаются другие потоки. Но на момент написания книги нам не удалось найти ни одного активно поддерживаемого сво� бодного инструмента. а также уметь его сообщить. В более сложных случаях может быть поставлена цель одновременно достичь нескольких взаимосвязанных целевых показателей. который относился бы к этой нише. Поэтому важно знать. ПРИМЕЧАНИЕ Как будет показано ниже. может потребоваться ускорить работу методов. Понятие о повышении производительности 217 для записи точного времени входа в метод и выхода из него. насколько быстро выполняется метод. Эти данные о хроно� метраже обычно записываются в разделяемую структуру данных. а дисперсию — на 25 %. либо связываются с се� тевым сервером. которые были сгенерированы в интерпретируемом режиме. необходимо сбросить значения хронометража. Следующий вопрос — какие цифры вы рассчитываете получить после того. но не менее важно знать и конечный результат настройки. как узнать. Необходимо учитывать.

Если достичь желаемых улучшений производительности не удалось. Наконец. — ко� гда вы достигнете поставленной цели. выполните еще одну серию измерений. воз� можно.5.2. когда стоит заканчивать оптимизацию. Необходимые технологии 6. Знайте. И на� оборот — если достичь цели не удается.2. затрачиваемые на настройку производительности. — это практически всегда напрасный труд.  Оптимизируйте то.  В первую очередь занимайтесь самыми важными методами (обычно это такие методы. Если все идет хорошо. Посмотрите. удастся с пользой потратить на другие дела. Оптимизация кода. Возможно. Еще один важный метод. что повышение производительности — процесс. Но на практике оказывается. а что — нет. Зачастую бывает достаточно добить� ся и 90%-ного результата от целевого показателя.  На выполнение анализа и подготовку оптимизации уходит время (а мы помним. помогающий регулировать усилия. как часто разработчики ударяются в такую оптимизацию. позволяющие определить. что важно. Просто удивительно. когда следует прекратить оптимизацию Теоретически несложно определить. не проходит даром. подведите предварительные итоги. но достаточно часто они работают и без адаптации. что стоит оптимизировать. повышающих производительность. что хорошо поддается оптимизации. но и знать. то хочется про� бовать все новые и новые стратегии. 6.218 Часть 2. а рабочее время инженера. в какой степени удалось достичь поставленных целей. несмотря на все старания. но при этом учитывайте. надеясь. что какая-то из них позволит добить� ся успеха.4. какой ценой дается повышение производительности Ни одна из уловок. на который приходится один процент времени исполнения программы или даже меньше. связанные с исправле� нием найденных недостатков (есть и такие виды оптимизации. нужно не только четко представлять себе свои цели.  Если можно что-то оптимизировать без особых усилий — оптимизируйте. вам понадобится приспособить эти сове� ты к конкретной ситуации. Чтобы уметь вовремя остановиться. насколько часто вызывается соответствующий код. что рабочее время программиста — как правило. то велик соблазн поднажать и сделать все еще лучше. — это поиск редко используемых участков кода. которые вызываются чаще всего).  Появляются дополнительные технические сложности. тогда и остановитесь. самая дорогая расходная статья в любом проекте). который затягивает. оказывают ли полученные результаты желаемый эффект на общее состояние производитель� ности. после которых код упрощается. Ниже даны очень простые рекомендации. . а не то. чего они стоят. Знайте. но в большинстве случаев складывается иная ситуация).

 Удаляйте сообщение отладочного журнала.  Опять же необходимо помнить. что оптимизации можно уделить не больше недели рабочего времени. обращайте внимание на такие проблемы и пытайтесь выявлять их до того.Глава 6. и во многих случаях оппоненты запоминают лишь последнюю часть этого высказывания.2. и при высокой нагрузке они могут вызывать непредвиденные эффекты во всей системе. 4 (Dec.  В краткой форме эта цитата используется как дежурное оправдание плохого дизайна системы или варианта ее исполнения.  В первой части высказывания Кнут намекает на необходимость измерений. задаваемое разработчикам. либо максимальное допустимое количество допол� нительных классов или строк кода. без которых мы не сможем определить. Понятие о повышении производительности 219  Для выполнения вспомогательных задач могут задействоваться дополнительные потоки.  Не выделяйте память на объект. как за� вершите очередной этап оптимизации. чем придется заплатить за повышение производительности. кото� рая состоит из сознательных и скоординированных действий. — http://pplab. Независимо от того. что совсем не всегда задержка возникает из-за проблем в коде.kr/courses/adv_pl05/papers/p261-knuth. который вам не нужен. Structured Programming with go to Statements / Computing Surveys. Например. 1974). По поводу этого утверждения в программистском сообществе велись жаркие споры.  Из полной цитаты очевидно. занятым на� стройкой производительности. 1 Knuth Donald E. которую вы готовы заплатить за повышение производительности. Например. Знайте об опасности поспешной оптимизации Одна из самых знаменитых цитат на тему оптимизации принадлежит Дональду Кнуту (Donald Knuth): «Программисты тратят массу времени и нервов на размышления о скорости работы некритичных компонентов их программ.ac. Некоторые виды оптимизации действительно являются элементом хорошего стиля. 6. какие части программы наиболее важны.snu. Это плохо по нескольким причинам. это может быть ограничение по времени. И эти попытки добиться эффективности оказывают сильное негативное влияние… преждевременная оптимизация — это корень зла»1. 6. что Кнут имеет в виду такую оптимизацию. — причина может заключаться совсем в другой части систем� ного окружения. — No. Зачастую бывает полезно иметь определенное представление о максимальной цене. если оно вам точно не понадо� бится. либо что опти� мизированные классы допускается увеличить не более чем на 100 % (то есть вдвое по сравнению с исходным размером). разработчик может решить.6.pdf .

Что пошло не так? И почему нас это должно волновать? В середине прошлого десятилетия (золотые были годы) казалось. Мы очертим контекст для изучения оставшихся тем этой главы и познакомим вас с концепциями. Почему же потом все пошло настолько плохо? Почему скорости процессоров уже не растут так быстро? Более того. почему даже самые убежденные разработчики программ не должны забывать и об аппаратном обеспечении. которое пришлось бы потратить на вызов currentTimeMillis() и на созда� ние объекта StringBuilder.3. нужно подробнее поговорить об аппа� ратном обеспечении. Чтобы ответить на эти вопросы. Итак. .isDebugEnabled()) log. как происходят неявные операции выделения объектов при конкатенации двух строк) и чем больше задумываетесь о производительности по ходу работы. что программисту нужно просто подождать несколько месяцев — и любой код. Скорость работы часов процессора росла как на дрожжах. заработает на новых мощ� ных процессорах. понима­ ете. чем машина с 2 ГГц? Откуда взялась такая тенденция — все наши товарищи по цеху вдруг стали беспокоиться о производительности? В этом разделе мы обсудим движущие силы упомянутой тенденции. Проверка такого рода называется страховкой на уровне логирования (loggability guard). создавалось впечатление. вы уже владеете терминологическим аппаратом для описания проблем с производительностью и для постановки целей. 6. объясним. Но мы по-прежнему не объяснили. что производи� тельность вообще не проблема. Необходимые технологии В следующем фрагменте кода мы добавили этап проверки. почему затронутые здесь проблемы должен решать именно программист и в чем их корень. тем более качественный код у вас получится. позволяющий узнать. используемого для такого сообщения.currentTimeMillis()). почему компьютер с процессором 3 ГГц с виду работает ненамного быстрее. написанный плохо.220 Часть 2. как подходить к решению проблем. то можно просто удалить этот код. будет ли логирующий объект что-то делать с записью из отладочного журнала. то код так и не создаст сообщения журнала. Но если от отладочного сообщения журнала вообще нет никакой пользы.debug("Useless log at: "+ System. Одна из особенностей настройки производительности заключается в том. Кроме того. которые понадобятся для глубокого по� нимания динамической компиляции и других достаточно сложных примеров. сэкономив еще пару циклов процессора (которые потратились бы на страховку на уровне логирования). будет сэкономле� но время. что все начинается с написания хорошего работоспособного кода. if (log. мы в общих чертах описали. Таким образом. Если подсистема логирования не настроена на ведение отладочных журна� лов. Чем лучше вы ори� ентируетесь в работе платформы и ее внутреннем поведении (например.

замечателен.2 мы показали на графике количество реальных процессоров из се� мейства Intel x86 — с 1980 года до процессора i7. в сущности. Многим разработчикам известно. 6. выведен на основании статьи. вы слышали широко растиражированный термин «закон Мура».1. Понятие о повышении производительности 221 Возможно.2. Тот факт. который.Глава 6. Рис. то есть до 1975 года.3. вероятно. На рис. что этот закон как-то связан с темпом совершен� ствования компьютеров. На гра� фике показано количество транзисторов на дату выпуска процессора. Логарифмический линейный график увеличения количества транзисторов с течением времени . которую Мур написал в 1965 году. вышедшего в 2010 году. сойдет на нет. несомненно. но детали этого закона сформулировать уже сложнее. 6. Изначально в ней давался прогноз на 10 лет. целесообразное с экономической точки зрения. вероятно. является наблюдением о тенденциях развития компьютерных процессоров. почему в самом ближайшем будущем он. Вот одна из наиболее распространенных формулировок этого за� кона: «Максимальное количество транзисторов на кристалле схемы. Закон Мура: прошлые и будущие тенденции изменения производительности Закон Мура назван по имени Гордона Мура (Gordon Moore). Этот закон. одного из основателей компании Intel. удваивается примерно за два года». Подробно рассмотрим смысл этого закона и выясним. что эта тенденция сохранилась до сих пор (и. 6. сохранится при� мерно до 2015 года).

Но если бы пришлось выбрать всего один фактор. что скорость таймера процессоров. который необходимо уяснить. что и удвоение за два года. Это означает. будет дей� ствовать еще какое-то время (оценки разнятся. что чем выше скорость таймера процессора. Учитывайте. то неважно. Истина заключается в том. тем выше производительность. который требуется обработать в ядре процессора?» и «Как скоро программа сможет дойти до этих данных. чтобы роскошествовать за счет инже� неров-системотехников. что мейнстримовый процессор Intel. пока данные не станут доступны. Это основной момент. имеет около 100 миллионов транзисторов. реальность гораздо сложнее.222 Часть 2. Даже общеизвестная идея о том. кото� рые связаны с задержками (латентностью) и на которые необходимо ответить: «Где находится ближайший экземпляр данных. чем у процессора. Понятие об иерархии латентности памяти Компьютерному процессору необходимы данные. на пересечение каждого вертикального уровня уходит шесть-семь лет. которые он будет обрабатывать. что мы должны остановиться на ней подробнее. Как видим. которую разработчики могут ожидать достичь в своем коде. по-видимому. Это в 100 раз больше. произведенный в 2005 году. Таким образом. Как видите. Это означает. Необходимые технологии Это линейный логарифмический график. и он. Это именно та часть памяти. ПРИМЕЧАНИЕ Количество транзисторов — не то же самое. Если у него нет этих данных. каждое приращение по оси Y в 10 раз больше предыдущего приращения. чтобы про� цессор мог приступить к их обработке?» Ниже перечислены возможные варианты ответов на эти вопросы. что существует два следующих фундаментальных вопроса. Важно отметить. .  Регистры — это ячейки с памятью. произведенного в 1990 году. почему одного лишь за� кона Мура для программиста недостаточно. расположенные в процессоре и готовые для немедленной обработки.2. над которой непосред� ственно работают команды. Этот график подтверждает справедливость закона Мура. линия почти пря� мая. что практическая производительность зависит от множества факторов и все они важны. 6. Это настолько важная концепция в области производительности. что закон Мура описывает именно рост количества транзисто� ров. — это грубое упрощение. Раньше закон Мура был хорошим ориентиром. а они все хуже отражают производительность.3. так как десятикратное увели� чение за шесть-семь лет — практически то же. то это была бы скорость поиска данных. насколько быстро тикают циклы процес� сора. Но закон Мура сформулирован именно на основании количества транзисторов. Процессор просто вынужден простаивать. что ось Y в этой системе координат градуирована логарифмиче� ской шкалой. релевантных для выполнения команд. но до 2015 года тенденция должна сохраниться).

Скорость работы памяти возрастала не так быстро. так и в ко� личестве транзисторов. такие как параллелизм на уровне команд .1 мс или менее. Понятие о повышении производительности 223  Основная память — обычно это динамическое ОЗУ (DRAM). так как в нем нет нужных данных. насколько кэши L1 и L2 превосходят по скорости работы основную память. как уве� личение количества процессоров транзистора. Для обеспечения постоянной загруженности процессора обрабатываемыми данными использовались довольно сложные аппаратные технологии. тем быстрее он работает). Это небольшие участки более быстрой памяти (стати� ческая оперативная память. Относительная длительность обращения к регистрам. что ядру процессора придется работать вхолостую. SRAM). но ниже мы подробно опишем. Закон Мура описывает экспоненциальный рост количества транзисторов. что две эти экспо� ненты неодинаковы.  Твердотельные диски (SSD) — для доступа к ним требуется 0. кэшам процессора и основной памяти В 1990-х и начале 2000-х годов наряду с добавлением кэшей активно использо� валась и иная технология. так как скорость доступа к ней тоже возрастает по экспоненте. 6. Именно поэтому SRAM не заменила в компьютерах всю DRAM. как сильно эта разница в скорости сказывается на производительно� сти работающего кода. как кэш процессора помогает избежать такой задержки. насколько важны эффекты кэша L1 для работающего кода. Но оказывается.Глава 6. На обращение к этой памяти тратится около 50 нс.6 (посвященном динамической компиляции) и на примере покажем. На рис. 6. Число указывает. Для внедрения все более сложных функций процессора предпринимались попытки избавиться от латентности памяти. Для решения этой проблемы были встроены кэши между регистрами процес� сора и основной памятью.  Жесткие диски — для обращения к такому диску и для загрузки требуемых данных в основную память требуется около 5 мс. чем обращение к обычным жестким дискам. Из-за этого постепенно повышался риск. Мы подробнее поговорим о кэшах в разделе 6. Такая быстрая память оказывается гораздо дороже. ко� торый также положительно сказывается на возможностях памяти. насколько близко к процессору расположен кэш (а чем ближе кэш. Ниже мы приведем пример того. чем динамическое ОЗУ (DRAM) как в денежном выражении.3 продемонстрировано. это гораздо быстрее. Кэши обозначаются как L1 и L2 (в некоторых машинах также есть L3).3. Рис. готовых к обработке.

Эти попытки предприни� мались в условиях растущей пропасти между мощностью процессоров и латентно� стью памяти. практически все делают сами.224 Часть 2. например руди� ментарное планирование потоков. Новые технологии стали потреблять значительный объем транзисторных мощностей процессора и оказывать серьезное негативное влияние на реальную производительность. что среда времени исполне� ния получает возможность приобрести определенный контроль над всей экосисте� мой. как и в любой другой управляемой среде времени исполнения) по природе более слож� но. остальные ядра смогут функ� ционировать в прежнем режиме.3. что в буду� щем в производстве процессоров будут доминировать механизмы со многими ядрами. что мы хотели бы еще раз подчеркнуть:  будущее принадлежит многоядерным процессорам. Чтобы сделать систему более работоспособной во всех отношениях. чем при работе с кодом.3. Почему так сложно выполнять оптимизацию производительности в Java Повышение производительности на виртуальной машине Java (впрочем. существующих в управляемой среде времени исполнения. Операци� онная система предоставляет лишь минимальный набор служб. если даже одно ядро будет дожидаться данных для обработки. Необходимые технологии (ILP) и внутрипроцессорная многопоточность (CMT). что программисты. Вот некоторые важные функции платформы.  сборка мусора. .  из-за этого возникает тесная связь между производительностью и многопоточ� ностью. пишущие на C/C++. из-за которых усложняется опти� мизация производительности:  диспетчеризация потоков. так что разработчику не приходится самому заботиться обо всех деталях. Благодаря этому повышается общая производительность труда программиста. Единственная альтернатива — отказаться вместо этого от всех преимуществ. Поговорим о них в следующем разделе. Это означает. но от контроля над системой в какой-то степени приходится отказаться. Вся суть управляемой системы заключается в том. что прогресс производительности неразрывно связан с многопо� точностью. Именно поэтому сформировалась точка зрения. Однако управляемая природа виртуальной машины Java оборачивается еще не� сколькими сложностями в этой сфере. работающим в неуправляемом виде. в ней необходимо увеличить количество процессорных ядер. 6. Такие аппаратные проблемы стоят не только перед Java-программистами. Таким образом.  динамическая компиляция. что практически всегда оборачивается более серьезными неудобствами. Эта связь настолько важна. Причина в том. чем необходимость дополнительной оптимизации производительности.

Понимание того. 6. как Java интегрируется с этими подсистемами. Это означает. Весь этот раздел доказывает. когда такие часы появились. что точные измерения — основной фактор. Когда система выключена. кто намерен серь� езно заниматься повышением производительности. которые получили название за свою новизну или быстроту. Такое соотношение частоты уходит корнями в структуру графической системы CGA. Такова судьба многих новинок. может быть до четырех аппаратных источников времени: RTC. — очень важный навык для всех. Например. хотя многие ма� шины используют протокол сетевого времени (Network Time Protocol. Именно этот тип ранее использовался для . Аппаратные часы В современной машине.4. как само время. и. в принципе. как диспетчеризация потоков и сборка мусора. они работают от батареи материнской платы. Источником времени в нем является кристалл с частотой 119. связанных с этим процессом). NTP) для синхронизации с сетевым сервером времени в процессе загрузки операционной системы. как это может показаться! Для повышения производительности нужно хорошо понимать детали работы со временем.4. обратимся к некоторым сложностям. Именно эти часы устанавливают системное время при запуске. опре� деляющий пути принятия решений при анализе производительности. 6. RTC) — это. Понятие о повышении производительности 225 Между этими функциями могут возникать довольно тонкие взаимодействия. Набор компилируемых методов может отличаться от запуска к запуску. что на множество методов — кандидатов для компи� ляции могут влиять такие факторы. работающей на процессоре x64. потом обсудим. такая же электроника. в Париже есть мост. В 1980-е годы. поговорим о базовом оборудо� вании. Чтобы приобрести такое понимание. Он был возведен в 1607 году и является самым старым мостом. 8254 — это программируемый чип времени. Часы реального времени (real-time clock. HPET. возможно. TSC и. Вопрос времени — от железа и вверх Вы когда-нибудь задумывались. которая работает в дешевых цифровых часах на кварцевых кристал� лах.Глава 6. Например. ВСЕ СТАРОЕ КОГДА-ТО БЫЛО НОВЫМ Выражение «часы реального времени» довольно неудачное. связанным с методом nanoTime(). какие методы компилировать. но картина не так проста. чтобы решать. 8254. старый. как платформа Java обращается со временем (а также понимание ограничений. где в компьютере хранится и обрабатывается вре� мя? В конечном счете за отслеживание времени отвечает аппаратное обеспечение. наконец. сохранившимся в городе. который называется Понт-Нёф (Новый мост).1. их дей­ ствительно можно было считать сравнимыми по точности с реальным временем. но теперь они просто непригодны для использования в реальных приложениях.318 КГц — это втрое меньше частоты цветовой поднесущей NTSC. система компиляции использует таймеры.

что он должен использоваться для записи длительности — для этого результат вызова nanoTime() вычитается из более раннего результата. сколько циклов про� цессора истекло. Но. Дело в том. 6.4. попытки скрыть эту зависимость не всегда оказываются успешными. чтобы отсчитывать время для планировщиков операционной системы (и под� держивать процесс квантования времени). К счастью. это полный беспорядок. Итак.1 перечислены основные различия между двумя этими методами. что в большинстве современных операционных систем этот метод получает информацию от «контрчасов» процессора — TSC. Второй используется для измерения времени с точностью выше миллисекунды. такие механизмы пока доступны не во всем оборудовании и поддерживаются не во всех операционных системах. чтобы инженеры могли бросить неблагодарную работу по уточнению старых часовых микросхем. Вывод nanoTime() относителен к определенному моменту времени. Сравнение встроенных в Java методов для доступа к времени currentTimeMillis nanoTime Разрешение в миллисекундах Квотируется в наносекундах Близко соответствует времени настенных часов Может отклоняться от хода времени практически в любых обстоятельствах на настенных часах Если описание метода nanoTime() из табл. Это означает. Таблица 6. TSC). Следующий фрагмент . что в области процессорного вре� мени творится какой-то беспорядок — да.226 Часть 2. Это означает. 6. это счетчик процессора. есть еще таймеры событий высокой точности (HPET). позволяющие разобраться с этой путаницей. Java скрывает зависимость от оборудования и поддержки опера� ционной системы и позволяет работать с этими характеристиками на уровне кон� кретной машинной конфигурации. так и с часами. И наконец. HPET работает как минимум с таймером 10 МГц. что это идеальные часы. Но этот счетчик зависит от процессора. то есть имеет точность до 1 мкс.1 показалось вам несколько проти� воречивым — хорошо.1. Сегодня эта функция уже выполняется в других местах или вообще не требуется. На первый взгляд кажется. Проблема с nanoTime() В Java есть два основных метода для доступа к времени: System. Они стали появляться в последнее время. как будет показано далее. вы угадали. В табл. Необходимые технологии того.nanoTime(). Если после всего сказанного вам показалось. 6.currentTimeMillis() и System. В принципе. отслеживающий. что разные процессо� ры могут рассинхронизироваться как друг с другом.2. Правда. переходим к наиболее распространенному в наши дни современному счетчику — он называется счетчиком меток реального времени (Time Stamp Counter. платформа Java предлагает возможности. во время исполнения на него потенциально могут влиять энергосберегающие и другие факторы. висящими на стене.

демонстрирует именно такой случай: long t0 = System.1... Отклонение таймера . нужно хорошо понимать принцип работы nanoTime(). Для того чтобы правильно использовать эти базовые методы при настройке производительности. long t1 = System. doLoop1(). Листинг 6.t0.Глава 6. рассмотренного ниже. В лис� тинге 6.nanoTime().nanoTime().1 продемонстрировано максимальное наблюдаемое отклонение времени между миллисекундным таймером и нанотаймером (обычно эта информация пре� доставляется TSC). long el = t1 . . Здесь el — это время в наносекундах. как выполнялся doLoop1(). Понятие о повышении производительности 227 кода. взятый из ситуативного исследования. истекшее с тех пор.

если не создается никаких объектов. полученный в более старой версии Solaris на том же оборудо� вании: Обратите внимание: в Solaris максимальное значение медленно повышается. А Linux подолгу работает нормально. В таком случае разница между работой счетчиков различных процессоров становится заметна в коде приложения. Но даже здесь заметно влияние виртуальной машины Java. что оно в некоторой степени определяется операционной системой. зна� чит. однако в случае с более длительными (макро� . чтобы в нем не создавалось ни� каких дополнительных потоков и даже объектов и можно было свести к минимуму вмешательство в платформы (например. но иногда происходят резкие скачки. что по прошествии достаточно длительного времени данные nanoTime() могут стать недостоверными. Виртуальная машина Java периодически приостанавливает работающий поток Java и перебрасывает его для исполнения на другое ядро. Оказывается.228 Часть 2. Этот таймер полезен при измерении не� больших временных промежутков. Код для этого примера был очень аккуратно подобран так. Это означает. Вот пример из Linux: А вот результат. что такие крупные скачки в хронометраже Linux обусловлены разницей в работе счетчиков TSC на разных процессорах. Необходимые технологии В результате выводится максимальное наблюдаемое отклонение. не происходит и сборки мусора). Оказывается.

как интерпретировать резуль� таты измерений. зафиксированных при конкретном акте измерения. В реальности вер� ное значение. Этот феномен называется прецизионностью измерений. Понятие о повышении производительности 229 скопическими) отрезками его приходится периодически сверять с данными мето� да currentTimeMillis(). а также понимать тонкости реализации. чем точность. ПОНЯТИЕ ОБ ИЗМЕРЕНИЯХ Интервал. Предположим. указанный с прецизионностью до наносекунд и равный 5945 нс. Например. поэтому определить правиль� ность бывает сложнее. которым не хватает точности (то есть базовое значение правильное.4. Роль времени при повышении производительности При оптимизации производительности нужно знать. Можно иметь правильные данные.Глава 6. достигающего точности до 1 мкс. Прецизионность — это мера содержания случайных помех. важно обладать базовыми знаниями теории измерений. . Остерегайтесь показателей производительности. что также необходимо понимать ограничения. максимально близкого к истинному.3. отличающиеся высокой точ� ностью. присущие любым измерениям времени на платформе. Правильность Правильность измерений (в нашем случае — измерений времени) — это возмож� ность получения значения. время часто измеряется с прецизионностью до миллисекунд. полученный от таймера. — всегда проверяйте прецизионность и точность измерений. как правило. Чтобы обеспечить максимально качественную оптимизацию производительно� сти. Хронометраж характеризу� ется высокой прецизионностью. Это означает. на самом деле находится где-то между 3945 и 7945 нс (с 95%-ной вероятностью). 6. если при многократных замерах получается малый разброс значений вокруг среднего. можно иметь неправильные результаты. Точность Временные отрезки обычно округляются до ближайшей единицы на определенной шкале. В таком случае обычный способ квотирования прецизионности — это квотирование ширины 95%-ного доверитель� ного интервала. которые кажутся чрезмерно точными. Правильность показывает уровень систематической ошибки при измерениях. но при разбросе значений попадаются случайные помехи). что измерения конкретного фраг� мента кода имеют нормальное распределение. Кроме того. записанные во время выполнения кода. остается неизвестным.

Просто не забывайте. Необходимые технологии Гранулярность Истинная гранулярность (granularity) системы — это предел частоты самого быстрого таймера. даже стандартные протоколы. рассмотрим пример.4. В Ethernet-сетях возникают похожие проблемы. Таким образом. Прежде чем перейти к обсуждению сборки мусора. обладающими высокой пропускной способностью. Подробное обсуждение хронометража. что они произошли «совсем рядом. возникающих при оптимизации производительности в системах. По мере того как мы будем прорабатывать уровни операционной системы. охватывающих несколько машин. 6. В листинге 6. Отличимость (distinguishability) — это кратчайший интервал.2 цикл проходит через массив размером 1 Мбайт и записывается время. вир� туальной машины и библиотечного кода. которое потребовалось на выполнение одного из двух циклов. Обеспечение синхронизации и хронометража в больших сетях — дело со� всем не простое. мы по одному разу затрагиваем каждую из строк кэша. Иногда данный показатель называется отличимостью. одним из основных факторов снижения производительности явля� ется количество кэш-промахов в кэше L1. пролегающий между двумя гарантированно автоном� ными событиями. В большинстве случаев инфор� мация о подобных кратчайших отрезках времени недоступна разработчику прило� жений. что прежде. Учтите.230 Часть 2. распределенного в сети. где вся работа происходит только на одной маши� не (хосте). выходит за рамки этой книги. различать такие экстремально краткие временные промежутки будет почти невозможно. потребует� ся «разогнать» код. Речь пойдет о воздействии кэшей памяти на произ� водительность кода. например NTP. В строке кэша L1 обычно находит� ся 64 байт (а целые числа в Java на 32-битной виртуальной машине имеют ширину 4 байт). О них говорят. тикающий не реже чем раз в 10 нс. могут быть не� достаточно точны для высокоточной работы. Такие промахи сопровождают исполне� ние кода приложения. но не одно� временно». чем можно будет получить точные результаты. о кото� ром мы уже упоминали выше. причем речь не только об Интернете. что в принципе очень сложно получить точные значения о хронометраже рабочих процессов. Обычно это таймер прерываний. распределенных по сети. Кроме того. распределенный в сети Основная часть нашего обсуждения производительности сосредоточена на центрах оптимизации в таких системах. Практический пример: понятие о кэш-промахах При работе со многими фрагментами кода. Первый цикл приращивается на 1 через каждые 16 записей int[]. Хронометраж.4. чтобы виртуальная машина Java смогла скомпилировать инте� . что существует несколько особых проблем. Но необходимо учитывать.

. Но вот пример типичных результатов. . Понятие о повышении производительности 231 ресующие вас методы.Глава 6. Листинг 6. Loop2: Loop2: Loop2: Loop2: Loop2: 868000 952000 930000 869000 798000 .6. что она выполняет в 16 раз больше работы. полученных на ноутбуке: Loop1: Loop1: Loop1: Loop1: Loop1: 634000 801000 676000 762000 706000 nanos nanos nanos nanos nanos . . чем loop1(). Понятие о кэш-промахах Вторая функция loop2() приращивается с каждым байтом массива. . поэтому кажется. О необходимости такого разгона мы подробнее поговорим в разделе 6.2.

 при работе с высокоточными данными времени необходимо действовать очень аккуратно. таких как Java и . возникающими из-за несовершенного управления памятью. действующих во фреймворке. Это означает. Несложно догадаться. В микросекунде 1000 нс. выбранный в Java. что в OS X базовые системные вызовы выполняются с точностью до миллисекунд. чем loop1(). затрачиваемые собственно на преобразование данных — это всего лишь малая толика от необхо� димого рабочего времени.nanoTime()) просто возвращает целое количество микросекунд. Большинству разработчиков неизвестны подробности функционирования памяти и системы сборки мусора. Мно� гие Java-разработчики не знают. Поэтому большинство приложений вполне обходятся без настройки. Этот пример взят с ноутбука Mac. В циклах loop1() и loop2() проис� ходит одинаковое количество считываний строк кода. А значит.  нужно знать точность и правильность измерений хронометража. примеряемой на платформе. Необходимые технологии ФОКУС ПРИ РАБОТЕ С ПОДСИСТЕМАМИ ХРОНОМЕТРАЖА Обратите внимание: результаты всех значений nanos — красивые круглые тысячи. Виртуальная машина отлично справляется с обработкой большинства приложений. Здесь мы имеем дело с результатами работы gettimeofday(). Это показывает. Результаты выполнения кода показывают. Но в последние годы технологии автоматического выделения памяти стали такими совершенными и надежными. Прежде чем двигаться дальше. В этой подсистеме есть великое множество настраиваемых элементов. как именно происходит управление памятью на платформе. что лежащий в их основе системный вызов (выполняемый в конечном итоге System. так как эти знания обычно им и не нужны. что работа без них уже непредставима. что на самом деле loop2() не требует в 16 раз больше времени. весь профиль производительности определяется количеством обращений к памяти. Сборка мусора Автоматическое управление памятью — одна из важнейших характеристик плат� формы Java. Далее мы поговорим о подсистеме сборки мусора.  миллисекундный хронометраж надежен и удобен для работы. Это одна из самых важных характеристик общей картины производительности. вспомним самые важные моменты о системах хронометража в Java:  в большинстве систем работает сразу несколько разных часов. разра� ботчик вполне мог потратить значительную часть карьеры на охоту за ошибками. 6. До появления управляемых платформ. чтобы избегать рассогласования. .232 Часть 2. и циклы. насколько успешным оказался подход. которые могут очень пригодиться в работе программиста. не требуя специальной настройки.5.NET. какие функции доступны разработчику и какая оптимизация возмож� на в условиях ограничений. занятого анализом производи� тельности.

мы поговорим о функциях. На рис. «пометка и очистка» (http://www. исследуем основы примене� ния алгоритма «отслеживание и очистка» (mark and sweep) и обсудим два полезных инструмента — jmap и VisualVM.1. выделяемую в куче). Куча — это место. называется «отслеживание и очистка» (mark and sweep)1. позволяющий узнать. 6. Переменные в стеке и в куче Обратите внимание: примитивные поля объекта тоже выделяются по адресам внутри кучи. Базовый алгоритм. которыми можно пользоваться для управления профилем памяти в виртуальной машине. Рис. Основы У стандартного процесса Java есть стек и куча.4 показано. мы поговорим о двух распространенных альтернативных сборщиках мусора: Concurrent Mark-Sweep (Параллельное отсле� живание и очистка. Наконец. расскажем.wikipedia. «отметить и очистить» (http://oooportal.5. где создаются хранилища для переменных различных типов. где создаются локальные переменные. Мы рассмотрим теоретические основы.3. 6. 6. посвященном jmap.ru/?cat=article&id=992). CMS) и о более новом — Garbage First (G1).5. содержащие примитивы (но локальные переменные ссы� лочного типа будут указывать на память. Наш перевод взят из источника http://tinyurl. что делать в тех случаях. В подразделе 6. «отметить и подмести» (http://tinyurl. Итак. Понятие о повышении производительности 233 В этом разделе мы обсудим. . у вас есть серверное приложение. 1 Другие варианты перевода этого названия — «алгоритм пометок» (http://ru. com/content/upravlenie-pamyatyu-v-sistemakh-avtomatizirovannogo-rasparallelivaniyaprogramm) и др. начнем с основ. не задействованной кодом приложения.Глава 6. мы рассмотрим простой способ. «очистка по меткам» (http://rus� lanshestopal. как происходит управление памятью для работающего процесса Java. Возможно. Стек — это место.com/apb95jc. когда оптимизация всетаки требуется. Кроме того. перев. — Примеч. не используют ли какие-то из ваших классов слишком много памяти.com/apvpobt). применяемый платформой для восстановления и переиспользования динамической памяти (памяти кучи). в котором создаются объекты.org/ wiki/Сборка_мусора). в котором из-за долгих пауз в ра� боте расходуется вся память.dissercat.4.com/actionscript3_tips/45).

либо очень долго. Отслеживание и очистка «Отслеживание и очистка» — простейший алгоритм сборки мусора. область уцелевших (Survivor). о ко� торых точно известно. первый из разработанных алгоритмов такого рода. На платформе Java базовый метод отслеживания и очистки был усовершен� ствован.2. что они «живые». в частности системы подсчета ссылок. насколько долго существует объект. на ко� торые присутствуют ссылки в каком-либо фрейме стека (ссылка может указывать на содержимое локальной переменной. параметра метода. учитывающий поколения объ� ектов» (generational GC). Существуют и другие техники автомати� ческого управления памятью. Обратите внимание: высвобожденная таким образом память может быть возвращена вирту� альной машине Java.5). чтобы платформа могла пользоваться таким свойством жизненных циклов объектов. такие механизмы проще. По завершении этой работы все оставшиеся объекты считаются мусором и собираются (подметаются). ссылки могут указывать на различные области памяти на протяжении его жизни (как показано на рис. Области памяти: Эдем (Eden). В зависимо� сти от того. как Perl и PHP. Напротив. временной переменной.234 Часть 2. 6. Различные области ди� намической памяти подобраны специально для того. К нему был добавлен «сборщик мусора. Причина такого упорядочения такова: анализ работающих систем показывает.5. «Живыми» называются объекты. Затем алгоритм проследует по дереву ссылок от живых объектов. Необходимые технологии 6. а не операционной системе. используемые в таких языках. но они и не тре� буют сборки мусора. В таком случае куча уже не является однородной областью памяти. В простейшей форме алгоритм «отслеживание и очистка» приостанавливает все работающие потоки программы и начинает работу с множества объектов. хранилище (Tenured) и постоянное поколение памяти (Permgen) . Пожалуй. Рис. участвующих в жизненном цикле объекта Java. в динамической памяти присутствует несколько различных областей. также есть и менее распространенные варианты) любого пользовательского пото� ка. помечая любой найденный по пути объект как «живой». 6.5. На разных этапах сборки мусора объект может перемещаться от области к об� ласти. что объекты живут либо очень недолго.

Все живые молодые объекты. в которых все пользовательские потоки могут быть ненадолго остановлены. Или же можно считать. Хранилище не очища� ется в ходе молодой сборки. Если говорить о серверных программах. пережившие «изгнание из Эдема» (отсюда и ее название).  Эдем (Eden) — это область динамической памяти. в которой изначально выделя� ются все объекты. где оказываются уцелевшие объекты. Этого следует избегать. которые уже достаточно стары (пережившие достаточное количе� ство предыдущих циклов сборки мусора). они покидают область уцелевших).  Область уцелевших (Survivor) — как правило. Иногда изобретаются сложные приемы. Области памяти В виртуальной машине Java предусмотрены различные области памяти. что существуют реальные проблемы. помогающие исключать паузы. Строго говоря. Как упоминалось выше. в памяти присутствует две обла­ сти уцелевших. решаемые благодаря такой полной уборке.  Хранилище (Tenured) — это область (также называемая «старым поколением»). найденные на этапе отслеживания. либо применяется полная уборка памяти. Сборка бывает двух типов: молодая и полная. для которых критичны такие паузы. эти области памяти также по-разному участвуют в сбор� ке. что область уцелевших обычно делится пополам. 1. Эта проблема часто утрируется. постоянное поколение не входит в состав динамической памяти. Возникают паузы.  Постоянное поколение памяти (PermGen) — здесь выделяется место для внут� ренних структур. рассмотренным ниже. Молодые сборки В ходе молодой сборки (young collection) система пытается очистить только области с молодыми объектами — Эдем и область уцелевших. одна из областей уцелевших пустует. что при сборке мусора методом отслеживания и очистки неизбежно возникают состояния «тотальной остановки». Этот процесс довольно прост. которые могут длиться неопределенно долго. Иногда два этих пространства называются Из (From) и В (To). перемещают� ся в следующие места: yy объекты. По причинам. возникающие в Java. обычные объекты сюда никогда не попадают. попадают в хранилище. Многие объекты никогда не покидают этой области памяти. yy все остальные молодые «живые» объекты отправляются в пустую область уцелевших. если только не происходит процесс сбора. то существует совсем мало приложений.NET заключается в том. Именно в нее попадают объекты. только если тщательный анализ не покажет. исполь� зуемые для хранения объектов в ходе их естественного жизненного цикла. . Понятие о повышении производительности 235 НЕСКОЛЬКО СЛОВ О ПАУЗАХ НЕОПРЕДЕЛЕННОЙ ДЛИТЕЛЬНОСТИ Один из часто критикуемых моментов работы Java и . например для определений классов. которые признаются «достаточно стары� ми» (таким образом.Глава 6.

Далее в главе мы познакомим� ся с более сложным подобным инструментом.5. Молодая сборка начинается после того. В противном случае может возникнуть ситуация. Такой процесс называется уплотнением (compacting).236 Часть 2. jmap. Это означает. позволяющие заглянуть вглубь работающих процессов. куда попадает вся эта память. то ссылки. чтобы при необходимости выделить крупный объект. также должны быть просмотрены и отслежены. но бывают и другие безопасные состояния. Полные сборки Если молодая сборка не может перевести объект в хранилище (из-за недостатка пространства). Это позволяет гаранти� ровать. то запускается полная сборка (full collection). но больше на этот объект из Эдема нет никаких ссылок. что в старом поколении хватает места. может потребоваться перемещать объекты в старом поколении. как используется память в запущенных приложениях. кроме мусора. Типичным примером безопасного состояния является точка вызова метода (call site). 6. поскольку в них больше нет ничего. Сделаем небольшой перерыв и познакомимся с простым инструментом. Самый незатейливый из них — jmap — отображает карты распределения памяти работающих процессов в памяти Java (он также может работать с файлом ядра Java . как Эдем оказывается целиком заполнен. в ко� торой объект из хранилища удерживает ссылку на объект из Эдема. что если у молодого объекта есть ссылка на объект из хра� нилища. Необходимые технологии 2. Напротив. Но поток нельзя приостановить для сборки му� сора в произвольный момент. все потоки приложения должны быть остановлены в безопасном состоянии. — их называют безопасными состояниями (safepoint). а также разобраться.3. существуют специальные периоды. не получится правильно обработать. Обратите внимание: на этапе отслеживания требуется обойти весь граф живых объектов. Поэтому вы определенно должны уметь обра� щаться с такими инструментами (а не переходить сразу к поиску средства с графи� ческим интерфейсом). который помогает понять. оснащенным графическим интер� фейсом. После этого Эдем и только что очищенная область уцелевших могут быть пе� резаписаны и переиспользованы. Чтобы могла произой� ти сборка мусора. В зависимости от того. какой механизм сборки применяется при работе со старым поколением. то объект из Эдема никогда не удастся увидеть и. Если не произвести полного обхода на этапе отслеживания. jmap В комплекте со стандартной виртуальной машиной Java от Oracle поставляются простые инструменты. Безопасные состояния Сборка мусора не может происходить без хотя бы самых кратких общих остановок работы всех потоков приложения. Но многие проблемы можно классифицировать с помощью очень простых инструментов командной строки. удерживаемые объектом из хранилища. соответ­ ственно. в ко� торые может происходить сборка мусора.

0 0x7ed18000 94K /usr/local/java/sunjdk/1.so. Вывод кучи Параметр -heap дает мгновенный снимок кучи по состоянию на тот момент.0_25/bin/java 0x55555000 108K /lib/ld-2. о которых мы поговорим ниже.2.0-b11 using thread-local object allocation. Число.0_25/jre/lib/i386/libnet.3.6. В выводе -heap вы увидите базовые параметры.4/lib/ libgss_all.. области уцелевших (поме� ченные From и To) и хранилище (Old Generation).. Здесь вы видите Эдем.0-b11 0x08048000 46K /usr/local/java/sunjdk/1. а также сопутствующую инфор� мацию: $ jmap -heap 22186 Attaching to process ID 22186.. Понятие о повышении производительности 237 и даже подключаться к удаленному отладочному серверу). Рассмотрим пример вывода.4. а мы еще не рассказали.so .0.6. Но мы все равно покажем такой вариант. связанные с про� цессом..so. Стандартный вывод (по умолчанию) В простейшей форме jmap демонстрирует нативные библиотеки. Debugger attached successfully. определяющее отношение этих областей. Обычно это не слишком интересно. some entries omitted 0x563e8000 535K /lib/libnss_db. если только у вас в приложении нет большого объема JNI-кода.so. Server compiler detected. please wait. .. Server compiler detected.1 0x80dcf000 1440K /usr/local/kerberos/mitkrb5/1. Но в составе молодого поколения у вас есть Эдем и область уцелевших. Размер кучи равен общему размеру молодого и старого поколений плюс размер постоянного поколения памяти. Debugger attached successfully. называется коэффициентом выживаемости (survivor ratio). как соотносятся размеры этих об� ластей. определяющие структуру динамической памяти процессов Java.3. которую хоти� те отображать в jmap: $ jmap 19306 Attaching to process ID 19306. если когда-нибудь забудете указать ту перспективу.4/lib/libkrb5. Вернемся к нашему примеру с серверным приложением для электронного магазина и исследуем это приложение в ходе работы с помощью jmap. чтобы вы не запутались.4.3.Глава 6.. JVM version is 20.4.so 0x80cf3000 2102K /usr/local/kerberos/mitkrb5/1.2 Гораздо более полезны переключатели -heap и -histo. когда вы его используете. please wait. JVM version is 20.

9375MB) OldSize = 4194304 (4.1875MB) // ������������������������������������������ Эдем�������������������������������������� = (From +To) * ���������������������� коэффициент����������� ���������� выживаемости used = 58652576 (55.0% used // область "To" в настоящий момент пуста PS Old Generation capacity = 89522176 (85. но в этом выводе вы не видите.0MB) MaxNewSize = 4294901760 (4095.81301661289516% used From Space: capacity = 7012352 (6.0MB) MaxPermSize = 67108864 (64. вы можете получить ценные подсказки о том.75MB) used = 30086280 (28. что происходит в куче. Имея возможность видеть.05748748779296875MB) 99. какие объекты наполняют память. Необходимые технологии Parallel GC with 13 thread(s) Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 536870912 (512.0MB) NewSize = 1048576 (1.9526824951171875MB) free = 2867664 (2. такая базовая структура областей может быть очень полезна.935455322265625MB) free = 105121888 (100.50201416015625MB) 6.87298583984375MB) free = 83363904 (79. в котором вы получаете удобную статистику именно такого рода.9375MB) // Эдем = (From +To) * коэффициент выживаемости used = 0 (0. К счастью. что вообще сейчас происходит в памяти.6875MB) // Эдем = (From +To) * коэффициент выживаемости used = 4144688 (3.7348175048828125MB) 59. . в jmap присутствует и ре� жим гистограммы.87904637170571% used PS Perm Generation capacity = 30146560 (28.69251251220703MB) free = 60280 (0.238 Часть 2.10553263726636% used To Space: capacity = 7274496 (6.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 163774464 (156.25204467773438MB) 35.0MB) free = 7274496 (6.375MB) used = 6158272 (5.9375MB) 0.80004352072011% used Конечно.0MB) NewRatio = 2 SurvivorRatio = 8 // Эдем = (From +To) * коэффициент выживаемости PermSize = 16777216 (16.

. пе� речисленные в главе 5.HashMap 23: 13725 329400 java.lang.ref.company. так как в гистограмме может быть очень много информации. то вы мало чем сможете помочь.HashMap$Entry 22: 9985 399400 java.Class. 27: 6903 220896 java. вам . массив символов записывается как [C.Method 16: 17842 713680 java.String. 15: 9733 778640 java.lang. какой объем памяти в системе занят экземпля� рами каждого типа (а также просматриваем некоторые внутрисистемные записи). чтобы было легко заметить самых крупных пожирателей памяти. а массивы объектов классов будут показаны как [Ljava.HeapCharBuffer 14: 13515 1173568 [Ljava.reflect.HeapByteBuffer 17: 7433 713568 java.nio. ограничившись парой десятков строк.lang. то вам будет гораздо удобнее как-то наладить ее использование.lang.Cache$AccountInfo 5: 439499 9145560 java.util. Но если какой-то из классов явственно выделяется по потреблению памяти.SoftReference // Внутренние объекты виртуальной машины и информация о типах Мы удалили весь последующий вывод. Например. Типы перечисляются в порядке убывания занимаемой ими памяти.Class 18: 10771 678664 [S 19: 1543 489368 <methodDataKlass> 20: 10620 456136 [[I 21: 18285 438840 java. Понятие о повышении производительности 239 Режим гистограммы В режиме гистограммы мы видим.lang.lang. 4: 2520 10547976 com.LinkedHashMap$Entry 25: 9793 249272 [Ljava.util.lang. Разумеется. Возможно.Object.nio.cache.String 6: 64466 7519800 [I 7: 64466 5677912 <constMethodKlass> 8: 96840 4333424 <methodKlass> 9: 6990 3384504 <symbolKlass> 10: 6990 2944272 <constantPoolKlass> 11: 4991 1855272 <instanceKlassKlass> 12: 25980 1247040 <constantPoolCacheKlass> 13: 17250 1209984 java.HashMap$Entry.Hashtable$Entry 24: 9839 314848 java.Class. Небольшое предупреждение: jmap применяет для типов внутренние имена. 26: 11927 241192 [Ljava.lang.Глава 6.util. $ jmap -histo 22186 | head -30 num #instances #bytes class name ---------------------------------------------1: 452779 31712472 [C 2: 76877 14924304 [B 3: 20817 12188728 [Ljava.util.util. если вся память уходит на обслуживание классов платформы и фреймворка.

вы немного взволнованы. вернемся к гистограмме. Но все объекты с учетной информацией «живы».Object.lang.cache. Массивы символьных данных часто находятся внутри объектов String (где они заключают в себе содержимое строки). что это окажет серьезную нагрузку. Эта информация может быть очень полезна. что ожидается крупная акция с промопродажами.5 Мбайт примерно на 2500 записей или по 4 Кбайт на запись. содержащейся в типе. В данном примере вы видите. 4: 141491 2187368 [C Только посмотрите. Поэтому в ближайший месяц в системе будет находиться вдесятеро больше поль� зователей. Согласитесь. . Так мы прикажем jmap обрабатывать только «живые» объекты. а около двух третей объектов String. Но на этот раз укажем -histo:live. оказались мусором. что по� добная информация потребляет много памяти. чем обычно. это как-то много для учетной информации. Объекты AccountInfo занимают много места: 10. чтобы помочь управлять процессом принятия решений о том.240 Часть 2. что еще раз доказывает.company. ко� торые вы видели при первом запуске. что значительную часть памяти потребляют такие записи. вам придется разделить кэш учетных записей. потребуется провести очень подробный анализ. учитывая. чтобы просматривать вывод гистограм� мы и находить интересующие вас детали. Возможно. как [C. Конечно. Необходимые технологии понадобится grep или другие инструменты. Но из гисто� граммы можно узнать и другие довольно интересные вещи. а не всю кучу (по умолчанию jmap работает сразу со всей инфор� мацией. Вы знаете. Вот как это выглядит: $ jmap -histo:live 22186 | head -7 num #instances #bytes class name ---------------------------------------------1: 2520 10547976 com. Информация от jmap может применяться в качестве исходных данных. но вы хотя бы уже приступили к анализу проблемы. Таким образом. попробовать уменьшить объем информации. две из которых мы сейчас и рассмотрим. или купить для сервера дополнительную оперативную память. как изменилась картина. но вы хотя бы знаете. Предположим.Cache$AccountInfo 2: 32796 4919800 [I 3: 5392 4237628 [Ljava. с чего начать. Объем символьных данных умень� шился с 31 Мбайт примерно до 2 Мбайт. в том числе с мусором. Записи Cache$AccountInfo — это единственные классы приложения. теперь к вам обращается начальник и говорит. пока остающимся в памяти и еще не собранным). ожидающим сборки. Cache$AccountInfo — это самый важный тип. над которым разработчику нужен полный контроль. как справляться с потен� циальными проблемами. Прежде чем вносить какие-либо изменения. Чтобы разобраться еще с одной интересной вещью. Вы уже узнали. насколько тяжелы объекты AccountInfo. какая часть ваше� го кода потребляет наибольшее количество памяти. которые попадаются среди верхних записей — все остальное в верхней части вывода отно� сится к типам платформы или фреймворка. что совсем не удивитель� но — в большинстве программ на Java строки очень распространены.

Если переключатель начинается с -XX:. такой переключатель не удастся портировать между различными реализациями виртуальной машины Java. Поэтому всегда следует делать несколько запусков. связан с его воз� можностью создавать файл дампа. Когда вы выпол� няете такие операции. это значение равно 1000). Создание файлов офлайнового дампа Последний режим работы с jmap. не рекомендуемый для нецелевого использования. К сожалению. Многие переключатели.hprof 19306 Дампы готовы для офлайнового анализа. Чтобы их включить или выключить. которые можно использовать для настройки многих характеристик работы машины во время исполнения. чтобы они лучше отвечали запросам вашего приложения. Ниже поговорим о других переключателях. чтобы быть правдой. в частности многие из стандартных. как. так и в инструменте jhat от Oracle (полное его название — инструмент для анализа кучи Java.format=b. 6. Обычно это делается с помощью параметров командной строки. . мы не можем здесь по­ дробно его обсудить. как в самом jmap когда-нибудь попоз� же. перед ними нужно указать + или . всегда будьте внимательны.соответственно. которыми можно пользоваться для управления виртуальной машиной Java и изменения режимов ее работы. Благодаря jmap можно начать обзор некоторых базовых настроек и потребления памяти в вашем приложении. сколько раз метод должен быть вызван. Java Heap Analysis Tool). ничего не принимают. -XX: CompileThreshold=1000 (здесь мы задаем. применяемые при сборке мусора. Другие переключатели принимают параметр.5. это означает. связанные с обеспечением производительности. А другие переключатели. Но для настройки производительности часто требу� ется получить более полный контроль над подсистемой сборки мусора. Некоторые переключатели по сути являются булевыми.4. прежде чем он будет рассмотрен для динамической компиляции. Понятие о повышении производительности 241 Работая с такими режимами jmap. Поэтому рассмотрим не� сколько параметров. являют­ ся расширенными. В этом разделе мы рассмот� рим некоторые переключатели.Глава 6. например. вот так: jmap -dump:live. Полезные переключатели виртуальной машины Java В составе виртуальной машины Java есть множество полезных параметров-пере� ключателей (не менее сотни).file=heap. что он нестандартный. НЕСТАНДАРТНЫЕ ПЕРЕКЛЮЧАТЕЛИ ВИРТУАЛЬНОЙ МАШИНЫ JAVA Если переключатель начинается с -X:. то это расширенный переключатель (extended switch). виртуальная машина Java продолжает работать (а если вам не повезло — она даже могла успеть провести сборку мусора между вашими мгно� венными снимками). Возможно. особенно если система выдает странные результаты или если они слишком хороши. который мы здесь рассмотрим.

Последний переключатель из списка выводит в журнал стандартную информа� цию о сборщике мусора. Как будет продемонстри� ровано в следующем подразделе о VisualVM.gc() Очень часто размер -Xms задается равным размеру -Xmx. оно также дается в таблице. вам может очень пригодиться инст� румент с графическим интерфейсом. Тем не менее важно уметь читать форматы журналов и знать основные переключатели. Необходимые технологии В табл. Об интерпретации этой информации мы поговорим в сле� дующем разделе. затраченное на сборщик мусора в условиях. 6. когда потоки приложения продолжают работать . Таблица 6.3. применяемые при сборке мусора. Дополнительные переключатели для получения расширенной информации Переключатель Эффект -XX:+PrintGCDetails Дает расширенные подробности о сборщике мусора -XX:+PrintGCDateStamps Сопровождает операции сборщика мусора метками времени -XX:+PrintGCApplicationConcurrentTime Время. В таком случае процесс проработает именно такой объем кучи.5.5.2 перечислены основные переключатели. и во время исполнения не потребуется менять размеры (а такие изменения могут приводить к неожиданному замедлению работы). влияющие на работу сборщика мусора.3. часто бывает полезно по� смотреть. позволяющий визуализировать поведение виртуальной машины. Некоторые наиболее по� лезные переключатели. 6. Если у переключателя есть значение по умолчанию. Основные переключатели для сборки мусора Переключатель Эффект -Xms<размер в Мбайт>m Исходный размер кучи (по умолчанию 2 Мбайт) -Xmx<размер в Мбайт>m Максимальный размер кучи (по умолчанию 64 Мбайт) -Xmn<размер в Мбайт>m Размер молодого поколения в куче -XX:-DisableExplicitGC Предотвращает какой-либо эффект от вызова System. применяемые с журналами сборщика мусора. что вы просто утопаете в информации. Наряду с базовым флагом verbose:gc существует и несколько других переключателей. Иногда чтение журналов сборщика мусора оказывается непростой задачей — мо� жет показаться.242 Часть 2.2. 6. Чтение журналов сборщика мусора Чтобы извлечь из сборки мусора максимальную пользу. приведены в табл. что делает подсистема. которые управляют выводимой информа� цией. Таблица 6. так как под рукой может и не оказаться такого графического инструмента.

6 показан стандартный экран со сводными показателями программы VisualVM. На самом деле здесь мы видим. проводи� мого постфактум. что означает приведенный выше фрагмент: <time>: [GC [<collector name>: <occupancy at start> -> <occupancy at end>(<total size>)] <full heap occupancy at start> -> <full heap occupancy at end>(<total heap size>).0007529 seconds Application time: 0. используемая до и после сборки молодого поколения.0085059 seconds Total time for which application threads were stopped: 0.5. Поэтому многие разра� ботчики и предпочитают пользоваться каким-нибудь инструментом с графиче� ским пользовательским интерфейсом для выполнения первичного анализа. Наряду с логирующими флагами сборщика мусора есть и такой флаг. Наконец. К счастью. <pause time> secs В первом поле видим время.0021318 seconds Эта информация не обязательно описывает общую длительность сборки мусо� ра. идут несколько полей для полной кучи. поставляемый в комплекте со стан� дартной виртуальной машиной Java от Oracle. Визуализация использования памяти с помощью VisualVM VisualVM — это инструмент для визуализации. 6. Затем идет имя сборщи� ка (PSYoungGen).0244970 secs] Разберемся. а также общий размер моло� дого поколения. который может вас немного запутать. Флаг -XX:+PrintGCAp plicationStoppedTime дает в журнале вот такие строки: Application time: 0. поэтому он требует объяснений. Далее указана память. Конечно. Среди этих операций не только сборка мусора. 0. который подбирает молодое поколение. но визуализировать ее не так просто.580: [GC [PSYoungGen: 486784K->7667K(499648K)] 1292752K->813636K(1400768K). Оно указано в секундах с момента запуска виртуальной машины Java. которые начались в безопасном состоянии. ко� торую мы подробно опишем ниже) поставляется с очень удобным инструментом такого рода. Этот экран вы увидите. в которое началась сборка мусора.0002074 seconds Application time: 0. Понятие о повышении производительности 243 Вместе эти переключатели дают примерно такой вывод: 6. насколько долго были остановлены потоки во время операций. На рис. 6.Глава 6. Он имеет модульную архитектуру и в стандартной конфигурации может использоваться как удобная замена для уже устаревающей программы JConsole. операции привязанных блокировок Java 6).6. но и другие виды работ.9279047 seconds Total time for which application threads were stopped: 0. если запустите VisualVM и подключите ее . вся эта информация полезна для логировании и анализа. виртуальная машина HotSpot (из стандартной установки Oracle. начинаемые в безопасном состоя� нии (например.

Это классическая визуализация того. Если базовый уровень остается стабилен (или даже снижается со временем). Рекомендуем установить эти модули в VisualVM прежде. Sampler. выделенные в Эдеме.6. Здесь мы видим объекты. но при работе в сети доступен не весь набор функций). . возникающей при при� менении области уцелевших и хранилища. Это отличный инструментарий. хорошо помогающий разобраться в некоторых динамических характеристиках среды времени исполнения Java. Рис. которые называются Extensions. что память используется исключительно разумно. а потом собранные при уборке молодого поколения. использованные. — это значит. как задействуется память на платформе Java.244 Часть 2. Экран со сводными показателями программы VisualVM Здесь вы видите. Именно эту установку мы использовали для подготовки примеров кода к нашей книге. как VisualVM профилирует экземпляр Eclipse. который соответствует общей нагрузке. чем приступать к серьезной работе с программой. На рис. MBeans и VisualVM. работающему на вашем локальном компьютере (VisualVM может подключаться и к удаленным приложениям. JConsole. После каждой молодой сборки объем используемой памяти вновь падает до базового уровня. работающий на MacBook Pro. В верхней части правой половины окна расположено несколько полезных вкла� док. 6. пока процесс продолжает работать.7 вы видите «пилообразный» вариант использования памяти. 6. По нему можно отслеживать состояние процесса Java с течением времени. Мы работаем с подключаемыми модулями (плагинами). Необходимые технологии к приложению.

Здесь вы видите Эдем. соответ­ ствующий состоянию. как изменяются размеры поколений. показывает. В таком случае рано или поздно начнется полная сборка. как сжимается Эдем. Один из ключевых моментов в данной визуализации заключается в том. влияют на производитель� ность. 6. то процесс вряд ли израсходует всю доступную ему память.8. а области уцелевших меняются ролями. особенно в комбинации с такими переключателями. то это означает. предоставляемые виртуальной машиной. Когда приложение работает. как работает ваш код. Если базовый уровень для полных сборок остается стабильным достаточ� но долго. что что-то пошло неправильно. как службы. вы сможете лучше понять. Понятие о повышении производительности 245 Рис. как Xmx и Xms. 6. Если требуется уменьшить частотность молодых сборок. Поэтому вам определенно не помешает поэкспериментировать с VisualVM. Эдем). В частности. При полной сборке на экране образуется второй пилообразный паттерн. две области уцелевших (S0 и S1). в свою очередь. В нем также будет наблюдаться свой базовый уровень использования памяти. что градиент уклона зубца на графике показывает скорость. . можно наблюдать. в котором память содержит лишь действительно «живые» объекты. что нужно попытаться сгладить кру� тизну зубца графика. по окончании молодой сборки вид� но. старое поколение и по� стоянное поколение памяти. Исследуя систему памяти и другие характеристики среды времени исполнения. с которой процесс расхо� дует молодые области памяти (как правило.7. Другой способ визуализации текущего потребления памяти показан на рис.Глава 6. это не обязательно означает. Просто некоторые объекты живут достаточно долго и попадают в хранилище. Это. Экран обзора в VisualVM Если базовый уровень постепенно повышается.

На первый взгляд. Так можно определить. какие уловки применяются в виртуальной машине Java для улучше� ния производительности. относящегося к актуальному методу. Анализ локальности Этот подраздел в основном справочный — он описывает изменения. 6.7.8.5. Ее суть заключается в том. анализ локальности (escape analysis) кажется довольно не� тривиальной идеей. где рассказывается о параллельном сборщике. Программист не может напрямую влиять на эти изменения или контролировать их. Эти изменения можно проиллюстрировать много� численными наглядными и описательными примерами. демонстрирующий сборку мусора Перейдем к следующему подразделу. Визуальный плагин VisualVM. недавно по­ явившиеся в виртуальной машине Java. задействуемой во время исполнения. а не использовать еще немного ди� намической памяти. то читайте далее.8. уменьшив количе� . где поговорим о новой технологии вирту� альной машины Java.5. После этого виртуальная машина Java может создать объект в стеке внутри фрейма. какие переменные не передаются другим методам или не возвращаются от актуального метода. какие локальные переменные (ссылочного типа) используются только внутри метода. Так можно улучшить производительность. В последних версиях Java оптимизация активизируется по умолчанию. Если вы хотите сами по� смотреть. 6. позволяющей автоматически уменьшать объем динамической памяти. Необходимые технологии Рис.246 Часть 2. В противном случае можете переходить к подразделу 6. чтобы проанализировать метод и посмотреть.

а потом поговорим о бо� лее новом сборщике. Теперь рассмотрим еще одну функцию. 6. которые происходят в ходе выполнения процесса. который называется Garbage First. Если паузы. то выделенные таким образом переменные не переходят в категорию мусора и никогда не требуют сборки.9. В Java 6. 6.9. поэтому в более новых версиях Java этот бонус скорости достается даром. позволяющий снизить интенсивность сборки мусора в виртуальной машине Java. . которая может значительно повлиять на ваш код. Поэтому для такого сокращения пауз может быть целесообразно запускать дополнительные потоки (и немного сильнее нагружать процессор). в принципе. это пойдет на пользу программе. Анализ локальности позволяет избегать выделения объектов в куче Это означает. Вместе с основным сборщиком вы мо� жете использовать переключатели. Понятие о повышении производительности 247 ство молодых сборок. позволяющие переводить платформу на другие стратегии сборки. По опыту работы с этой возможностью можно утверждать.Глава 6. В следующих разделах мы рассмотрим два сборщика. что. В этом случае. Анализ локальности — это новый подход. обеспечи� вающих такие возможности. автоматически высвобождается память. затрачиваемые в приложении на сборку мусора. Это может радикально повлиять на количество молодых сборок. станут короче. что вы пожелаете сами управлять тем. особенно если ваши процессы активно соби� рают мусор. если выделе� ние происходит не в куче. что можно избежать выделения в куче. — выбор стратегии сборки. которые требуется выполнить вашей программе. По многим причинам можно предпочесть высокопроизводительный сборщик. Это не так много. такой подход позволяет улучшить общую производительность на несколько процентов. так как когда происходит возврат актуального метода. которая ис� пользовалась для содержания локальной переменной. но и не помешает. Начнем с классического высокопроизводи� тельного варианта (параллельное отслеживание и очистка). Возможно также. как часто программа прерывается на сборку мусора. Рис.23 и выше анализ локальности активизирован по умолчанию. Это пока� зано на рис.

4. а также работает парал� лельно с другими потоками приложения в оставшуюся часть цикла по сборке му� сора.570: . приведенных в табл.  вы можете гарантированно собрать весь мусор лишь в том случае.692+0000: 90434. Таким образом. Обычно такая работа лучше выполняется на машинах с большим коли� чеством ядер. Необходимые технологии 6. но. чтобы работа могла протекать в основном без остановки потоков при� ложения. если на время сборки все потоки приложения будут остановлены. которые важны в данном случае:  неизбежно некоторое количество пауз. Такой сборщик будет выполнять максимальное количество работы по сборке.8. Переключатели для сборщика CMS Переключатель Эффект -XX:+UseConcMarkSweepGC Переключается на CMS-сборку -XX:+CMSIncrementalMode Инкрементный режим (обычно необходим) -XX:+CMSIncrementalPacing Инкрементный режим (обычно необходим) -XX:+UseParNewGC Параллельное выполнение молодых сборок -XX:ParallelGCThreads=<N> Количество потоков. Приходится пойти на эти дополнительные издержки. которые следует использовать при сборке мусора Эти переключатели переопределяют стандартные настройки сборки мусора.248 Часть 2.570: [ParNew: 14777K->14777K(14784K). Параллельное отслеживание и очистка Сборщик для параллельного отслеживания и очистки (Concurrent Mark-Sweep.570: [GC 90434.5. Кроме того.  подсистема сборки мусора ни в коем случае не должна собирать «живые» объекты — в противном случае мы рискуем аварийным завершением виртуаль� ной машины Java или даже чем-нибудь похуже. 6. Он делает две очень краткие паузы с полной остановкой всех потоков. когда приостанавливаются все работа­ ющие потоки. Он за� действуется с помощью комбинации переключателей. CMS) — это высокопроизводительный сборщик мусора. Таблица 6. 0. В журнале получаются примерно такие строки: 2010-11-17T15:47:45. CMS избегает получения «ложноотрицательных» результатов. так как в описанной ситуации возникают условия гонки. где возможны более частые и краткие паузы. что является и что не является мусором. может упустить часть мусора (такой упущенный мусор будет собран в следующем цикле). Как работает такой параллельный подход? Существует три ключевых фактора алгоритма отслеживания и очистки. CMS при работе приходится вести более сложный учет того.4. При работе CMS активно использует именно последний пункт. рекомендованный для ис� пользования в Java 5 и на протяжении большей части существования Java 6.0000595 secs]90434. конфигурируя сборщик мусора CMS с N параллельных потоков для сборки. реализуемое при параллельной обработке в данной системе.

9086004 secs] [Times: user=0. которые работают с учетом поколений (хотя в нем и используется алгоритм отслеживания и очистки). В установках Java 6 такой сборщик используется нечасто. в чем заключается его функциональная новиз� на и чем он принципиально отличается от всех существующих сборщиков мусора для Java. Во время паузы объекты эвакуируются в другую область (по� добно тому как объекты из Эдема переходят в область уцелевших). но здесь есть дополнительные разделы. а освобожден� ная область помещается в список пустых областей.9083496 secs] 129465K->117349K(129472K). real=0. с которы� ми мы сталкивались раньше.Глава 6.5. относящи­ еся к сборщикам CMS и CMS Perm. G1 — новый сборщик мусора для Java G1 — это новейший сборщик мусора для платформы Java.91 sys=0. 0. Такое новое разделение кучи на области одинаковых размеров проиллюстрировано на рис. 0. на 20 мс один раз в 5 ми� нут). на какое время программа может прервать работу во время исполнения ради сборки мусора (например. 6.91 secs] Эти строки напоминают базовый фрагмент журнала для сборки мусора.4. G1 будет делать все возможное.00.10. по 1 Мбайт). Рассмотрим этот новаторский инструмент. G1 не из тех сборщиков мусора. самый лучший высокопроизводительный сборщик мусора — Garbage First (G1). Центральная идея. В последние годы у CMS появился новый серьезный конкурент. но по мере распространения Java 7 ожидается. 6. рас� смотренный в подразделе 6. что он войдет в состав Java 7. При работе G1 делит кучу на области равного размера (например. Разделение кучи в сборщике G1 . Это коренным образом отличает его от всех сборщиков мусора. Первоначально плани� ровалось. называется «желательная пауза» (pause goal). Полнофункциональный статус G1 приобрел в Java 7. чтобы работать с заданными желательными паузами. не различая молодых и старых областей. [CMS Perm : 49636K->49634K(65536K)] icms_dc=100 . что G1 станет стандартным вариантом для высокопроизводительных (а возможно. Рис. Этот параметр показывает. выясним. Понятие о повышении производительности 249 [CMS: 114688K->114688K(114688K). Вооружившись G1. разработчик может гораздо полнее контролировать процесс сборки мусора. и для любых) приложений.4. но ранняя версия этого инструмента была доступна уже в технических релизах Java 6. 6.10.9. лежащая в основе G1.

чем ожидалось). 6. а в конце раздела расскажем. обязательно обратите внимание на этот инструмент. Флаги для сборщика мусора G1 Переключатель Эффект -XX:+UseG1GC Включает сборку G1 -XX:MaxGCPauseMillis=50 Указывает G1. перечисленные в табл. в результате чего преобра� зуются в машинный код. на котором возникает необходимость настройки сборки мусора в вашей программе. чтобы вы могли видеть. Таблица 6. Если задать жела� тельную паузу длительностью 1 мс один раз в 100 лет. Разумеется.5. В следующем разделе мы поговорим о динамической компиляции. Динамическая компиляция с применением HotSpot В главе 1 мы уже говорили о том. Зная это. какие методы ком� пилируются в настоящий момент. что классы приложения подвер� гаются дальнейшей компиляции во время исполнения. что платформу Java лучше всего представлять как динамически компилируемую. Чтобы приступить к работе с G1. G1 соберет мусор из стольких областей. что паузы должны отстоять друг от друга не менее чем на 200 мс. вы сможете задавать обоснованную целевую паузу. 6. как активизировать ло� гирование динамической компиляции. установить максимальную желательную паузу 50 мс и указать.250 Часть 2. Обычно такой компиляции подвергается только один метод в единицу време� .6. Такой процесс именуется динамической (just-in-time) компиляцией или джейтин� гом. у системы G1 есть предел скорости. чтобы G1 успел убрать мусор. если на уборку последней области уходит больше времени. что между сборками мусора должно проходить не менее 200 мс Переключатели можно комбинировать: например. используйте настройки. сколько времени (в среднем) уходит на сбор� ку мусора в конкретной области. что в ходе отдельно взятой сборки необходимо избегать пауз дольше 50 мс -XX:GCPauseIntervalMillis=200 Указывает G1. Во многих программах (или даже в большинстве программ) динамическая компиляция — важ� нейший фактор создания высокопроизводительного кода. Пауза долж� на быть достаточно длительной. G1 кажется очень многообещающим сборщиком при самых разных нагрузках и типах приложений. Это означает.5. Если вы дошли до этапа. Необходимые технологии Подобное изменение стратегии сборки мусора позволяет платформе собирать статистическую информацию о том. сколько успеет обработать за заданную паузу (хотя возможны и превышения отпущенного времени. Мы рассмотрим основы динамической компиляции. это будет недостижимое и неграмотное требование.

Как только достигается по� роговое значение (по умолчанию равное 10 000 раз) и метод готов для дальнейше� го использования. чем интерпретируемый код. остающийся в интерпрети� руемом виде.  целесообразно сначала компилировать те методы. динамически компилируемые языки. могут работать быстрее языков. чтобы идентифицировать важные части любой объемной базы кода. если только такой скомпилированный вариант не будет по какойто причине поврежден или деоптимизирован. как скомпилированный (иногда компи� ляция метода не удается. не имеют доступа к какой-либо информации среды времени исполнения — например. Понятие о повышении производительности 251 ни. то при всех последующих вызовах метода будет применяться скомпили� рованная форма. поскольку в обычных условиях любой метод. почему бы не делать ее заранее. В частности. — это обычно залог оптимизации производитель� ности. Другой. те языки.  компилируемые методы работают гораздо быстрее. чем тот же метод в интерпретируемом виде. ЗАЧЕМ НУЖНА ДИНАМИЧЕСКАЯ КОМПИЛЯЦИЯ? Иногда приходится слышать вопрос: а зачем вообще на платформе Java нужна динамическая компиляция. чем иметь дело с по-разному скомпилированными бинарными файлами для каждой из целевых платформ. подобные Java. о доступности определенных команд. как часто вызывается метод (а также ведет другую статистику). что делается в фоновом режиме. которые обеспечивают динамическую компиляцию. Открывается интересная возможность: по сути. Если компиляция будет выполнена успешно. которые используются наи� более активно. какие методы особенно важны для программы и какие из важных методов компилируются. но это случается довольно редко). как в C++. находящимся в байт-коде. применяющие динамическую компиляцию.  занимаясь динамической компиляцией. поток виртуальной машины Java компилирует код метода в ма� шинный код. Последний пункт означает. более амбициозный ответ. В зависимости от конкретной природы кода в методе скомпилированный метод может работать вплоть до 100 раз быстрее. что гораздо удобнее использовать в качестве базовой единицы развертывания независимые от платформы артефакты (файлы . использующих раннюю компиляцию. которые выполняют компиляцию перед исполнением (ahead-of-time compilation). Понимание того. заключается в том. что сначала следует рассматривать скомпилирован� ный код. а также к статистике о работе кода. что языки. Виртуальная машина Java отслеживает. характеристик аппаратного обеспечения. всегда стоит идти по пути наименьше� го сопротивления. обычно могут предоставить компилятору больше информации. задействуется не так часто. Рассмотрим некоторые базовые факты о динамической компиляции:  практически в любой современной виртуальной машине Java найдется тот или иной динамический компилятор.Глава 6. На первом этапе работы с методом он является интерпретируемым представле� нием. . Необходимо понимать этот процесс.jar и .  полностью интерпретируемые виртуальные машины работают очень медленно по сравнению с теми. Обычно это объясняется тем.class).

Это означает. какие именно методы компилируются в настоящий момент. как активизировать логирование компиляции методов.6. если откажется от оптимизационного решения. указываемый в командной строке). Клиентский компилятор Клиентский компилятор ориентирован в первую очередь на использование в при� ложениях с графическим пользовательским интерфейсом. Для выбора режима нужно задать переключатель -client или -server при запуске виртуальной машины (это должен быть первый переключатель. Многие общие сообра� жения. Если валидность не подтвердится. который входит в состав HotSpot. применимы и к другим виртуальным машинам. Два этих режима предпочти� тельны для применения в различных практических ситуа­циях.252 Часть 2. выполняемого в лю� бой момент. HotSpot — виртуальная машина. в которой жизненно важна согласованность (непротиворечивость) программы. Серверный компилятор Напротив. что он не может неожиданно остановиться. Чтобы гарантировать корректность кода. Такой активный подход может обеспечивать более высокую производительность. которая называется HotSpot. приобретенная Oracle после покупки компа� нии Sun Microsystems (до этого Oracle уже владела виртуальной машиной JRockit. Это область. 6. Благодаря ему вы сможете отслеживать. но специфические детали могут значительно разниться. проверяющую валидность сделанного пред� положения. разработанной в компании BEA Systems). ко� торое оказалось неверным или принималось на основании неверных предполо� жений. которая служит основой OpenJDK. Начнем с вводного рассказа об особом динамическом компиляторе. чем использование кли� ентского компилятора. Она может работать в двух самостоятельных режи� мах — клиентском и серверном. серверный компилятор (C2) в ходе компиляции активно оперирует предположениями. Знакомство с HotSpot HotSpot — это виртуальная машина.1. а потом опишем два наиболее мощных доступных вари� анта оптимизации — встраиваемую подстановку (inlining) и мономорфную диспет� черизацию (monomorphic dispatch). Итак. высказанные далее. C2 добавляет быструю динамическую проверку (обычно называемую охранным условием (guard condition)). В заключение этого краткого раздела мы по� кажем. то машина откажется от активной компиляции и попробует применить другой вариант. . Необходимые технологии В оставшейся части нашего разговора о динамической компиляции речь пойдет о виртуальной машине Java. Поэтому клиентский компилятор (иногда именуемый C1) в ходе компиляции обычно при� нимает сравнительно консервативные решения. начнем с введения в HotSpot. всегда работающего «с оглядкой».

именуемая Java реального времени (real-time Java). Изменение разброса значений и средней длительности ожидания . а не вариант HotSpot). Многие разработчики задаются вопросом. Для этого системы реального времени могут идти на повышение средней длительности ожидания. требуемых для выполнения определенных операций. по� чему код. не всегда оказывается самой быстрой. это самостоятельная виртуальная машина Java. но более слабым разбросом зна� чений. Понятие о повышении производительности 253 Java реального времени В последнее время была разработана особая форма языка Java. отражающих ожидание. Рис. Оказывается. 6.Глава 6. Серия 2 (верх� няя группа точек) характеризуется повышенной средней длительностью ожидания (поскольку находится выше по шкале ожидания). На рис. Для обеспечения более со� гласованной работы приложения система может немного пожертвовать произво� дительностью. чем точки из серии 1. просто не исполь� зует эту платформу (кстати. несмотря на распространенное заблуждение. 6. Программирование реального времени целиком завязано на гарантиях.11 мы видим две серии точек.11. которые можно дать в том или ином случае. что система реального времени. которые сравнительно сильно разбросаны. Точки более плотно сгруппированы вокруг среднего уровня. от которого требуется высокая производительность. С точки зрения статистики система реального времени пытается уменьшить разброс временных промежутков.

Таким образом. напишите тестовую программу примерно как в листинге 6.254 Часть 2. методы какого размера должны встра­ иваться. В «промышленном» коде они не слишком нужны. как в серии 1. Встраиваемая подстановка методов Встраиваемая подстановка (inlining) — один из самых мощных приемов. А ЧТО НАСЧЕТ МЕТОДОВ ДОСТУПА? Некоторые разработчики ошибочно полагают. не станет ли вызывающий метод в результате встраивания слишком велик и не по� влияет ли это на кэши кода). прибегать к ним стоит лишь в тех случаях.2 и сравните скорость разогнанного метода доступа и скорость обращения к общедоступному полю. Дело в том. нежели механизмы. а также насколько часто должен вызываться метод. и в ре� зультате получается такой разброс точек. . При его применении вызов встроенного метода не выпол� няется. 6. Ход рассуждений при этом таков: если переменная закрыта. Встраивание методов — полностью автоматический процесс. выпол� няющие раннюю компиляцию. компилируя методы в машинный код. При этом метод доступа просто заменяется прямым доступом к закрытому полю. Существуют пе� реключатели. позволяющие регулировать. Одно из преимуществ платформы — компилятор может принять решение о встра­ ивании на основании последних статистических данных о том.2. широко используемой для повышения эффек� тивности в любых средах времени исполнения — серверных. когда никакими другими способами не удается повысить производительность.6. что метод доступа (общедоступный метод-установщик. Это неверно. обычно пытаются снизить среднюю длительность задержки даже ценой большего разброса значений. как встраивание методов организовано на внутрисистемном уровне. так как весь контроль доступа осуществляется на этапах загрузки и связывания классов. Эти переключатели будут интересны в основном любопытным программистам. обраща­ ющийся к закрытой переменной экземпляра) нельзя встраивать с помощью HotSpot. практически во всех случаях вам подойдут значения. которые желают лучше понять. Это не противоречит модели безопасности. прежде чем он будет рассмотрен для встраиваемой подстановки. серверный компилятор обычно активно занимается оптимизацией. то от вызова метода нельзя отказаться ради оптимизации. задаваемые по умолчанию. поскольку доступ к такой переменной извне класса запрещен. стремящиеся повысить производительность. Если вы все же хотите убедиться. Далее мы поговорим о практике. Таким образом. что такое поведение реализуемо. Вместо этого код вызываемого метода встраивается прямо в вызывающий элемент. что такие переключатели могут оказывать непредсказуемые эффекты на произво� дительность всей среды времени исполнения. действующей в Java. предо­ ставляемых в HotSpot. клиентских и реаль� ного времени. как часто вызыва� ется конкретный метод. компилятор HotSpot может принимать значительно более разумные решения о встраивании. Необходимые технологии Команды. а также на основании других факторов (например. HotSpot может игнорировать контроль доступа и делает это.

использующие раннюю компиляцию. Этот прием основан на том наблюдении. чтобы застраховаться от такого случая. то среда времени исполнения уклонится от такого шага оптимизации. и с его подклассом. Пред� положим. Понятие о повышении производительности 255 6. на которую совершенно не способны языки. которым мы будем пользоваться.callMyMethod() практически никогда не будет иметь дело и с классом. соответствующего callMyMethod(). что все объекты. подразумевает. Если такое предположение когданибудь не оправдается. среда времени исполнения путем проверки удостоверяется. описанной выше. Чтение журналов компиляции Рассмотрим пример. точка вызова obj.6. по которой метод getInstance() в опреде� ленных обстоятельствах не мог бы вернуть объект типа MyActualClassNotInterface. а в других обстоятельствах — объект какого-либо подкласса. какую пользу можно извлечь из сооб� щений журналов. которую можно наблюдать в определенную ночь в конкретной точке Земли. не сделает чего-то неправильного. как это и ожидалось. является оптимизация с при� менением мономорфных вызовов. приме� няемый только серверными компиляторами. Если добавить его в командную строку для запуска виртуальной машины Java. конечно. обрабатывающее этот каталог и генерирующее звездные карты с картиной неба. что в большинстве случаев подобный вызов метода к объекту: MyActualClassNotInterface obj = getInstance().Глава 6. Но на практике это практически никогда не происходит. Клиентские компиляторы и компи� ляторы реального времени так не работают. Иными словами.3. будут отно� ситься к одному и тому же типу. у нас есть приложение.4. а программа этого даже не заметит и.6. которые можно наблюдать с Земли. Динамическая компиляция и мономорфные вызовы Примером активной оптимизации. то потоки . В таком случае подстановку метода Java можно заменить прямым вызовом скомпилиро� ванного кода. Тем не менее. что тип obj был вставлен компилятором. Возьмем образец вывода. Нет никакой технической причины.callMyMethod(). obj. вкратце рассмотренных выше. Это один из расширенных переключателей. регистрирующих работу динамического компилятора. демонстрирующего. платформа может выполнять оптимизацию. СОВЕТ Мономорфная диспетчеризация — это пример профилирования во время исполнения. В катало� ге звезд Гиппарха перечислены звезды. позволяющий понять. 6. когда мы запускаем наше приложение для построения звездных карт. которым адресованы такие вызовы. какие методы компилируются. применяемого в виртуальной машине Java. — -XX:+PrintCompilation. Таким образом. Основной флаг виртуальной машины. Итак. мы рассмотрели достаточно агрессивный вариант оптимизации.

Опасайтесь зомби Изучая образцы журналов работающего кода.nio.. выполненной при загрузке класса. Обычно это происходит из-за какой-то операции.BigInteger::montReduce (99 bytes) sun.lang. 1 2 3 4 5 6 7 8 9 1% ! .lang.math. наверное. который был скомпилирован. что их стоит скомпилировать.DelimitedLine::getNextString (5 bytes) org.String::charAt (33 bytes) sun.  % — замещение в стеке (on-stack replacement. А вот описание некоторых других полей:  s — указывает. что объясняется динамической природой платформы.256 Часть 2. Метод был скомпилиро� ван и заменил интерпретируемую версию прямо в работающем коде. когда те или иные методы преодолевают порог компиляции и преобразуются в машинный код.SingleByteDecoder::decodeArrayLoop @ 129 (308 bytes) sun. теперь поврежден. Эти сообщения указывают. 2% ! 65 s java. Обрати� те внимание: OSR-методы имеют собственный порядок нумерации.misc. первыми в разряд компили� руемых попадают методы платформы (например. Со временем будут компилироваться и методы приложения (например. Приведенные выше строки показывают.provider.String::hashCode (64 bytes) java. Строки вывода сопровождаются номерами.CamelotStarter::populateStarStore @ 25 (106 bytes) java.FloatingDecimal::doubleValue (1289 bytes) org.BigInteger::multiplyToLen (219 bytes) java..math. Необходимые технологии динамической компиляции получат команду записывать сообщения в стандартный журнал.BigInteger::addOne (77 bytes) java.lang.  ! — говорит о том..camelot.BigInteger::squareToLen (172 bytes) java. Эти строки означа� ют.security. указывающими.Star::parseStar (301 bytes) org. какие методы были признаны настолько «востребованными». что метод синхронизирован. и ожидали. 39 40 41 ! .BigInteger::mulAdd (81 bytes) java.math. вы иногда можете заметить строки made not entrant (стал непригоден для входа) и made zombie (стал зомби). что определенный метод.cs.SHA::implCompress (491 bytes) java. String#hashCode).camelot. Как вы.hipparcos.. начина­ ющийся с 1.StringBuffer::append (8 bytes) Перед нами типичный вывод от PrintCompilation. org. .camelot.hipparcos. что в методе есть обработчики исключений.math. Обратите внимание. OSR). Star#parseStar. применяемый в данном примере для синтаксического анализа фор� мы записи из астрономического каталога). что от запуска к запус� ку этот порядок может немного меняться. который был создан с помощью сер� верного компилятора (C2).math. в каком порядке методы компилируются в ходе работы.BigInteger::primitiveLeftShift (79 bytes) java.hipparcos.math.camelot.

Итак. Следовательно. могут зависеть от точной версии виртуальной машины Java и ис� пользуемой операционной системы. что оптимизация кода в такой ма� шине иногда может быть нетривиальной задачей. что мы скажем об этом повторно). Ошибочно полагать. Оказывается. а результаты измерений могут получаться не� ожиданными. эти моменты необходимо измерять. Лишь так мож� но найти истинные причины проблем с производительностью. 6. Вспомним рассмотренные нами ключевые моменты. предоставляемой виртуальной маши� ной Java:  виртуальная машина Java — невероятно мощная и сложная среда времени ис� полнения. если оптимизация была произве� дена на основании предположения. может пятикратно различаться в размерах на Linux и Solaris. недостаточно просто уставиться в код и молиться об озарении. необхо� димо заниматься измерениями. Резюме Принимаясь за настройку производительности. один и тот же метод может быть многократно деоптимизирован и перекомпилирован.  учитесь читать журналы и другие индикаторы.  вам могут очень пригодиться инструменты мониторинга и другие инструменты. предоставляемые на платфор� ме.  природа виртуальной машины Java такова. . без измерений здесь не обойтись. Зачастую код после этого пересматривается. Понятие о повышении производительности 257 Деоптимизация Машина HotSpot умеет деоптимизировать код. Настройка производительности требует постоянного устранения источников ошибок. оказавшегося ошибочным.Глава 6. выявляемых в ходе тестирования. какие методы будут компилироваться. что количество скомпилированных методов стабили� зируется. — специальные инструменты будут под рукой не всегда.7. что самый безобидный на вид метод Java.  уделяйте особое внимание подсистеме сборки мусора и динамическому компи� лятору. преобразован� ный в машинный код посредством динамической компиляции. связанные с оптимизацией производительности в динамической среде. внима� ние к деталям и терпение. настройка производительности — это скрупулезные измерения. Как и многие другие параметры производительности. Код достигает равновесного скомпилированного состояния и остается в ос� новном без дальнейших изменений. Со временем вы заметите. что на всех платформах будет образовываться один и тот же набор скомпилированных методов и что ском� пилированный код конкретного метода будет иметь на разных платформах прибли� зительно одинаковый размер. Нет. откуда проистекают те или иные проблемы. Стандартными быстрыми заплатками тоже не обой� тись.  чтобы точно представлять.  необходимо измерять и ставить цели (это настолько важно. Конкретные детали того. после чего проводится альтернативная оптимизация.

Но многие пара� метры производительности платформы окажутся полезны и в более широком контексте — особенно детали. . Необходимые технологии Теперь вы уже должны обладать базовыми знаниями. Вы уже понимаете. как механизмы обеспечения производительности влияют на ваш собственный код. В следующей главе мы выйдем за пределы языка Java и рассмотрим другие языки.258 Часть 2. что вы чувствуете себя достаточно опытными и уверенными. чтобы непредвзято анализировать все эти данные и применять полученные выводы для решения проблем. связанных с производительностью. предлагаемые для работы на виртуальной машине Java. необходимыми для ис� следования продвинутых возможностей оптимизации производительности на плат� форме и проведения соответствующих экспериментов. Надеемся. касающиеся динамической компиляции и сборки мусора.

Clojure: программирование повышенной надежности .ЧАСТЬ 3 Многоязычное программирование на виртуальной машине Java Глава 7. Язык Scala — мощный и лаконичный Глава 10. Альтернативные языки для виртуальной машины Java Глава 8. Groovy — динамический приятель Java Глава 9.

 — Примеч. Но они сводятся всего лишь к тому. как функциональное программиро� вание может проявляться в таком языке. Эта часть начинается с главы. что непросто понять. которые слегка напоми� нают функциональное программирование. Исследуя незнакомую точку зрения на проблему. почему Java не всегда идеально подходит для решения любых проблем. Уильям Гибсон1 Оказывается. в котором вам доведется реализовывать новые проекты. вы мо� жете обнаружить у себя новые таланты и освоить навыки. Будущее уже здесь — просто оно неравномерно распределено. В последнее время появилось много книг и статей. . изучая новый язык. уже сегодня присутствуют в других языках виртуальной машины Java����������� ��������������� . Она не только обеспечивает производительность и мощность. Не более понятно и то.ru/articles/2007/08/13/ gibson/. Это скорее стиль и постепенное изменение общих принципов мышления разработчика.260 Часть 3. Таким образом. которые будут востребованы в будущем. то можете спросить: «А за� чем мне учить другие языки?» Как мы говорили в главе 1. но в ближайшем бу� дущем станут неотъемлемыми частями технологической среды. профессиональный Java-разработчик старается постоянно совершенствоваться во всех тонкостях язы� ка Java. Это каса� ется. Если до сих пор вы программировали только на Java. Во многих подобных статьях функциональное программирование описано так. а не только Java. Многоязычное программирование на виртуальной машине Java Эта часть книги посвящена исследованию новых языковых парадигм и многоязыч� ному программированию на виртуальной машине Java. а другой язык. вы можете украдкой заглянуть в другой мир. 1 Интересная статья об этом человеке находится по адресу http://lenta. Для этого необходимо вникать в темы. но и дает программисту удивительную гибкость в работе. На самом деле виртуальная машина Java по� зволяет сделать первый шаг к исследованию других языков. что многие новые идеи. Изучив новый язык для виртуальной машины �������������������������������������������������������� Java���������������������������������������������������� . которые пока только вырисовываются на горизонте. полнее осваивать платформу и экосистему. напоми� нающий мир будущего. например. как Java. функционального программирования. перев. чем полезны концепции функционального программирования и почему для конкретного проекта может понадобиться не Java. На ней вы можете опробовать совершенно новые для вас подходы к программи� рованию. которые пригодятся вам в дальнейшем. в которых продвигается мысль о том. В главе 8 мы покажем примеры. с чем его едят. объясняющей. что в ближайшем будущем функциональное программирование будет основным рабочим навыком любого практикующего разработчика. На самом деле функциональное программирование вообще не является чем-то единым целым. вы сможете применить уже имеющиеся знания под новым углом. Виртуальная машина Java — замечательная среда времени исполнения.

посвященной языку Clojure.Часть 3. . а потом возвращайтесь сюда и по� знакомьтесь с языками. взяв для примера язык Scala�������������������������������������� ������������������������������������������� . Более чистый подход к функционально� му программированию (который полностью избавлен от объектной ориентации) мы обсудим в главе 10. необходимыми для реализации таких приемов. В части 4 мы рассмотрим несколько практических случаев. в которых можно написать превосходные решения на альтернативных языках. Многоязычное программирование на виртуальной машине Java 261 чтобы обрабатывать код коллекций в более чистом и безошибочном стиле. то прямо сейчас загляните в часть 4. В главе 9 мы поговорим об «объектно-функциональ� ном» стиле. Если хотите убедить� ся в этом. Для это� го мы применим язык �������������������������������������������������������� Groovy�������������������������������������������������� .

 как виртуальная машина Java работает с другими языками. другой язык для нашей виртуальной машины. Следующий раздел представляет собой объемный пример. Мы познакомим вас с тремя языками (Groovy. но и другие языки.  типы языков. чем ко всему этому приступить. как можно подмешивать в проект. напи� санный на Java. Она действи� тельно настолько хороша. В этой главе мы обсудим способы описания языков различных типов (например. Здесь же мы начнем двигаться к пониманию того языкового стиля. мы хотели бы более убедительно рассказать о некоторых недостатках Java. порой вам хотелось. Наверное. К счастью. на свете есть неподражаемая виртуальная машина Java — вы могли оценить широту ее возможностей в нескольких последних главах. статических и динамических). вероятно. мы объясним. В этой главе мы покажем. в котором акцентируются отрицательные стороны. . замечали. что иногда код получается слишком пространным и не� уклюжим. Но прежде. Кроме того.7 Альтернативные языки для виртуальной машины Java В этой главе:  зачем вам могут понадобиться альтернативные языки для виртуальной машины Java. который известен под на� званием «функциональное программирование». чтобы все было иначе — как-то по­ проще. что на ней с успехом приживаются не только Java.  критерии выбора альтернативных языков. Scala и Clojure). зачем вам могут понадо� биться альтернативные языки и какими критериями следует руководствоваться при их подборе. которые будут более подробно рассмотрены в третьей и четвертой частях нашей книги. Если вы применяли язык Java для создания достаточно объемных проектов. то.

Такие системы на языке ��������������������������������������������������� Java�����������������������������������������������  — как хлеб насущный. ������������������������� Java��������������������� -разработчикам посто� янно приходится создавать что-то подобное. Языку Java недостает гибкости? Это провокация! Допустим. чем следует. что инфор� мация действительно будет попадать в базу данных. решаемые при программировании. Альтернативные языки для виртуальной машины Java 263 7. . 7. могут упростить многие распространенные задачи. вы создаете новый компонент в системе. например словарь (map) и фильтр (filter). расположенная в верхней части. 7. Мы познако� мим вас с сущностью функционального программирования и покажем. в системе два источника данных — подсистема поступающих зака� зов. принимающий два параметра: sourceData (данные от веб-службы. Система согласования Нам требуется система согласования. как функ� циональные идиомы. почему с таким кодом может быть неудобно работать. соб� ранные в словаре) и dbIds.1. Далее мы объясним. что из-за отсутствия в Java������������������������������������������������� ����������������������������������������������������� прямой поддержки для этих идиом процесс програм� мирования становится сложнее.1. Рис. Упрощенный вид системы показан на рис. В этом разделе мы покажем простой пример кода. Этот код занят согласованием работы двух источников данных. 7. которая занимается обработкой торговых операций (транзакций).1. Основа такой системы — метод reconcile(). Вы увидите.1.Глава 7. которую можно запрашивать через веб-сервис. которая позволит гарантировать. Пример системы для обработки транзакций Как видите.1. и находящаяся ниже распределенная база данных.

который для ясности помечен как MAIN (главный). Предположим. В таком случае зака� зы отобразятся в распределенной базе данных. который необходимо проверить. произошедшие в двух системах) все строки. Листинг 7. которые вы получили из базы данных. что воспользовался «живой» системой). какие из строк отсутствуют. Многоязычное программирование на виртуальной машине Java Необходимо получить ключ main_ref из sourceData и сравнить его с первичным ключом строк. Чтобы справиться с таким исключительным случаем. работающий внутри компании. этот цикл показывает. как делается такое сравнение. сделал несколько тестовых заказов через административный интерфейс (не зная.1 показано. Вот вывод от пробного запуска: 7172329 OK 1R6GV OK 1R6GW OK main_ref: 1R6H2 not present in DB main_ref: 1R6H3 not present in DB 1R6H6 OK . В листинге 7. а вот в системе входящих заказов их не будет.264 Часть 3. которые были зафиксированы в базе данных. Но есть и другая возможность. — попадает ли в распреде� ленную базу данных вся информация. Согласование работы двух источников данных Основной момент. нужен второй цикл. Кроме того. Эта задача решается в верхнем цикле for. содержит ли видимое множество (все операции. поступающая в систему в качестве входящих заказов. что сотрудник. Он проверяет.1.

1R6H2 присутствует в распределенной базе данных. чувствителен.println(pTradeRef +" OK"). Приходится просматривать коллекцию в цикле. лучше. Действительно. как правило.out. seen.add(pTradeRef). но возвращает true лишь при точном совпадении. Возможно. } придется заменить вот таким циклом: for (String id : dbIds) { if (id.println("main_ref: "+ pTradeRef +" not present in DB").Глава 7. встречается ли искомый аргумент в рассматриваемой коллекции. } } System.println(pTradeRef +" OK").equalsIgnoreCase(pTradeRef)) { System.  Как было бы здорово. Концептуальные основы функционального программирования В последнем примере есть две идеи. что надо. — лишь .  Если оперировать целыми коллекциями. seen. 7. Получается громоздко. ведь его к тому же проще осмысливать. становится ясно. позволяющий корректировать поведение имеющихся методов при работе с объектами.contains(pTradeRef)) { System. если бы можно было добавить небольшой логический компонент. метод. Это означает. но там он называется 1r6h2. то код получается и короче и. которого пока нет! Поэтому следующий фрагмент кода: if (dbIds.out.1. что про� блема состоит в использовании метода contains(). а нисходящий — наоборот. Он проверяет. что восходящий поток нечувствителен к реги� стру. } else { System. Если внимательно рассмотреть код из листинга 7. нужно писать краткий код.2. вам когда-либо доводилось писать код коллекций и раздражаться из-за того.println("main_ref: "+ pTradeRef +" not present in DB"). а не об� рабатывать ее как единое целое. который делает практически то. Альтернативные языки для виртуальной машины Java 265 Что пошло не так? Оказывается.add(pTradeRef). на которых мы хотели бы заострить ваше вни� мание. но при этом хрупок. что на самом деле вам требуется метод containsCaseInsensitive().1.out. что вот он. По мере роста вашего приложения этот недостаток лаконичности станет все более заметным. continue MAIN. Код не слишком лаконичен. чем при переборе содержимого коллекции.out.

вам потребуется придумать способ представ� ления вашей сопоставительной функции так. } Если бы у вас была такая возможность. нужно передать в метод представление вашего блока кода в качестве параметра.println("main_ref: "+ pTradeRef +" not present in DB"). будет выглядеть примерно так (мы выделили полужирным шрифтом специальный метод contains()): if (dbIds.out. Для того чтобы заниматься функциональным программированием. — в данном случае для проверки совпадения при нечувствительности к регистру.1. 7. то метод contains() можно было бы дорабатывать для любого тестового использования.primary_key). в кото� ром скрываются еще кое-какие идеи функционального программирования. в котором вызывается метод reconcile(): reconcile(sourceData. который вы хотели бы писать в таком случае. Чтобы постфактум добавить собственный код.println(pTradeRef +" OK"). нужна возможность написать часть кода как функциональный ли� терал.out. которое могло бы вам понадо� биться. new HashSet<String>(extractPrimaryKeys(dbInfos))). если у вас будет возможность немного изменить функциональность метода. заключается в невозможности добавления дополнительной логики к уже имеющимся методам. } else { System. } return out. как будто они явля� ются значениями. Иначе говоря. Многоязычное программирование на виртуальной машине Java немного бы его подправить. основной лимитирующий фактор. как если бы она была значением.3.add(tinfo. matchFunction)) { System.add(pTradeRef).contains(pTradeRef. for (DBInfo tinfo : dbInfos) { out. и мы будем возвращаться к ней. что подобные досадные не� удобства легко исправить благодаря функциональному программированию (ФП). Чтобы достичь этой цели. } . Другими словами. private List<String> extractPrimaryKeys(List<DBInfo> dbInfos) { List<String> out = new ArrayList<>(). Код. Идиомы словаря и фильтра Немного расширим наш пример и рассмотрим контекст. Это подводит нас к одной из основных идей функционального программирования: что. что имеется в виду. а потом присвоить эту конструкцию переменной. необходимо уметь представлять фрагменты логики (обычно — методы) так.266 Часть 3. добавив в него часть собственного кода? Поясним. seen. Но сначала рассмотрим еще один пример на Java��������� ������������� . не позволяющий писать более лаконичный (и безопасный) объектно-ориентированный код. Это центральная идея функционального программирования. В таком случае сообщаем.

ПРОСТОЙ ПРИЕМ ОПТИМИЗАЦИИ В вызове reconcile() мы применили полезный и немного хитрый трюк. Так и создается новый список. нужна функция. Так мы с удобством дедуплицируем список List. а строите новый список List. образующих коллекцию. указывающая. Если результат проверки элемента равен true. содержащихся в новом списке List. возвращающей булево значение (boolean). Альтернативные языки для виртуальной машины Java 267 Метод extractPrimaryKeys() возвращает список значений первичных ключей (в строковом виде). которая показана в листинге 7. возвращаемый от метода. может отличаться (String) от типа. она не изменит этого значения. Именно поэтому такое программирование и называется функциональным — функции ведут себя как математические выражения. Вы передаете возвращенный список List от extractPrimaryKeys() в конструктор HashSet для преобразования его в Set. Чтобы форма-фильтр работала. Ведь. формой filter().2. Она часто применяется в паре с другим широко известным паттерном. Такое применение map() — классическая идиома функционального программи� рования. следует ли включить конкретный элемент в готовый список. находящегося во входящем списке List (DBInfo). если передать в функцию f(x) = x * x значение 2. и вызову contains() приходится выполнять меньше работы в методе reconcile(). Вы не изменяете имеющийся List (соответственно форма filter() действует как математическая функция). Любители функционального программирования назвали бы такое выражение map() — метод extractPrimaryKeys() принимает список List и возвращает другой список List. Исходный список при этом никак не затрагивается. по� лучаемый после совершения определенной операции над каждым элементом спи� ска. .2. Листинг 7. которые были извлечены из объектов базы данных. что эта функция отвечает на вопрос «Следует ли пропустить этот элемент через фильтр?» относи� тельно всех элементов.Глава 7. Обратите внимание: тип элементов. а вернет уже новое значе� ние — 4. проверяя каждый его элемент в функции. то вы добавляете его в конечный список List. Можете считать. Форма filter Обратите внимание на шаг защитного копирования — здесь вы возвращаете новый экземпляр List.

Многоязычное программирование на виртуальной машине Java Такие функции называются предикативными (predicate function). помогающими мыс� лить в нужном контексте. «императивные и функциональные языки». а не как строгими академическими схемами. но никакая из систем классификации не является совершенной. Именно это нам и требуется. а также оригинальные языки и их повторные реализации. чем другие. ясности кода и про� изводительности. Если вы хотите освоить эти стили и применять их себе на пользу. встречаются самые разнообразные стили и под� ходы к программированию. так как если заказ отменен — он будет отсутствовать в рас� пределенной базе данных. В последние годы в языках также наблюдается тенденция к добавлению функ� ций из всего спектра имеющихся возможностей. Вот пример написания такой функции на псевдо� коде (перед нами почти Scala): (msg) -> { !msg. при развертывании). Оказывается. Это означает.268 Часть 3.equalsIgnoreCase("CANCELLED") } Это функция. В нем делается акцент на безопасности. Поэтому Java мирится с некоторой пространностью и малой гибкостью кода (например. В принципе. принимающая один аргумент (msg) и возвращающая в качестве результата булево значение. либо «динамически типизированный. При применении такой функции в качестве фильтра она отсеет все отмененные сообщения. Такая классификация иногда называется языковым зоопарком. воплощенные в различных языках. 7. а в про� тивном случае — true. но прежде встретим такие функциональные литералы (так� же именуемые лямбда-выражениями) в некоторых других контекстах. языки каких типов могут работать на виртуальной машине Java. Для начала поговорим о том. и нам нужен способ.2. такими классифи� кациями следует пользоваться как удобными инструментами. при необходимости допускающий возмож� ность статической типизации».get("status"). чем другой язык. если msg был отменен. чтобы их представлять. то должны понимать разницу между языками и принципы их классификации. Иными словами. ПРИМЕЧАНИЕ Эти классификации помогают осмысливать разнообразие языков. Она возвращает false. именно с таким синтаксисом данная функция будет записывать� ся в Java 8 (но этот код в значительной мере напоминает Scala и C#). что зачастую полез� но представлять конкретный язык как «менее функциональный». В различных языках могут суще� . Перед вызовом согласующего кода нужно удалить все отмененные заказы. Мы поговорим о следующих вариантах классификации: «интерпретируемые и компилируемые языки». Java — это компилируемый во время исполнения статически типизированный императивный язык. Языковой зоопарк Существует множество разновидностей языков программирования и вариантов их классификации. Мы вернемся к этой теме в главе 14. Одни варианты классификации более четкие.

написанный на Java). а другие — нет. В последнее время названное различие стало размываться. Для примера рассмотрим небольшой фрагмент кода. как мы уже указывали в главе 1. Например. А затем ситуация немного меняется. В примере переменная answer начинает работу в значении 40 — естественно. Альтернативные языки для виртуальной машины Java 269 ствовать разные приоритеты. Во втором случае в языке будет интерпретатор (возможно.2. Надеемся. 7. В некоторых языках есть и компи� лятор. Использо� вание байт-кода еще более усложняет проблему. В 1980-е годы и в на� чале 1990-х разница была довольно четкой: C������������������������������ ������������������������������� /����������������������������� C���������������������������� ++ и им подобные были компи� лируемыми языками. Интерпретатор JavaScript также может различать два варианта использования оператора +. динамически типизированные языки могут делать акцент на скорости развертывания. а Perl и Python — интерпретируемыми. в данном . которые начинают работу с применения компилятора для превращения человекочитаемого исходного кода в двоичную форму. строка за строкой. answer = "What is the answer? " + answer. Говоря о языках для виртуальной машины Java. Потом мы прибавляем к нему 2 и получаем 42.2.1. ��������������������������������������������������������� Java����������������������������������������������������� обладает чертами как первых. Этим интерпретируемые языки отличаются от компили� руемых. которые мы изучим в этой части книги. Для начала сравним интерпретируемые и компилируемые языки. answer = answer + 2. но настоящим машинным кодом он также не явля� ется. Сравнение интерпретируемых и компилируемых языков В интерпретируемом языке каждый шаг исходного кода выполняется как есть — при этом для исполнения не требуется предварительно преобразовывать всю про� грамму в машинный код. написанный на JavaScript — широко известном динамическом языке. и мы записываем в переменную answer строковое значение.Глава 7. так и вторых. используемый для исполнения исходного кода. 7. даже если вы не знакомы с этим языком в деталях: var answer = 40. Но. Определенно байт-код непри� годен для чтения человеком. что этот пример будет понятен. Сравнение динамической и статической типизации В языках с динамической типизацией переменная в разное время может содержать различные типы. и интерпретатор. Первый вариант — это арифметическая операция сложения. В динамическом языке это очень распространенный прием — из-за него не возни� кает никаких синтаксических ошибок. а в других предоставляется интерпретатор и динамический компилятор. это числовое значение. порождающий байт-код для виртуальной машины Java.2. мы будем пользоваться следующим признаком: одни языки создают из исходного кода файл класса и выполняют его.

значения каких типов содержатся в переменных (например. что разработчик. а не со значениями.2.2. В трех следующих главах мы поговорим о трех разных языках. В функциональных языках во главу угла ставятся сами вычисления. Как показано на рис. присущей функциональным языкам. что объектно-ориентированная картина мира противоречит картине мира. Функции оперируют значениями. преобразующих это рабочее состояние. В объектно-ориентированных языках в большей или меньшей степени присутствует еще одна информационная струк� тура — метаданные (например. содержа� щимися в них. но не изменяют полученный ввод. образуя объекты.1. информация о классах). В динамически типизируемых языках информация о типах содержится в зна� чениях. моделирующие рабочее состояние программы в виде изменяемых данных и выдающие набор команд. Потом поговорим о языке Scala. записываемых в переменных. Сами по себе они не имеют состояния. где реализуется «почти функциональный стиль» для обработки таких коллекций. поскольку необходимая для этого информация остается неизвестной вплоть до времени исполнения. числовые или строковые). В таком случае судить о потенциальных нарушениях типов становится гораздо сложнее. Процедурные языки. а работают в точности как математические функции и возвращают новые значения. который . Это означает. При статической же типизации отслеживается информация о типах переменных. и нет никакого смысла связывать их с каким-то внешним состоянием. Императивные языки делятся на два основных подтипа.270 Часть 3. Сравнение императивных и функциональных языков Java ��������������������������������������������������������������������� 7 — классический образец императивного языка. Многоязычное программирование на виртуальной машине Java случае сложения 2 и 40. Таким образом. Второй подтип — это объектно-ориентированные языки. о которых мы говорили в разделе 7. Статическая типизация хорошо подходит для компилируемого языка. ПРИМЕЧАНИЕ Основная идея в данном случае — при динамической типизации отслеживается информация о том. опираясь на изложенную выше трактовку функционального программирования. посколь� ку вся информация о типах связана с переменными. такие как BASIC����������������������������������������������������������������� ���������������������������������������������������������������������� и �������������������������������������������������������������� Fortran������������������������������������������������������� . функции можно считать «маленькими вычислитель� ными машинками».3. где данные и код (в форме мето� дов) связываются вместе. А из контекста следующей строки интерпретатор заключает. принимающими значения и выдающими новые значения. состояние программы — центральная концепция императивных языков. имел в виду сцепление (конкатена� цию) строк. обрабатывают код и данные совершенно раздельно и дей� ствуют в соответствии с простой парадигмой «код оперирует данными». как и в процедурных языках. 7. Императивными называют� ся языки. Начнем с Groovy. 7. применивший этот оператор. В результате становится очень легко судить о потенциальных нару� шениях системы типов во время компиляции.

Ruby — это динамически типизированный объектно-ориентированный язык с некоторыми функциональ� ными чертами. Таким образом. обеспе� чивают гораздо более тесную связь между своими системами типов и нативными типами виртуальной машины. те языки. без всякой объектной ориентации). Следующие три языка для виртуальной машины Java являются повторными реализациями уже существующих языков. искавший способ использовать из Python высокопро� изводительные библиотеки Java. Императивные и функциональные языки 7. рассчитанными на применение на JVM.  Jython — его разработка была начата в 1997 году. Jython — это повторная реализация Python для виртуальной машины Java. а другие являются лишь адаптациями (повторными реализациями) уже имеющихся языков.2. динамический. в основном объектноориентированный язык.Глава 7. Альтернативные языки для виртуальной машины Java 271 в значительно большей степени связан с функциональным программированием. Автором проекта стал Джим Хаганин (Jim Hugunin). обсудим Clojure (чисто функциональный язык. что одни языки сразу писались с учетом специфики виртуальной машины Java. 7. Сравнение повторной реализации и оригинала Еще одно важное отличие между языками для виртуальной машины ����������� Java������� заклю� чается в том. которые создавались специально для работы на виртуальной машине Java��������� ������������� . работа протекает примерно так же. Наконец.  JRuby — повторная реализация языка Ruby для JVM.4. а потом преобразует его в байт-код виртуальной машины Java. Рис. На виртуальной машине Java он. как и в типичном интерпретируемом . Итак. является интер� претируемым. Вообще. но в последних версиях появился динамический компилятор времени исполнения. в сущности. который в подходящих условиях может создавать байт-код виртуальной машины Java.2. При работе он генерирует внутриязыковой байт-код Python.

Основной технологический момент. Возможно. это язык Kawa — реализация Lisp. Rhino может работать как в компилируемом. Теперь поговорим о многоязычном программировании и о том. JavaScript — это динамически типизированный объектно-ориентированный язык (но объектная ориентация в нем организована во многом иначе. считается на виртуальной машине Java компилируемым. Многоязычное программирование зачастую . в котором можно создавать файлы классов. Не все они могут считаться активными и широко используемыми.272 Часть 3. в то время как основа программы написана на Java. позволяющий столь многим языкам рабо� тать на виртуальной машине �������������������������������������������������� Java���������������������������������������������� . и отследить их все уже практически невозможно. использу­ ющих один или несколько языков для виртуальной машины Java. Язык поставляется вместе с Java 7 (подробнее см. работающих с Java. так и в интерпретируемом режиме. нежели в Java). 7. КАКОЙ ЯЗЫК ДЛЯ ВИРТУАЛЬНОЙ МАШИНЫ JAVA САМЫЙ ДРЕВНИЙ? Сложно назвать самый ранний не Java-язык для виртуальной машины Java. Это выражение было придумано для описания проектов. В дальнейшем языки стали расти как грибы после дождя. что для работы с вир� туальной машиной Java может применяться около 200 языков. насколько оживленной платформой для разработки и внедрения язы� ков является виртуальная машина Java. Любой язык. которые мы рассмот� рели в главе 5. как эта область стала особенно интересной для программистов. почему вам в вашем проекте может понадобиться альтернативный язык. из спецификации виртуальной машины были удалены все ссылки на язык Java. с генерированием байт-кода виртуальной машины Java и сохранением результирующих файлов классов на диск. Кроме того. применяемых на JVM. Многоязычное программирование на виртуальной машине Java режиме Python. но такое большое количество показывает. работа может идти с применением ранней компи� ляции. работающий на вир� туальной машине Java. У него нет каких-либо привилегий. script. потом — в рамках про� екта Mozilla. Многоязычное программирование на виртуальной машине Java Феномен «многоязычное программирование на виртуальной машине Java������� ����������� » отно� сительно нов. Представляет собой реализацию JavaScript для виртуальной машины Java. в пакете com. которые были выпущены с появлением Java 7.javascript).  Rhino — изначально разрабатывался компанией Netscape. — формат файлов классов. ПРИМЕЧАНИЕ В тех версиях спецификаций языка и виртуальной машины ����������������������������������� Java������������������������������� . появившаяся в 1997 году или около того.sun. Мы объясним базовые концепции многоязычного программирования и расскажем.3. Теперь Java — один из многих языков. На момент написания этой книги можно предположить.

потенциально существует три уровня.1. Эта схема иногда называется «пира� мидой многоязычного программирования». Scala Как видите.3. 7. Мезанизм оценки рисков в банке может работать на протяжении пяти и более лет. и гибкостью и стремительным развертыванием в верхней части схемы. хорошо протестированный.3. Это — своеобразный компромисс между такими проблемами. гибкая разработка функционала Groovy. Drools. как производительность и тщательное тестирование. Три уровня пирамиды многоязычного программирования Название Описание Примеры Языки предметных областей Тесно связан с конкретной частью прикладной области Apache Camel DSL. Как вы видите на рис. в уровнях наблюдаются закономерности — статически типизирован� ные языки обычно используются для решения задач. ее автор — Ола Бини (Ola Bini). тем ближе к основанию пирамиды он располагается.1 все три уровня описаны более подробно.273 Глава 7. не относящиеся к Java������������������������������������ ���������������������������������������� . JSP-страницы для сайта могут существовать несколько месяцев. относящихся к стабильному . 7. на которых могут при� годиться технологии. высокопроизводительный Java. В табл. продуктивная. поскольку длительность эксплуатации разных частей кода значительно отличается. Альтернативные языки для виртуальной машины Java понимается как одна из форм функционального разделения ответственности. стабильный. динамический и стабильный. В пирамиде мы видим три четко разграниченных уровня: уровень предметных областей. Рис. Пирамида многоязычного программирования СЕКРЕТ МНОГОЯЗЫЧНОГО ПРОГРАММИРОВАНИЯ Многоязычное программирование целесообразно. веб-шаблонизаторы Динамические Быстрая. Самый короткоживущий код на стартапе может использоваться в течение всего нескольких дней. Чем дольше существует код. Clojure Стабильные Основной функционал. 7. Таблица 7. расположенными внизу пирамиды. Jython.

почему нужно обращать внимание на другие языки. который может использоваться в работе лишь на протяжении нескольких недель. например в ������������������������������������������������������������������������� Scala�������������������������������������������������������������������� (пример такой возможности в Scala���������������������������������� ���������������������������������������  — превосходная поддержка параллелизма). находящихся в верхней части пирамиды. Зачем использовать другой язык вместо Java Поскольку Java — это универсальный. Этот слой также отличается наибольшей гибкостью — во многих случаях он может частично перекрывать оба соседствующих слоя. а потом опишем кое-какие из основных критериев. важные в вашем будущем проекте. а также богатым набором API������������������������������������������������������� ���������������������������������������������������������� этого языка и обширной библиотечной поддержкой.1. на наш взгляд. придете к выводу. Благо� даря этим качествам Java хорошо подходит для выполнения трудоемких задач. кроме Java. 7. Но. В этой книге мы не сможем обсудить все имеющие� ся альтернативы.3. чтобы нарушить нормальный ход работы программиста.  статическая типизация бывает негибкой. Еще подробнее изучим эту схему и посмотрим. почему Java — не самый лучший язык для решения некоторых задач. Здесь у вас могут возникнуть вопросы: «Какие типы проблем программирования относятся к этим уровням? Какие языки мне следует выбрать?» Профессиональный Java-разработчик знает. что отдельные возможности. как правило. не следует отказываться от готового рабочего кода для стабильного уровня и переписывать этот код на другом стабильном языке. Для начала об� судим.  синтаксис Java плохо подходит для создания языков предметных областей. Многоязычное программирование на виртуальной машине Java уровню. что панацеи не существует. у него немало достоинств. относящихся к стабильному уровню. Это достаточно долго. поэтому сосредоточимся на языках. В средней части пирамиды важнейшую роль играют языки динамического слоя. которые можно учитывать при выборе. то. по� крывают широкий спектр возможных разумных вариантов для Java-среды. которые. из-за этого возникают длительные этапы рефакторинга. представленных в пирамиде. Например:  перекомпиляция отличается трудоемкостью. Такие длительные сборки плохо подходят для разработ� ки кода. которые следует учитывать. Прагматичное решение — воспользоваться сильными сторонами Java. . лучше представлены в другом языке для стабильного уровня. Длительность перекомпиляции и пересборки проекта Java быстро доходит до полутора и даже до двух минут. статически типизированный компилиру­ емый язык.274 Часть 3. зато есть критерии. ПРИМЕЧАНИЕ Если вы начинаете новый проект с нуля.  развертывание — очень тяжеловесный процесс. И наоборот — менее мощные и менее универсальные технологии хорошо подходят для выполнения задач. подбирая для проекта иной язык. Благодаря этим качествам язык отлично подходит для реализации функционала на стабильном уровне. Но именно эти качества становятся обременительными в верхних частях пирамиды. возможно.

Clojure Clojure — язык. . Язык Scala многое заимствовал из Java. содержится значительное количество новых возможностей (особенно в области параллельной обработки). Его история уходит корнями в 2003 год. в Scala активно задействуется выведение типов во время исполнения. Это динамически типизированный функциональный язык. но. Зачастую именно Groovy оказывается первым после Java языком для JVM. Альтернативные языки для виртуальной машины Java 275 7. у которых уже есть множество сторонников в среде многоязычного программирования. http:// lisper. В Clojure. Это динамический компилируемый язык.3. статически типизированный компилируемый язык. по� чему же три этих языка наращивают обороты? Вкратце рассмотрим каждый из них. 1 Имеется в виду ироническая расшифровка LISP как аббревиатуры: Lots of Irritating Superfluous Parentheses — «Множество раздражающих ненужных скобок».2. Scala и Clojure). за исследование которого берется разработчик или команда. Scala Scala���������������������������������������������������������������������  — это объектно-ориентированный язык. разработанный Ричардом Хики (Rich Hickey) и относящийся к се� мейству Lisp. Groovy широко используется как сценарный язык и язык для быстрого прототипирования. и отдельные специалисты утверждают. он известен как отличная отправная точка для создания языков пред� метных областей (DSL). как и Java. что в один прекрасный день он может бросить вызов Java как «новый язык номер один для JVM». в обозримом будущем окажутся наиболее долговечными и влиятельными. когда разработкой этого нового языка занялся Мартин Одерски (Martin Odersky). Итак. но в его структуре исправлены досадные недостатки. связанные с дженериками в Java. которые. Это язы� ки для виртуальной машины Java (Groovy. распространяются в виде исходных текстов — причины этого мы рассмотрим ниже. Groovy Язык Groovy был изобретен Джеймсом Страханом (James Strachan) в 2003 году. что Groovy относится к динамическому уровню.ru/pcl/syntax-and-semantics.Глава 7. перев. см. но обычно программы. написанные на нем. синтаксис которого очень напоминает синтаксис Java. с которыми давно были вынуждены мириться Java-разработчики. — Примеч. В главе 9 мы делаем введение в Scala. в отличие от Java. на наш взгляд. Ранее Мартин вел проекты. Язык Clojure — компилируемый. Scala относится к стабильному уровню. Он наследует от этого семейства многие синтаксические черты (и зна� менитое множество скобок)1. также поддерживающий многие ме� тоды функционального программирования. Можно считать. Scala — это. Кроме того. что неудивительно для представителя Lisp. В главе 8 мы делаем введение в Groovy. наряду с Lisp-основой. В результате Scala зачастую напоминает динамический язык. но более гибок. Многообещающие языки В оставшейся части книги мы поговорим о трех языках.

Тесты (для разработки через тестирование и разработки через реализацию поведения) Стабильный Параллельный код. Контейнеры приложений. Но посколь� ку он хорошо поддерживает параллелизм и некоторые другие возможности. 7. 7. чем другие языки семейства Lisp. Таблица 7. набор его возможностей будет очень интересен разработчикам прило� жений для финансовой сферы). удобные для решения на каждом уровне Уровень Примеры решаемых проблем Уровень предметной области Сборка. Скриптинг (написание сценариев). Прототипирование. Но Clojure. которые полезно учитывать при оценке технологического стека. В гла� ве 10 мы делаем введение в Clojure. Далее следует оценить. непрерывное развертывание. непрерывная интеграция. Области проекта. Devops. то нужно определить. Многоязычное программирование на виртуальной машине Java Принято считать. Моделирование по принципу Enterprise integration pattern.4. Обычно считается. какие части проекта естественно вписываются в разные уровни. в зависимости от круга которых стоит выбирать тот или иной язык. В табл. мы перечислили несколько перспективных языков. Но идентификация задачи. Итак.2.276 Часть 3. — стабильный. что Clojure относится к динамическому уровню. Интерактивные административные и пользовательские консоли. теперь рассмотрим проблемы. что он спр