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

Java. Библиотека профессионала, том 1. Основы [Кей С. Хорстманн] (pdf) читать онлайн

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


 [Настройки текста]  [Cбросить фильтры]
Java®
Библиотека профессионала

Том

1. Основы

Core Java®
Volume 1- Fundamentals
Eleventh Edition

Сау

S. Horstmann

е

Pearson
Boston • Columbus • New York • San Francisco • Amsterdam ·Саре Town
Dubai • London • Madrid • Milan • Munich • Paris • Montreal • Toronto • Delhi • Mexico City
Sao Paulo • Sydney • Hong Kong • Seoul • Singapore • Taipei • Tokyo

ava®
Библиотека профессионала

Том

1. Основы

Одиннадцатое издание

Кей Хорстманн

Москва • Санкт-Петербург

2019

ББК

32.973.26-018.2.75
Х82

удк

681.3.07
СЮО "Диалектика"

Зав. редакцией С.Н. Тригуб
Перевод с английского и редакция И.В. Берттеuна

По общим вопросам обращайтесь в издательство "Диалектика" по адресу:

info@dialektika.com, http://www.dialektika.com
Хорстманн, Кей С.

Х82

1. Основы. 11-е изд.: Пер.
2019. - 864 с.: ил. - Парал. тит. англ.
ISBN 978-5-907114-79-1 (рус., том 1)
ISBN 978-5-907144-30-9 (рус., мноrотом)

Java.

Библиотека профессионала, том

с англ.

СПб.: ООО "Диалектика",

ББК

32.973.26-018.2.75

Все названия проrраммных продуктов являются зареrистрированными торговыми марками

соответствующих фирм.
Никакая часть настоящеrо издания ни в каких целях не может бьrrь воспроизведена в какой
бы то ни было форме и какими бы то ни было средствами, будь то электронные или механиче­
ские, включая фотокопирование и запись на маmитный носитель, если на это нет письменноrо

разрешения издательства

Prentice Hall, lnc.
Copyright © 2019 Ьу Dialektika Computer PuЬlishing Ltd.
Authorized Russian translation of the English edition of Core /ащ Vo/ume 1: Fundamenta/~, 11/h Edition
(ISBN 978-0-13-516630-7) © 2019 Pearson Education lnc.
Portions copyright © 1996-2013 Oracle and/or its affiliates. All Rights Reserved.
This translation is puЫished and sold Ьу permission of Pearson Education Inc., which owns or
controls all rights to puЬlish and sell the same.
All rights reserved. No part of this book may Ье reproduced or transmitted in any form or Ьу any
means, electronic or mechanical, including photocopying, recording, or Ьу any information storage
or retrieval system, without the prior written permission of the copyright owner and the PuЫisher.
Научно-популярное и.>дание

Кей С. Хорстманн

Java. Библиотека профессионала, том 1
Основы. 11-е издание
Подписано в печать

05.03.2019.

Формат 70х100/16

Гарнитура Тimes
Усл. печ. л.
Тираж

69,66. Уч.-изд. л. 54,1
500 экз. Заказ № 2231

Отпечатано в АО "Первая Образцовая типоrрафия"
Филиал "Чеховский Печатный Двор"

142300, Московская область, r. Чехов, ул. Полшрафистов, д. 1
Сайт: www.chpd.ru, E-mail: sales@chpd.ru, тел. 8 (499) 270-73-59
ООО "Диалектика",

ISBN 978-5-907114-79-1
ISBN 978-5-907144-30-9
ISBN 978-0-13-516630-7

195027,

(рус., том

Санкт-Петербурr, Маmитоrорская ул., д.

1)

30,

© 2019

лит. А, пом.

848

ООО "Диалектика"

(рус., мноrотом)
(анrл.)

© 2019 Pearson Education lnc.

Оглавление
Предисловие

15

Глава

1. Введение в язык Java

21

Глава

2. Среда программирования на Java

35

Глава

3. Основные языковые конструкции Java

51

Глава

4. Объекты и

Глава

5. Наследование

Глава

6. Интерфейсы, лямбда-выражения

Глава

7. Исключения, утверждения

Глава

8. Обобщенное программирование

393

Глава

9. Коллекции

437

Глава

10. Программирование графики

509

Глава

11. Компоненты пользовательского интерфейса в Swing

567

Глава

12. Параллелизм

661

Глава

13. Библиотека JavaFX

759

классы

Приложение А. Ключевые слова
Предметный указатель

125
195
и внутренние классы

и протоколирование

Java

273
339

849
851

Содержание
Предисловие
Глава

1. Введение в язык Java

1.1. Программная платформа Java
1.2. Характерные особенности Java
1.2.1. Простота
1.2.2. Объектно-ориентированный характер
1.2.3. Поддержка распределенных вычислений в сети
1.2.4. Надежность
1.2.5. Безопасность
1.2.6. Независимость от архитектуры компьютера
1.2.7. Переносимость
1.2.8. Интепретируемость
1.2.9. Производительность
1.2.10. Мноrопоточность
1.2.11. Динамичность
1.3. Аплеты и Интернет
1.4. Краткая история развития Java
1.5. Распространенные заблуждения относительно Java
Глава

2. Среда программирования на Java

2.1. Установка комплекта Java Development Kit
2.1.1. Загрузка комплекта JDK
2.1.2. Установка комплекта JDK
2.1.3. Установ ка библиотек и документации
2.2. Применение инструментов командной строки
2.3. Применение IDE
2.4. Утилита JShell
Глава

3. Основные языковые конструкции Java

3.1. Простая программа на Java
3.2. Комментарии
3.3. Типы данных
3.3.1. Целочисленные типы данных
3.3.2. Числовые типы данных с плавающей точкой
3.3.3. Тип данных char
3.3.4. Юникод и тип char
3.3.5. Тип данных boolean
3.4. Переменные и константы
3.4.1. Объявление переменных
3.4.2. Инициализация переменных
3.4.3. Константы
3.4.4. Перечислимые типы
3.5. Операции
3.5.1. Арифметические операции
3.5.2. Математические функции и константы

15
21
21
22
23
23
24
24
24
25
25
26

26
27
27

28
29
32

35
35
36
37
39

40
45
48

51
51

55
55
56
57
58
59
60
61
61

62
63
64
64
64
65

Содержание

3.5.3. Преобразование числовых типов
3.5.4. Приведение типов
3.5.5. Сочетание арифметических операций с присваиванием
3.5.6. Операции инкремента и декремента
3.5.7. Операции отношения и логические операции
3.5.8. Поразрядные логические операции
3.5.9. Круглые скобки и иерархия операций
3.6. Символьные строки
3.6.1. Подстроки
3.6.2. Сцепление строк
3.6.3. Принцип постоянства символьных строк
3.6.4. Проверка символьных строк на равенство
3.6.5. Пустые и нулевые строки
3.6.6. Кодовые точки и единицы
3.6.7. Прикладной программный интерфейс API класса String
3.6.8. Оперативно доступная документация на API
3.6.9. Построение символьных строк
3.7. Ввод и вывод
3.7.1. Чтение вводимых данных
3.7.2. Форматирование выводимых данных
3.7.3. Файловый ввод и вывод
3.8. Управляющая логика
3.8.1. Область видимости блоков
3.8.2. Условные операторы
3.8.3. Неопределенные циклы
3.8.4. Определенные циклы
3.8.5. Оператор swi tch для многовариантного выбора
3.8.6. Операторы прерывания логики управления программой
3.9. Большие числа
3.10. Массивы
3.10.1. Объявление массивов
3.10.2. Доступ к элементам массива
3.10.3. Цикл в стиле for each
3.10.4. Копирование массивов
3.10.5. Параметры командной строки
3.10.6. Сортировка массивов
3.10.7. Многомерные массивы
3.10.8. Неровные массивы
Глава

4. Объекты и классы

4.1. Введение в ООП
4.1.1. Классы
4.1.2. Объекты
4.1.3. Идентификация классов
4.1.4. Отношения между классами
4.2. Применение предопределенных классов
4.2.1. Объекты и объектные переменные
4.2.2. Класс LocalDate из библиотеки Java
4.2.3. Модифицирующие методы и методы доступа
4.3. Определение собственных классов
4.3.1. Класс Employee
4.3.2. Использование нескольких исходных файлов
4.3.3. Анализ класса Employee

67
68
69
69
70
70
71
72
72
73
73
75
76
76
77
80
82
84
84
86
91
93
93
93
97
100
104
106
108
111
112
113
114
115
116
116
119
122

125
126
126
128
128
129
130
131
134
135
139
139
142
142

Содержание

4.3.4. Первые действия с конструкторами
4.3.5. Объявление локальных переменных с помощью
4.3.6. Обработка пустых ссылок на объекты
4.3.7. Явные и неявные параметры
4.3.8. Преимущества инкапсуляции
4.3.9. Привилегии доступа к данным в классе
4.3.10. Закрытые методы
4.3.11. Конечные поля экземпляра
4.4. Статические поля и методы
4.4.1. Статические поля
4.4.2. Статические константы
4.4.3. Статические методы
4.4.4. Фабричные методы
4.4.5. Метод main ()
4.5. Параметры методов
4.6. Конструирование объектов
4.6.1. Перегрузка
4.6.2. Инициализация полей по умолчанию
4.6.3. Конструктор без аргументов
4.6.4. Явная инициализация полей
4.6.5. Имена параметров
4.6.6. Вызов одного конструктора из другого
4.6.7. Блоки инициализации
4.6.8. Уничтожение объектов и метод finalize ()
4.7. Пакеты
4.7.1. Именование пакетов
4.7.2. Импорт классов
4.7.3. Статический импорт
4.7.4. Ввод классов в пакеты
4.7.5. Область видимости пакетов
4.7.6. Путь к классам
4.7.7. Указание пути к классам
4.8. Архивные JАR-файлы
4.8.1. Создание JАR-файлов
4.8.2. Файл манифеста
4.8.3. Исполняемые JАR-файлы
4.8.4. Многоверсионные архивные JАR-файлы
4.8.5. Примечание к параметрам командной строки
4.9. Документирующие комментарии
4.9.1. Вставка комментариев
4.9.2. Комментарии к классам
4.9.3. Комментарии к методам
4.9.4. Комментарии к полям
4.9.5. Комментарии общего характера
4.9.6. Комментарии к пакетам
4.9.7. Извлечение комментариев
4.10. Рекомендации по разработке классов
Глава

5. Наследование

5.1. Классы, супер.классы и подклассы
5.1.1. Определение подклассов
5.1.2. Переопределение методов
5.1.3. Конструкторы подклассов

ключевого слова

var

143
144
145
146
147
149
150
150
151
151
152
153
154
154
157
163
163
164
164
165
166
167
167
171
172
172
172
174
174
177
179
181
182
182
183
184
184
186
187
188
188
189
189
190
191
191
192

195
196
196
197
199

Содержание

5.1.4. Иерархии наследования
5.1.5. Полиморфизм
5.1.6. Представление о вызовах методов
5.1.7. Предотвращение наследования: конечные классы и методы
5.1.8. Приведение типов
5.1.9. Абстрактные классы
5.1.10. Защищенный доступ
5.2. Глобальный суперкласс Object
5.2.1. Переменные типа Object
5.2.2. Метод equals ()
5.2.3. Проверка объектов на равенство и наследование
5.2.4. Метод hashCode ()
5.2.5. Метод toString ()
5.3. Обобщенные списочные массивы
5.3.1. Объявление списочных массивов
5.3.2. Доступ к элементам списочных массивов
5.3.3. Совместимость типизированных и базовых списочных массивов
5.4. Объектные оболочки и автоупаковка
5.5. Методы с переменным числом параметров
5.6. Классы перечислений
5.7. Рефлексия
5.7.1. Класс Class
5.7.2. Основы обработки исключений
5.7.3. Ресурсы
5.7.4. Анализ функциональных возможностей классов
с помощью рефлексии

5.7.5. Анализ объектов во время выполнения с помощью рефлексии
5.7.6. Написание кода универсального массива с помощью рефлексии
5.7.7. Вызов произвольных методов и конструкторов
5.8. Рекомендации по применению наследования
Глава

6. Интерфейсы, лямбда-выражения и внутренние классы

6.1. Интерфейсы
6.1.1. Понятие интерфейса
6.1.2. Свойства интерфейсов
6.1.3. Интерфейсы и абстрактные классы
6.1.4. Статические и закрытые методы
6.1.5. Методы с реализацией по умолчанию
6.1.6. Разрешение конфликтов с методами по умолчанию
6.1.7. Интерфейсы и обратные вызовы
6.1.8. Интерфейс Comparator
6.1.9. Клонирование объектов
6.2. Лямбда-выражения
6.2.1. Причины для употребления лямбда-выражений
6.2.2. Синтаксис лямбда-выражений
6.2.3. Функциональные интерфейсы
6.2.4. Ссылки на методы
6.2.5. Ссылки на конструкторы
6.2.6. Область видимости переменных
6.2.7. Обработка лямбда-выражений
6.2.8. Еще о компараторах
6.3. Внутренние классы
6.3.1. Доступ к состоянию объекта с помощью внутреннего класса

203
203
205
207
209
211
216
217
218
218
219
223
225
231
232
234
237
238
242
243
245
246
248
249
251
257
263
266
270

273
274
274
280
281
282
283
284
286

289
290

296
296

298
300
302
305
306
308
311
312
313

Содержание

6.3.2. Специальные синтаксические правила для внутренних классов
6.3.3. О пользе, необходимости и безопасности внутренних классов
6.3.4. Локальные внутренние классы
6.3.5. Дос1уп к конечным переменным из внешних методов
6.3.6. Анонимные внутренние классы
6.3.7. Статические внутренние классы
6.4. Загрузчики служб
6.5. Прокси-классы
6.5.1. О применении прокси-классов
6.5.2. Создание прокси-объектов
6.5.3. Свойства прокси-классов
Глава

7. Исключения, утверждения

и протоколирование

7.1. Обработка ошибок
7.1.1. Классификация исключений
7.1.2. Объявление проверяемых исключений
7.1.3. Порядок генерирования исключений
7.1.4. Создание классов исключений
7.2. Перехват исключений
7.2.1. Перехват одного исключения
7.2.2. Перехват нескольких исключений
7.2.3. Повторное генерирование и связывание исключений в цепочку
7.2.4. Блок оператора finally
7.2.5. Оператор try с ресурсами
7.2.6. Анализ элементов трассировки стека
7.3. Рекомендации по обработке исключений
7.4. Применение утверждений
7.4.1. Понятие утверждения
7.4.2. Разрешение и запрет утверждений
7.4.3. Проверка параметров с помощью утверждений
7.4.4. Документирование предположений с помощью утверждений
7.5. Протоколирование
7.5.1. Элементарное протоколирование
7.5.2. Усовершенствованное протоколирование
7.5.3. Смена диспетчера протоколирования
7.5.4. Локализация
7.5.5. Обработчики протоколов
7.5.6. Фильтры
7.5.7. Средства форматирования
7.5.8. "Рецепт" протоколирования
7.6. Рекомендации по отладке программ
Глава

8. Обобщенное программирование

8.1. Назначение обобщенного программирования
8.1.1. Преимущества параметров типа
8.1.2. На кого рассчитано обобщенное программирование
8.2. Определение простого обобщенного класса
8.3. Обобщенные методы
8.4. Ограничения на переменные типа

8.5.

Обобщенный код и виртуальная машина

8.5.1.
8.5.2.

316
317
320
321
322
325

329
331
332
332
336

339
340
341
343
345
347
348
348
350
351
352
355
356
361
364
364
365
366
367
368

369
369
371
373
374
378
378
378
387

393
394
394

395
396
398
399

Стирание типов

402
402

Преобразование обобщенных выражений

403

Содержание

8.5.3. Преобразование обобщенных методов
8.5.4. Вызов унаследованного кода
8.6. Ограничения и пределы обобщений
8.6.1. Параметрам типа нельзя приписывать простые типы
8.6.2. Во время выполнения можно запрашивать только базовые типы
8.6.3. Массивы параметризованных типов недопустимы
8.6.4. Предупреждения о переменном числе ар~ументов
8.6.5. Нельзя создавать экземпляры переменных типа
8.6.6. Нельзя строить обобщенные массивы
8.6.7. Переменные типа в статическом контексте обобщенных
классов недействительны

8.6.8. Нельзя

404
406
407
407
407
408
409
410
410
412

генерировать или перехватывать экземпляры

8.6.9. Преодоление ограничения на обработку проверяемых исключений
8.6.10. Остерегайтесь конфликтов после стирания типов
8.7. Правила наследования обобщенных типов
8.8. Подстановочные типы
8.8.1. Понятие подстановочного типа
8.8.2. Ограничения супертипа на подстановки
8.8.3. Неограниченные подстановки
8.8.4. Захват подстановок
8.9. Рефлексия и обобщения
8.9.1. Обобщенный класс Class
8.9.2. Сопоставление типов с помощью параметров Class
8.9.3. Сведения об обобщенных типах в виртуальной машине
8.9.4. Литералы типов

412
413
415
416
417
418
419
422
423
425
425
427
427
431

Глава

437

обобщенного класса в виде исключений

9. Коллекции

9.1. Каркас коллекций в Java
9.1.1. Разделение интерфейсов и реализаций коллекций
9.1.2. Интерфейс Collection
9.1.3. Итераторы
9.1.4. Обобщенные служебные методы
9.2. Интерфейсы в каркасе коллекций Java
9.3. Конкретные коллекции
9.3.1. Связные списки
9.3.2. Списочные массивы
9.3.3. Хеш-множества
9.3.4. Древовидные множества
9.3.5. Одно- и двухсторонние очереди
9.3.6. Очереди по приоритету
9.4. Отображения
9.4.1. Основные операции над отображениями
9.4.2. Обновление записей в отображении
9.4.3. Представления отображений
9.4.4. Слабые хеш-отображения
9.4.5. Связные хеш-множества и отображения
9.4.6. Перечислимые множества и отображения
9.4.7. Хеш-отображения идентичности
9.5. Представления и оболочки
9.5.1. Мелкие коллекции
9.5.2. Поддиапазоны
9.5.3. Немодифицируемые представления

437
438
440
441
443
446
448
450
458
459
463
467
468
470
470
473
474
476
477
478
479
481
481
482
483

Содержание

9.5.4. Синхронизированные представления
9.5.5. Проверяемые представления
9.5.6. О необязательных операциях
9.6. Алгоритмы
9.6.1. Назначение обобщенных алгоритмов
9.6.2. Сортировка и перетасовка
9.6.3. Двоичный поиск
9.6.4. Простые алгоритмы
9.6.5. Групповые операции
9.6.6. Взаимное преобразование коллекций
9.6.7. Написание собственных алгоритмов
9.7. Унаследованные коллекции
9.7.1. Класс HashtaЫe
9.7.2. Перечисления
9.7.3. Таблицы свойств
9.7.4. Стеки
9.7.5. Битовые множества
Глава

10.1.

484
485

485
489

и массивов

10. Программирование графики

490
491
493
495
496
497
498
499

500
500

501
504
505

509

История развития инструментальных средств для разработки

на Java
10.2. Огображение фреймов
10.2.1. Создание фрейма
10.2.2. Свойства фрейма
10.3. Огображение данных в компоненте
10.3.1. Двухмерные формы
10.3.2. Окрашивание цветом
10.3.3. Применение шрифтов
10.3.4. Воспроизведение изображений
10.4. Обработка событий
10.4.1. Общее представление об обработке собьrrий
10.4.2. Пример обработки событий от щелчков на экранных кнопках
10.4.3. Краткое обозначение приемников событий
10.4.4. Классы адаптеров
10.4.5. Действия
10.4.6. События от мыши
10.4.7. Иерархия событий в библиотеке AWT
10.5. Прикладной интерфейс Preferences API

509
511
511
513
517
521
528
530
536
537
537
539
543
544
546
551
557
560

Глава

567

GUI

11.1.

11. Компоненты пользовательского интерфейса в Swiпg

Библиотека Swing и проектный шаблон
11
модель-представление-контроллер"
Введение в компоновку пользовательского интерфейса

11.2.
11.2.1. Диспетчеры компоновки
11.2.2. Граничная компоновка
11.2.3. Сеточная компоновка
11.3. Ввод текста
11.3.1. Текстовые поля
11.3.2. Метки и пометка компонентов
11.3.3. Поля для ввода пароля
11.3.4. Текстовые области
11.3.5. Панели прокруrки

568
572
572
574
576
577
578
580
581

582
583

Содержание

11.4. Компоненты для выбора разных вариантов
11.4.1. Флажки
11.4.2. Кнопки-переключатели
11.4.3. Границы
11.4.4. Комбинированные списки
11.4.5. Ре~улируемые ползунки
11.5. Меню
11.5.1. Создание меню
11.5.2. Пиктограммы в пунктах меню
11.5.3. Пункты меню с флажками и кнопками-переключателями
11.5.4. Всплывающие меню
11.5.5. Клавиши быстрого доступа и оперативные клавиши
11.5.6. Разрешение и запрет доступа к пунктам меню
11.5.7. Панели инструментов
11.5.8. Всплывающие подсказки
11.6. Расширенные средства компоновки
11.6.1. Диспетчер сеточно-контейнерной компоновки
11.6.2. Специальные диспетчеры компоновки
11.7. Диалоговые окна
11.7.1. Диалоговые окна для выбора разных вариантов
11.7.2. Создание диалоговых окон
11.7.3. Обмен данными
11.7.4. Диалоговые окна для выбора файлов

585
585
588
592
594
598
604
605
607
608
609

Глава

661

12. Параллелизм

12.1. Назначение потоков исполнения
12.2. Состояния потоков исполнения
12.2.1. Новые потоки исполнения
12.2.2. Исполняемые потоки
12.2.3. Блокированные и ожидающие потоки исполнения
12.2.4. Завершенные потоки исполнения
12.3. Свойства потоков исполнения
12.3.1. Прерывание потоков исполнения
12.3.2. Потоковые демоны
12.3.3. Именование потоков исполнения
12.3.4. Обработчики необрабатываемых исключений
12.3.5. Приоритеты потоков исполнения
12.4. Синхронизация
12.4.1. Пример СОСТОЯНИЯ ГОНОК
12.4.2. Объяснение причин, приводящих к состоянию гонок
12.4.3. Объекты блокировки
12.4.4. Объекты условий
12.4.5. Ключевое слово synchronized
12.4.6. Синхронизированные блоки
12.4.7. Принцип монитора
12.4.8. Поля и переменные типа volatile
12.4.9. Поля и переменные типа final
12.4.10. Атомарность операций
12.4.11. Взаимные блокировки
12.4.12. Локальные переменные в потоках исполнения
12.4.13. Причины, по которым методы stop () и suspend ()
не рекомендованы к применению

611
613
618

620
621
622
632
636
636
641
645
651
662

667
667
667
668
669

670
670
673

674
674
675

676
676
679
681
684

689
693

694
695
697
697
699
702
703

Содержание

12.5. Потокобезопасные коллекции
12.5.1. Блокирующие очереди
12.5.2. Эффективные отображения, множества и очереди
12.5.3. Атомарное обновление записей в отображениях
12.5.4. Групповые операции над параллельными хеш-отображениями
12.5.5. Параллельные представления множеств
12.5.6. Массивы, копируемые при записи
12.5.7. Алгоритмы обработки параллельных массивов
12.5.8. Устаревшие потокобезопасные коллекции
12.6. Задачи и пулы потоков исполнения
12.6.1. Интерфейсы CallaЫe и Future
12.6.2. Исполнители
12.6.3. Управление группами задач
12.6.4. Архитектура вилочного соединения
12.7. Асинхронные вычисления
12.7.1. Завершаемые будущие действия
12.7.2. Составление завершаемых будущих действий
12.7.3. Длительные задачи в обратных вызовах пользовательского интерфейса
12.8. Процессы
12.8.1. Построение процесса
12.8.2. Выполнение процесса
12.8.3. Дескрипторы процессов
Глава

759

13. Библиотека JavaFX

13.1. Отображение данных на сцене
13.1.1. Первое JаvаFХ-приложение
13.2.2. Рисование геометрических форм
13.2.3. Текст и изображения
13.3. Обработка событий
13.3.1. Реализация обработчиков событий
13.3.2. Реагирование на изменения свойств
13.3.3. События от мыши и клавиатуры
13.4. Компоновка
13.4.1. Панели КОМПОНОВКИ
13.4.2. Язык FXМL
13.4.3. Стилевые таблицы CSS
13.5. Элементы управления пользовательского интерфейса
13.5.1. Элементы управления вводом текста
13.5.2. Элементы управления выбором разных вариантов
13.5.3. Меню
13.5.4. Простые диалоговые окна
13.5.5. Специальные элементы управления
13.6. Свойства и привязки
13.6.1. Свойства в библиотеке JavaFX
13.6.2. Привязки
13.7. Длительные задачи в обратных вызовах пользовательского
Приложение А. Ключевые слова
Предметный указатель

705
705
712
713
717
719
720
720
721
722
723
725
727
732
735
735
738
744
751
752
753
755

Java

интерфейса

759
759
763
767
771
772
772
775
782
783
789
795
800
800
804
811
819
828
832
832
835
841

849
851

Предисловие
К читателю
В конце

1995

года язык программирования

Java

вырвался на просторы Интерне­

та и моментально завоевал популярность. Технология

Java

обещала стать универсаль­

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

-

от веб-серверов, баз данных, поставщиков информации или любого

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

Java

есть

все, чтобы выполнить эти обещания. Это весьма основательно сконструированный
язык, получивший широкое признание. Его встроенные средства защиты и безопас­
ности обнадежили как программистов, так и пользователей программ на

Java

Java.

Язык

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

как сетевое программирование, взаимодействие с базами данных и параллелизм.

С 1995 года было выпущено одиннадцать главных версий комплекта Java
Development Kit. За последние двадцать лет прикладной программный интерфейс
(API) языка Java увеличился от 200 до более 4 тысяч классов и теперь охватывает са­
мые разные предметные области, включая конструирование пользовательских интер­

фейсов, управление базами данных, интернационализацию, безопасность и обработ­
ку данных в формате

XML.

Книга, которую вы держите в руках, является первым томом одиннадцатого из­

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

Java Development Kit,

каждый раз перепи­

сывая ее, чтобы вы могли воспользоваться преимуществами новейших средств

Java.

Настоящее издание обновлено с учетом новых языковых средств, появившихся в вер­
сиях

Java Standard Edition (SE) 9, 10

и

11.

Как и все предыдущие издания этой книги, настоящее издание по-прежнему адре­

суется серьезным программистам, которые хотели бы пользоваться

Java

()ля разработки

настоящих проектов. Автор этой книги представляет себе вас, дорогой читатель, как
грамотного

ках, кроме

специалиста

Java,

с

солидным

опытом

программирования

на

других язы­

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

примеров вроде программ управления тостерами или животными в зоопарке либо
"прыгающим текстом". Ничего подобного вы не найдете в этой книге. Цель автора
помочь вам понять язык

Java

-

и его библиотеки в полной мере, а не создать иллюзию

такого понимания.

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

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

Предисловие
Автор предполагает, что вы сrремитесь (и даже жаждете) узнать обо всех расши­
ренных средсrвах, которые

Java

предосrавляет в ваше распоряжение. Поэтому в пер­

вом томе насrоящею издания подробно рассматриваются следующие темы.











Объектно-ориентированное программирование.

Рефлексия и прокси-классы.
Интерфейсы и внутренние классы.
Обработка исключений.
Обобщенное программирование.
Каркас коллекций.
Модель приемников событий.

Проектирование графическою пользовательскою интерфейса.
Параллельное программирование.

В связи со стремительным росrом библиотеки классов
недосrаточно для описания всех языковых средств

Java

одного тома оказалось

о которых следует знать се­

Java,

рьезным программисrам. Поэтому книга была разделена на два тома. В первом томе,

который вы держите в руках, главное внимание уделяется фундаментальным поня­
тиям языка

Java,

а также основам программирования пользовательского интерфей­

са. Второй том посвящен средсrвам разработки приложений масштаба предприятия
и усовершенсrвованному программированию пользовательских интерфейсов. В нем

вы найдете подробное обсуждение следующих вопросов.










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

API.

Обработка файлов и регулярные выражения.
Базы данных.
Обработка данных в формате ХМL.
Аннотации.
Интернационализация.

Сетевое программирование.
Расширенные компоненты графического пользовательского интерфейса.



У совершенсrвованная графика.



Платформенно-ориентированные методы.

При написании книги ошибки и неточности неизбежны, и автору книги очень
важно знать о них. Но он, конечно, предпочел бы узнать о каждой из них только один

раз. Поэтому перечень часrо задаваемых вопросов, исправлений, ошибок и обходных
приемов был размещен по адресу

http://horstmann.com/corejava,

куда вы можете

обращаться за справкой.

Краткий обзор книги
В главе

1

дается краткий обзор тех функциональных возможностей языка

Java,

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

ется, что было задумано разработчиками

Java

и в какой мере им удалось воплотить

задуманное в жизнь. Затем приводится краткая исrория развития языка
зывается, как он сrал тем, чем он есrь в насrоящее время.

Java

и пока­

Предисловие
В rлаве

2

сначала поясняется, как загрузить и установить инструментарий JDК,

а также примеры программ к этой книге. Затем рассматривается весь процесс компи­
ляции и запуска трех типичных программ на

Java (консольного
JDK, текстового

ского приложения и аплета) только средствами

ориентированного на
В rлаве

Java,

3 начинается

приложения, графиче­
редактора, специально

а также интегрированной среды разработки на

обсуждение языка программирования

Java

Java.

и излагаются са­

мые основы: переменные, циклы и простые функции. Если у вас имеется опыт про­

граммирования на С или С++, вам нетрудно будет усвоить материал этой главы, по­
скольку синтаксис этих языковых средств, по существу, ничем не отличается в

Java.

А если вам приходилось программировать на языках, не похожих на С, например
на

Visual Basic,

прочитайте эту главу с особым вниманием.

- господствующая ме­
Java. В rлаве 4 представ­

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

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

-

первой из двух фундаментальных составляющих объект­

ной ориентации, а также механизмы, реализующие ее в языке
дополнение к правилам языка

Java

Java:

классы и методы. В

здесь также приводятся рекомендации по правиль­

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

javadoc,

форматирующий комментарии из исходного кода

в набор веб-страниц с перекрестными ссылками. Если у вас имеется опыт програм­
мирования на С++, можете лишь бегло просмотреть эту главу. А тем, кому раньше не
приходилось программировать на объектно-ориентированных языках, придется потра­
тить .больше времени на усвоение принципов ООП, прежде чем изучать

Классы и инкапсуляция

-

Java

дальше.

это лишь часть методики ООП, и поэтому в rлаве

представлен еще один ее краеугольный камень

-

5

наследование. Наследование позво­

ляет модифицировать существующий класс в соответствии с конкретными потреб­
ностями программирующего. Это
на

Java.

Механизм наследования в

- основополагающий прием программирования
Java очень похож на аналогичный механизм в С++.

Опять же программирующие на С++ могут сосредоточить основное внимание лишь
на языковых отличиях в реализации наследования.

В rлаве

6

поясняется, как пользоваться в

Java

понятием интерфейса. Интерфейсы

дают возможность выйти за пределы простого наследования, описанного в главе

5.

Овладение интерфейсами позволит в полной мере воспользоваться объектно-ориен­
тированным подходом к программированию на

Java.

После интерфейсов рассматри­

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

Java, называемое внутренними класса.ми.
7 посвящена обработке исключений - надежному

ковое средство

Глава

механизму

Java,

призван­

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

аций в ней все-таки может произойти неожиданный сбой. Во второй части этой гла­

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

В rлаве

8 дается

краткий обзор обобщенного програ.м.мирования. Обобщенное про­

граммирование делает прикладные программы легче читаемыми и более безопасны­

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

Java.

Предисловие
Глава

9

посвящена каркасу коллекций на платформе

Java.

Всякий раз, когда тре­

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

В главе

10

представлено введение в программирование графических пользователь­

ских интерфейсов. Будет показано, как создаются окна, как в них выполняется раскра­

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

Swing.

11

посвящена более подробному обсуждению инструментальных средств

Набор инструментов

Swing

позволяет строить межплатформенный графиче­

ский пользовательский интерфейс. В этой главе вы ознакомитесь с различными вида­
ми экранных кнопок, текстовых компонентов, рамок, ползунков, комбинированных

списков, меню и диалоговых окон. Но знакомство с некоторыми из более совершен­
ных компонентов

Глава

12

Swing будет отложено до

второго тома настоящего издания.

посвящена обсуждению параллельного программирования, которое по­

зволяет выполнять программируемые задачи параллельно. Это очень важное и лю­
бопытное применение технологии

Java

в эпоху многоядерных процессоров, которые

нужно загрузить работой, чтобы они не простаивали.
В главе

13,

завершающей первый том настоящего издания, приведено краткое вве­

дение в библиотеку

JavaFX,

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

пользовательский интерфейс настольных приложений.
В приложении А перечислены зарезервированные слова языка

Java.

Условные обозначения
Как это принято во многих книгах по программированию, моноширинным шриф­
том выделяется исходный код примеров.
НА ЗАМЕТКУ! Этой пиктограммой выделяются примечания.

СОВЕТ. Этой пиктограммой выделяются советы .

••

ВНИМАНИЕ! Этой пиктограммой выделяются предупреждения о потенциальной опасности .

НА ЗАМЕТКУ С++! В этой книге имеется немало примечаний к синтаксису С++, где разъясняются от­

личия между языками

Java

и С++. Вы можете пропустить их, если у вас нет опыта программирования

на С++ или же если вы склонны воспринимать этот опыт как страшный сон, который лучше забыть.

Язык

Java

сопровождается огромной библиотекой в виде прикладного программ­

ного интерфейса

(API).

При упоминании вызова какого-нибудь метода из приклад­

ного программного интерфейса

API

в первый раз в конце соответствующего разде­

ла приводится его краткое описание. Эти описания не слишком информативны, но,
как мы надеемся, более содержательны, чем те, что представлены в официальной

Предисловие
оперативно доступной документации на прикладной проrраммный интерфейс
Имена интерфейсов выделены полужирным

API.

моноширинНЬIИ шрифтом, а число по­

сле имени класса, интерфейса или метода обозначает версию

JDK,

в которой данное

средство было внедрено, как показано ниже.
Название

прикладного

программного

интерфейса

9

Проrраммы с доступным исходным кодом орrанизованы в виде примеров, как по­
казано ниже.

Листинг

1.1.

Исходный код из файла Inputтest/InputTest.

java

Примеры исходного кода
Все примеры исходною кода, приведенные в этой книrе, доступны по соответствую­
щей ссылке в архивированном виде на посвященном ей веб-сайте по адресу

horstmann.com/corejava.

русскою издания книrи по адресу:

978-5-907114-79-1.html.
жений на

h t tp: / /

Код примеров можно также заrрузить с неб-страницы

http://www. williamspuЫishing. com/Books/

Подробнее об установке комплекта для разработки прило­

Java (Java Development Kit - JDK)

и примеров кода речь пойдет в rлаве

2.

Благодарности
Написание книrи всеrда требует значительных усилий, а ее переписывание не на­

мноrо леrче, особенно если учесть постоянные изменения в технолоrии

Java.

Чтобы

сделать книrу полезной, необходимы совместные усилия мноrих преданных делу лю­
дей, и автор книrи с удовольствием выражает признательность всем, кто внес свой
посильный вклад в настоящее издание книrи.

Большое число сотрудников издательств

Pearson

оказали неоценимую помощь,

хотя и остались в тени. Я хотел бы выразить им свою признательность за их усилия.

Как всеrда, самой юрячей блаrодарности заслуживает мой редактор из издательства

Prentice Hall Греr Доенч (Greg Doench) -

за сопровождение книrи на протяжении всею

процесса ее написания и издания, а также за то, что он позволил мне пребывать в бла­
женном неведении относительно мноrих скрытых деталей этою процесса. Я блаюда­

рен Джули Нахил

(Julie Nahil)

за оказанную помощь в подютовке книrи к изданию,

а также Дмитрию и Алине Кирсановым

-

за литературное редактирование и набор

рукописи книrи. Приношу также свою блаrодарность моему соавтору по прежним из­

даниям Гари Корнеллу

(Gary Comell),

который с тех пор обратился к друrим занятиям.

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

Среди рецензентов этою и предыдущих изданий хотелось бы отметить Чака Алли­
сона

(Chuck Allison) из университета долины Юты, Ланса Андерсона (Lance Anderson,
Oracle), Пола Андерсона (Paul Anderson, Anderson Software Group), Алека Битона (ШМ),
Клиффа Берrа, Эндрю Бинстока (Andrew Binstock, Oracle), Джошуа Блоха (Joshua
Bloch), Дэвида Брауна (David Brown), Корки Картрайта (Corky Cartwright), Френка
Коена (Frank Cohen, PushToTest), Криса Крейна (Chris Crane, devXsolution), доктора

Предисловие
Николаса Дж. Де Лилло

(Dr. Nicholas J. De Lillo) из Манхетrенскоrо колледжа, Ракеша
(Rakesh Dhoopar, Oracle), Дэвида Гири (David Geary), Джима Гиша (Jim Gish,
Oracle), Брайана Гоетца (Brian Goetz, Oracle), Анжелу Гордон (Angela Gordon) и Дэна
Гордона (Dan Gordon, Electric Cloud), Роба Гордона (Rob Gordon), Джона Грэя (John
Gray) из Хартфордскоrо университета, Камерона Грегори (Cameron Gregory, olabs.
сот), Марти Холла (Marty Hall, coreservlets. сот, lnc.), Винсента Харди (Vincent
Hardy, Adobe Systems), Дэна Харки (Dan Harkey) из университета штата Калифорния
в Сан-Хосе, Вильяма Хипинса (William Higgins, IВМ), Владимира Ивановича (Vladimir
lvanovic, PointBase), Джерри Джексона (Jerry Jackson, СА Technologies), Тима Киммета
(Tim Kimmet, Walmar)t, Криса Лаффра (Chris Laffra), Чарли Лаи (Charlie Lai, Apple),
Анжелику Лангер (Angelika Langer), Дуга Лэнгстона (Doug Langston), Ханг Лау (Hang
Lau) из университета имени Макгилла, Марка Лоуренса (Mark Lawrence), Дуга Ли
(Doug Lea, SUNY Oswego), Грегори Лонгшора (Gregory Longshore), Боба Линча (ВоЬ
Lynch, Lynch Associates), Филиппа Милна (Philip Milne, консультанта), Марка Мор­
рисси (Mark Morrissey) из научно-исследовательскою инсти~уга штата Ореrон, Махеш
Нилаканта (Mahesh Neelakanta) из Атлантического университета штата Флорида, Хао
Фам (Нао Pham), Пола Филиона (Paul Philion), Блейка Рагсдейла (Blake Ragsdell), Стю­
арта Реджеса (Stuart Reges) из университета штата Аризона, Рича Розена (Rich Rosen,
lnteractive Data Corporation), Питера Сандерса (Peter Sanders) из университета ЭССИ
(ESSI), г. Ницца, Франция, доктора Пола Сангеру (Dr. Paul Sanghera) из университета
штата Калифорния в Сан-Хосе и колледжа имени Брукса, Пола Сэвинка (Paul Sevinc,
Teamup AG), Деванг Ша (Devang Shah, Oracle), Бредли А. Смита (Bradley А. Smith), Сти­
вена Стелтинга (Steven Stelting, Oracle), Кристофера Тэйлора (Christopher Taylor), Люка
Тэйлора (Luke Taylor, Valtech), Джорджа Тхируватукала (George Thiruvathukal), Кима
Топли (Кim Topley, StreamingEdge), Джанет Трауб (Janet Traub), Пола Тиму (Paul Tyma,
консультанта), Питера Ван Дер Линдена (Peter van der Linden), Кристиана Улленбума
(Christian Ullenboom), Берта Уолша (Burt Walsh), Дана Ксю (Dan Xu, Oracle) и Джона
Завrрена (John Zavgren, Oracle).
Кей Хорстманн, Сан-Франциско, шт. Калифорния, июнь 2018 г.

Дхупара

От издательства
Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим

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

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

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

а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обя­
зательно учтем

ero

при отборе и подготовке к изданию последующих книг.

Наши электронные адреса:

E-mail:
WWW:

info@williamspuЬlishing. сот
http://www.williamspuЫishing.coт

ГЛАВА

Введение в язык

Java

В этой главе ...
• Программная платформа Java
• Характерные особенности Java
• Аплеты Java и Интернет
• Краткая история развития Java
• Распространенные заблуждения относительно Java
На появление первой версии

Java

в

1996

году откликнулись не только специали­

зирующиеся на вычислительной технике газеты и журналы, но даже такие солидные

издания, как

The New York Times,

Тhе

Washington Post

и

Business Week. Java -

единствен­

ный язык программирования, удостоившийся десятиминутного репортажа на Наци­

ональном общественном радио в США. Для ра:~работки и сопровождения программ ­
ных продуктов то.лько на этом языке программирования был учрежден венчурный

фонд в

100

миллионов долларов. Это было удивительное время. Тем временам и по­

следующей истории развития языка

1.1.

Программная платформа

Java

посвящена эта ~·лава.

Java

Java было сказано следующее: "Как язык програм­
Java перевыполнил рекламные обещания. Определенно, Java - хороший

В первом издании этой книги о

мирования,

язык программирования. Несомненно, это один из лучших языков, доступных серьез­
ным программистам. Потенциально

Java

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

ким языком про1раммирования, но теперь время для этого уже, вероятно, упущено.

Как только появляется новый язык пр01раммирова11ия, сразу же возникает неприят­

ная проблема его совместимости с созданным раньше программным обеспечением".

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

Java.

Sun Microsystems,

где первоначально был разработан

Но и сейчас, по прошествии длительною времени, такая оценка кажется

Глава

Введение в язык

1 •

правильной. Действительно,

Java

Java

обладает целым рядом преимуществ, о которых

речь пойдет далее в этой главе. Но более поздние дополнения далеко не так изящны,

-

пресловутые требования совме­

Java

никогда не был только языком.

как исходный вариант этого языка, и виной тому
стимосrи.

Как уже отмечалось в первом издании книги,
Хорошие языки

-

не редкость, а появление некоторых из них вызвало в свое время

насrоящую сенсацию в обласrи вычислительной техники. В отличие от них,

Java -

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

программ, котораяобеспечивает безопасносrь, независимость от операционной си­
сrемы и автоматическую сборку "мусора".
Программисrам нужны языки с четкими синтаксическими правилами и понятной
семантикой (т.е. определенно не С++). Такому требованию, помимо

Java,

отвечают

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

Java

объединяет в себе прекрас­

ный язык, высококачесrвенную среду выполнения программ и обширную библиоте­

ку. В результате многие программисrы осrановили свой выбор именно на

1.2.

Характерные особенности

Создатели

Java

Java.

Java

сосrавили официальное техническое описание, в котором объяс­

нялись цели и достоинсrва нового языка. В этом документе приведено одиннадцать

характерных особенносrей

Java.

Эrот язык




объектно-ориентированный;



распределенный;




надежный;



не зависящий от архитектуры компьютера;




переносимый;



высокопроизводительный;




многопоточный;

просrой;

безопасный;

интерпретируемый;

динамичный.

В данном разделе приводятся цитаты из официального описания

Java,

раскры­

вающие особенносrи этого языка программирования, а также комментарии к ним,
основывающиеся на личном опьrrе работы автора с текущей версией

Java.

НА ЗАМЕТКУ! Уnомянутое выше официальное оnисание Java можно найти no адресу www. oracle.
com/technetwork/java/langenv-140151. html. Там же оnисаны характерные особенности
Java. А краткий обзор одиннадцати характерных особенностей Java nриведен no адресу http: / /
horstmann.com/corejava/java-an-overview/7Gosling.pdf'.

1.2.

1.2.1.

Характерные особенности

Java

Простота

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

fava был разработан

как .можно более похожим

на С++. А исключи.ли .мы .лишь редко используемые, малопонятные и невразу.миrт:.льные

средства С++, которые, по нашему .мнению, приносят больше вреда, чем пользы".
Синтаксис

Java,

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

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

Java

от С++ упоминаются на протяжении

всей книги в специальных врезках.) Но создатели

Java

не стремились исправить все

недостатки языка С++. Например, синтаксис оператора
менным. Зная С++, нетрудно перейти на
Когда был выпущен язык

Java,

в

swi tch

Java

остался неиз­

Java.

С++ был отнюдь не самым распространенным язы­

ком программирования. Многие разработчики пользовались языком

Visual Basic

средой программирования путем перетаскивания. Этим разработчикам

и его

Java показался

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

разработки на

Java.

В настоящее время среды разработки на

продвинулись далеко

Java

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

"Аруюй аспект простоты

-

краткость. Одна из целей языка

Java -

обеспечить

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

40 Кбайт;

стандартные библиотеки и средства поддержки

потоков, в том числе автономное микроядро, занимают еще

175

Кбайт".

На то время это было впечатляющим достижением. Разумеется, с тех пор библио­
теки разрослись до гигантских размеров. Но теперь существует отдельная платформа

Java Micro Edition

с компактными библиотеками, более подходящая для встроенных

устройств.

1.2.2.

Объектно-ориентированный характер

"По существу, объектно-ориентированное проектирование

-

это .методика про­

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

который он изготавливает, и .лишь во вторую очередь его интересуют необходимые
для этого инструменты; в то же время "не объектно-ориентированный" столяр ду­

мает в первую очередь о своих инструментах. Объектно-ориентированные средства

fava

и С++ по существу совпадают".

Объектно-ориентированный подход к программированию вполне упрочился
на момент появления языка

Java.

Действительно, особенности

Java,

связанные с объ­

ектами, сравнимы с языком С++. Основное отличие между ними заключается в меха­

низме множественного наследования, который в
ем интерфейсов. Язык

Java обладает большими

Java

заменен более простым поняти­

возможностями для самоанализа при

выполнении, чем С++ (подробнее об этом речь пойдет в главе

5).

Глава

Введение в язык

1 •

Java

1.2.З. Поддержка распределенных вычислений в сети
"Я3ык

]ava предоставляет ра.~работчику обrиирную библиотеку программ для пере­
TCPIIP, НТТР и FTP. Приложения на ]ava способны от­

дачи данных по протоколу

крывать объекты и по,\учать к ним доступ по сети с такоit же ,\егкостью, как и в
.лока.льноit фаit.ловой системе, испо.лwуя
В настоящее время эта особенность

ющееся, но в

Basic

1995

URL

Java

для адресации".

воспринимается как само собой разуме­

году подключение к веб-серверу из программы на С++ или

Visual

считалось немалым достижением.

1.2.4.

Надежность

"Я3ык

]ava

предна.~начен для написания программ, которые должны надежно рабо­

тать в .любых условиях. Основное внимание в этом юыке уделяется раннему обна­
ружению воJможных оитбок, контролю в процессе выполнения программы, а так­

же устранен11ю с11туациu, которые могут вызвать 01иибк11". Единственное су1це­
ственное отличие я.Jыка

]ava

от С++ кроется в моде.ли ука.~ателей, принятоu в

fava,

которая исключает во.~можность 3аписи в прои.звольно выбранную область памяти
и повреждения данных''.

Компилятор

Java

выявляет такие ошибки, которые в других языках обнаружива­

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

неверного указателя, будут обрадованы тем, что в работе с

Java

подобные осложнения

не могут даже в принципе возникнуть.

1.2.5.

Безопасность

"Я.1ык

Java

предна.~начен для исполь.ювания в сетевой или распределенной среде. По

этоit пр11ч11не бо,\ыuое внимание было уделено бе3опасности.

fava

позволяет созда­

вать системы, за~цищенные от в11русов и несанкц11онированного доступа''.
Ниже перечислены некоторые виды нарушения защиты, которые с самого начала

нредотвращает система безопасности



Java.

Намеренное переполнение стека выполняемой программы

-

один из распро­

страненных способов нарушения защиты, используемых вирусами и "червями".



Повреждение данных на участках памяти, находящихся за пределами про­
странства, выделенного процессу.



Несанкционированное чтение файлов и их модификация.

Изначально в

Java

было принято весьма ответственное отношение к загружаемо­

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

Java

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

"песочницы" наружу.
Но модель безопасности в

Development Kit

Java

сложна. Вскоре после выпуска первой версии

Java

группа специалистов по безопасности из Принстонского универси­

тета обнаружила в системе безопасности едва заметные программные ошибки, кото­
рые позволяли совершать атаки на главную систему из ненадежного кода.

Характерные особенности

1.2.

Java

Эти ошибки были быстро исправлены. Но, к сожалению, злоумышленники ухи­
трились найти незначительные прорехи в реализации архитектуры безопасносrи. И

компании

Sun Microsystems,

а затем и компании

Oracle

пришлось потратить немало

времени на устранение подобных прорех.
После целого ряда крупномасштабных атак производители браузеров и специа­
листы из компании

Oracle стали

осмотрительнее. Теперь модули

Java,

подключаемые

к браузерам, больше не доверяют удаленному коду, если отсутствует цифровая под­
пись этого кода и согласие пользователей на его выполнение.
НА ЗАМЕТКУ! Оглядывая назад, можно сказать, что модель безопасности в

Java

оказалась не такой

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

ный механизм доставки кода, предложенный корпорацией

Microsoft,

опирался только на цифро­

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

Microsoft

-

лю­

может подтвердить, что даже

программы широко известных производителей часто завершаются аварийно, создавая тем самым
опасность повреждения данных.

1.2.6.

Независимость от архитектуры компьютера

"Компилятор генерирует объектный фай.л, фор.мат которого не зависит от архи­
тектуры компьютера. Скомпи.лированная программа может выпо.лняться на .лю­

бых процессорах, а д.ля ее работы требуется .ли1иь испо.лняю~цая система
генерируемый компи.лятором

]ava,

]ava.

Код,

на..~ывается байт-кодом. Он разработан таким

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

рования, как

Lisp, Smalltalk и Pascal.

Очевидно, что байт-код, интерпретируемый с помощью виртуальной машины,

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

У виртуальной машины имеется еще одно важное преимущество по сравнению

с непосредственным выполнением программы. Она существенно повышает безопас­
ность, поскольку в процессе работы можно оценить последствия выполнения каждой
конкретной команды.

1.2. 7.

Переносимость

"В отличие от С и С++, ни один из аспектов спецификации

]ava

не зависит от ре­

ализации. Ра..~рядность примитивных типов данных и арифметические операции
над ними строго опреде.лены".

Например, тип
тип

int

int

в

Java всегда
16-, так

может означать как

означает 32-разрядное целое число, а в С и С++
и 32-разрядное целое число. Единственное огра­

ничение состоит в том, что разрядность типа
сти типа

short int

int

и больше разрядности типа

не может быть меньше разрядно­

long int.

Фиксированная разряд­

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

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

Глава

1 •

Введение в язык

Java

связанных с разным порядком следования байтов на различных платформах. Сим­
вольные строки сохраняются в стандартном формате Юникода.
"Библиотеки, являющиеся частью системы, предоставляют переносимые интер­
фейсы. Например, в

]ava

предусмотрен абстрактный класс

для операционных систем
Пример класса

Unix, Windows

и

Window и

его реализации

Macintosh".

по-видимому, был выбран не совсем удачно. Всякий, ког­

Window,

да-либо пытавшийся написать программу, которая одинаково хорошо работала бы

под управлением операционных систем
ОС

Unix,

Windows,

Мае

OS

и десятка разновидностей

знает, что это очень трудная задача. Разработчики версии

Java 1.0

пред­

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

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

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

интерфейсам, библиотеки

Java

отлично справляются, позволяя разработчикам рабо­

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

файлами, регулярными выражениями, ХМL-разметкой, датами и временем, базами
данных, сетевыми соединениями,

потоками исполнения

и

прочими средствами,

не

опираясь на базовую операционную систему. Программы на

переносимыми, но и

Java не только становятся
прикладные программные интерфейсы Java API нередко оказыва­

ются более высокого качества, чем их платформенно-ориентированные аналоги.

1.2.8.

Интепретируемость

"Интерпретатор

]ava

может выполнять байт-код непосредственно на любой ма­

щине, на которую перенесен интерпретатор. А поскольку процесс компоновки но­
сит в боль1Uей степени пошаговый и относительно простой характер, процесс разра­

ботки программ может быть заметно ускорен, став более творческим".
С этим можно согласиться, но с большой натяжкой. Всем, кто имеет опыт про­
граммирования на

Lisp, Smalltalk, Visual Basic, Python, R или Scala,

хорошо известно,

что на самом деле означает "ускоренный и более творческий" процесс разработки.
Опробуя что-нибудь, вы сразу же видите результат. Но такой опыт просто отсутству­

ет в работе со средами разработки на
лось инструментальное средство

Java.

jshell,

Но так было до версии

Java 9,

когда появи­

поддерживающее быстрое эксперименталь­

ное программирование в диалоговом режиме.

1.2. 9.

Производительность

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

для того процессора, на котором вьтолняется данное приложение".
На ранней стади развития

Java

многие пользователи были не согласны с утверж­

дением, что производительности "более чем достаточно". Но теперь динамические

компиляторы (называемые иначе ]IТ-компиляторами) настолько усовершенствованы,

1.2.

Характерные особенности

Java

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

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

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

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

если потребуется, такую оптимизацию.

1.2.1 О.

Многопоточность

"К преимуществам .многопоточности относится более высокая интерактивная ре­
акция и поведение программ в реальном .масштабе времени".
В настоящее время все большее значение приобретает распараллеливание выпол­
няемых задач, поскольку действие закона Мура подходит к концу. В нашем распо­

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

простаивали. Но, к сожалению, в большинстве языков программирования проявля­
ется поразительное пренебрежение этой проблемой.

И в этом отношении

Java

опередил свое время. Он стал первым из основных языков

программирования, где померживалось параллельное программирование. Как следу­

ет из утюмянутого выше официального описания

Java,

побудительная причина к такой

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

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

Java сделано немало,

чтобы этот процесс стал более управляемым.

1.2.11. Динамичность
"Во .многих отно1иениях язык

]ava

является более динамичным, чем языки С и Сн.

Он был разработан таким обра.ю.м, чтобы легко адаптироваться к постоянно из.ме­
няющейся среде. В библиотеки .можно свободно включать новые .методы и объекты,

ни коим образом не затрагивая приложения, пользующиеся библиотека.ми. В языке

]ava

совсем не трудно получить информацию о ходе выполнения программы".

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

Java

были хорошо осведомлены о динамических языках программирования,

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

НА ЗАМЕТКУ! После первых успехов
под названием

J++,

Java

корпорация

Microsoft

выпустила программный продукт

включавший в себя язык программирования, очень похожий на

туальную машину. В настоящее время корпорация

Microsoft

прекратила поддержку

ла свои усилия на другом языке, который называется С# и чем-то напоминает

зуется другая виртуальная машина. Языки

J++

Java, а также вир­
J++ и сосредоточи­

Java, хотя

и С# в этой книге не рассматриваются.

в нем исполь­

Глава

1 •

Введение в язык

Java

1.Э. Аплеты и Интернет
Первоначальная идея была проста

-

пользователи загружают байт-коды

по сети и выполняют их на своих машинах. Программы

Java,

Java

работающие под управ­

лением веб-браузеров, называются an,\emaмu. Для использования аплета требуется
веб-браузер, померживающий язык

Лицензия на исходные коды языка

Java и способный интерпретировать байт-код.
Java принадлежит компании Oracle, насгаиваю­

щей на неизменности как самого языка, так и структуры его основных библиотек,

и поэтому аплеты
ет

Java.

Java должны

запускаться в любом браузере, который помержива­

Посещая всякий раз неб-страницу, содержащую аплет, вы получаете послед­

нюю версию этой прикладной программы. Но важнее другое: благодаря средствам

безопасности виртуальной машины вы избавляетесь от необходимосrи беспокоиться
об угрозах, исходящих от вредоносного кода.

Ввод аплета на веб-сrранице осуществляется практически так же, как и всrраива­
ние изображения. Аплет становится часrью сrраницы, а тексr обтекает занимаемое

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

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

На рис .

1.1

приведен аплет

J mo l,

предназначенный для отображения моделей

молекул. Чтобы лучше понять сrруктуру молекулы, ее можно повернуть или и з ме­
нить масштаб изображения, пользуясь мышью . В то время, когда были изобретены

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

шенные сценарии

JavaScript и

отсутсrвовал холсr в виде элемента НТМL-разметки.

tolt Ed< \OeW "'1' 0IY B•okmark1 Iool

t!O~

2J 6111 • 2..J 1em

....."

~*

А
Accooot<
~

Tme & l1119u.ige

r•

(::r

в

Eaie: of AcceS1.

P11vacy



··i.g

"

'°"

СО1"1 1•.:ii~t

Рис.

2.1.

Установка свойств системы в

Windows 10

х

-

1c::V."•1Jdk·t.OA' b1n

P.ih
ПМР

"WSUPP:Of1["-\AppD,ш\L«tl\м.croиilt .t\,ndo\o.~t.

ШР

~
Рис.

2.2.

Установка переменной окружения

''""'

Path в Windows 10

2.1.Э. Установка библиотек и документации
Исходные файлы библиотек поставляются в комплекте
щегося в файле

s rc. z i

JDK

в виде архива, храня­

р. Распакуйте этот файл, чтобы получить досrуп к исходному

коду. С этой целью выполните следующие действия.

1.

Убедитесь в том, что комплект

JDK

установлен, а имя каталога jdk / Ьin нахо­

дится в списке путей к исполняемым файлам.

Глава

2.

2 •

Среда программирования на

Создайте каталог

javasrc

Java

в своем начальном каталоге. При желании можете

сделать это в окне терминала, командной строки или оболочки, введя следую­
щую команду:

mkdir javasrc

3.

Найдите архивный файл

4.

Распакуйте архивный файл

src. zip

в каталоге

src. zip

jdk/lib.

в каталог

javasrc.

При желании можете

сделать это в окне терминала, командной строки или оболочки, введя следую­
щие команды:

cd javasrc
jar xvf jdk/src.zip
cd ..
СОВЕТ. Архивный файл

src. zip

содержит исходный код всех общедостуnных библиотек. Чтобы nо­

лучить доnолнительный исходный код iкомnилятора, виртуальной машины, собственных методов и за­
крытых всnомогательных классов), nосетите веб-страницу no адресу http://openjdk.java.net.

Документация содержится в отдельном от
по адресу

html.

1.

JDK

архиве. Ее можно загрузить

http://www. oracle. com/technetwork/j ava/j avase/downloads/ index.

Для этого выполните следующие действия.

Загрузите архивный файл документации под названием

jdk-11. О. х doc-all.

zip.

2.

Распакуйте упомянутый выше архивный файл и переименуйте каталог
на нечто более описательное вроде

j avadoc.

doc

При желании можете сделать это

в окне терминала, командной строки или оболочки, введя следующие команды:

jar xvf Downloads/jdk-11.0.x_doc-all.zip
mv docs jdk-11-docs

3.

Перейдите в окне своего браузера к странице

jdk-11-docs/index.html

и вве­

дите эту страницу в список закладок.

Кроме того, установите примеры программ к данной книге. Их можно загрузить
по адресу

http://horstmann.com/corej ava. Примеры программ упакованы в ар­
corejava. zip. Распакуйте их в свой начальный каталог. Они распо­
каталоге corej ava. При желании можете сделать это в окне терминала,

хивный файл
ложатся в

командной строки или оболочки, введя следующую команду:

jar xvf Downloads/corejava.zip

2.2.

Применение инструментов командной строки

Если у вас имеется опыт работы в

IDE Microsoft Visual Studio,

значит, вы уже зна­

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

JDK

не входят

средства, даже отдаленно напоминающие интегрированную среду разработки

(IDE).

Все команды выполняются из командной строки. И хотя такой подход к разработке
программ на

Java

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

ется весьма существенным навыком. Если вы устанавливаете платформу

Java

впервые,

2.2.

Применение инструментов командной строки

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

IDE.

Но выполняя даже самые элементарные действия самостоятельно, вы получаете

лучшее представление о внутреннем механизме работы

IDE.

После того как вы освоите самые элементарные действия для компиляции и вы­
полнения программ на

Java,

вам, скорее всего, потребуется

IDE.

Подробнее об этом

речь войдет в следующем разделе.

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

1.

Откройте окно командной оболочки.

2.

Перейдите к каталоrу

corej ava

corejava/vlch02/Welc ome.

грамм из данной книги, как пояснялось в разделе

3.

(Напомним, что каталог

был специально создан для хранения исходного кода примеров про­

2.1.3.)

Введите следующие команды :

javac Welcome. java
java Welcome
На экране должен появиться результат, приведенный на рис.

2.3.

-s cd corejava/Vlch82/Wetcome
- /corejava/vlch82/Wetcome$ javac Wetcome.java
-/corejava/vlch82/WetcomeS java Wetcome
~elcome to Core Java!
-/corejava/vlch82/Wetcome$

1

L:
Рис.

2.3.

Компиляция и выполнение программы

Welcome. java

Примите поздравления! Вы только что в первый раз скомпилировали и выполни­
ли программу на

Java.

Что же произошло? Служебная программа (иначе
пилятор

Java.

-

утилита)

javac -

это ком­

Она скомпилировала исходный код из файла

разовала его в байт-код, сохранив последний
запускает виртуальную машину

Java.

Она выполняет байт-код, который компилятор

поместил в указанный файл с расширением
Программа

Welcome

. class.

очень проста и лишь выводит сообщение на экран. Исходный

код этой программы приведен в листиJJГе
в следующей главе.

Wel come. j ava и преоб­
в файле Welcome. class. А утилита java

2.1,

а о том, как она работает, речь пойдет

Глава
Листинг

1

2

2.1.

/**
* Эта

*

Среда программирования на

2 •

Исходный код из файла

программа

Java

Welcome. java

отображает

приветствие

автора

книги

@versioп

3
4
5
6

1.30 2014-02-27
@author Сау Horstmann

puЫic

7

{

*
*/

class Welcome

puЫic

8
9
10
11
12
13
14

static void rnain(String[J args)

{
String greeting = "Welcome to Core Java!";
System.out.println(greeting);
for (int i =О; i < greeting.length(); i++)
System.out.print("=");
System.out.println();

16
17
В эпоху визуальных сред разработки программ многие проrраммисты просто не

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



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

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



welcome или WELCOME.
Компилятор требует указывать имя фай.ла (в данном случае Welcome. j ava). При
запуске программы следует указывать имя класса (в данном случае Welcome) без
расширения



. java

или

Welcome,

а не

. class.

Если вы получите сообщение

"Bad command or file name" (Неверная команда
"j avac: command not

или имя файла) или упоминавшееся ранее сообщение

found",

проверьте, правильно ли выполнена установка

Java

и верно ли указаны

пути к исполняемым файлам.



Если компилятор

javac

выдаст сообщение

(невозможно прочитать файл

"cannot read: Welcome. j ava"
Welcome. j ava}, следует проверить, имеется ли

нужный файл в соответствующем каталоге.



Если вы работаете в

Linux,

проверьте, правильно ли набраны прописные бук­

вы в имени файла
каталогов по
дника

Welcome. j ava. А в Windows просматривайте содержимое
команде dir, а не средствами графического интерфейса Прово­

Windows.

Некоторые текстовые редакторы (в частности,

няют текст в файлах с расширением

. txt.

тором для редактирования содержимого файла
его в файле
расширение

Welcome. j ava. txt.

. txt,

Welcome. j ava,

По умолчанию Проводник

сохранить его, указав имя в кавычках, например

он сохранит

Windows

скрывает

ren, или
"Welcome. j ava ".

повторно

Если при запуске программы вы получаете сообщение об ошибке типа

lang. NoClassDefFoundError,



w,

java.

проверьте, правильно ли вы указали имя файла.

Если вы получите сообщение касательно имени
строчной буквы

сохра­

поскольку оно предполагается по умолчанию. В этом случае

следует переименовать файл, воспользовавшись командой



Notepad)

Если вы пользуетесь таким редак­

еще раз выполните команду

welcome, начинающегося со
java Welcome, написав это имя

2.2.
с прописной буквы

Применение инструментов номандной строки

W. Не забывайте, что в Java учитывается регистр символов .

Если же вы получите сообщение по поводу ввода имени
случайно ввели команду



Если вы ука :~али имя

j ava Welcome. j ava.

Welcome,

Welcome/j av a, значит, вы
j ava Welcome.

Повторите команду

а виртуальная машина не в состоянии найти

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

ременная окружения

CLASSPATH в

вашей системе. Эту переменную, как пра­

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

установщики программного обеспечения

это делают. Последуйте

11 Windows

той же самой процедуре, что и для установки переменной окружения РАТН, но
на этот раз удалите устано11ку переменной окружения

СОВЕТ. Отличное учебное пособие имеется по адресу

tutorial/getStarted/cupojava.

https: //docs. oracle. com/javase/

В нем подробно описываются скрытые препятствия , кото­

рые нередко встречаются на пути начинающих программировать на

НА ЗАМЕТКУ! В версии

JDK 11

CLASSPATH.

вместо команды

javac

Java.

с единственным исходным файлом можно

пользоваться сценариями оболочки, начинающимися со строки

# ! /path/to/java с

шебангом

lт.е. со знаками решетки и восклицания в самом начале! .
Программа

Welcome

не особенно впечатляет. Поэтому рассмотрим пример гра­

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

1.
2.
3.

Огкройте окно терминала или командной оболочки.
Перейдите к каталогу

coreja v a / vlch02/Imag eVi ewer.

В11едите следующие команды:

javac ImageViewer.java
java ImageViewer
На экране поя11ится новое окно приложения

ImageViewer.

Выберите команду

меню File~Open (Файл~Огкрьпь) и найдите файл изображения, чтобы открыть ею .

(В одном каталоге с данной программой находится несколько графических файло11.)
Изображение появится в окне (рис.
щелкните на кнопке

Close

(Закрыть)

2.4). Чтобы завершить выполнение программы,
11 строке заголовка текущею окна или выберите

команду меню File~Exit (Файл~Выйти).


lmageViev1er

Рис.

- с:: х

2.,. Окно выполняющеюся

приложения

ImageViewer

Глава

2 •

Среда программирования на

Java

Беrло просмотрите исходный код данной проrраммы, приведенный в листин­


2.2.

Эта проrрамма заметно длиннее, чем первая, но и она не слишком сложна,

особенно если представить себе, сколько строк кода на языке С или С++ нужно было
бы написать, чтобы получить такой же результат. Написанию приложений с rрафи­

ческим пользовательским интерфейсом
щена rлава

10.

Листинг

Исходный код из файла

1

2
3
4
5
6
7
8
9
10

2.2.

(GUI),

подобных данной проrрамме, посвя­

ImageViewer/ImageViewer. java

import java.awt.*;
import java.io.*;
import javax.swing.*;
/**

*

Программа

для просмотра

изображений.

* @version 1.31 2018-04-10
* @author Сау Horstmann
*/
puЬlic class ImageViewer

11 (
puЫic static void main(String[] args)
12
(
13
14
EventQueue.invokeLater(() -> (
15
var frame = new ImageViewerFrame();
16
frame.setTitle("ImageViewer");
17
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
18
frame.setVisiЬle(true);
19
));
20
21
22
23 /**
24 * Фрейм с текстовой меткой для вывода изображения.
25 */
26 class ImageViewerFrame extends JFrame

27 (

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

private static final int DEFAULT WIDTH = 300;
private static final int DEFAULT HEIGHT = 400;
puЫic

ImageViewerFrame()

(
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
//использовать метку для

вывода изображений на экран

var label = new JLabel();
add(label);
//установить

селектор файлов

var chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));
//установить

строку меню

var menuBar = new

JМenuBar();

2.3.
45
46
47
48

Применение

IDE

setJMenuBar(menuBar);
var menu = new JМenu("File");
menuBar.add(menu);

49

50
51
52
53
54
55
56
57
58
59
60
61

var openitem = new JMenuitem("Open");
menu.add(openitem);
openitem.addActionListener(event -> {

62

)
} 1;

//отобразить

диалоговое окно селектора файлов

int result = chooser.showOpenDialog(null);
// если файл выбран, задать его в качестве
// пиктограммы для метки
if (result == JFileChooser.APPROVE_OPTION)
String name = chooser.getSelectedFile() .getPath();
label.seticon(new Imageicon(name));

63
64
65
66
67
68
69

var exi titem = new JМenuitem ( "Exi t") ;
menu.add(exititem);
exititem.addActionListener(event -> System.exit(O));

2.З. Применение

IDE

В предыдущем разделе было показано, каким образом программа на

Java

ком­

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

для повседневной работы следует выбрать интегрированную среду разработки

(IDE).

Такие среды стали настолько эффективными и удобными, что профессионально раз­
рабатывать программное обеспечение без их помощи просто не имеет смысла. К чис­

лу наиболее предпочтительных относятся

IDE Eclipse, NetBeans и lntelliJ IDEA. В этой
IDE Eclipse. Но вы вольны выбрать

главе будет показано, как приступить к работе с
другую

IDE

для проработки материала данной книги.

Прежде всего загрузите

downloads/,

IDE Eclipse по адресу ht tp: / /www. ecl ipse. org /
IDE Eclipse для Linux, Мае OS Х, Solaris и Windows.

где имеются версии

Выполните программу установки и выберите установочный набор

Developers (IDE Eclipse

Eclipse IDE for Java

Java).
Java в IDE Eclipse,

для разработчиков программ на

Чтобы приступить к написанию программы на

выполните сле­

дующие действия.

1.

После запуска

Eclipse

выберите из меню команду File~New

Project (Файл~Со­

здать проект).

2.

Выберите вариант
тов (рис.

3.

Java Project

(Проект

Java)

в диалоговом окне мастера проек­

2.5).

Щелкните на кнопке

Next (Далее). Сбросьте флажок Use default location (Исполь­
Browse (Обзор) и перейдите

зовать место по умолчанию). Щелкните на кнопке
к каталогу

corejava/vlch02/Welcome

(рис.

2.6).

Глава

2 •

Среда проrраммирования на

Java

1- New Pro/ect

- " •

Setect а wl1.11rd

Create

а

Java proJect

Wizards:

typt> h\t~r text
• • General
• - Gradle


java
• Java Project from Exlsting Ant Bulldhle

·'

Next >

Рис.

2.5. Диалоговое

!" New Java

окно

Cancel

Eclipse для создания нового

Project

проекта

-

L:

х

J,
Create а java project in the workspace or in an extemal location

Project name: lwetcom~

--------

J

1use default locatJon
Browse".

Location: /homeJcay/corejava/Vlch02/Welcome

JRE
~ Use an executlon environment JRE:
o use а project specifkJRE:

)avaSE-9



, jdk·9.0.l

OUse defaultJRE (currently 'jdk-9.0.l')

_ _ _ _ _conftgure JREs...

Project layout
o use project folder as root for sources and class flles
• Create separate folders for sources апd class files
Worklng sets
1 г дdd project to working sets

l

< Back
Рис.

J

New...

Next >

2.6. Настройка

Cancei

проекта в

Eclipse

Flnlsh

2.3.

Применение

IDE

4.

Щелкните на кнопке

5.

Щелкайте по очереди на треугольных кнопках слева от имени проекта до тех

Finish

(Готово). В итоге будет создан новый проект.

пор, пока не найдете файл

Welcome. j ava,

а затем дважды щелкните на нем. В

итоге появится окно с исходным кодом программы, как показано на рис.

l:lle fdlt :;ource Refactor ttavlgate seorch

eroJect

1>·0·1\ ·

вun

G· -

2.7.

Jlilr.dow ttelp

,

-t •

. "\\

'feva

:s

Packa9e ExplOrer



=

'1.1 Welcome.Java

Q

~

t / •
"!;"

Th1S pro(Jrrt di!i.pla}'5o а 9reetinq for tkto rea-d11:r
r "" 1.38 zен ez 21

.; we1come
v 111 (def(больше), (больше

или равно).
В языке

Java,
11 -

И, а знаки

клицания

(!)

как и в С++, знаки && служат для обозначения логической операции
для обозначения логической операции ИЛИ. Как обычно, знак вос­

означает логическую операцию отрицания. Операции && и

1 1 задают

порядок вычисления по сокращенной схеме: если первый операнд определяет значе­

ние всего выражения, то остальные операнды не вычисляются. Рассмотрим для при­
мера два выражения, объединенных логической операцией &&:
выражение

1 &&

выражение_2

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

!=О

&&

1/х

> х+у //не делить на нуль

Таким образом, деление на нуль не происходит. Аналогично значение выраже­
ние

1

11

выражение_ 2 оказывается истинным, если истинным является значение

первого выражения. В этом случае вычислять второе выражение нет нужды.
В языке

Java

имеется также тернарная операция

? :,

которая иногда оказывается

полезной. Ниже приведена ее общая форма.
условие

? выражение

1 :

выражение_2

Если условие истинно, то вычисляется первое выражение, а если оно ложно
второе выражение. Например, вычисление выражения х

>

и

I strtnoContont

Ja•af.

J8Yah.ut1I 5trt"

The jd\-i\F\ ЛРI~ de-Пne

erte:r

Jevatx.css.con\lert'°r s-trtnuconverur

dt>\~Jopuщ rkh ci.Кn' аррЬ: Javьtx Ьe!SМ.~nd ng. Strinofxpr1ssiCМ1
onJ.omg.COR&A .st.mo Hotde.r
.)Ova.lang.Strtngtnd8':it0utor&ound$Exceptic;n
J8V• .\lhl

Jow•SI!

5tr1f\QJotnФr

jOVO.:C.П'IOMg@'m~nt . n'loONtoг. st.r1nvMon.t.oi

Modula

O.sc.rlptiD

"..,a.actWat;on

)'vox.monagitm.!nt.mGf'\Jtьг Strtng '"tomtwMS@Ьn

~JШ.5 t1щ: 1V9.0fn9.COSNt.mlng.~amtngC.OМOtЬtPodr.a91!' S1:rtng~o"Т1artToignDr~lls~IS.

pubHc tnt

rin9 strl

Cornp.srt> tv.·u tnщJ~ \1·xk'ograph1rc1Uy 'gnonnci с~· dJ!li:rPnce-t ТЬ n1 thOO rtturn:i: itn tnteg r whuse
!ilfJR 1s that uf •·aJhng COIJIPairero w1th noimttl!l°i'd 't't·~n~ of thr 'itnno~ \\ber) c.1se dl!!Prentб ha\-' Ьесn
el1mtnatf'.d bv .-oUmg Ch•r•cter. toL-rt•••(Cl\!tncter. tOIJl>percase(charocter)) on ach charact= target)
{

performance
bonus = 100;

"Satisfactory";

В этом фрагменте кода все операторы, заключенные в фигурные скобки, будуг вы­
полнены при условии, что значение переменной
менной

target

или равно ему (рис.

yourSa l es

больше значения пере­

3.7).

performance
~"Sat ' sfactory "

bonus~lOO

Рис.

3.7. Блок-схема,

иллюсrрирующая принцип

действия условною оператора

if

НА ЗАМЕТКУ! Блок, иначе называемый составным оператором. позволяет включать несколько

!простых! операторов в любую языковую конструкцию

лишь один !простой! оператор.

Java,

которая в противном случае допускает

З.8. Управляющая лоrика
Ниже приведена более общая форма условного оператора
действия в данной форме наглядно показан на рис.

if

(условие)

оператор1

else

3.8 и

i f в Java. А принцип его

в приведенном далее примере.

оператор 2

performance
• "Unsatisfac ory"

performance
• "Satisfac ory"

bonus bonuз •О

100+0 . ОР

(yourSa leз-tarqet)

1

1


Рис.

3.8. Блок-схема,

иллюстрирующая принцип

действия условного оператора

if/else

if (yourSales >= target)
{

perf ormance
"Sa t isfactory ";
bonus = 10 0 + 0.0 1 * (yourSa l es - t arget);
else
performance
bonus = О;
Часть

e ls e

"Unsatisfactory";

данного оператора не является обязательной и объединяется с бли­

жайшим условным оператором
тор

if

else


if.

Таким образом, в следующей строке кода опера­

относится ко второму оператору

=

О:

");

n = in.nextlnt();

if (n < 0) //условие
break read_data;

/!

для прерывания цикла

прервать цикл

)

11
11
if

этот оператор выполняется сразу же после
оператора
(п

<

0)

break

с меткой

//поверить

наличие

недопустимой

ситуации

{

11

принять меры против недопустимой ситуации

else
11
//

выполнить действия при нормальном ходе
выполнения программы

Если было введено неверное число, оператор

break

с меткой выполнит переход

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

break.

D

13

НА ЗАМЕТКУ! Любопытно, что метку можно связать с любым оператором - даже с условным оператором if или блоком, как показано ниже.

Глава З



Основные яэыновые конструкции

Java

нетжа:

if

(условие)

break

метжа;

//выход из

блока

1
11
11

При выполнении оператора

break

управление

передается в эту точку

Итак, если вам крайне необходим оnератор

goto

для безусловного nерехода, nоместите блок, из

которого нужно немедленно выйти, неnосредственно nеред тем местом, куда требуется nерейти,

и nримените оnератор Ьreak! Естественно, такой nрием не рекомендуется nрименять в nрактике
nрограммирования на

Java.

Следует также иметь в виду, что nодобным сnособом можно выйти из

блока, но невозможно войти в него.

Существует также оператор

continue,

который, подобно оператору

рывает нормальный ход выполнения программы. Оператор

break, пре­
continue передает

управление в начало текущего вложенного цикла. Ниже приведен характерный при­
мер применения данного оператора.

Scanner in
new Scanner(System.in);
while (sum <goal)
{

System.out.print("Enter а numЬer: ");
n = in.nextint();
if (n < 0) continue;
sum += n; //не выполняется, если n <
Если

n <

О, то оператор

continue

О

выполняет переход в начало цикла, пропуская

оставшуюся часть текущего шага цикла. Если же оператор
в цикле

for,

continue

применяется

он передает управление оператору увеличения счетчика цикла. В каче­

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

for (count = l; count -

текста моноширинным шрифтом,
полужирным,

... > ( -

/ -

... ... -

...










10.5.









Если в прикладной программе используются глобальные параметры настройки,
ее пользователям можно предоставить возможность экспортировать и импортиро­

вать эти глобальные параметры, чтобы упростить перенос программы с одного ком­
пьютера на другой. Такой подход демонстрируется в примере программы, исходный
код которой приведен в листинге

10.7.

В этой программе сохраняются координаты

и размеры главного окна. Попробуйте изменить размеры окна, выйти из программы
и запустить ее снова на выполнение. Размеры окна должны совпадать с теми разме­
рами, которые были заданы перед выходом из программы. Импортируйте свои гло­
бальные параметры настройки, чтобы вернуть окно на прежнее место.
Листинг

1

10.7. Исходный

код из файла

preferences/ImageViewer. java

package preferences;

2

3
4
5
6
7
8

import
import
import
import
import

java.awt.EventQueue;
java.awt.event.*;
java.io.*;
java.util.prefs.*;
javax.swiпg.*;

9
10
11
12
13
14
15
16

/**
* В этой программе проверяются глобальные параметры
* настройки. В ней запоминаются положение и размеры
* фрейма, а также последний выбранный файл.
* @versioп 1.10 2018-04-10
* @author Сау Horstmanп
*/
puЫic class ImageViewer

17

{

18
19
20
21
22
23
24
25

puЫic

static void

maiп(String[]

args)

{
() -> {
var frame = new ImageViewerFrame();
frame.setTitle("ImageViewer");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);

EveпtQueue.iпvokeLater(

frame.setVisiЬle(true);

26

}} ;

27
28
29
30
31

/**
* Средство

просмотра

изображений,

восстанавливающее

Глава

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

1О •

Программирование графики

*

положение,

*

пользовательских глобальных

*

обновляющее

размеры и просматриваемое изображение из
эти параметры по

параметров

*/
class ImageViewerFrame extends JFrame
(
private static final int DEFAULT WIDTH = 300;
private static fiпal int DEFAULT HEIGHT = 200;
private String image;
puЫic

ImageViewerFrame()

(
Preferences root = Preferences.userRoot();
Preferences node = root.node(
"/com/horstmanп/corej ava/ImageViewer");
//получить положение,

размеры и заглавие из свойств

int left = node.getint("left", 0);
int top = node.getint("top", 0);
int width = node.getint("width", DEFAULT WIDTH);
int height = node.getint("height", DEFAULT_HEIGHT);
setBounds(left, top, width, height);
image = node.get("image", null);
var label = new JLabel();
if (image != null)
label.seticon(new Imageicon(image));
addWindowListener(new WindowAdapter()
(
puЫic

void windowClosing(WindowEvent event)

(

node.putint("left", getX());
node.putint("top", getY());
node.putint("width", getWidth());
node.putint("height", getHeight());
node.put("image", image);
)
));
//воспользоваться меткой для воспроизведения
//изображений

add(label);
//установить

селектор файлов

var chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));

77

78
79
80
81
82
83
84
85
86
87
88

настройки и

завершении работы

//установить

строку меню

var menuBar = new

JМenuBar();

setJМenuBar(menuBar);

var menu = new JMenu("File");
menuBar.add(menu);
var openitem = new JMenuitem("Open");
menu.add(openitem);
openitem.addActionListener(event -> (

10.5.

Прикладной интерфейс

Preferences API

11 отобразить диалоговое окно селектора файлов
int result = chooser.showOpenDialog(null);

89

90
91
92

94

11 если файл выбран, установить его в виде
11 пиктограммы метки
if (result == JFileChooser.APPROVE_OPTION)

95

{

93

image = chooser. getSelectedFile () . getPath ();
label.seticon(new Imageicon(image));

96
97
98
99

}
}) ;

100
101
102
103
104
105

var exititem = new JMenuitem("Exit");
menu.add(exititem);
exititem.addActionListener(event -> System.exit(O));

java.util.prefs.Preferences 1.4
Preferences userRoot ()
Возвращает корневой узел из дерева глобальных nараметров настройки для nользователя вызы­
вающей nрограммы.



Preferences systemRoot()



Preferences node (String path)

Возвращает системный корневой узел из дерева глобальных nараметров настройки.

Возвращает узел, достуnный из текущего узла

no

заданному nути. Если в качестве nараметра

path указан абсолютный nуть, который обычно начинается со знака косой черты

l/J,

стуnен из корня дерева глобальных nараметров настройки. Если же узел отсутствует

no

то узел до­
заданному

nути, он создается.

Preferences userNodeForPackage(Class cl)
Preferences systemNodeForPackage(Class cl)
Возвращают узел из дерева текущего nользователя или системного дерева. абсолютный nуть к ко­
торому соответствует имени nакета, содержащего заданный класс

cl.

String[] keys()



Возвращает все ключи, nринадлежащие данному узлу.



String get (String key, String defval)



int getint (String key, int defval)
long getLong (String key, long defval)
float getFloat(String key, float defval)



douЫe



boolean



Ьуtе[]

getDouЬle(String
getвoolean

key,

douЫe

defval)

(String key, boolean defval)

getвyteArray(String

key,

byte[] defval)

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

no умолчанию.

Глава

1О •

Программирование графики

java. util .prefs. Preferences 1. 4

(окончание}



void put(Strinq key, Strinq va.lue)



void putint (Strinq key, int va.lue)



void putLonq(Strinq key, lonq va.lue)



void putFloat (Strinq key, float va.lue)



void

putDouЫe



void

putвoolean



void

putвyteArray(Strinq

(Strinq key,

douЫe

va.lue)

(Strinq key, boolean va.lue)
key, byte[]

va.lue)

Сохраняют пару "ключ-значение" в заданном узле дерева.



void

exportSuЬtree

(OutputStream out)

Выводит в указанный поток глобальные параметры настройки, хранящиеся в заданном узле и про­
изводных от него узлах.



void exportNode (OutputStream out)
Направляет в указанный поток вывода глобальные параметры настройки, хранящиеся в заданном
узле, игнорируя производные от него узлы.



void

iшportPreferences(InputStream

in)

Импортирует параметры глобальных настроек из указанного потока ввода.

На этом завершается введение в программирование

GUI

на

Java.

В следующей

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

Swing.

ГЛАВА

Компоненты
пользовательского

интерфейса в

Swing

В этой главе".
~

Библиотека Swing и проектный шаблон "модель-представление-контроллер"

~

Введение в компоновку пользовательского интерфейса

~

Ввод текста

~

Компоненты для выбора разных вариантов

~

Меню

~

Расширенные средства компоновки

~ Диалоговые окна

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

Java.

Проработав ее, вы приобрели знания и навыки, без которых немыс­

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

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

Swing.

Чтобы эффективно пользоваться современными

компонентами пользовательского интерфейса, нужно как следует разбираться в их

функционировании. Затем будет показано, как применять обычные компоненты

Глава

11 •

Компоненты попьзоватепьского интерфейса в

пользовательского интерфейса из библиотеки

Swing,

Swing

включая текстовые поля, кноп­

ки-переключатели и меню. Далее поясняется, как пользоваться возможностями

диспетчеров компоновки в

от визуального стиля

GUI.

Java,

чтобы размещать компоненты в окне независимо

И в заключение главы будет показано, каким образом диа­

логовые окна создаются средствами

Swing.

В этой главе описываются основные компоненты библиотеки

Swing,

в том

числе текстовые поля, экранные кнопки и полосы прокрутки. Это самые важные

и наиболее употребительные компоненты пользовательского интерфейса. А более
сложные компоненты

Swing

будут рассматриваться во втором томе настоящего

издания.

11.1.

Библиотека Swiпg и проектный шаблон
модель-представление-контроллер"

11

Напомним, из чего состоят компоненты

GUI,

например, экранная кнопка, фла­

жок, текстовое поле или сложное окно управления древовидной структурой элемен­

тов. Каждый из этих компонентов обладает следующими характеристиками.



Содержимое, например, состояние кнопки (нажата или отпущена) или текст
в поле редактирования.




Вне~иний вид (цвет, размер и т.д.).

Поведение (реакция на события).

Даже у таких, на первый взгляд, простых компонентов, как экранные кнопки, эти
характеристики тесно связаны между собой. Очевидно, что внешний вид экранной

кнопки зависит от визуального стиля интерфейса в целом. Экранная кнопка в стиле

Metal

отличается от кнопки в стиле

Windows

или

Motif.

Кроме того, внешний вид

зависит от состояния экранной кнопки: в нажатом состоянии кнопка должна выгля­

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

Разумеется, когда вы используете экранную кнопку в своих программах, то рас­

сматриваете ее как таковую, не особенно вдаваясь в подробности ее внутреннего
устройства и характеристик. В конце концов, технические подробности

-

удел про­

граммиста, реализовавшего эту кнопку. Но программисты, реализующие экран­
ные кнопки и прочие элементы

GUI,

должны тщательно обдумывать их внутреннее

устройство и функционирование, чтобы все эти компоненты правильно работали не­
зависимо от выбранного визуального стиля.
Для решения подобных задач разработчики библиотеки

Swing

обратились к хо­

рошо известному проектному шаблону "модель-представление-контроллер"

View-Control\er -

отдельных объекта, представляющие следующие его составляющие.





(Model-

МVС), который предписывает разработчикам предоставить три

Моде.ль, где хранится содержимое.
Представление, отображающее содержимое.
Контро.л.лер, обрабатывающий вводимые пользователем данные.

11.1.

Бмб.11мотека

Swing

и проектный шаблон "м0Ае.11ь-представ.11енме-контро.11.11ер"

Проектный шаблон "модель-представление-контроллер" точно обознача­
ет взаимодействие этих объектов. Модель хранит содержимое и не реализует
пользовательский интерфейс. Содержимое экранной кнопки тривиально
небольшой

-

это

абор признаков, означающих, нажата кнопка или отпущена, акти­

визирована или неактивизирована и т.д. Содержимым текстового поля является
символьная строка, не совпадающая с представлением. Так, если содержимое пре­

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

модель

11.1).
"The quick brown fox jumps over the lazy dog•

представление lьrown lfoи jump 1
Рис.

11.1. Модель

и представление текстового поля

Модель должна реализовывать методы, изменяющие содержимое и раскрываю­
щие его смысл. Например, модель текстового поля имеет методы для ввода символов

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

-

обя­

занность представления.

НА ЗАМЕТКУ! Термин модель, по-видимому, выбран не совсем удачно, поскольку его часто связы­
вают со способом представления абстрактного понятия. Например, авиаконструкторы и конструк­
торы автомобилей строят модели для того, чтобы имитировать настоящие самолеты и автомобили .

Но эта аналогия не подходит к шаблону "модель-представление-контроллер". В данном случае
модель хранит содержимое, а представление отвечает за ее полное или неполное визуальное ото­

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

Одно из преимуществ шаблона "модель-представление-контроллер" состоит
в том, что модель может иметь несколько представлений, каждое из которых отра­

жает отдельный аспект ее содержимого. Например, редактор НТМL-разметки до­
кументов часто предлагает одновременно два представления одних и тех же данных :

WYSIWYG (Что видишь на экране, то и получишь
(рис. 11.2). Когда контроллер обновляет модель,

при печати) и ряд дескрипторов
изменяются оба представления.

Получив уведомление об изменении, представление обновляется автоматически.
Разумеется, для таких простых компонентов пользовательского интерфейса, как
кнопки, нет никакой необходимости предусматривать несколько представлений од­
ной и той же модели.

Контроллер обрабатывает события, связанные с поступающей от пользователя

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

троллер вызовет из модели команду "вставить символ". Затем модель уведомит
представление обновить изображение. Представлению вообще неизвестно, почему

Глава

11 •

Компоненты попьзоватепьсного интерфейса в

Swing

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

11.3.
Swing классы модели реализуют интерфейсы,
имена которых оканчиваются словом Mode l. В частности, для экранных кнопок
используется интерфейс ButtonMod el. Классы, реализующие этот интерфейс,

ствие модели, представления и контроллера схематически показано на рис.

Для большинства компонентов

могут определять состояние разнотипных экранных кнопок. Кнопки настоль­

ко просты, что для них в библиотеке

DefaultButtonModel,

Swing

предусмотрен отдельный класс

реализующий данный интерфейс. Понять, какого рода дан­

ные поддерживаются в модели кнопки, можно, рассмотрев свойства интерфейса

ButtonModel

(табл.

11.1).

11 ••1.1111111•• 111.1
Модель

1111 •• 111.
1111 • •••••
111111.111.

11.11111 •• 1111 •• 11 ••• 11.1
1. 11111111 •• 1111 •• 11.1
2. 111111111 •• 1111 •• 11.
3. 11111111 •• 11111 •• 11.

Рис.

11.2. Два

11 11111.•llll••ll


11 l11 l1 •• l lll
11111 l l ••lll
1111111 •111111



разных представления одной и той же модели

11.1.

Библиотека

Swing

и проектный шаблон ··модепь-nредставленне-контроллер"

[п-]

EJ
1

1
1

рисует

[

Модмь

1

1

1
1
1
1
1
1

1
1
1
1
1
1
1
1

представление

]

1
читает
содержимое

1

о

Рис. 11.З. Взаимодействие объектов модели, представления и контроллера
Таблица

11.1. Свойства

интерфейса

ButtonModel

Имя свойства

Значение

actionCommand

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

mnemonic

Мнемоническое обозначение экранной кнопки

armed

Логическое значение

true,

если экранная кнопка была нажата , а курсор мыши еще

если экранная кнопка доступна

находится на кнопке

enaЫed

Логическое з начение

true,

pressed

Логическое значение

true, если

экранная кнопка была нажата , а кнопка мыши еще

не отпущена

rollover

Логическое значение

selected

Логическое значение true. если экранная кнопка включена !используется
для флажков и кнопок-переключателей!

true,

если курсор мыши находится на экранной кнопке

Глава

11 •

Компоненты попьзоватепьского интерфейса в

В каждом объекте типа

Swing

хранится объект модели кнопки, который можно

JButton

извлечь оттуда следующим образом:

var button = new JButton("Blue");
ButtonModel model = button.getModel();
На практике подробности, касающиеся состояния экранной кнопки, интересуют
лишь представление, которое рисует ее на экране. Но из класса

влечь и друrую полезную информацию
кнопка. (Для этого класс

JButton

-

JButton

можно из­

в частности, заблокирована ли экранная

запрашивает модель экранной кнопки.)

Обратимся еще раз к интерфейсу

ButtonModel и попробуем определить, что

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

DefaultButtonModel)

ис­

пользуется для поддержки нажимаемых кнопок, флажков кнопок-переключателей
и даже для пунктов меню. Разумеется, каждая из этих разновидностей экранных
кнопок имеет свое собственное представление и отдельный контроллер. Если реа­

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

Metal,

BasicButtonUI,

то в качестве представления в классе

а качестве контроллера

В общем, у каждого компонента библиотеки

Swing

класс

JButton ис­
ButtonUIListener.

имеется связанный с ним объект

представления, название которого заканчивается на

Swing

-

UI.

Но не у всех компонентов

JButton,

вы можете спросить: а что

имеется свой собственный объект контроллера.

Итак, прочитав это краткое введение в класс

на самом деле представляет собой класс JВutton? Это просто класс-оболочка, произво­

дный от класса

JComponent

и содержащий объект типа

Defaul tButtonModel,

некоторые

данные, необходимые для отображения (например, метку кнопки и ее пиктограмму),
а также объект типа

11.2.

BasicButtonUI,

реализующий представление экранной кнопки.

Введение в компоновку пользовательского интерфейса

Прежде чем перейти к обсуждению таких компонентов

Swing,

как текстовые поля

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

во фрейме. Разумеется, если вы программируете на

Java

в соответствующей

IDE,

то

в этой среде, скорее всего, предусмотрены средства, автоматизирующие некоторые

из задач построения

GUI

методом перетаскивания. Несмотря на это, вы обязаны

ясно представлять, каким образом размещаются компоненты. Ведь даже те

GUI,

ко­

торые автоматически построены с помощью самых совершенных инструментальных

средств, обычно нуждаются в ручной доработке.

11.2.1. Диспетчеры

компоновки

Вернемся для начала к примеру программы из лисшнга

10.4,

где экранные кноп­

ки служили для изменения цвета фона во фрейме.
Экранные кнопки содержатся в объекте типа
поточной компоновки

11.4

-

JPanel

и управляются дuС11етчером

стандартным диспетчером для компоновки панели. На рис.

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

кнопки. Как видите, если кнопки не помещаются в текущем ряду, они переносятся

11.2.

Введение в компоновку попьзоватепьского интерфейса

в новый ряд. Более того, экранные кнопки будуг отцентрованы на панели, даже если
пользователь изменит размеры фрейма (рис.

8

[_ :о

Bнttor1Tes t

l 'Yello

[ Blue

l

Oг.i.ngt

Rtd

1[

Fucl1s

11.5).

lx

1[ Grнn

l8

J

! B11ttonтest

11.4. Панель с

l

а

шестью экранными

кнопками, расположенными подряд

!o ! Х

' Ytllow }[ в1u;i

Or

Рис.

f_

Рис.

R;d J[Gret~
П!J~ [

11.5. При

FUfhSlit

]

изменении разме­

ров панели автоматически изме­

диспе1чером поточной компоновки

няется расположение кнопок

В целом компоненты размещаются в контейнерах, а диснетчер комноновкu опре­
деляет порядок расположения и размеры компонентов в контейнере. Классы экран­

ных кнопок, текстовых полей и прочих элементов пользовательского интерфейса
расширяют класс

Component.

Компоненты мoryr размещаться в таких контейнерах,

как панели. А поскольку одни контейнеры мoryr размещаться в других контейнерах,
то класс

Container

расширяет класс

Component.

11.6 схематически
Component.

На рис.

иерархия наследования всех этих классов от класса

показана

ObJect

W1ndow

JМenuBar

JMenultem

Рис.

11.6.

Иерархия наследования от класса

Component

Глава

11 •

Компоненты попьэоватепьскоrо интерфейса в

Swing

НА ЗАМЕТКУ! К сожалению, иерархия наследования выглядит не совсем ясной по двум причинам.
Во-первых, окна верхнего уровня, например типа
от класса

Container,

контейнерах. Более того, класс
от класса

Component,

JFrame, являются подклассами, производными
Component, но их нельзя разместить в других
JComponent является производным от класса Container, а не

а следовательно, и от класса

и поэтому другие компоненты можно добавлять в контейнер типа JВutton.

!Хотя эти компоненты и не будут отображаться.]

У каждого контейнера имеется свой диспетчер компоновки по умолчанию, но

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

GridLayout

используется для размещения

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

add ()

кон­

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

panel.setLayout(new GridLayout(4, 4) );

java.awt.Container 1.0


void setLayout (Layoutмanager m)
Задает диспетчер компоновки для данного контейнера.



Component add (Component

с)



Component add(Component

с,

Object constraints) 1.1

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

java.awt.FlowLayout 1.0
FlowLayout ()


FlowLayout (int align)
FlowLayout(int align, int hgap, int vgap)
Конструируют новый объект типа

ние по левому

11.2.2.

FlowLayout. В качестве параметра align задается
ILEFT] краю, правому IRIGHT] краю или по центру ICENТER].

выравнива­

Граничная компоновка

Диспетчер граничной компоновки по умолчанию выбирается для панели содержи­

мого, присутствующей в объекте типа

JFrame.

В отличие от диспетчера поточной

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

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

же слева или справа, как по сторонам света (рис.

11.7).

Например:

frame.add(component, BorderLayout.SOUTH);
При размещении компонентов сначала выделяется место по краям контейнера,

а оставшееся свободное пространство считается центральной областью. При измене­
нии размеров контейнера размеры компонентов, располаrамых по краям, остаются

11.2.

Введение в компоновку попьзоватепьского интерфейса

прежними, а изменяются лишь размеры центральной области. При вводе компонен­
та на панели указываются константы CENTER (Центр), NORTH (Север), SOUTH (Юг), EAST
(Воа·ок) или

WEST (Запад), определенные в классе Bo rderLayout. Занимать все места

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

CENTER,

т.е. расположение по центру.

North

West Center

East

South

Рис.

11.7. Граничная

компоновка

НА ЗАМЕТКУ! Константы в классе

константа

BorderLayout определены как символьные строки . Например,
BorderLayout. sоuтн представлs~ет собой символьную строку 11 South 11 • Пользоватьсs~

константами вместо символьных строк надежнее. Так. если вы случайно сделаете опечатку в сим­
вольной строке при вызове

frame. add (component,

"South"), компилs~тор не распознает ее

как ошибку.

В отличие от поточной компоновки, при 1-раничной компоновке все компоненты

растягиваются, чтобы заполнить свободное пространство. (А при поточной компо­
новке предпочтительные размеры каждого компонента остаются без изменения.) Это
может послужить препятствием к добавлению экранной кнопки, как показано ниже .

frame.add(yellowButton, Borde rLayou t.SOUTH);
11 Не рекомендуется!
На рис.

11.8

показано, что произойдет, если попытаться выполнить приведенную

выше строку кода. Размеры экранной кнопки увеличатся, и она заполнит всю ниж­

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

[8 [8tltto11rest _ _ _ _ _ _ Г- ГEПx

ellow
Рис.

11.8. Граничная

компоновка

одиночной экранной кнопки

Гпава

11 •

Компоненты пользовательского интерфейса в

Swing

В качестве выхода из этого затруднительного положения можно воспользоваться

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

11.9.

Все три кнопки в нижней части экрана находятся на одной панели,

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

г

Рис.

ellov.

11.9.

Blue

Red

Панель с тремя кнопками,

располагаемая в южной области фрейма
Для такого расположения сначала создается новый объект типа

JPane l , в

который

затем вводятся отдельные экранные кнопки. Как упоминалось ранее, с обычной пане­
лью по умолчанию связывается диспетчер поточной компоновки типа

В данном случае он вполне подходит. С помощью метода

add ( )

Fl owLayout.

на панели размеща­

ются отдельные экранные кнопки, как было показано ранее. Расположение и разме­
ры кнопок определяются диспетчером типа

FlowLayout.

Это означает, что кнопки

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

ми располагается в нижней части панели содержимого фрейма, как пока :~но 11 11ри ­
веденном ниже фрагменте кода. Граничная компоновка растягивает панель с тремя
экранными кнопками, чтобы она заняла всю нижнюю (южную) область фрейма.

var panel = new JPanel();
panel.add(yellowButton);
panel.add(ЬlueButton);

panel.add(redButton);
frame.add(panel, BorderLayout.SOUTH ) ;

java.awt.BorderLayout 1.0


BorderLayout()



BorderLayout(int hgap, int vgap)
Конструирует новый объект тиnа

BorderLayout.

11.2.Э. Сеточная компоновка
При сеточной компоновке компоненты располагаются рядами и столбцами, как
в таблице. Но в этом случае размеры всех компонентов оказываются одинаковыми.

На рис.

11.10

показано окно, в котором для размещения кнопок калькулятора при­

меняется сеточная компоновка. При изменении размеров окна экранные ююnки

11.Э. Ввод текста
автоматически увеличиваются или уменьшаются, причем размеры всех кнопок оста­
ются одинаковыми.

7

8

9

4

5

6

1

2

з

о

Рис.

1

+

11.10. Сеточная компоновка
кнопок калькулятора

Требуемое количество рядов и столбцов указывается в конструкторе объекта типа

GridLayout

следующим образом:

panel.setLayout(new GridLayout(4, 4 ) ) ;
Компоненты вводятся построчно : сначала в первую ячейку первого ряда, затем во
вторую ячейку первого ряда и так далее:

pa nel.add (new JButton("l") );
panel.add(new JButton("2"));
На практике лишь в немногих программах применяется такая жесткая компонов­
ка

GUI,

как на лицевой панели калькулятора, хотя небольшие сеточные компоновки

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

ряд. Очевидно, что в конструкторе диспетчера сеточной компоновки необ ходимо за­
дать один ряд, а число столбцов должно равняться количеству экранных кнопок.

java.awt.GridLayout 1.0



Gric!Layout (int ro'lfs, int columns)
Gric!Layout(int ro'lfs, int columns, int hgap, int vgap)
Создают новый объект типа GridLayout с заданным расстоянием между рядами
по горизонтали и по вертикали. Один из параметров ro'lfs и co.lumns, но не оба

и столбцами
сразу, может

принимать нулевое значение.

11.З. Ввод текста
Теперь рассмотрим компоненты поль:ювательского интерфейса, входящие в со­
став библиотеки

Swing.

Начнем с компонентов, дающих пользователю возможность

вводить и править текст. Для этой цели предусмотрены два компонента : текстовое
поле типа

JTextField

и текстовая область типа

JText Area.

ввести только одну текстовую строку, а в текстовой области
типа

JPa s s wo r d Fi e ld

В текстовом поле можно

-

несколько строк. Поле

принимает текстовую строку, не отображая ее содержимое.

Глава

11 •

Компоненты польэовательсноrо интерфейса в

Swing

Все три упомянутых выше класса для ввода текста расширяют класс

Component.

JText

Создать объект этого класса нельзя, поскольку он является абстрактным.

С другой стороны, как это часто бывает при программировании на
тре документации на прикладной интерфейс
ды, которые определены именно в классе

API

Java,

при просмо­

оказывается, что существуют мето­

JTextComponent

и лишь наследуются его

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

javax.swing.text.JТextComponent



String getText ()



void setText (String text)

1.2

Получают и устанавливают текст в данном текстовом компоненте.

boolean isEdi tаЫе ()


void

setEditaЫe(boolean

Ь)

Получают и устанавливают свойство editaЫe, определяющее, может ли пользователь редакти­
ровать содержимое данного текстового компонента.

11.3.1. Текстовые

поля

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

var panel = new JPanel();
var textField = new JTextField("Default input", 20);
panel.add(textField);
В приведенном выше фрагменте кода вводится текстовое поле, инициализиру­
емое текстовой строкой

"Defaul t

input"

(Ввод по умолчанию). Второй параметр

конструктора задает длину текстовой строки. В данном случае длина строки равна
символам. К сожалению, символы

-

20

не очень точная единица измерения. Их шири­

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

на один или два символа. Следует также учесть, что заданное количество символов
считается в

AWT,

а следовательно, и в

Swing,

лишь предпочтите.л1,ноu длиной стро­

ки. Решив уменьшить или увеличить текстовое поле, диспетчер компоновки изменит

длину строки. Длина строки, задаваемая параметром конструктора типа

JTextField,

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

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

задавать с определенным запасом. Если же во время выполнения возникает необхо­
димость изменить эти размеры, следует вызвать метод

'1
V

setColumns ().

СОВЕТ. После изменения размеров текстового nоля методом setColumns () следует вызвать ме­
тод revalidate () из контейнера, содержащего данный компонент, как показано ниже.

11.3. Ввод текста

textField.setColumns(lO);
panel.revalidate();
В методе

revalidate ()

заново рассчитываются размеры и взаимное расположение всех ком­

понентов в контейнере. Затем диспетчер компоновки перерисовывает контейнер, изменяя раз­
меры текстового поля.

Метод

revalidate ()

относится к классу

JComponent.

Его выполнение не приводит к немедлен­

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

тов. Если же требуется изменить размеры компонентов в контейнере типа
метод

validate (),

поскольку класс

JFrame

JFrame, следует вызвать
JComponent.

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

Обычно пользователь программы должен иметь возможносrь вводить текст или ре­

дактировать содержимое текстового поля. Нередко в начале работы программы тексто­
вые поля оказываются пустыми. Чтобы создать пустое текстовое поле, достаточно опу­
стить соответствующий параметр в конструкторе класса JТextField, как показано ниже.

var textField = new JTextField(20);
Содержимое текстового поля можно изменить в любой момент, вызвав метод

setText ()

из родительского класса

TextComponent

следующим образом:

textField.setText("Hello!");
Как упоминалось ранее, определить, какой именно текст содержится в текстовом

поле, можно с помощью метода getтext

(),

который возвращает текст, набранный

пользователем. Чтобы отбросить лишние пробелы в начале и в конце текста, следует
вызвать метод

trim ()

по значению, возвращаемому методом

getText (),

как показа­

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

setFont ().

метод

String text = textField.getText() .trim();
javax.swing.JТextField



JТextField

1.2

(int cols)

Создает пустое текстовое поле типа JТextField с заданным числом столбцов.



JТextField(String

text, int cols)

Создает текстовое поле указанных размеров с первоначальной символьной строкой и заданным
числом столбцов.



int getColumns ()



void setColumns (int cols)
Получают или устанавливают число столбцов для данного текстового поля.

javax.swing.JComponent 1.2


void revalidate ()



void setFont (Font .f)

Обусловливает перерасчет местоположения и размеров компонента.

Устанавливает шрифт для данного компонента.

Глава

11 •

Компоненты пользовательского интерфейса в Swiпg

java.awt.Component 1.0


void valida te ()
Обусловливает перерасчет местоположения и размеров компонента. Если компонент является
контейнером, местоположение и размеры содержащихся в нем компонентов должны быть также
пересчитаны заново.



Font getFont ()
Получает шрифт данного компонента.

11.3.2.

Метки и пометка компонентов

Метки являются компонентами, хранящими текст надписей. Они не имеют об­
рамления и других видимых элементов (например, границ), а также не реагируют
на ввод данных пользователем. Метки могут использоваться для обозначения компо­
нентов. Например, в отличие от экранных кнопок, текстовые компоненты не имеют
меток, которые позволили бы их различать. Чтобы пометить компонент, не имею­

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

1.
2.

Создать компонент типа

JLabel,

содержащий заданный текст.

Расположить его достаточно близко к компоненту, чтобы пользователь мог
ясно видеть, что данная метка относится именно к этому компоненту.

Конструктор класса

JLabel

позволяет задать текст или пиктограмму, а если требу­

ется, то и выровнять содержимое компонента. Для этой цели служат константы, объ­

явленные в интерфейсе

SwingConstants. В этом интерфейсе определено несколько
LEFT, RIGHT, CENTER, NORTH, EAST и т.п. Класс JLabel

полезных констант, в том числе

является одним из нескольких классов из библиотеки

Swing,

реализующих этот ин­

терфейс. В качестве примера ниже показаны два варианта задания метки, текст над­
писи в которой будет, например, выровнен по левому краю.

var label = new JLabel("User name: ", SwingConstants.RIGHT);
или

var label = new JLabel("User name: "
А с помощью методов

JLabel.RIGHT);

setText () и seticon () можно задать текст надписи и пик­

тограмму для метки во время выполнения.

СОВЕТ. В качестве надписей на экранных кнопках, метках и пунктах меню можно использовать как
обычный текст, так и текст, размеченный в формате
сей на экранных кнопках в формате

HTML

HTML.

Тем не менее указывать текст надпи­

не рекомендуется, поскольку он нарушает общий стиль

оформления пользовательского интерфейса. Впрочем, для меток такой текст может оказаться до­

вольно эффективным. Для этого текстовую строку надписи на метке достаточно расположить между
дескрипторами

. . . следующим образом:
label = new JLabel("Required entry:");
Но первый компонент с меткой, набранной текстом в формате

HTML,

отображается на экране с за­

паздыванием, поскольку для этого нужно загрузить довольно сложный код интерпретации и вос­

произведения содержимого, размеченного в формате

HTML.

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

11.Э. Ввод текста

javax.swing.JLaЬel




1.2

JLaЬel

(String text)

JLaЬel

(Icon icon)

JLaЬel

(String text, int align)

JLaЬel

(String text, Icon icon, int align)

Создают метку с текстом и nиктограммой. В качестве nараметра

align указывается

дующих констант, оnределяемых в интерфейсе SwingConstants: LEFT
ИЛИ

одна из сле­

!no умолчанию), CENTER

RIGHT.

String getText ()
void

setтext

(String text)

Получают или устанавливают текст данной метки.



Icon geticon ()



void seticon (Icon icon)
Получают или устанавливают nиктограмму данной метки.

11.З.З. Поля для ввода пароля
Поля для ввода пароля представляют собой особый вид текстовых полей. Симво­

лы пароля не отображаются на экране, чтобы скрыть его от посторонних наблюда­
телей. Вместо этого каждый символ в пароле заменяется эхо-си.мво.лом, обычно мар­
кером(•). В библиотеке

Swing

предусмотрен класс

JPasswordField,

реализующий

такое текстовое поле.

Поле для ввода пароля служит еще одним примером, наглядно демонстрирую­

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

javax.swing.JPasswordField 1.2


JPasswordField(String text, int columns)



void setEchoChar (char echo)

Создает новое nоле для ввода nароля.

Задает эхо-символ, который может зависеть от визуального стиля оформления nользовательского
интерфейса. Если задано нулевое значение, выбирается эхо-символ



no умолчанию.

char [] getpassword ()
Возвращает текст, содержащийся в nоле для ввода nароля. Для обесnечения большей безоnасно­

сти возвращаемый массив следует nерезаnисать nосле исnользования. Пароль возвращается как
массив символов, а не как объект тиnа

String.

Причина такого решения заключается в том, что

символьная строка может оставаться в виртуальной машине до тех

жена системой сборки "мусора".

nop,

nока она не будет уничто­

Глава

11 •

11.3.4. Текстовые

Компоненты пользовательского интерфейса в

Swing

области

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

JTextArea.

Внедрив этот компонент

в свою программу, разрабо~чик предоставляет пользователю возможность вводить
сколько угодно текста, разделяя его строки нажатием клавиши

стовая строка завершается символом

'\n ',

. Каждая тек­
Java. Пример

как это предусмотрено в

текстовой области в действии приведен на рис.

11.11.

Ustr ni\me; 11ro o se v~ 11
P41ssword: • ••• •• ••••

'--~~~~~-

IJser name

1roose .~tt

===~о=о=========

Password. Jabb~rv.·o ck

111

lnsert
Рис.

11.11. Текстовая

область вместе

с другими текстовыми компонентами

В конструкторе компонента типа

JTextArea

указывается количество строк и их

длина, как в следующем примере кода:

textArea = new JTextArea(8, 40); // 8 строк по 40
11 столбцов в каждой
Параметр

c o lumns,

задающий количество столбцов (а по существу, символов)

в строке, действует так же, как и для текстового поля; его значение рекомендуется

немного завысить. Пользователь не ограничен количеством вводимых строк и их дли­

ной. Если длина строки или число строк выйдет за пределы заданных параметров,

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

setColumns

(),а для изменения их количества

-

метод

setRows ().

Эrи параметры

задают лишь рекомендуемые размеры, а диспе~чер компоновки может самостоятель­

но увеличивать или уменьшать размеры текстовой области.

Если пользователь введет больше текста, чем умещается в текстовой области,
остальной текст просто отсекается. Эrого можно избежать, установив автоматический
перенос строки следующим образом:

textArea.setLineWrap(true);

//в длинных строках

//

вып олняется

перенос

11.3.

Ввод текста

Автоматический перенос строки проявляется лишь визуально. Текст, хранящийся
в документе, не изменяется

-

в него не вставляются символы

'\n'.

11.З.5. Панели прокрутки
В библиотеке

Swing

текстовая область не снабжается полосами прокрутки. Если

они требуются, текстовую область следует ввести на панели прокрутки, как показано
ниже.

textArea = new JTextArea(B, 40);
var scrollPane = new JScrollPane(textArea);
Теперь панель прокрутки управляет представлением текстовой области. Полосы
прокрутки появляются автоматически, когдатекст выходит за пределы отведенной

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

тывать события, связанные с прокруткой.
Это универсальный механизм, который пришден для любого компонента, а не
только для текстовых областей. Чтобы ввести полосы прокрутки в компонент, его до­
статочно разместить на панели прокрутки.

В программе из листинга

11.1

демонстрируются различные текстовые компонен­

ты. Эга программа отображает текстовое поле, поле для ввода пароля и текстовую
область с полосами прокрутки. Текстовое поле и поле для ввода пароля снабжено
метками. Чтобы ввести предложение в конце текста, следует щелкнуть на кнопке

lnsert

(Вставить).
НА ЗАМЕТКУ! Компонент типа JТextArea позволяет отображать только простой текст без форма­
тирования и выделения специальными шрифтами. Для отображения отформатированного текста

!например, в виде НТМL-разметки) можно воспользоваться классом

JEditorPane,

рассматриваемым во втором томе настоящего издания.

Листинг

1

11.1.

Исходный код из файла

text/TextComponentFrame. java

package text;

2

3
4

import java.awt.BorderLayout;
import java.awt.GridLayout;

5

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import
import
import
import
import
import
import
import
import

javax.swing.JButton;
javax.swing.JFrame;
javax.swing.JLabel;
javax.swing.JPanel;
javax.swing.JPasswordField;
javax.swing.JScrollPane;
javax.swing.JTextArea;
javax.swing.JTextField;
javax.swing.SwingConstants;

/**

*

Фрейм с

образцами текстовых

компонентов

*/
puЫic

{

class TextComponentFrame extends JFrame

подробнее

Глава

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

11 •

Компоненты пользовательского интерфейса в Swiпg

puЫic

static final int TEXTAREA ROWS = 8;
static final int TEXTAREA COLUMNS
20;

puЬlic

TextComponentFrame()

puЬlic

{
var textField = new JTextField();
var passwordField = new JPasswordField();
var northPanel = new JPanel();
northPanel.setLayout(new GridLayout(2, 2) );
northPanel.add(new JLabel("User name: ",
SwingConstants.RIGHT) );
northPanel.add(textField);
northPanel.add(new JLabel("Password: ",
SwingConstants.RIGHT) );
northPanel.add(passwordField);

37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

add(northPanel, BorderLayout.NORTH);
var textArea = new JTextArea(TEXTAREA_ROWS,
TEXTAREA_ COLUMNS) ;
var scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
//

ввести кнопку для заполнения области текстом

JPanel southPanel = new JPanel();
JButton insertButton = new JButton("Insert");
southPanel.add(insertButton);
insertButton.addActionListener(event ->
textArea.append("User name: "
+ textField.getText()
+ " Password: "
+ new String(passwordField.getPassword())
+ "\n"));

add(southPanel, BorderLayout.SOUTH);
pack () ;

javax.swing.JТextArea

1.2



JТextArea

()



JТextArea

(int rows, int cols)

JТextArea(String

text, int rows, int cols)

Создают новую текстовую область.



void setColumns (int cols)
Задает предпочтительное число столбцов, определяющее длину строк в текстовой области.

11.4.

Компоненты дnя выбора разных вариантов

j avax. swing. JТextArea 1. 2 (окончание)


void setRows (int rows)



void append (String



void setLineWrap (boolean wrap)

Задает nредnочтительное число строк в текстовой области.
newТext)

Добавляет заданный текст в конце содержимого текстовой области.

Включает и отключает режим автоматического nереноса строк.



void setWrapStyleWord(boolean word)
Если nараметр
ется



no

void

word nринимает логическое значение true, nеренос в длинных строках выnолня­

границам слов, а иначе границы слов во внимание не nринимаются.

setTaЬSize

(int

с)

Устанавливает nозиции табуляции через каждые с символов. Следует, однако. иметь в виду, что
символы табуляции не nреобразуются в nробелы и лишь выравнивают текст

no

следующей nози­

ции табуляции.

javax.swing.JScrollPane 1.2


JScrollPane (Component

с)

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

11.4.

Компоненты дпя выбора разных вариантов

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

бором из конечного числа вариантов. Эги варианты моrуг бьпь представлены экран­
ными кнопками или списком выбираемых элементов. (Как правило, такой подход
освобождает от необходимости отслеживать ошибки ввода.) В этом разделе описыва­

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

11.4.1.

Флажки

Если данные сводятся к двухзначной логике вроде положительного или отрица­
тельного ответа, то для

их ввода

можно воспользоваться таким компонентом, как

флажок. Чтобы установить флажок, достаточно щелкнуть кнопкой мыши на этом

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

-

щелкнуть на нем еще раз. Уста­

новить или сбросить флажок можно также с помощью клавиши пробела, нажав ее
в тот момент, когда на данном компоненте находится фокус ввода.

На рис.

11.12

показано простое окно прикладной программы с двумя флажками,

один из которых включает и отключает курсивное, а другой

-

полужирное начерта­

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

Глава

11 •

Компоненты пользовательского интерфейса в

Swing

пользователь щелкает на флажке, содержимое окна обновляется с учетом нового на­
чертания шрифта.

The qwck br0\1'YI

ох Jumps

over rhe

!а;:у

dog.

Bold ~ ltalic
Рис.

11.12. Флажки

Флажки сопровождаются метками, указывающими их назначение. Текст метки за­

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

bold

= new

JCheckBox("Bold");

Для установки и сброса флажка вызывается метод

setSelected () :

bold.setSelected(true);
Метод

isSelected ()

позволяет определить текущее состояние каждого флажка.

Если он возвращает логическое значение
а если логическое значение t

rue -

false,

это означает, что флажок сброшен,

флажок установлен.

Щелкая на флажке, пользователь инициирует определенные события. Как всегда,
с данным компонентом можно связать объект приемника событий . В рассматрива­

емом здесь примере программы для обоих флажков предусмотрен один и тот же
приемник действий:

ActionListener listener = . . .
bold.addActionListene r(listener);
italic.addActionListener(listener );
В приведенном ниже методе

act ion Performed () обработки событий запрашива­
bold и i tali c, а затем устанавливается начертание

ется текущее состояние флажков

шрифта, которым должен отображаться обычный текст: полужирный, курсив или
полужирнь~й курсив.
puЫic

void action Pe rformed(ActionEvent event)

{

int mode = О;
if (bold.isSelected()) mode += Font. BOLD;
if (italic.isSel ected()) mode += Font.ITALIC ;
label.setFont(new Font("Serif", mode , FONTSI ZE) );
В листинге

11.2 приведен

весь исходный код программы, демонстрирующей мани­

пулирование флажками при построении

GUI.

11.4.
Листинг

11.2.

Исходный код из файла checkBox/CheckBoxTest.

1
2
3
4
5
6

package checkBox;

7

/**

java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

*

8
9
10
11
12

Компоненты дnя выбора разных вариантов

Фрейм с меткой

образцового текста

и

* флажками для выбора шрифта

* attributes.
*/
puЫic class CheckBoxFrame extends JFrame

13 {
14
15
16

17
18

private
private
private
private

JLabel label;
JCheckBox bold;
JCheckBox italic;
static final int FONTSIZE

24;

19
20

puЫic

21

//

22
23
24
25
26

label = new JLabel("The quick brown fox"
+ "jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.BOLD, FONTSIZE));
add(label, BorderLayout.CENTER);

27
28
29
30
31

CheckBoxFrame()

{
ввести метку образцового текста

//В этом приемнике

событий устанавливается

//атрибут шрифта для воспроизведения метки

11

по состоянию флажка

32
33
34
35
36

ActionListener listener = event -> {
int mode = О;
if (bold.isSelected()) mode += Font.BOLD;
if (italic.isSelected()) mode += Font.ITALIC;
label.setFont(new Font("Serif", mode, FONTSIZE));

37

};

38

39
40
41
42
43
44
45
46

47
48
49
50
51
52
53
54
55

//ввести флажки

JPanel buttonPanel = new JPanel();
bold = new JCheckBox ( "Bold");
bold.addActionListener(listener);
bold.setSelected(true);
buttonPanel.add(bold);
italic = new JCheckBox("Italic");
italic.addActionListener(listener);
buttonPanel.add(italic);
add(buttonPanel, BorderLayout.SOUTH);
pack () ;

Глава

11 •

Компоненты пользовательского интерфейса в Swiпg

javax.swing.JCheckВox



1.2

JCheckВox(String

laЬel)

JCheckBox(String

laЬel,

Icon icon)

Создают флажок, который исходно сброшен.



JCheckBox(String

laЬel,

boolean state)

Создает флажок с указанной меткой и заданным исходным состоянием.




boolean isSelected ()
void setSelected(boolean state)
Получают или устанавливают новое состояние флажка.

Кнопки-переключатели

11.4.2.

В предыдущем примере программы пользователь мог установить оба флажка,

один из них или ни одного. Но зачастую требуется выбрать только один из пред­

лагаемых вариантов. Если пользователь установит другой флажок, то предыдущий
флажок будет сброшен. Такую группу флажков часто называют грутюi1 кнопок-11Lре­
к.\ючате.\еi1, поскольку они напоминают переключатели диапазонов на радиоприем­
никах

-

при нажатии одной из таких кнопок ранее нажатая кнопка возвращается

в исходное состояние. На рис.

11.13

приведен типичный пример окна прикладной

программы с группой кнопок-переключателей. Пользователь может выбрать размер

шрифта

(Малый),

- Small

Medium

(Средний),

Large

(Крупный) и

Extra large

(Очень

крупный). Разумеется, выбрать можно лишь один размер шрифта .

8

;Radi0Butto11Test

----------Г-~о ГХ

he qtlick bro vn fo~ 7
Sm~ll

Рис.
Библиотека

Swing

1tdlum

11.1 З. Группа

Lir9e

•••

• Ew~ lir9e

кнопок-переключателей

позволяет легко реализовать группы кнопок-переключателей.

Для этого нужно создать по одному объекту типа

Bu t t onGroup

на каждую группу.

Затем в группу кнопок-переключателей следует ввести объекты типа
Объект типа

But tonGroup

JRadi oBu t ton.

предназначен для тою, чтобы отключать выбранную ранее

кнопку-переключатель, если пользователь щелкнет на новой кнопке . Ниже показано,
каким образом все это воплощается непосредственно в коде.

var group

=

new ButtonGroup();

var smallButton = new JRadioButton ("Small", false );
group.add(smallButton);

11.4.

Компоненты дnя выбора разных вариантов

var mediumВutton = new JRadioButton("Medium", true);
group.add(mediumButton);
Второй параметр конструктора принимает логическое значение

true,

если из­

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

false, если она должна быть выключена. Следует, однако, иметь в виду, что
ButtonGroup управляет лишь повеоением кнопок-переключателей. Если

типа

объект
нужно

объединить несколько групп кнопок-переключателей, их следует разместить в кон­
тейнере, например, в объекте типа

JPanel.

Обратите внимание на то, что на рис.

11.12

и

11.13

кнопки-переключатели отлича­

ются по внешнему виду от флажков. Флажки изображаются в виде квадратов, причем
на установленных флажках указывается галочка, в то время как кнопки-переключате­
ли имеют круглую форму: включенные

-

с точкой внутри, а выключенные

-

пустые.

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

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

ActionListener listener = event -> label.setFont(
new Font("Serif", Font.PLAIN, size) );
Сравните этот приемник событий с приемником событий от флажка. Каждой
кнопке-переключателю соответствует свой объект приемника событий. И каждому
приемнику событий точно известно, что нужно делать

-

установить конкретный раз­

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

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

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

шрифта с конкретной кнопкой-переключателем.

if (smallButton.isSelected()) size = 8;
else if (mediumButton.isSelected()) size = 12;

НА ЗАМЕТКУ! В гpynne может быть выбрана только одна кноnка-nереключатель. Хорошо бы за­
ранее знать, какая именно, не nроверяя каждую кноnку-nереключатель в гpynne. Объект тиnа

ButtonGroup

уnравляет всеми кноnками-nереключателями, и nоэтому было бы удобно, если

бы он nредоставлял ссылку на выбранную кноnку-nереключатель. В самом деле, в классе

ButtonGroup

имеется метод

getSelection (),

но он не возвращает ссылку на выбранную

кноnку-nереключатель. Вместо этого он возвращает ссылку тиnа ButtonМodel на модель, свя­
занную с этой кноnкой-nереключателем. К сожалению, все методы из интерфейса ButtonМodel
не nредставляют собой ничего ценного в этом отношении.

Интерфейс ButtonModel наследует от интерфейса IternSelectaЫe метод
getSelectedObjects (), возвращающий совершенно бесnолезную nустую ссылку null. Метод
getActionCornrnand () выглядит предпочтительнее, поскольку он позволяет определить тексто­
вую строку с командой действия, а по существу, с текстовой меткой кнопки-переключателя. Но ко­

манда действия в модели этой кнопки-переключателя оказывается nустой

lnull). И только в том

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

Глава

Компоненты попьзовате11ьского интерфейса в

11 •

setActionCommand ().

Swing

в модели установятся значения, соответствующие каждой команде дей­

ствия. А в дальнейшем команду действия для включенной кнопки-переключателя можно будет
определить, сделав вызов ЬuttonGroup.

В листинrе

11.3

qetSelection () . qetActionCommand () .

представлен весь исходный код программы, в которой размер

шрифта устанавливается с помощью кнопок-переключателей.

Листинг 11.Э. Исходный код из файла

1

package radioButton;

2
3
4
5

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

radioButton/RadioButtonFrame. j ava

6
7

/**
8
* Фрейм с меткой образцового текста и
9
* кнопками-переключателями для выбора размера шрифта
10 */
11 puЫic class RadioButtonFrame extends JFrame
12 {
13
private JPanel buttonPanel;
14
private ButtonGroup group;
15
private JLabel label;
16
private static final int DEFAULT SIZE
36;
17
18
puЬlic RadioButtonFrame()
19
{
20
//ввести метку с образцовым текстом
21
22
label = new JLabel("The quick brown fox jumps
23
+ "over the lazy dog.");
24
label.setFont(new Font("Serif", Font.PLAIN,
25
DEFAULT_SIZE));
add(label, BorderLayout.CENTER);
26
27

28
29

//

30

buttonPanel = new JPanel();
group = new ButtonGroup();

31
32
33
34
35
36
37
38
39
40

ввести

кнопки-переключатели

addRadioButton("Small", 8);
addRadioButton("Medium", 12);
addRadioButton("Large", 18);
addRadioButton("Extra large", 36);
add(buttonPanel, BorderLayout.SOUTH);
pack();

41
42

/**

43
44
45
46
47

*

Вводит

кнопку-переключатель,

устанавливающую

* размер шрифта для выделения образцового текста
* @param name Строка надписи на кнопке
* @param size Размер шрифта, устанавливаемый

*

данной

кнопкой

11.4.

Компоненты дпн выбора разных вариантов

48

*/

49
50
51
52
53
54
55
56

puЫic

57
58
59

//

60
61

ActionListener listener = event -> label.setFont(
new Font("Serif", Font.PLAIN, size}};

void addRadioButton(String name, int size}

{
boolean selected = size == DEFAULT SIZE;
JRadioButton button =
new JRadioButton(name, selected};
group.add(button};
buttonPanel.add(button};
этот приемник событий устанавливает размер шрифта

//для образцового текста метки

62
63
64
65

button.addActionListener(listener};

javax.swing.JRadioButton 1.2


JRadioButton(String

laЬel,

Icon icon)

Создает кнопку-переключатель. которая исходно не выбрана.



JRadioButton(String

laЬel,

boolean state)

Создает кнопку-переключатель с заданной меткой и в указанном исходном состоянии.

javax.swing.ButtonGroup 1.2
void

add(AЬstractвutton

Ь)

Вводит кнопку-переключатель в группу.



ButtonМodel

getSelection()

Возвращает модель выбранной кнопки.

javax.swing.ButtonМodel

1.2

String getActionCommand ()
Возвращает команду для модели данной экранной кнопки.

javax.swing.AЬstractвutton



1.2

void setActionCommand(String s)
Задает команду для данной кнопки и ее модели.

Г.11ава

11.4.3.

11 •

Компоненты по.11ьзовательсноrо интерфейса в

Swing

Границы

Если в одном окне расположено несколько групп кнопок-переключателей, их

нужно каким-то образом различать. Для этого в библиотеке

Swing

предусмотрен на­

бор границ. Границу можно задать для каждого компонента, расширяющего класс

Обычно границей обрамляется панель, заполняемая элементами поль­

JComponent.

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

1.

Вызовите статический метод из класса

в одном из следующих стилей (рис.

BorderFact o ry,

со здающий границу

11.14):

Bo1dtr t\ pts
RilStd btYtl

" LOWtttd bt tl

li

Etchtd

- -- - --- - -

Рис.

2.

Llnt

- --

·-

Empty

• Mittt

..---

~

-

~-

--

11.14. Опробование различных видов границ



Lowered bevel (Утопленная фаска)



Raised bevel (Приподнятая фаска)



Etched

(Гравировка)

(Линия)



Line



Matte

(Кайма)



Empty

(Пустая

создается пустое пространство, окружающее компонент)

-

Если требуется, дополните границу заголовком, сделав вызов

Borde rFactory.

createTitledBorder().

3.

Если

требуется,

объедините

несколько

границ

в

одну,

сделав

вы зов

BorderFactory.createCompoundBorder ( ) .

4.

Добавьте полученную в итоге границу с помощью метода
класса

setBorder ()

из

JComponent.

В приведенном ниже фрагменте кода на панели вводится граница в стиле грави­
ровки с указанным заголовком.

Border etche d
Border tit l ed

BorderFactory .createEtchedBorder() ;
BorderFactory.createTitl edBorder(
etched, "А Title" ) ;
panel.setBorder(titled);
=

У различных границ имеются разные возможности для задания ширины и цве­
та . Подробнее об этом см. в документации на прикладной интерфейс

API. Истинные

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

SoftBeve l Border

и

LineBo r der.

11.4.

Компоненты дnя выбора разных вариантов

Такие границы можно создать только с помощью конструкторов этих классов, по­
скольку для них не предусмотрены соответствующие методы в классе

BorderFactory.

javax.swing.BorderFactory 1.2



static Border createLineBorder (Color color)
static Border createLineBorder (Color color, int thickness)
Создают nростую границу в стиле обычной линии.



static мatteBorder createмatteBorder(int top, int left,
int bottom, int right, Color color)
static мatteBorder createмatteBorder(int top, int left,
int bottom, int right, Icon tileicon)
Создают широкую границу, заnолняемую цветом или рисунком из nовторяющихся nиктограмм.

static Border createEmptyBorder ()
static Border createEmptyBorder(int top, int left,
int bottom, int right)
Создают nустую границу.





static
static
static
static

Border createEtchedВorder()
Border createEtchedВorder (Color highlight, Color shadow)
Border createEtchedВorder(int type)
Border createEtchedВorder (int type, Color highlight,Color shadow)

Создают nростую границу в стиле линии с трехмерным эффектом. В качестве nараметра type ука­

.RAISED или EtchedВorder .LOWERED.
Border createBevelBorder(int type)
Border createВevelBorder(int type, Color highlight, Color shadow)
Border createLoweredВevelBorder()
Border createRaisedВevelBorder()

зывается одна из констант EtchedВorder






static
static
static
static

Создают границу с эффектом утоnленной или nриnоднятой nоверхности. В качестве nараметра

type








указывается одна из констант

BevelBorder. RAISED

или

BevelBorder. LOWERED.

static TitledВorder createTitledВorder(String title)
static TitledВorder createTitledВorder(Border border)
static TitledВorder createTitledВorder(Border border,
String title)
static TitledВorder createTitledВorder(Border border,
String title, int justification, int position)
static TitledВorder createTitledВorder(Border border,
String title, int justification, int position, Font font)
static TitledВorder createTitledВorder(Border border,
String title, int justification, int position,
Font font, Color color)
Создают границу с заданными свойствами и снабженную заголовком. В качестве nараметра

jusTi tledВorder:
LEFT, CENТER, RIGHT, LEADING, TRAILING или DEFAULT_JUSTIFICATION !no левому краю),
а качестве nараметра position - одна из констант АВОVЕ ТОР, ТОР, BELOW ТОР, АВОVЕ
воттом. BOTTcx.f, BELOW_ВOTTOM или DEFAULT_POSITION !Вверху).
tification

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

Глава

11 •

Компоненты попьэоватепьскоrо интерфейса в

Swing

javax. swing. BorderFactory 1. 2 (окончание)
static

Compound.Вorder

createCompoundВorder(Border

outsideВorder,

Border

insideВorder)

Объединяет две границы в одну новую границу.

javax.swing.border.SoftвevelBorder



SoftвevelBorder



SoftвevelBorder(int

1.2

(int type)

type, Color highlight, Color shadow)

Создают скошенную границу со сглаженными углами. В качестве параметра
одна из следующих констант: SoftвevelBorder.

type

или SoftвevelBorder.

указывается

LOWERED.

1.2

javax.зwing.border.LineBorder



RAISED

puЬlic LineBorder (Color color, int thickness,
boolean roundedCorners)

Создает границу в стиле линии заданной толщины и цвета. Если параметр
нимает логическое значение

true,

roundedCorners

при­

граница имеет скругленные углы.

javax.swing.JComponent 1.2


void

setвorder(Border

border)

Задает границу для данного компонента.

11.4.4.

Комбинированные списки

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

11.15).

Если раскрывающийся список является реiJактируемым, то выбранный из него
элемент можно поправить так же, как и в обычном тесговом поле. Таким образом,

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

список называется комбинированным. Компоненты комбинированных списков созда­

ются средствами класса

JComboBox.

Начиная с версии

Java 7,

класс

ется обобщенным. Например, комбинированный список типа
состоит из строковых объектов типа
JComЬoBox

-

String,

JComboBox явля­
JComboBox

а комбинированный список типа

из целочисленных значений.

11.4.

8

Компоненты дnя выбора разных вариантов

[_ :о !Х

iComl}oBoxTest

he

qш~к ьrо '11

rox junips o"er tM laz

do~

,... ,
S nsStr1f
Monospictd
D1ilo9
Dlilogl11put
Рис.

11.15.

Раскрывающийся список

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

setEdi tаЫе ().Однако

изменения вносятся только в теку­

щий элеме1п списка. Перечень вариантов выбора все равно остается прежним.
Выбранный вариант в исходном или отредактированном виде можно получить
с 11омощью метода

getSelectedltem ( ) .

Но для комбинированного сниска этот эле­

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

6

второю тома настоящего издания . ) Если же сни­

и не допускает редактирования своих элеме1пов, то

для получения выбранного варианта требующегося типа лучше сделать следующий
вызов:

comЬo.getitemAt(comЬo.getSelectedindex())

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

1юс1ъ выбрать стиль шрифта из предварительно заданного списка
ками,

SunsSerif -

без засечек,

Mono spaced -

(Se r i f -

с засеч­

моноширинный и т.д.) . Кроме того,

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

addl tem ()

additem () .

В данной про1рамме метод

вызывается только в конструкторе, как показано ниже, но 11ри необходи­

мости к нему можно обратиться и:~ любой части программы.

var

faceComЬo =

new

JComЬoBox();

faceComЬo.additem("Serif");
faceComЬo.

addltem ( "SansSeri f") ;

Э1·от метод добавляет символьную строку в конце списка. Если же требуется
вставить символьную строку в любом другом месте списка, нужно вызвать метод

insertitemAdd ()

следующим образом:

f асеСоmЬо. insert I temAt ( "Monospaced", О) ;
11 ввести элемент в начале списка
В список можно вводить элементы любою типа, а мя их отображения вы­
зывается метод

toString ().

Если во время выполнения во:311икает потребнос1ъ

Глава

11 •

Компоненты пользовательского интерфейса в

Swing

удалить элемент из списка, для этой цели вызывается метод

removeitemAt

removeitem ()

или

(),в зависимости от тоrо, что указать: сам удаляемый элемент или его

месrоположение в списке, как показано ниже. А для удаления сразу всех элементов
из списка предусмотрен метод
faceComЬo.

removeAllitems ().

removeitem ( "Monospaced");

faceComЬo.removeiternAt(O);

//удалить первый элемент

11

иэ списка

СОВЕТ. Если в комбинированный список требуется включить большое количество объектов, приме­
нять для этой цели метод

additem О

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

мы. Вместо этого лучше сконструировать объект типа DefaultComЬoBoxМodel, заполнить его
элементами составляемого списка, вызывая метод
setмodel

()

addElement О,

а затем обратиться к методу

из класса JComЬoBox.

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

следует вызвать метод

getSource ()

с данным событием в качестве параметра. Этот

метод возвращает ссылку на список, являющийся источником собьrrия. Затем следу­
ет вызвать метод

getSelecteditem (),

возвращающий вариант, выбранный из списка.

Значение, возвращаемое этим методом, необходимо привести к соответствующему
типу (как правило, к типу
честве параметра методу

String). Но если возвращаемое значение передается в ка­
getitemAt (),то приведение типов не требуется, как выде­

лено ниже полужирным.

ActionListener listener = event -> label.setFont(new Font(
faceComЬo.qetitemAt(faceComЬo.qetSelectedindex()),

ont.PLAIN,
DEFAULT_SIZE) );
Весь исходный код нроrраммы, демонстрирующей применение комбинированно­

го списка в пользовательском интерфейсе, приведен в листинге
Листинг

1

11.,, Исходный

package

код из файла comЬoBox/ComЬoBoxFrame.

соmЬоВох;

2

3
4
5

import java.awt.BorderLayout;
import java.awt.Font;

6
7

import javax.swing.JComЬoBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

8
9

10
11 /**
12 * Фрейм с образцовым текстом метки и комбинированным
13 * списком для выбора начертаний шрифта
14 */
15 puЫic class ComЬoBoxFrame extends JFrame
16 {
17
private JComЬoBox faceComЬo;
18
private JLabel label;

11.4.

java

11.4.
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

Компоненты для выбора разных вариантов

private static final int DEFAULT SIZE = 24;
puЫic ComЬoBoxFrame()

{
//

ввести метку с

образцовым текстом

label = new JLabel("The quick brown fox jumps
+ "over the lazy dog.");
label.setFont(new Font("Serif", Font.PLAIN,
DEFAULT_SIZE) );
add(label, BorderLayout.CENTER);
//
//

составить

комбинированный

список и ввести

в него названия начертаний шрифта

= new

faceComЬo

JComЬoBox(J;

faceComЬo.additem(

"Serif");

faceComЬo.additem("SansSerif");
faceComЬo.

additem( "Monospaced");

faceComЬo.additem("Dialog");
faceComЬo.additem("Dialoginput");

40

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

//приемник событий от
//изменяет на

//

комбинированного списка

выбранное

которым набран

начертание шрифта,

текст метки

-> label.setFont(
(new Font ( faceComЬo. getitemAt (

faceComЬo.addActionListener(event

faceComЬo.getSelectedindex()),

Font.PLAIN, DEFAULT_SIZE)) );
//
//

ввести комбинированный

список на

панели

у южной границы фрейма

JPanel

comЬoPanel

= new JPanel();

comЬoPanel.add(faceComЬo);

add(comЬoPanel,

BorderLayout.SOUTH);

pack ();

javax.swing.JComЬoВox



boolean



void

1.2

isEditaЬle()

setEditaЬle(boolean

Ы

Получают или устанавливают свойство

edi tаЫе

данного комбинированного списка.

void additem(Object item)
Вводит новый элемент в список.



void insertitemAt(Object item, int index)
Вводит заданный элемент в сnисок

no указанному

индексу.

Глава

11 •

Компоненты nользовательскоrо интерфейса в

javax. swing. JComЬoBox 1. 2

Swing

(окончание)



void removeitem(Object item)



void removeitemAt(int index)



void removeAllitems ()

Удаляет заданный элемент из списка.

Удаляет из списка заданный элемент по указанному индексу.

Удаляет из списка все элементы.



Object getSelecteditem()
Возвращает выбранный элемент списка.

11.4.5.

Регулируемые ползунки

Комбинированные списки дают пользователю возможность делать выбор из дис­
кретного ряда вариантов. А ре~улируемые ползунки позволяют выбрать конкретное
значение в заданных пределах, например, любое число от

1 до 100.

Чаще всего ре~у­

лируемые ползунки создаются следующим образом:

var slider = new JSlider(min, max, initialValue);
Если опустить минимальное, максимальное и начальное значения, то по умолча­
нию выбираются значения О,

100

и

50

соответственно. А если ре~улируемый ползу­

нок должен располагаться вертикально, то для этой цели служит следующий кон­
структор:

var slider = new JSlider(SwingConstants.VERTICAL, min,
max, initialValue);
Каждый такой конструктор создает простой ползунок. В качестве примера можно
привести самый верхний ползунок в окне, показанном на рис.

11.16. Далее

будут рас­

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

ChangeEvent.

Чтобы получать уведомления об изменении выбираемого значения при перемеще­
нии ползунка, следует сначала создать объект класса, реализующего функциональ­

ный интерфейс

ChangeListener,

а затем вызвать метод

addChangeListener ().

При

обратном вызове извлекается значение, на котором установлен ползунок:

ChangeListener listener = event -> {
JSlider slider = (JSlider) event.getSource();
int value = slider.getValue();
);

Ре~улируемый ползунок можно дополнить отметками, как на шкале. Так, в про­
грамме, рассматриваемой здесь в качестве примера, для второго ползунка задаются
следующие установки:

slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);

11.4.

Компоненты дnя выбора разных вариантов

Тit\..s

SПiP

Q

to tlO.S

о tг.i.ck

lnverttd

libtls
о

20

40

60

в

с

D

80 100
Custom libtls

А

Е

F

Q
lcon libtls

50
Рис.

11.16. Ре1улируемые

ползунки

Ре1улируемый ползунок снабжается ос1юв11ыми отметками, следующими через

20 единиц измерения, а также всrюмо1·ательными, следующими через каждые
5 единиц измерения. Сами единицы измерения привязываются к :шачениям , на кото­
каждые

рых устанавливается ползунок, и не имеют никакою отношения к пикселям на экране .

В 11риведе11ном выше фрагменте кода лишь усганавливаются отметки регулируемо1·0
пол:1унка. А для тою •побы вывести их на экран, нужно сделать следующий вызов:

s lide r . setPaintTic ks(true);
Основные и вспомогательные отметки дейс1вуют независимо. Можно, например,
установить основные отметки через каждые
11ые

-

через каждые

7

20

единиц измерения, а вспом01·атель-

единиц измерения, но в ито1·е шкала ре1улируемого ползунка

получится беспорядо•шой.
Ре1улируемый ползунок можно принудителыю привязаrт, к отметкам . Всякий
раз, когда пользователь завершает перемещение ползунка в режиме привязки к от­

меткам, 11олзунок сразу же уста~1авливается на ближайшей отметке. Такой режим
:~адается с помощью следующего вы :юва:

s lider. s e t SnapToT icks (t r ue );



ВНИМАНИЕ! В режиме привязки к отметкам регулируемый ползунок ведет себя не совсем пред­
сказуемым образом. До тех пор, пока ползунок не установится точно на отметке, приемник изме­
нений получает значения. не соответсrвующие отметкам . Так . е сли щелкнуть кнопкой мыши ря­

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

Глава

11 •

Компоненты пользовательского интерфейса в Swiпg

Сделав следующий вызов, можно обозначить основные отметки ре1улируемого
ползунка:

slider.setPaintLabels(true);
Так, если регулируемый ползунок перемещается в пределах от О до
а промежуток между основными отметками составляет

ползунка будут обозначены цифрами О,

20, 40, 60, 80

и

20
100.

100

единиц,

единиц, отметки такого

Кроме цифр, отметки можно, например, обозначить символьными строками или
пиктограммами (см. рис.

11.16),

хотя сделать это не так-то просто. Сначала нужно

заполнить хеш-таблицу с ключами типа

Integer

и значениями типа

Component,

а за­

тем вызвать метод setLabelTaЬle ().Соответствующие компоненты располагаются

под обозначаемыми отметками ползунка. Для этой цели обычно служат объекты
типа

JLabel.

вами А, В, С,

var

Ниже показано, как обозначить отметки регулируемого ползунка бук­
Е и

D,

labelTaЬle

F.

= new

HashtaЫe();

new JLabel("A"));
labelTaЬle.put(20, new JLabel("B") );
labelTaЬle.put(O,

labelTaЬle.put(lOO,

new JLabel("F"));

slider.setLabelTaЬle(labelTaЬle);

В листинге

11.5

приведен пример программы, демонстрирующий построение ре­

гулируемого ползунка с отметками, обозначаемыми пиктограммами.

m

СОВЕТ. Если отметки и их обозначения не выводятся на экран, проверьте, вызываются ли методы
setPaintTicks (true) и setpaintLaЬels (true).

'1;1

У четвертого регулируемого ползунка на рис.

11.16

отсутствует полоса перемеще­

ния. Подавить отображение полосы, по которой передвигается ползунок, можно, сде­
лав следующий вызов:

slider.setPaintTrack(false);
Для пятого регулируемого ползунка на этом же рисунке направление движения
изменено с помощью метода

slider.setinverted(true);
Регулируемые ползунки, создаваемые в рассматриваемом здесь примере програм­

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

Листинг

11.5.

Исходный код из файла

1
2

package slider;

З

import
import
import
import

4
5
б

slider/SliderFrame. java

java.awt.*;
java.util.*;
javax.swing.*;
javax.swing.event.*;

7

8
9

/**

*

Фрейм с

несколькими

ползунками и текстовым полем

11.4.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

*

для показа

*

устанавливаются ползунки

значений,

на

которых

Компоненть1 дпя выбора разных вариантов
по очереди

*/
puЫic

class SliderFrame extends JFrame

{
private JPanel sliderPanel;
private JTextField textField;
private ChangeListener listener;
puЫic

SliderFrame()

{
sliderPanel = new JPanel();
sliderPaпel.setLayout(new GridBagLayout());
//общий приемник событий для всех ползунков

listener = event -> (
// обновить текстовое

поле,

если

//выбранный ползунок установится

// на отметке с другим значением
JSlider source = (JSlider) event.getSource();
textField.setText("" + source.getValue());
};
//ввести простой ползунок

var slider = new JSlider();
addSlider(slider, "Plain");
//ввести ползунок с

основными и

//неосновными отметками

slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider ( slider, "Ticks") ;
//ввести ползунок,

привязываемый к отметкам

slider = new JSlider();
slider.setPaintTicks(true);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Snap to ticks");
//ввести ползунок без отметок

slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
slider.setPaintTrack(false);
addSlider(slider, "No track");
//

ввести обращенный ползунок

Глава

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

11 •

Компоненты попьэоватепьского интерфейса в

Swing

slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
slider.setlnverted(true);
addSlider ( slider, "Inverted");
//ввести ползунок с числовыми

обозначениями отметок

slider = new JSlider();
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Labels");
//

ввести ползунок с

буквенными

//обозначениями отметок

slider = new JSlider();
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
var

labelTaЬle

new

=

Component>();
new JLabel("A"));
labelTaЫe.put(20, new JLabel("B"));
labelTaЬle.put(40, new JLabel("C"));
labelTaЫe.put(60, new JLabel("D"));
labelTaЫe.put(80, new JLabel("E"));
labelTaЫe.put(lOO, new JLabel("F") );
HashtaЫe setVisiЬle(false));
panel.add(ok);
add(panel, BorderLayout.SOUTH);
setSize (250, 150);

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

ского интерфейса, в данном случае метки и кнопку. Кроме того, он вводит обработ­
чик событий от кнопки и задает размеры окна. Чтобы отобразить диалоговое окно,
необходимо создать новый объект типа

JDialog

и вызвать метод setVisiЫe

()

сле­

дующим образом:

var dialog = new

AЬoutDialog(this);

dialog.setVisiЬle(true);

На самом деле в программе, рассматриваемой здесь в качестве примера, диалою­
вое окно создается только один раз, а затем оно используется повторно всякий раз,

когда пользователь щелкает на кнопке

About:

11. 7.

Диалоговые окна

if (dialog == null) // в первый раз
dialog = new AЬoutDialog(this);
dialog.setVisiЬle(true);

Коrда пользователь щелкает на кнопке ОК, диалоrовое окно должно закрывать­

ся. Такая реакция на действия пользователя определяется в обработчике событий
от кнопки ОК, как следует из приведенной ниже строки кода.

ok.add.ActionListener(event ->

setVisiЬle(false)

);

Коrда же пользователь закрывает диалоrовое окно, щелкая на кнопке
исчезает из виду. Как и в классе
щью метода

JFrame, такое
setDefaul tCloseOperation ().

В лисr·инге

11.11

Close,

оно

поведение можно изменить с помо­

приведен исходный код класса фрейма для примера проrраммы,

rде демонстрируется применение модальноrо диалоговою окна, создаваемою само­

стоятельно. А в листинге

11.12

представлен исходный код класса для создания этого

диалоrовоrо окна.

Листинг

1
2
3
4
5
6

11.11.

Исходный код из файла

dialog/DialogFrame. java

package dialog;
import javax.swing.JFrame;
import javax.swing.JМenu;
import javax.swing.JМenuBar;
import javax.swing.JМenuitem;

7

8 /**
9
* Фрейм со строкой меню, при выборе команды FileQAЬout
10 * из которого появляется диалоговое окно AЬout
11 */
12 puЫic class DialogFrame extends JFrame
13 {
14
private static final int DEFAULT WIDTH = 300;
15
private static final int DEFAULT HEIGHT = 200;
16
private AЬoutDialog dialog;
17
18
puЫic DialogFrame()
19

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

{

setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
//

сконструировать

var = new

меню

File

JМenuBar();

setJМenuBar(menuBar);

var fileMenu = new JМenu("File");
menuBar.add(fileMenu);
//

ввести в меню пункты AЬout и

//При выборе

//

пункта меню AЬout

Exit
открывается

одноименное диалоговое окно

var aboutitem = new JМenuitem("AЬout");
aboutitem.add.ActionListener(event -> {

Глава

36
37
38
39

11 •

Компоненты пользовательского интерфейса в Swiпg

if (dialog
dialog

==
=

null) //в первый раз
new AЬoutDialog(DialogFrame.this);

dialog.setVisiЬle(true);

//показать

диалоговое окно



}) ;

41
42
43
44
45
46
47
48
49
50

fileMenu.add(aboutitem);
//При выборе пункта меню

//

происходит

var exititem = new JМenuitem("Exit");
exititem.addActionListener(event -> System.exit(O)};
fileMenu.add(exititem);

Листинг

1

Exit

выход из программы

11.12.

Исходный код из файла dialog/AЬoutDialog.

java

package dialog;

2

3

import java.awt.BorderLayout;

4

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

import
import
import
import
import

javax.swing.JButton;
javax.swing.JDialog;
javax.swing.JFrame;
javax.swing.JLabel;
javax.swing.JPanel;

/**
* Образец

*

модального

диалогового

выводится сообщение

окна,

и ожидается до

в

котором

тех пор,

пока

* пользователь не щелкнет на кнопке ОК
*/
puЫic class AЬoutDialog extends JDialog
(
puЬlic AboutDialog(JFrame owner)
(

super (owner,

"AЬout

DialogTest", true);

//ввести НТМL-метку по центру окна

add (
new JLabel("Core Java
By Сау Horstmann"),
BorderLayout .CENTER);
//При выборе

кнопки ОК диалоговое

var ok = new JButton("OK");
ok.addActionListener(event ->
//

окно закрывается

setVisiЬle(false));

ввести кнопку ОК в нижней части окна

//у южной его

границы

var panel = new JPanel();

11.7. Диалоговые
38
39
40
41
42
43

окна

panel . add(ok ) ;
add(panel, BorderLayout.SOUTH I ;
pack();

javax.swing.JDialog 1.2
puЬlic

JDialoq(Frame parent, String title , boolean modal)

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

11.7.З. Обмен данными
Чаще всего диалоговые окна создаются мя того, чтобы получить информацию
от пользователя. Выше было показано, насколько просто создаются объекты типа

JDialog:

достаточно указать исходные данные и вызвать метод set Vis i Ы e

(true},

чтобы вывести окно на экран. А теперь покажем, как вводить данные в диалоговом

окне. Обратите внимание на диалоговое окно, 11оказа111юе на рис .

11.32.

Е1·0 можно

использовать мя получения имени 1юл1,:ювателя и пароля при подключении к опе­

ративно дос1упной службе.

DataExchangeTest

_ох

Alt

str n getWidt h())
i con = new Irnagei con(ic on.get i rnage() .ge tSc aled instance (

11.7. Диалоговые

окна

getWidth(), -1, Image.SCALE_DEFAULT));
seticon(icon);
repaint();

Остается лишьпреодолеть еще одно затруднение. Предварительно просматри­
ваемое изображение требуется обновлять всякий раз, когда пользователь выбира­

ет новый файл. С этой целью в диалоговом окне для выбора файлов применяется
механизм компонентов

JavaBeans,

уведомляющий заинтересованные приемники

событий об изменениях свойств данного окна. Выбранный файл

-

это свойство,

которое можно отслеживать с помощью установленного приемника событий типа

PropertyChangeListener. В приведенном ниже фрагменте кода показано, каким об­
разом организуется перехват уведомлений, направляемых приемнику событий.

chooser.addPropertyChangeListener(event ->
if (event.getPropertyName() ==
JFileChooser.SELECTED FILE CHANGED PROPERTY)
var newFile = (File) event.getNewValue();
11 обновить вспомогательный компонент

}) ;

javax.swing.JFileChooser 1.2
JFileChooser ()
Создает диалоговое окно для выбора файлов, которое можно использовать во многих фреймах.

void setCurrentDirectory (File dir)
Задает исходный каталог, содержимое которого отображается в диалоговом окне для выбора файлов.




void setSelectedFile (File :file)
void setSelectedFiles (File [] :file)
Задают файл, выбираемый в диалоговом окне по умолчанию.



void

setмultiSelectionEnaЫed(boolean

Ь)

Устанавливает или отменяет режим выбора нескольких файлов.



void



mode может принимать следующие значения: JFileChooser. FILES_ ONLY,
JFileChooser. DIRECTORIES ONLY и JFileChooser. FILES AND DIRECTORIES.
int showOpenDialog(Component parent)
int showSaveDialog(Component parent)
int showDialog(Component parent, String approveВuttonText)

setFileSelectionМode(int

mode)

Позволяет выбирать только файлы !по умолчанию!, только каталоги или же каталоги вместе с фай­
лами. Параметр

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

Save

или произвольной меткой, указанной в символьной строке approveВuttonтext. Возвращают

следующие значения:

APPROVE OPTION, CANCEL OPTION !если пользователь щелкнул
ке Cancel или закрыл диалогов;;-е окно! или ERROR_OPTION !если возникла ошибка!.

на кноп­

Глава

11 •

Компоненты пользовательского интерфейса в

Swing

j avax. swing. JFileChooser 1 . 2 (окончание)


File getSelectedFile ()
File[] getSelectedFiles()
Возвращают файл или несколько файлов, выбранных пользователем, а если он ничего не вы­

- пустое значение null.
void setFileFil ter (FileFil ter f i l ter)
брал



Устанавливает маску файлов в диалоговом окне для выбора файлов. В этом окне отображаются
только те файлы, для которых метод

fil ter. accept ()

возвращает логическое значение

true.

Кроме того, вводит фильтр в список выбираемых фильтров.



void



void setAcceptAllFileFilterUsed(boolean

addChoosaЫeFileFil

ter (FileFil ter f i l ter)

Вводит фильтр в список выбираемых фильтров.
Ы

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



void

resetChoosaЫeFileFilters()

Очищает список фильтров, где остается фильтр всех файлов, если только он не удален из списка
специально.



void setFileView (FileView

view)

Устанавливает представление файлов для предоставления сведений о файлах, отображаемых
в диалоговом окне для выбора файлов.



void setAccessory(JComponent component)
Устанавливает вспомогательный компонент.

javax.swing.filechooser.FileFilter 1.2
boolean accept (File

f)

Возвращает логическое значение

true,

если указанный файл должен отображаться в диалоговом

окне.

String getDescription ()
Возвращает описание указанного фильтра, например

!Файлы изображений с расширением

"Image files

*. gif и *. jpegl.

( *. gif,

*. jpeg) "

javax.swing.filechooser.FileNameExtensionFilter 6


FileNameExtensionFil ter (String

description,

String . . . extensions)

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

11. 7.

Диалоговые окна

javax.swing.filechooser.FileView 1.2


String getName (File .f)
Возвращает имя указанного файла :f или пустое значение
вызова метода



null.

Обычно возвращается результат

f. getName () .

String getDescription (File .f)
Возвращает удобочитаемое описание указанного файла :f или пустое значение

null.

Так, если

указанный файл :f представляет собой НТМL-документ, этот метод может возвратить его заголовок.



String

getТypeDescription

(File .f)

Возвращает удобочитаемое описание типа указанного файла

:t или

пустое значение

null. Так,

если указанный файл :f представляет собой НТМL-документ, этот метод может возвратить сим­
вольную строку



"Hypertext document".

Icon geticon (File .f)
Возвращает пиктограмму, назначенную для указанного файла :f, или пустое значение

null. Так,

если указанный файл :f относится к формату JPEG, этот метод может возвратить пиктограмму с ми­
ниатюрным видом его содержимого.



Boolean

isTraversaЫe

(File .f)

Если пользователь может открыть указанный каталог, возвращается значение

Boolean. TRUE.

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

Boolean. FALSE. Подобно
значение null, отмечая тот

методам из класса

FileView,

этот метод может возвращать пустое

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

зовано представление файлов, устанавливаемое

no

умолчанию.

На этом обсуждение особенносrей программирования
А более развитые компоненты

Swing и

GUI

на

Java

завершается.

усовершенсrвованные методики работы с 1ра­

фикой обсуждаются во втором томе насrоящего издания.

ГЛАВА

Параллелизм
В этой главе ...











Назначение потоков исполнения
Состояния потоков исполнения

Свойства потоков исполнения
Синхронизация

Потокобезопасные коллекции
Задачи и пулы потоков исполнения
Асинхронные вычисления

Процессы

Вам, вероятно, хорошо известна много.задачность используемой вами операци­
онной системы

-

возможность одновременно выполнять несколько про1·рамм. На­

пример, вы можете печатать во время редактирования документа или приема элек­

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

ограничивается количеством процессоров. Онерационная система выделяет время
процессора квантами для

каждого процесса, со:~давая впечатление параллельного

выполнения программ.

Многопоточные программы расширяют принцип многозадачности, перенося его

на один уровень ниже, чтобы отдельные приложения мо1·ли вьшолнять многие за­

дачи одновременно. Каждая задача обычно называется 11отоком ис11олн ения, или по­
током управления. Программы, способные одновременно выполнять больше 011,но1·0
потока исполнения, называются много11оточными.

Так в чем же отличие во мно1·их процессах и потоках исполнения? Оно состоит
в следующем: если у каждого процесса имеется собственный набор переменных, то
потоки исполнения могут разделять одни и те же общие 11,анные . Это кажется не­
сколько рискованным, и на самом деле так оно и есть, как станет ясно далее в этой

главе. Но разделяемые переменные обеспечивают более высокую эффективность

Глава

12 •

Параллелмэм

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

-

они требуют меньших издержек на свое создание и уничто­

жение по сравнению с процессами.

Многопоточная обработка имеет исключительную практическую ценность. На­

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

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

Java.

Однако многопоточная обработка может оказаться очень сложным делом. В этой

главе будут рассмотрены все инструментальные средства, которые понадобятся при­
кладным программистам. Но для более сложного программирования на системном
уровне рекомендуется обратиться к таким основательным первоисточникам, как, на­
пример, книга Брайана Гоетца (Brian Goetz) ]ava Concurrency in Practice
Addison-Wesley Professional, 2006 г.).

(издательство

Назначение потоков испопнения

12.1.

Начнем с рассмотрения примера программы, в которой применяются два потока

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

transfer () -

Bank

для хранения остатков на банковских

для перевода денежных сумм с одного счета на дру­

гой. Конкретная реализация данной программы приведена в листинге
потоке исполнения денежные средства переводятся со счета О на счет
потоке

-

со счета

2 на

счет

12.2. В
1, а во

первом
втором

3.

Ниже приведена простая процедура исполнения конкретной задачи в отдельном
потоке.

1.

Введите код выполняемой задачи в тело метода

run ()

из класса, реализующе­

го интерфейс RunnaЫe. Этот интерфейс очень прост и содержит следующий
единственный метод:
puЫic

interface

RunnaЫe

{

void run();

2.

Интерфейс RunnaЫe является функциональным, поэтому его экземпляр мож­
но создать с помощью лямбда-выражения следующим образом:
RunnaЫe

3.

r = () -> {

код задачи};

Сконструируйте объект типа

Thread

из объекта

r

типа RunnaЬle, как показано

ниже.

var = new Thread(r);

4.

Запустите поток на исполнение следующим образом:

t.start();

12.1.

Назначение потоков исполнения

Чтобы перевести денежные средства в отдельном потоке исполнения, достаточно
ввести соответствующий код в тело метода

run ( )

и запустить данный поток на ис­

полнение:

RunnaЫe

r = () -> {

try
{

for (int i =

О;

i < STEPS; i++)

{

(int) (bank.size() * Math.random());
int toAccount
МАХ_АМОUNТ * Math.random();
amount
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep( (int) (DELAY * Math.random()));
douЫe

catch (InterruptedException

е)

{
)
};

var t = new Thread(r);
t.start();
В течение заданного количества шагов цикла в данном потоке исполнения перево­
дится произвольная денежная сумма, после чего он переходит в состояние ожидания

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

InterruptedException,

sleep ()

то оно должно

быть перехвачено. Более подробно о перехвате исключений в потоках исполнения

речь пойдет далее в разделе

12.3.1.

Как правило, прерывание потока исполнения слу­

жит для отправки запроса на завершение данного потока. И когда возникает исклю­
чение типа

InterruptedException,

метод

run ()

завершается.

Данная программа запускает второй поток на исполнение, а также переводит де­

нежные средства со счета

2 на

счет

3.

Выполнение данной программы приводит к ре­

зультату, аналогичному следующему:

Thread[Thread-1,5,main] 606.77 from 2 to 3
Total Balance: 400000.00
Thread[Thread-0,5,main] 98.99 from О to 1
Total Balance: 400000.00
Thread[Thread-1,5,main] 476.78 from 2 to 3
Total Balance: 400000.00
Thread[Thread-0,5,main] 653.64 from о to
Total Balance: 400000.00
Thread[Thread-1,5,main] 807.14 from 2 to 3
Total Balance: 400000.00
Thread[Thread-0,5,main] 481.49 from о to 1
Total Balance: 400000.00
Thread[Thread-0,5,main] 203.73 from о to 1
Total Balance: 400000.00
Thread[Thread-1,5,main] 111.76 from 2 to 3
Total Balance: 400000.00
Thread[Thread-1,5,main] 794.88 from 2 to 3
Total Balance: 400000.00

Глава

12 •

Параллелизм

Как видите, результаты исполнения двух потоков перемежаются. Это ясно ука­
зывает на то, что они исполняются параллельно. В действительности эти результаты
становятся несколько запуганными, когда две выводимые строки перемежаются.

Вот, собственно, и все! Данный пример ясно показывает, каким образом организу­
ется параллельное выполнение задач. В остальной части этой главы поясняется, как

управлять взаимодействием потоков исполнения. Исходный код рассмотренной здесь
программы полностью приведен в листингах

12.1

и

12.2.

НА ЗАМЕТКУ! Потоки исполнения можно также определить, создавая подклассы, производные
от класса Тhread, как показано ниже.

class MyThread extends Thread
{
puЫic

void run()

(
код

задачи

Далее конструируется объект этого подкласса и вызывается его метод

start ().

Но такой подход

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



ВНИМАНИЕ! Не вызывайте метод

12.6.2.

run ()

из класса

Thread

исполнения не будет запущен. Вместо этого вызывайте метод Тhread.
новый поток, где будет выполнен метод run ().

Листинг

12.1.

Исходный код из файла threads/ThreadTest. java

package threads;
2

3
4
5
6
7

puЫic

8

{

9
10
11
12
13
14
15
16
17
18
19

20
21

или объекта типа RunnaЫe. При

прямом вызове этого метода конкретная задача будет выполнена в том же потоке, а новый поток

/**

* @version 1.30 2004-08-01
@author

*

Сау

Horstmann

*/

class ThreadTest

puЫic

static final int DELAY = 10;
static final int STEPS = 100;
static final douЫe МАХ AМOUNT

puЫic

static void main(String[] args)

puЬlic
puЫic

(
var bank = new Bank(4, 100000);
RunnaЫe taskl = () ->
try
(

for (int i
{

О;

i < STEPS; i++)

1000;

start (),

который создаст

12.1.

Назначение потоков мсnопненмя

amount =МАХ AМOUNT * Math.random();
bank.transfer(O, 1, amount);
Thread.sleep( (int) (DELAY * Math.random()));
douЫe

22
23
24
25
26
27

catch (InterruptedException

е)

{
)

28
29
30

);

31
32
33
34

RunnaЫe

() ->

task2

try
{

35

for (int i =

36
37
38
39
40
41

О;

i < STEPS; i++)

МАХ_АМОUNТ * Math.random();
douЫe amount
bank.transfer(2, 3, amount);
Thread.sleep((int) (DELAY * Math.random()));

42
43
44

catch (InterruptedException
{

е)

)

45

46
47

);

48
49

new Thread(taskl) .start();
new Thread(task2) .start();

50

51

12.2.

Листинг

Исходный код из файла

threads/Bank. java

package threads;
2
3
4

import java.util.*;

5

puЬlic

6

{

class Bank

7

private final

8
9

/**

10
11
12
13

Конструирует

*

объект

accounts;

банка

* @param n Количество счетов
* @param initialBalance Первоначальный

*

на

каждом

остаток

счете

*/

14

15

puЫic

16

{

17
18
19
20
21
22

douЬle[]

Bank(int n,

douЫe

initialBalance)

accounts = new douЬle[n];
Arrays.fill(accounts, initialBalance);

/**

*

Переводит деньги

с одного счета на другой

Глава

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

Параллелизм

12 •

* @param from Счет, с которого переводятся деньги
* @param to Счет, на который переводятся деньги

* @param amount

Сумма перевода

*/
puЬlic

void transfer(int from, int to,

douЫe

amount)

{
if (accounts[from] < amount) return;
System.out.print(Thread.currentThread() );
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d",
amount, from, to);
accounts[to) += amount;
System.out.printf(" Total Balance: %10.2f%n",
getTotalBalance() );
/**
Получает

*

* @return

сумму

остатков

Возвращает

общий

на

всех

счетах

баланс

*/
puЫic

douЫe

getTotalBalance()

{
douЫe

sum =

for (douЫe
sum += а;
return sum;
/**
* Получает

* @return

а

О;

: accounts)

количество

счетов

в

банке

Возвращает количество счетов

*/
puЫic

int size()

(
return accounts.length;

j ava . lanq. Thread 1 . О


Thread (RunnaЬle target)
Конструирует новый поток исполнения, вызывающий метод

run ()

для указанного целевого

объекта.



void start ()
Запускает поток исполнения, инициируя вызов метода

run () . Этот

метод немедленно возвращает

управление. Новый поток исполняется параллельно.



void run()
Вызывает метод



run ()

для связанного с ним объекта типа RunnaЬle.

static void sleep (long millis)
Переходит в состояние ожидания на заданное число миллисекунд.

12.2.
java.lang.RunnaЫe



Состояния потоков исполнения

1.0

void run()
Этот метод следует переопределить и ввести в него инструкции для исполнения требуемой задачи
в потоке.

Состояния потоков исполнения

12.2.

Потоки мoryr находиться в одном из шести состояний:






новый;
исполняемый;

блокированный;
ожидающий;



временно ожидающий;



завершенный .

Каждое из этих состояний поясняется в последующих разделах. Чтобы опреде­
лить теку~цее состояние потока исполнения, достаточно вызвать метод

12.2.1.

getState ().

Новые потоки исполнения

Если поток исполнения создан в результате операции

Th read ( r),

new,

например

new

то он еще не запущен на выполнение. Это означает, что он находится

в новом состоянии и проrрамма еще не запустила на исполнение код в данном пото­

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

12.2.2.

Исполняемые потоки

Как только вызывается метод

start

(),поток оказывается в исполняемом состоя­

нии. Исполняемый поток может выполняться или не выполняться в данный момент,

поскольку от операционной системы зависит, будет ли выделено потоку время на ис­
полнение. (Но в спецификации

Java

это отдельное состояние не указывается. Поток

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

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

Системы приоритетного планирования выделяют каждому исполняемому потоку
по кванту времени для выполнения его задачи. Когда этот квант времени истекает,
операционная система выrружает поток исполнения и дает возможность выполнить­

ся другому потоку (рис.

12.2).

Выбирая следующий поток исполнения, операционная

система принимает во внимание приоритеты потоков исполнения, как поясняется

далее, в разделе

12.3.5.

Во всех современных настольных и серверных операционных системах применяет­

ся приоритетное (вытесняющее) планирование. Но на переносных устройствах вроде

Глава

12 •

Параллелизм

мобильных телефонов может применяться кооперативное планирование. В таких
устройствах поток исполнения теряет управление только в том случае, если он вызы­

вает метод

yield (),заблокирован

или находится в состоянии ожидания.

На машинах с несколькими процессорами каждый процессор может исполнять

поток, что позволяет иметь несколько потоков, работающих одновременно. Очевид­
но, что если потоков больше, чем процессоров, то планировщик вынужден занимать­
ся разделением времени для их исполнения. Не следует, однако, забывать, что в лю­
бой момент времени исполняемый поток может выполняться или не выполняться.
Именно поэтому рассматриваемое здесь состояние потока называется исполняемым,
а не исполняющимся.

java.lang.Thread 1.0
static void yield()
Вынуждает текущий исполняемый поток уступить управление другому потоку. Следует, однако,
иметь в виду, что этот метод

статический.

-

12.2.З. Блокированные и ожидающие потоки исполнения
Когда поток исполнения находится в состоянии блокировки или ожидания, он
временно не активен. Он не выполняет никакого кода и потребляет минимум ре­

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



Когда поток исполнения пытается получить встроенную блокировку объектов
(но не объект типа

Lock

из библиотеки j

ava. util. concurrent),

которая в на­

стоящий момент захвачена другим потоком исполнения, он становится блоки­

рованным. (Блокировки из библиотеки j
даться в разделе

12.4.3,

ava. util. concurrent будут обсуж­
- в разделе 12.4.5.)

а встроенные блокировки объектов

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



Когда поток исполнения ожидает от другого потока уведомления планиров­
щика о наступлении некоторого условия, он входит в состояние ожидания. Эги

12.4.4. Переход в состояние ожидания про­
Obj ect. wai t () или Thread. j oin () или в ожида­
Condition из библиотеки j ava. util. concurrent.

условия рассматриваются в разделе
исходит при вызове метода

нии объекта типа

Lock

или

Но на практике отличия состояний блокировки и ожидания несущественны.



Несколько методов принимают в качестве параметра время ожидания. Их

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

получено соответствующее уведомление. К числу методов со временем ожида­
ния относятся

Obj ect. wait (), Thread. j oin (), Lock. tryLock ()

и

Condition.

await().
На рис.

12.1

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

а также возможные переходы между ними. Когда поток исполнения находится в со­
стоянии блокировки или ожидания (и, конечно, когда он завершается), к запуску пла­
нируется другой поток. А когда поток исполнения активизируется повторно (напри­
мер, по истечении времени ожидания или в том случае, если ему удается захватить

12.2.
блокировку), планировщик потоков сравнивает

ero

Состояния потоков исполнения
приоритет с приоритетом выпол­

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

G

8

запуск

метод

run ()
завершен

8
Рис.

12.2.4. Завершенные

12.1. Состояния

потока исполнения

потоки исполнения

Поток исполнения завершается по одной из следующих причин.



Прекращает свое существование естесrвенным образом при нормальном завер­
шении метода



run ( ) .

Прекращает свое существование внезашю, поскольку неперехваченное исклю­
чение прерывает выполнение метода

run ( ) .

В частности, поток исполнения можно уничтожить, вызвав
рирующий объект ошибки типа

ния. Но метод

st op ( )

ThreadDeath,

ero

метод

stop (),гене­

который уничтожает поток исполне­

не рекомендован к применению, поэтому следует избегать его

применения в прикладном коде.

Глава

12 •

Параллелизм

java.lang.Тhread



1.0

void join ()
Ожидает завершения указанного потока.



void join(lonq millis)



Thread. State qetState () 5

Ожидает завершения указанного потока исполнения или истечения заданного периода времени.

Получает состояние данного потока исполнения. Может принимать значения
BLOCКED,



WAITING,

NEW, RUNNAВLE,

TIМED_ WAITING или TERМINATED.

void stop ()
Останавливает поток исполнения. Этот метод не рекомендован к применению.

void suspend ()
Временно приостанавливает поток исполнения. Этот метод не рекомендован к применению.

void resume ()
Возобновляет поток исполнения. Вызывается только после вызова метода

suspend (). Этот метод

не рекомендован к применению.

12.З. Свойства потоков исполнения
В последующих разделах рассматриваются разнообразные свойства потоков ис­
полнения: приоритеты потоков, потоковые демоны, группы потоков и обрабо1чики

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

12.Э.1. Прерывание потоков исполнения
Поток исполнения прерывается, ко1·да его метод
выполнив оператор
случае, если

return

возникает исключение,

В первоначальной версии

run ( )

возвращает управление,

вслед за последним оператором в своем теле, или в том

Java

которое не перехватывается

также присутствовал метод

в данном

stop (),который

методе.

мог бьтть

вызван из другого потока исполнения, чтобы прервать исполнение данного потока.
Но теперь этот метод не рекомендуется к применению, как поясняется далее, в ра~J­

деле

12.4.13.

Поток можно прервать принудительно только с помощью метода

stop (),

не реко­

мендованного к применению. Но для Janpoca на прерывание потока исполнения мо­
жет бьтть вызван метод

interrupt

().Когда метод

interrupt ()

вызывается для пото­

ка исполнения, устанавливается состояние прерывания данного потока. Эго состояние
устанавливается с помощью признака типа

boolean,

имеющегося у каждого потока

исполнения. В каждом потоке исполнения следует периодически проверять значение

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

Thread. currentThread (), получающий текущий поток
isinterrupted (),как показано ниже.

и вызывающий далее метод

while (!Thread.currentThread() .isinterrupted()
&&

дополнительные действия)

исполнения

12.Э. Свойства потоков исполнения

выполнить дополнительные действия

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

Когда метод

interrupt ()

ero

прерыва­

InterruptedException.

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

ван, например, в результате вызова метода

зов прерывается исключением типа

sleep () или wai t (), блокирующий вы­
InterruptedException. (Существуют блокирую­

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

об этом речь пойдет в главах
В языке

Java

2и4

второго тома настоящего издания.)

не существует такого требования, чтобы прерванный поток прекра­

тил свое исполнение. Прерывание лишь привлекает внимание потока. А прерванный
поток может сам решить, как реагировать на прерывание

ero

исполнения. Некоторые

потоки настолько важны, что должны обрабатывать исключение и продолжать свое
исполнение. Но зачастую поток должен просто интерпретировать прерывание как
запрос на прекращение своеrо исполнения. Метод

run ()

такоrо потока имеет следу­

ющий вид:
RunnaЫe

r = () -> {

try
{

while (!Thread.currentThread() .isinterrupted()
&& дополнительные действия)
выполнить дополнительные действия

catch(InterruptedException

е)

{

11

поток прерван во время ожидания или приостановки

finally
{
выполнить

11

очистку,

выходом из метода

если требуется

run()

завершается исполнение потока

};

Вызывать метод

isinterrupted ()

для тоrо, чтобы проверить состояние преры­

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

sleep ()

(или другой прерываемый метод) после каждоrо ра­

бочею шага цикла. Если же метод

sleep ()

вызывается, когда установлено состояние

прерывания, поток исполнения не переходит в состояние ожидания. Вместо этого он
очищает свое состояние

Так, если метод

(!)
sleep ()

и генерирует исключение типа

InterruptedException.

вызывается в цикле, то проверять состояние преры­

вания не следует. Вместо этого лучше организовать перехват исключения типа

InterruptedException,
RunnaЫe

try
(

r = () -> {

как показано ниже.

Глава

while

12 •

Параллелизм

(дополнительные действия)

{
выполнить дополнительные действия

Thread.sleep(delay);

catch(InterruptedException

е)

{

11

поток прерван во время ожидания

finally
{
выполнить очистку,

если требуется

)

11

выходом из метода

run()

завершается исполнение потока

);

НА ЗАМЕТКУ! Для проверки состояния прерывания потока исполнения имеются два очень похожих
метода:

interrupted ()

и

isinterrupted () . Статический

метод

interrupted ()

проверяет,

был ли прерван текущий поток исполнения. Более того, вызов этого метода приводит к очистке со­
стояния прерывания потока исполнения. С другой стороны, метод экземпляра

isinterrupted()

можно использовать для проверки, был ли прерван любой поток исполнения. Его вызов не приво­
дит к изменению состояния прерывания.

Можно найти немало опубликованных примеров кода, где прерывание типа

InterruptedException

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

void mySubTask()
{

try { sleep(delay); )
catch (InterruptedException

е)

{)

//НЕ ИГНОРИРОВАТЬ!

Не поступайте так! Если вы не можете придумать ничего полезного из того, что

можно было бы сделать в блоке оператора

catch,

вам остаются на выбор два обосно­

ванных варианта.



Сделать

в

interrupt

блоке

оператора

catch

вызов

Thread. currentThread ().

(),чтобы установить состояние прерывания потока исполнения, как

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

void mySubTask()
{

try { sleepJdelay); 1
catch (InterruptedException
Тhread.currentТhread()

е)

.interrupt();

12.3.



А еще лучше указать выражение

Свойства потоков исполнения

throws InterruptedException

в сигнатуре

метода, как выделено ниже полужирным, а также удалить блок оператора

try

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

-

в методе

run () .

void mySubTask() throws InterruptedException
(

sleep(delay);

java.lang.Thread 1.0
void interrupt ()
Посылает потоку исполнения запрос на прерывание. Признак состояния прерывания потока ис­
полнения устанавливается равным логическому значению
блокирован вызовом метода

sleep (),

true.

Если поток в данный момент

генерируется исключение типа

InterruptedException.

static boolean interrupted ()
Проверяет, был ли прерван текущий поток исполнения. Следует, однако, иметь в виду, что это ста­
тический метод. Его вызов имеет побочный эффект: признак состояния прерывания текущего по­
тока исполнения устанавливается равным логическому значению



false.

boolean islnterrupted ()
Проверяет, был ли прерван поток исполнения. В отличие от статического метода

interrupted (),

вызов этого метода не изменяет состояние прерывания потока исполнения.



static Thread

currentТhread()

Возвращает объект типа Тhread, представляющий текущий поток исполнения.

12.Э.2. Потоковые демоны
Превратить поток исполнения в потоковый демон можно, сделав следующий вы­
зов:

t.setDaemon(true);
Правда, в таком потоке исполнения нет ничего демонического. Демон

-

это лишь

поток, у которого нет других целей, кроме служения другим. В качестве примера
можно привести потоки исполнения таймера, посылающие регулярные "такты" дру­
гим

потокам, или же потоки исполнения, очищающие устаревшие записи в кеше.

Когда остаются только потоковые демоны, виртуальная машина завершает работу.
Нет смысла продолжать выполнение программы, когда все оставшиеся потоки ис­
полнения являются демонами.

java.lang.Thread 1.0


void setDaemon (boolean isDaemon)
Помечает данный поток исполнения как демон или пользовательский поток. Этот метод должен
вызываться перед запуском потока исполнения.

Глава

12 •

Параллелизм

12.З.З. Именование потоков исполнения
По умолчанию потоку исполнения присваиваются привлекающие внимание име­

на вроде

Thread-2. Тем не менее потоку исполнения можно присвоить любое имя,
setName (), как показано ниже. Такой возмож1юстью удобно пользо­

вызвав метод

ваться при выводе потоков исполнения из оперативной памяти на экран или печать.

var t = new Thread(runnaЬle);
t.setName("Web crawler");

12.З.4. Обработчики необрабатываемых исключений
Метод

run ( )

потока исполнения не может генерировать никаких проверяемых

исключений, но может быть прерван непроверяемым исключением. В этом случае

поток исполнения уничтожается. Но такой конструкции

catch,

куда может распро­

страниться исключение, не существует. Вместо этого, перед тем, как поток исполне­

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

интерфейс

Thread. UncaughtExceptionHandler.

У этого интерфейса имеется един­

ственный метод:

void uncaughtException(Thread t,

ThrowaЫe

е)

Этот обработчик можно установить в любом потоке исполнения с помощью ме­

тода

setUncaughtExceptionHandler

().Кроме того, можно установить обработчик

по умолчанию для всех потоков с помощью статического метода

ghtExceptionHandler ()

из класса

Thread.

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

API

setDefaultUncau

В заменяющем обработчике может ис­

протоколирования для отправки 01четов

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

(null).

Но если не установить обработчик для отдельного потока исполне­

ния, то им становится объект потока типа
НА ЗАМЕТКУ! Группа потоков

-

ThreadGroup.

это коллекция потоков исполнения, которой можно управлять со­

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

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

Java

имеются более совершенные

средства для выполнения операций над коллекциями потоков исполнения, поэтому пользоваться

группами потоков в собственных программах не рекомендуется.

Класс

ThreadGroup реализует интерфейс Thread. UncaughtExceptionHandler.
uncaughtException () выполняет следующие действия.

Его метод

1.

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

2.

uncaughtException ().

Иначе, если метод

Thread. getDefaul tExceptionHandler () возвращает
null), то вызывается именно этот обработчик.

непу­

стой обработчик (т.е. не

3.

Иначе, если объект типа ThrowaЫe является экземпляром класса

ThreadDea th,

то ничего не происходит.

4.

Иначе имя потока исполнения и трассировка стека объекта типа ThrowaЫe

выводятся в стандартный поток сообщений об ошибках

System. err.

12.3.

Свойства потоков мспо.11ненмя

В итоге производится трассировка стека, которую, без сомнения, приходилось не

раз наблюдать в своих программах.

java.lang.Thread 1.0



static void setDefaultUncaughtExceptionHandler
(Тhread. UncaughtExceptionНandler handler) 5
static Тhread.UncaughtExceptionHandler getDefaultUncaughtException
Handler() 5




Устанавливают или nолучают обработчик no умолчанию для необрабатываемых
void setUncaughtExceptionHandler(Тhread.UncaughtException
Handler handler) 5



Тhread.UncaughtExceptionНandler

исключений.

getUncaughtExceptionHandler() 5

Устанавливают или nолучают обработчик для необрабатываемых исключений. Если обработчик во­
обще не установлен, таким обработчиком становится объект rpynnы nотоков исnолнения.

java.lang.Тhread.UncaughtExceptionНandler



void

uncaughtException(Тhread

t,

ThrowaЬle

Оnределяется для nротоколирования сnециального отчета

5
е)

no

завершении nотока исnолнения с не­

обрабатываемым исключением.

java.lang.ThreadGroup 1.0


void uncaughtException (Тhread t,

ТhrowaЬle

е)

Этот метод вызывается из родительской rpynnы nотоков, если таковая имеется, или же вызывается
обработчик

no умолчанию

из класса Тhread, если таковой имеется, а иначе выводится трассиров­

ка стека в стандартный nоток сообщений об ошибках. !Но если е

-

объект тиnа ТhreadDeath, то

трассировка стека nодавляется. Объекты тиnа ТhreadDeath формируются устаревшим и не реко­

мендованным к nрименению методом

12.3.5.

stop () .1

Приоритеты потоков исполнения

В языке программирования

Java

у каждого потока исполнения имеется свой прио­

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

setPriori ty ().

А установить приоритет потока исполнения можно,

указав любое значение в пределах от
ным

1)

до МAX_PRIORITY (равно

NORМ_PRIORITY, равное

10).

MIN _ PRIORITY

(определено в классе

Thread

рав­

Обычному приоритету соответствует значение

5.

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

ализацию потоков средствами главной платформы, на которой она выполняется,

Глава

12 •

Параппепиэм

приоритеты потоков

привязываются к уровням приоритетов этой платформы,

Java

где может быть больше или меньше уровней приоритетов.

Например, в
оритеты

Java

Windows

предусмотрено семь уровней приоритетов. Некоторые при­

привязываются к тому же самому уровню приоритета операционной

системы. В вир1уальной машине

Oracle

для

Linux

приоритеты потоков исполнения

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

Java,

где использовались потоки исполнения операционной системы, а теперь поль­

зоваться ими не рекомендуется.

java. lang. Thread 1. О
void setPriority(int newPriority)



Устанавливает приоритет потока исполнения. Приоритет должен находиться в пределах
от

Thread.MIN_PRIORITY

до Thread.МAX_PRIORIТY. Для нормального приоритета указыва­

ется значение Thread.NORМ



PRIORITY.

static int MIN PRIORITY
Минимальный приоритет, который может иметь объект типа
оритета равно



static int

NORМ



Thread

по умолчанию. Значение нормального приоритета по умолчанию

5.

static int

МАХ

PRIORITY

Максимальный приоритет, который может иметь объект типа
приоритета равно

12.4.

Значение минимального при­

PRIORITY

Приоритет объекта типа
равно

Thread.

1.

Thread.

Значение максимального

10.

Синхронизация

В большинстве практических многопоточных приложений двум или более пото­
кам исполнения приходится разделять общий доступ к одним и тем же данным. Что
произойдет, если два потока исполнения имеют доСiуп к одному объекту и каждый
из них вызывает метод, изменяющий состояние этого объекта? Нетрудно догадаться,
что потоки исполнения станут наступать друг другу на пятки. В зависимости от по­

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

12.4.1.

Пример состояния гонок

Чтобы избежать повреждения данных, совместно используемых многими потока­
ми, нужно научиться синхронu3Uроватъ доступ к ним. В этом разделе будет показано,
что произойде1~ если не применять синхронизацию. А в следующем разделе поясня­
ется, как синхронизировать обращение к данным.

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

12.1,

в данном слу­

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

transfer ()

из класса

Bank.

12.4.
void transfer(int from, int to,

puЫic

11
11

ВНИМАНИЕ:

вызывать

нескольких потоков

douЫe

-=

amouпt)

этот метод из
небезопасно!

System.out.priпt(Thread.currentThread()
accouпts[from)

Синхронизация

);

amouпt;

System.out.priпtf("

%10.2f from %d to %d",
from, to);

amouпt,

accouпts[to)

+=

amouпt;

System.out.priпtf("

Total

Ваlапсе:

getTotalBalaпce()

%10.2f%п",

);

Ниже приведен исходный код для экземпляров типа RunnaЫe. Метод

run ()

пе­

реводит деньги с фиксированного банковского счета. В кажлой транзакции метод

run () выбирает случайный целевой
transfer () для объекта Bank, а затем

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

JJ

состояние ожи­

дания.

r = () -> {

RuппаЫе

try
{

while (true)
{
iпt

toAccouпt

douЫe amouпt

=
=

(iпt)

(baпk.size()

*

МАХ_АМОUNТ

baпk.traпsfer(fromAccouпt,

Thread.sleep(

catch

(iпt)

toAccouпt,

(DELAY *

(IпterruptedExceptioп

*

Math.raпdom());

Math.raпdom();

amount);

Math.raпdom()));

е)

{
}
};

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

В конце каждой транзакции метод

transfer ()

заново вычисляет итоговую сумму

на счетах и выводит ее. Данная программа вообще не прекращает выполняться. Что­

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

588.48
100000.00
Thread[Thread-12,5,maiп] 976.11
Total Ваlапсе: 100000.00
Thread[Thread-14,5,maiп) 521.51
Total Ваlапсе: 100000.00
Thread[Thread-13,5,main) 359.89
Total Ваlапсе: 100000.00
Thread[Thread-11,5,maiп)

Total

from 11 to 44

Ваlапсе:

from 12 to 22
from 14 to 22
from 13 to 81

Thread[Thread-36,5,main) 401.71 from 36 to 73

.

Ниже приведен типич­

Гпава

12 •

Параппепизм

Total Balance: 99291.06
Thread[Thread-35,5,main] 691.46 from 35 to 77
Total Balance: 99291.06
Thread[Thread-37,5,main] 78.64 from 37 to 3
Total Balance: 99291.06
Thread[Thread-34,5,main] 197.11 from 34 to 69
Total Balance: 99291.06
Thread[Thread-36,5,main] 85.96 from 36 to 4
Total Balance: 99291.06
Thread[Thread-4,5,main]Thread[Thread-33,5,main]
7.31 from 31 to 32
Total Balance: 99979.24
627.50 from 4 to 5 Total Balance: 99979.24
Как видите, что-то в этой программе пошло не так. В течение нескольких тран­

закций общий баланс в имитируемом банке оставался равным сумме $100000, что
совершенно верно, поскольку первоначально было 100 счетов по $1000 на каждом. Но
через некоторое время общий баланс немного изменился. Запустив эту программу

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

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

12.3

из листинга

12.2.

Листинг

Исходный код из файла unsynch/UnsynchВankTest. java

1
2
3

4
5

12.3.

package unsynch;

/**
* В

*
*

этой

данных

6
7
8

* @author

9

*/

и в классе

Bank

А причины ее появления будут раскрыты в следующем разделе.

программе

демонстрируется

при произвольном доступе

данных из многих

нарушение

к структуре

потоков

* @version 1.32 2018-04-10
Сау

Horstmann

10 puЫic class UnsynchBankTest
11 {
12
puЫic static final int NACCOUNTS = 100;
13
puЬlic static final douЫe INITIAL BALANCE = 1000;
puЫic static final douЬle МАХ AМOUNT = 1000;
14
15
puЫic static final int DELAY = 10;
16
17
puЫic static void main(String[] args)
18
{
19
var bank
new Bank(NACCOUNTS, INITIAL BALANCE);
20
for (int i = О; i < NACCOUNTS; i++)
21
{
22
int fromAccount
i;
23
RunnaЫe r = () -> {
24
try

12.4.
25
26

while (true)

27

{

28
29
30

int toAccount

Синхронизация

(bank. size ()
* Math.random());
douЫe amount
МАХ AМOUNT * Math.random();
bank.transfer(fromAccount, toAccount,
amount);
Thread.sleep( (int) (DELAY * Math.random()));

31
32

33
34

(int)

35

36

catch (InterruptedException

37
38
39

{
)

е)

};

var t = new Thread(r);
t.start();

40
41
42
43
44

12.4.2.

Объяснение причин, приводящих к состоянию гонок

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

Допустим, что в двух потоках исполнения одновременно выполняется следующая
операция:

accounts[to] += amount;
Дело в том, что такие операции не являются атомарны.ми. Приведенная выше опе-

рация может быть выполнена поэтапно следующим образом.

1.
2.
3.

Загрузить значение из элемента массива
Добавить значение

accounts [to]

в регистр.

amount.

Переместить результат обратно в элемент массива

accounts [to].

А теперь представим, что в первом потоке выполняются операции из пп.

1

и

2,

после чего его исполнение приостанавливается. Допустим, что второй поток испол­
нения выходит из состояния ожидания и в нем обновляется тот же самый элемент

массива

accounts.

Затем из состояния ожидания выходит первый поток, и в нем вы­

полняется операция из п.

3.

Такое действие уничтожает изменения, внесенные во вто­

ром потоке исполнения. В итоге общий баланс подсчитан неверно (рис.

12.2).

Такое

нарушение данных обнаруживается в рассматриваемой здесь тестовой программе.
(Разумеется, существует вероятность получить сиmал ложной тревоги, если поток ис­

полнения будет прерван во время тестирования!)

Глава

12 •

Параллелизм

TransferThread 1

accounts[to]

TransferThread 2
Pentcтp потока

Реn1стр потока 2

1
1

заrw.~ить 1....______
5000
....1


·····!······················································1
'
····

5000

1
1
1
1

5500

:

5000

'
заrруэить

,__sooo
_

__,f.·····f....

5900

сохранить

5900

sooo
5000

••..

j""".j__5900
_

__,

1
1
1

5500

·····~·········+·················································~~-5500-~
1
1

Рис.

12.2. Одновременный доступ

к данным из двух потоков исполнения

НА ЗАМЕТКУ! Байт-коды. выполняемые виртуальной машиной в классе Bank, можно просмотреть.
Для этого достаточно ввести следующую команду декомпиляции файла

javap



Bank. class:

-v Bank

Например, строка кода

accounts[to] += amount;
транслируется в следующий байт-код :

aload О
getfield #2;
il oad 2
dup2
daload
dload З
dadd
dastore

/ /поле

accounts: [D

Неважно. что именно означают эти байт-коды. Важнее другое : операция инкрементирования со­
стоит из нескольких команд. и исполняющий их поток может быть прерван на любой из них.

Какова вероятность повредить данные? В современных многоядерных 11ро1~ессора х

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

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

скольку в каждом потоке будет выполняться настолько мало операций, прежде чем
он перейдет в сос1·ояние ожидания, что прерывание посреди вычислений окажется

12.4.

Синхронизация

маловероятным. Но и в этом случае риск повреждения данных не исключается полно­
стью. Если запустить достаточно много потоков исполнения на сильно загруженной ма­

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

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

transfer ()

может быть прервано на полпути к его завершению. Если удастся

гарантировать нормальное завершение этого метода до того, как его поток утратит

управление, то состояние объекта банковского счета вообще не будет нарушено.

12.4.З. Объекты блокировки
Имеются два механизма для защиты блока кода от параллельного доступа. В язы­
ке

Java для

этой цели предусмотрено ключевое слово

появился еще и класс

ReentrantLock.

synchronized, а в версии Java 5
synchronized автоматически

Ключевое слово

обеспечивает блокировку, как и связанное с ней "условие", которое удобно указы­
вать в большинстве случаев, когда требуется явная блокировка. Но понять ключевое

слово

synchronized проще, если рассмотреть блокировки и условия по отдельности.
java. util. concurrent предоставляются отдельные классы для реали­

В библиотеке

зации этих основополагающих механизмов, принцип действия которых поясняется

в разделе

12.4.4.

Разъяснив, каким образом устроены эти основные составляющие

12.4.5.
ReentrantLock

многопоточной обработки, мы перейдем к разделу
Защита блока кода средствами класса

в общих чертах выглядит

следующим образом:

rnyLock.lock();
try

//объект

типа

ReentrantLock

{
критический раздел кода

finally
{

rnyLock.unlock{);

//непременно снять
//даже

блокировку,

если генерируется исключение

Такая конструкция гарантирует, что только один поток исполнения в единицу

времени сможет войти в критический раздел кода. Как только один поток исполне­

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

lock ().И если другие

потоки исполнения попъrгаются вызвать метод

lock (),

то они будут деактивизированы до тех пор, пока первый поток не снимет блокировку
с объекта блокировки .



ВНИМАНИЕ! Крайне важно расположить вызов метода

unlock ()

в блоке

finally.

Если код

в критическом разделе сгенерирует исключение, блокировка должна быть снята. В противном слу­
чае другие потоки исполнения будут заблокированы навсегда.

НА ЗАМЕТКУ! Пользоваться блокировками вместе с оператором try с ресурсами нельзя. Ведь ме­
тод разблокировки не называется

close () . Но

даже если бы он так назывался, то его все равно

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

try с ресурсами, поскольку в заголовке этого

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

Глава

12 •

Параллелизм

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

transfer ()

из класса

Bank,

как

показано ниже.

puЫic

class Bank

(

private Lock bankLock = new ReentrantLock();
11 объект класса ReentrantLock,
11 реализующего интерфейс Lock
puЫic

void transfer(int from, int to, int amount)

{

bankLock.lock();
try
(

System.out.print(Thread.currentThread() );
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d",
amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",
getTotalBalance());
finally
(

bankLock.unlock();

Допустим, в одном потоке исполнения вызывается метод

transfer ()

и до его

завершения этот поток приостанавливается, и во втором потоке исполнения также

вызывается метод

transfer

().Второй поток не сможет захватить блокировку и оста­

нется заблокированным при вызове метода

lock ().

Напротив, он будет деактиви­

зирован и вынужден ждать до тех пор, пока выполнение метода

transfer ()

не за­

вершится в первом потоке. И только тогда, когда первый поток снимет блокировку,
второй поток сможет продолжить свое исполнение (рис.

12.3).

Опробуйте описанный выше механизм синхронизации потоков исполнения, вве­
дя блокирующий код в метод

transfer ()

и снова запустив рассматриваемую здесь

программу. Можете прогонять ее бесконечно, но общий баланс банка на этот раз не
нарушится.

Однако у каждого объекта типа

ReentrantLock.

Bank

имеется свой собственный объект типа

Если два потока исполнения попытаются обратиться к одному

и тому же объекту типа

Bank,

блокировка послужит для сериализации доступа. Но

если два потока исполнения обращаются к разным объектам типа

Bank,

то каж­

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

Bank.

Блокировка называется реентерабельной, потому что поток исполнения может
повторно захватывать блокировку, которой он уже владеет. Для блокировки пред­

lock ().
unlock (), чтобы,

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

И для каждого вызова

lock ()

в потоке должен быть вызван метод

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

12.4.

Пот0tе

Поток 2

1

ПОТОIС 1

Синхронизация

По!О1:2

перевод

Рис. 12.З. Сравнение синхронизированных
и не синхронизированных потоков исполнения

Например, метод

tra nsfe r () вьвывает метод getTota lBa l a nce () ,который так ­
bankLock, у которого теперь значение счетчика захватов рав1ю 2. Ко1да метод g e tTotalBalance () завершается, значение счетчика захватов воз­
вращается к 1. При выходе из метода t ransfer ( ) счетчик захватов имеет значение О,
же блокирует объект

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

= amount)
bank.transfer(from, to, amount)
Ведь вполне возможно, что текущий поток будет деактивизирован в промежутке
между успешным выполнением проверки и вызовом метода

transfer ():

if (bank.getBalance(from) >= amount)
11 поток исполнения может быть
11 деактивизирован в этом месте кода
bank.transfer(from, to, amount);
В тот момент, когда возобновляется исполнение потока, остаток на счете может из­

мениться, т.е. уменьшиться ниже допустимого предела. Поэтому нужно каким-то об­
разом гарантировать, что никакой другой поток не сможет изменить остаток на счете

между его проверкой и переводом денег. Для этого придется защитить как проверку

остатка на счете, так и сам перевод денег с помощью следующей блокировки:
puЬlic

{

void transfer(int from, int to, int amount)

12.4.

Синхронизация

bankLock.lock();
try
{

while (accounts[from] < amount)

11
11

ожидать

перевести денежные

средства

finally
{

bankLock.unlock();

Что делать дальше, если на счете нет достаточной суммы? Ожидать до тех пор,

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

bankLock,

так что ни в од­

ном другом потоке нет возможности пополнить счет. И здесь на помощь приходит

объект условия.
С объектом блокировки может быть связан один или несколько объектов условий,

которые получены с помощью метода

newCondi t ion ().

Каждому объекту условия

можно присвоить имя, напоминающее об условии, которое он представляет. Напри­

мер, объект, представляющий условие" достаточных денежных средств", устанавлива­
ется следующим образом:

class Bank
{

private Condition sufficientFunds;
puЫic

Bank ()

{

suf ficientFunds

Если в методе

bankLock.newCondition();

transfer ()

будет обнаружено, что средств на счете недостаточно,

он сделает следующий вызов:

sufficientFunds.await();
Текущий поток исполнения теперь деактивизирован и снимает блокировку. Это
дает возможность пополнить счет в другом потоке.

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

исполнения вызывается метод

awai t (),

awai t ().

Как только в потоке

он входит в набор ожидания, установленный

для данного условия. Поток не становится исполняемым, когда доступна блокировка.
Вместо этого он остается деактивизированным до тех пор, пока другой поток не вы­
зовет метод

signalAll ()

по тому же условию.

Когда перевод денег будет произведен в другом потоке исполнения, в нем должен

быть сделан следующий вызов:

sufficientFunds.signalAll();

Глава

12 •

Параллелизм

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

да

await ().
В этот момент условие должно быть снова проверено в потоке исполнения. Но нет

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

signalAll ()

просто

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

НА ЗАМЕТКУ! Вообще говоря, вызов метода

while ( !

(можно

await О

должен быть введен в цикл следующей формы:

продолжить))

условие.аwаit();

Крайне важно, чтобы в конечном итоге метод

signallAll () был вызван в ка­
await () вызывается в потоке

ком-нибудь другом потоке исполнения. Коl'да метод

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

И здесь он полностью полагается на другие потоки. Если ни один из них не поза­

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

awai t ()

будет вызван

в последнем активном потоке без разблокировки какого-нибудь другого потока, этот
поток также окажется заблокированным. И тогда не останется ни одного потока ис­

полнения, I'Де можно было бы разблокировать другие потоки, а следовательно, про­
грамма зависнет.

Когда же следует вызывать метод

signalAll ()?Существует эмпирическое пра­

вило: вызывать этот метод при таком изменении состояния объекта, которое может

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

редную возможность для проверки остатков на счетах. В данном примере метод

signalAll ()
puЫic

вызывается по завершении перевода денег, как показано ниже.

void transfer(int from, int to, int amount)

{

bankLock.lock();
try
{

while (accounts[from] < amount)
sufficientFunds.await();
11 перевести денежные средства
sufficientFunds.signalAll();
finally
{

bankLock.unlock();

12.4.
Однако вызов метода

signalAll ()

Синхронизации

не влечет за собой немедленной активизации

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

signal (),

разблокирует только один поток из набора ожидания,

выбирая его случайным образом. Это более эффективно, чем разблокировать все

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

signal

(),то система

перейдет в состояние взаимной блокировки.

ВНИМАНИЕ! По условию в потоке исполнения может быть вызван только метод awai t (),



signalAll О

или

signal ().

когда этот поток владеет блокировкой по данному условию.

Запустив на выполнение видоизмененный вариант имитирующей банк програм­
мы из листинга

ланс в

$100000

12.4,

вы обнаружите, что теперь она работает правильно. Общий ба­

сохраняется неизменным, и ни на одном из счетов нет отрицательного

остатка. (Но для того чтобы прервать выполнение этой программы, вам снова при­
дется нажать комбинацию клавиш

Вы можете также заметить, что про­

.)

грамма работает немного медленнее

-

это та цена, которую приходится платить за

дополнительные служебные операции, связанные с механизмом синхронизации.
На практике правильно употреблять условия не так-то просто. Поэтому, прежде
чем пытаться реализовать собственные объекты условий, стоит рассмотреть примене­
ние конструкций, описанных далее, в разделе

Листинг

1
2
3
4

12.1+.

Исходный код из файла

12.5.

synch/Bank. java

package synch;
import java.util.*;
import java.util.concurrent.locks.*;

5

6
7

8
9
10
11

/**
* Программа,

*

имитирующая

банк

со

счетами и использующая

блокировки для организации последовательного доступа

* к остаткам на счетах

*/
puЫic

class Bank

12 {

13
14
15
16
17
18
19
20

private final douЬle[) accounts;
private Lock bankLock;
private Condition sufficientFunds;
/**

*

Конструирует

*

на

каждом

остаток

счете

*!

21

22

puЫic

23

{

24
25

объект банка

* @param n Количество счетов
* @param initialBalance Первоначальный

Bank(int n,

douЫe

initialBalance)

accounts = new douЬle[n);
Arrays.fill(accounts, initialBalance);

Г.11ава

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

12 •

Пара11.11елмэм

bankLock
new ReentrantLock();
sufficientFunds = bankLock.newCondition();
/**
* Переводит деньги с одного счета на другой
* @param from Счет, с которого переводятся деньги
* @param to Счет, на который переводятся деньги
* @param amount Сумма перевода

*/
puЫic

void transfer(int from, int to,
throws InterruptedException

amount)

bankLock.lock();
try
{
while (accounts[from] < amount)
sufficientFunds.await();
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d",
amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",
getTotalBalance());
sufficientFunds.signalAll();
finally
{
bankLock. unlock () ;

/**

* Получает сумму остатков на всех
* @return Возвращает общий баланс
*/
puЫic douЫe

счетах

getTotalBalance()

(
bankLock.lock();
try
{
douЫe sum = О;
for (douЫe
sum += а;

а

: accounts)

return sum;
finally
{
bankLock.unlock();

77

78
79
80
81

douЫe

/**

*

Получает количество счетов

* @return

в

банке

Возвращает количество счетов

12.4.
82
83
84
85
86
87

Синхронизация

*/
puЫic

int size()

{
return accounts.length;

java.util.concurrent.locks.Lock 5
Condi tion newCondi tion О



Возвращает объект условия, связанный с данной блокировкой.

java.util.concurrent.locks.Condition 5


void awai t

О

Вводит поток исполнения в набор ожидания по данному условию.



void signalAll ()



void signal О

Разблокирует все потоки исполнения в наборе ожидания по данному условию.

Разблокирует один произвольно выбранный поток исполнения в наборе ожидания по данному ус­
ловию.

12.4.5.

Ключевое слово

synchronized

В предыдущих разделах было показано, как пользоваться объектами блокировки
типа

Lock

и условиями типа

Condi tion.

Прежде чем двигаться дальше, подведем

краткие итоги, перечислив главные особенности блокировок и условий.



Блокировка защищает критические разделы кода, позволяя выполнять этот
код только в одном потоке в единицу времени.



Блокировка управляет потоками исполнения, которые пытаются войти в защи­
щенный раздел кода.



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

Интерфейсы

Lock

и

Condi tion

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

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

Java обладает
synchronized,

Java.

Еще со времен версии

1.0

каждый

объект в

встроенной блокировкой. Если метод объявлен с ключевым

словом

то блокировка объекта защищает весь этот метод. Следова­

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

Иными словами, следующий фрагмент кода:
puЫic

synchronized void

{
тело

метода

метод()

Глава

Параппепизм

12 •

равнозначен такому фрагменту:
puЫic

void

метод()

{
this.встроеннаяБлокировка.lосk();

try
{
тело

метода

finally {

this.встроеннаяБлокировка.unlосk();

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

Bank

как

synchronized.

transfer ()

Встроенная блокировка объектов имеет единствен­

ное связанное с ней условие. Метод

wai t () вводит поток исполнения в набор ожи­
noti fyAll () /noti fy () разблокируют ожидающие потоки. Иными
метода wai t () или notifyAll () равнозначен следующему коду:

дания, а методы
словами, вызов

встроенноеУсловие.аwаit();

встроенноеУсловие.signаlАll();

НА ЗАМЕТКУ! Методы

wai t () , notifyAll () и notify () являются конечными lfinall ме­
Object. А методы из интерфейса Condi tion должны именоваться awai t,
signal, чтобы не вступать в конфликт с этими методами.

тодами из класса

signa!All

и

Например, класс

Bank

можно реализовать только языковыми средствами

Java

сле­

дующим образом:

class Bank
private
puЫic

douЬle[]

accounts;

synchronized void transfer(int from, int to,
int amount) throws InterruptedException

while (accounts[from] < amount)

wait();

//ожидать

по

единственному условию

// встроенной блокировки
accounts[from] -; amount;
accounts[to] +; amount;

notifyAll();

//уведомить

11
puЫic

synchronized

все

объектов

потоки,

ожидающие по данному условию
douЫe

getTotalBalance() { . . . )

Как видите, применение ключевого слова

synchronized

порождает намного бо­

лее краткий код. Разумеется, чтобы понять такой код, нужно знать, что каждый объ­

ект обладает встроенной блокировкой и что эта блокировка имеет встроенное усло­
вие. Блокировка управляет потоками исполнения, которые пытаются войти в метод

synchronized.

А условие управляет потоками исполнения, вызвавшими метод

wai t ().

СОВЕТ. Синхронизированные методы относительно просты. Но начинающие программировать
на

Java

нередко испытывают затруднения в обращении с условиями. Поэтому, прежде чем приме­

нять методы

wai t () /notifyAll (),
12.5.

санных в разделе

рекомендуется ознакомиться с одной из конструкций, опи­

12.4.

Синхронизация

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

Bank

имеется статический синхронизированный ме­

тод, при его вызове захватывается блокировка объекта типа

Bank. class.

В результате

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

Встроенным блокировкам и условиям присущи некоторые ограничения, в том
числе приведенные ниже.



Нельзя прервать поток исполнения, который пытается захватить блокировку.




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

Что же лучше использовать в прикладном коде: объекты типа

Lock

и

Condition

или синхронизированные методы? Ниже приведены некоторые рекомендации, кото­
рые дают ответ на этот вопрос.

Лучше не пользоваться ни объектами типа

Lock/Condi tion, ни ключевым
synchronized. Зачастую вместо этого можно выбрать подходящий
механизм из пакета j ava. util. concurrent, который организует блокиров­
ку автоматически. Так, в разделе 12.5.1 будет показано, как пользоваться бло­



словом

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



Если ключевое слово

synchronized

подходит в конкретной си~уации, непре­

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

12.5

приведен пример

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

Пользуйтесь объектами типа



Lock/Condi tion,

если действительно нуждаетесь

в дополнительных возможностях подобных конструкций.
Листинг

1

12.5. Исходный

код из файла

synch2/Bank. java

package synch2;

2

3
4

irnport java.util.*;

5

/**
* Программа,

6
7
8
9
10

имитирующая

*

примитивные

*

потоков исполнения

языковые

банк

со

счетами,

конструкции для

используя

синхронизации

*/
puЫic

class Bank

11 {

12
13
14
15
16
17
18

private final

douЬle[]

accounts;

/**

*

Конструирует

* @param n

объект банка

Количество счетов

* @param initialBalance

Первоначальный

*

на

каждом

счете

остаток

Глава

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

12 •

Параллелизм

*/
puЫic

Bank(int n,

douЫe

initialBalance)

{
accounts = new douЬle[n];
Arrays.fill(accounts, initialBalance);

/**
* Переводит

деньги с

* @param from

Счет,

одного счета на другой

с которого nереводятся деньги

* @param to Счет, на который переводятся
* @param amount Сумма перевода

деньги

*/
puЫic

synchronized void transfer(int from, iпt to,
amount) throws InterruptedExceptioп

douЫe

while (accounts[from] < amount)
wait ();
System.out.print(Thread.currentThread() );
accounts[from] -= amount;
System.out.printf (" %10.2f from %d to %d",
amount, from, to);
accounts[to] += amount;
System.out.printf (" Total Balance: %10.2f%n",
getTotalBalance());
notifyAll();

/**
*

Получает сумму остатков на

* @return

всех

счетах

Возвращает общий баланс

*/
puЫic

synchronized

douЫe

getTotalBalance()

(
douЫe

sum =

for (douЫe
sum += а;

а

О;

: accounts)

return sum;

/**

*

Получает сумму остатков на

* @return
*/
puЫic

всех

Возвращает общий баланс

int size()

{
return accounts.length;

счетах

12.4.

Синхронизация

java.lang.Object 1.0


void notifyAll ()
Разблокирует nотоки исnолнения, вызвавшие метод

wai t ()

для данного объекта. Может быть вы­

зван только из тела синхронизированного метода или блока кода. Генерирует исключение тиnа

IlleqalМoni torStateException, если поток исполнения не владеет блокировкой данного
объекта.



void notify ()
Разблокирует один nроизвольно выбранный nоток исnолнения среди nотоков, вызвавших метод

wai t ()

для данного объекта. Может быть вызван только из тела синхронизированного метода или

блока кода. Генерирует исключение тиnа IlleqalМoni torStateException, если поток исnол­
нения не владеет блокировкой данного объекта.

void wait()
Вынуждает nоток исnолнения ожидать уведомления в течение указанного периода времени. Вы­

зывается только из синхронизированного метода или блока кода. Генерирует исключение тиnа
IlleqalМoni torStateException, если поток исnолнения не владеет блокировкой данного
объекта.



void wait(lonq millis)



void wait(lonq millis, int nanos)
Вынуждают поток исnолнения ожидать уведомления в течение указанного периода времени. Вы­

зываются только из синхронизированного метода или блока кода. Генерируют исключение тиnа
IlleqalМoni torStateException, если nоток исnолнения не владеет блокировкой данного
объекта. Задаваемое количество миллисекунд не может nревышать

12.4.6.

1000000.

Синхронизированные блоки

Как упоминалось выше, у каждого объекта в

Java

имеется собственная встроен­

ная блокировка. Поток исполнения может захватить э1у блокировку, вызвав синхро­
низированный метод. Но есrь и другой механизм захвата блокировки

-

вхождение

в синхронизированный блок. Когда поток исполнения входит в блок кода, объявляемый

в приведенной ниже форме, он захватывает блокировку объекта

synchronized (obj) //
11

obj.

это синтаксис
синхронизированного блока

критический раздел кода

Иногда в прикладном коде вСiречаются специальные блокировки вроде следующей:
puЫic

class Bank

(

private douЬle[] accounts;
private var lock = new Object();
puЫic

void transfer(int from, int to, int amount)

(

synchronized (lock) //

специальная блокировка

{

accounts[from] -= amount;
accounts[to] += amount;

Глава

12 •

Параллелизм

System.out.println( . . . );

Здесь объект

lock

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

которая имеется у каждого объекта в

Java.

Встроенной блокировкой объекта иногда

пользуются для реализации дополнительных атомарных операций. Такая практика

получила название к.лиентскоu блокировки. Рассмотрим для примера класс

Vector,

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

void

puЫic

transfer ().

accounts, int from,
int to, int amount) //ОШИБКА!

transfer(Vector

accounts.set(from, accounts.get(from) - amount);
accounts.set(to, accounts.get(to) + amount);
System.out.println( . . . );
Методы

get ()

и

set ()

из класса

Vector

синхронизированы, но это вряд ли по­

может. Вполне возможно, что поток исполнения будет приостановлен в методе

transfer ()

по завершении первого вызова метода

get ().

В другом потоке испол­

нения может быть затем установлено иное значение на той же позиции. Блокировку
можно захватить следующим образом:

void

puЫic

accounts, int from,
int to, int amount)

transfer(Vector

synchronized (accounts)
{

accounts.set(from, accounts.get(from) - amount);
accounts.set(to, accounts.get(to) + amount);
System.out.println( . . . );
Такой подход вполне работоспособен, но он полностью зависит от того факта, что

встроенная блокировка используется в классе

Vector

для всех его модифицирующих

методов. Но так ли это на самом деле? В документации на класс

Vector

этого не

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

ванные модифицирующие методы. Как видите, клиентская блокировка

-

весьма не­

надежный прием, и поэтому он обычно не рекомендуется для применения.
НА ЗАМЕТКУ! В вир~уальную машину

Java

встроена поддержка синхронизированных методов. Тем

не менее синхронизированные блоки компилируются в длинные последовательности байт-кодов
для управления встроенной блокировкой.

12.4. 7.

Принцип монитора

Блокировки и условия

-

эффективные инструментальные средства синхронизации

потоков исполнения, но они не слишком объектно-ориентированы. В течение многих
лет исследователи искали способы обеспечения безопасносrи мноrопоточной обработки,

12.4.

Синхронизация

чтобы избавить пршраммисюв от необходимости думать о явных блокировках. Одно из

- принцип монитора, который был впервые предложен
(Per Brinch Hansen) и Тони Хоаром (Tony Hoare) в 1970-х годах.

наиболее успешных решений

Пером Бринчем Хансеном
В терминологии

Java монитор обладает следующими свойствами.
- это класс, имеющий только закрытые поля.



Монитор



У каждого объекта такого класса имеется связанная с ним блокировка.



Все методы блокируются этой блокировкой. Иными словами, если клиент вы­
зывает метод

obj . method (),

блокировка объекта

obj

автоматически захваты­

вается в начале этого метода и снимается по его завершении. А поскольку все
поля класса монитора закрытые, то такой подход гарантирует, что к ним нель­

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



У блокировки может быть любое количество связанных с ней условий.

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

>= balance,

accounts [from)

Так, можно было просто сделать вызов

awa i t

не указывая явную условную переменную. Но иссле­

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

неэффективной. Проблема была решена благодаря применению явных условных пе­
ременных, каждая из которых управляет отдельным рядом потоков исполнения.

Создатели

Java

вольно адаптировали принцип монитора. Каждый объект в

Java

об­

ладает встроенной блокировкой и вСiроенным условием. Если метод объявлен с клю­
чевым словом

synchronized,

он действует как метод монитора. А переменная усло­

вия доступна через вызовы методов

Но объекты в

Java

wai t (), notifyAll (), notify ().

отличаются от мониторов в следующих трех важных отношени-

ях, нарушающих безопасность потоков исполнения.

Поля не обязательно должны бьгrь закрытыми




Методы не обязаны быть синхронизированными



Встроенная блокировка доступна клиентам.

Это

-

(private).
(synchronized).

явное пренебрежение требованиями безопасности, изложенными Пером

Бринчем Хансеном. В уничижительном обозрении, посвященном примитивам мно­

гозадачной обработки в

Java,

он пишет: "Для меня является непостижимым тот факт,

что небезопасный параллелизм столь серьезно принят сообществом программистов,

и это спустя четверть века после изобретения мониторов и языка
Этому нет оправданий".

[Java's Insecure Parallelism,

АСМ

Concurrent Pascal.
SIGPLAN Notices 34:38-45,

April 1999].

12.4.В. Поля и переменные типа

vola tile

Плата за синхронизацию кажется порой непомерной, когда нужно просто прочи­

тать или записать данные в одно или два поля экземпляра. В конце концов, что тако­
го страшного может при этом произойти? К сожалению, современные процессоры

и компиляторы оставляют немало места для появления ошибок.



Компьютеры с несколькими процессорами могут временно удерживать значе­
ния из памяти в регистрах или локальных кешах. Вследствие этого в потоках,
исполняемых на разных процессорах, могут быть доступны разные значения
в одной и той же области памяти!

Глава



12 •

Параллелизм

Компиляторы моrут менять порядок выполнения команд для досrижения мак­
симальной производительности. Они не меняют этот порядок таким образом,
чтобы изменился смысл кода, а лишь делают предположения, что значения
в памяти изменяются только явными командами в коде. Но значение в памяти

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

кеши и не изменяя порядок следования команд. Подробнее об этом можно узнать из

документа

f ava Memory Model and Thread Specification

(Спецификация модели памяти

и потоков исполнения в

Java), разработанного экспертной группой JSR 133 (https://
www.jcp.org/en/jsr/detail?id=133). Большая часть этого документа довольно сложна
и полна технических подробностей, но в нем приведен также целый ряд наглядных
примеров. Более доступный обзор данной темы, автором которого является Брайан

Гоетц, доступен по адресу

https://www.ibm.com/developerworks/library/j-jtp02244/.

НА ЗАМЕТКУ! Брайан Гоетц предложил следующий "девиз синхронизации": если вы записываете
в переменную данные, которые могут быть затем прочитаны в другом потоке исполнения, или же
читаете из переменной данные, которые были записаны в другом потоке исполнения, то обязаны
использовать синхронизацию.

Ключевое слово

volatile

обозначает неблокирующий механизм синхронизиро­

ванного доступа к полю экземпляра. Если поле объявляется как

volatile,

то ком­

пилятор и виртуальная машина принимают во внимание тот факт, что поле может
быть параллельно обновлено в другом потоке исполнения.
Допусrим, у объекта имеется поле признака

done

типа

boolean,

который усrанав­

ливается в одном потоке исполнения и опрашивается в другом. Как пояснялось ранее,

для этой цели можно организовать всrроенную блокировку следующим образом:

private boolean done;
synchronized boolean isDone() { return done;
puЫic synchronized void setDone() { done = true; }
puЫic

Применять встроенную блокировку объекта
Ведь методы

isDone ()

и

setDone ()

-

вероятно, не самая лучшая идея.

моrут блокироваться, если другой поток исполне­

ния заблокировал объект. В таком случае можно воспользоваться отдельной блокиров­
кой только для данной переменной. Но это повлечет за собой немало хлопот. Поэтому
в данном случае имеет смысл объявить поле как

volatile

следующим образом:

private volatile boolean done;
boolean isDone() { return done; }
puЫic void setDone() { done = true; }
puЬlic

В таком случае компилятор вставит подходящий код, чтобы изменения, вносимые

в переменную

done

в одном потоке исполнения, были доступны в любом другом по­

токе исполнения, где читается ее содержимое



ВНИМАНИЕ! Изменчивые переменные типа

.

volatile

не гарантируют никакой атомарности опе­

раций. Например, приведенный ниже метод не гарантирует смены значения поля на противопо­
ложное.

puЫic

void flipDone ()

{ done = !done; } / /

не

атомарная

операция!

12.4.

12.4.9.

Поля и переменные типа

Синхронизация

final

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

или модификатор доступа

volatile.

Но имеется еще одна возможность получить

надежный доступ к общему полю, если оно объявлено как

final.

Рассмотрим следу­

ющую строку кода:

final var accounts = new HashMap();
Переменная

accounts

станет доступной из друmх потоков исполнения по завер­

шении конструктора. Если не объявить ее как

final, то нет никакой гарантии, что
accounts окажется доступным из друrих потоков
исполнения. Ведь если конструктор класса HashMap не завершится нормально, значе­
ние этой переменной может оказаться пустым (null). Разумеется, операции над ото­
обновленное значение переменной

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

12.4.1 О.

Атомарность операций

Общие переменные моrут быть объявлены как

volatile,

при условии, что

над ними не выполняется никаких операций, кроме присваивания. В пакете

util. concurrent. atomic

j ava.

имеется целый ряд классов, в которых эффективно исполь­

зуются команды машинною уровня, гарантирующие атомарность других операций

без применения блокировок. Например, в классе

incrementAndGet ()

и

decrementAndGet (),

Atomicinteger

имеются методы

атомарно инкрементирующие или де­

крементирующие целое значение. Так, безопасно сформировать последовательность

чисел можно следующим образом:

static AtomicLong nextNumЬer = new AtomicLong();
11 В некотором потоке исполнения ...
long id = nextNumЬer.incrementAndGet();

puЫic

Метод incrementAndGet () автоматически инкрементирует переменную типа
AtomicLong и возвращает ее значение после инкрементирования. Эго означает, что
операции получения значения, прибавления

1,

установки и получения новоrо значе­

ния переменной не моrут быть прерваны. Эгим гарантируется правильное вычисле­
ние и возврат значения даже при одновременном доступе к одному и тому же экзем­
пляру из нескольких потоков исполнения.

Имеются также методы для автоматической установки, сложения и вычитания зна­
чений, но если требуется выполнить более сложное их обновление, то придется вы­
звать метод

compareAndSet ().

Допустим, в нескольких потоках исполнения требуется

отслеживать наибольшее значение. Приведенный ниже код для этой цели не rодится.

static AtomicLong largest = new AtomicLong();
11 В некотором потоке исполнения ...
largest.set(Math.max(largest.get(), observed));
11 ОШИБКА из-за условия гонок!

puЫic

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

largest.updateAndGet(x -> Math.max(x, observed));

Глава

12 •

Параллелизм

или

largest.accurnulateAndGet(observed, Math::rnax);
Метод accumulateAndGet ()

принимает двоичную операцию, применяемую

для объединения атомарного значения и предосrавляемого арrумента. Имеются также
методы

getAndUpdate ()

и

getAndAccumulate

(),возвращающие прежнее значение.

НА ЗАМЕТКУ! Упомянутые выше методы предоставляются также для классов Atomicinteger,
Atomicin tegerArray, Atomicin tegerFieldUpda ter, AtomicLongArray,
AtomicLongFieldUpda ter, А tomicReference, AtomicReferenceArray
и AtomicReferenceFieldUpdater.

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

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

классы

Java 8

предосrавляются

LongAdder и LongAccumulator. В часrносrи, класс LongAdder сосrоит из не­

скольких полей, общая сумма значений в которых составляет текущее значение. Раз­
ные слаrаемые этой суммы моrут обновляться во мноrих потоках исполнения, а новые
слаrаемые автоматически предосrавляются по мере увеличения количесrва потоков.

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

AtomicLong следует воспользоваться классом
LongAdder. Методы в этом классе называются несколько иначе. Так, для инкремен­
тирования счетчика вызывается метод increment (),для прибавления величины метод add (), а для извлечения итоrовой суммы - метод sum (), как показано ниже.

сrуп к общим данным, то вмесrо класса

var adder = new LongAdder();
for ( . . . )
pool.submit( () ->
while ( . . . ) {
if ( . . . ) adder.increment();
)
});

long total = adder.sum());

t=J

1,3

НА ЗАМЕТКУ! Безусловно, метод increment О не возвращает прежнее значение. Ведь это свело
бы на нет весь выигрыш в эффективности от разделения суммы на многие слагаемые.

Подобный принцип обобщается в классе LongAccumulator до произвольной опера­
ции накопления. Консrруктору этого класса предоставляется нужная операция, а также
нейтральный элемент. Для внедрения новых значений вызывается метод

а для получения текущего значения

-

такой же результат, как и приведенный выше, rде применялся класс

var adder = new LongAccumulator(Long::sum, 0);
//В некотором потоке исполнения

adder.accumulate(value);

accumulate (),

метод get () . Так, следующий фраrмент кода дает

...

LongAdder:

12.4.
В накапливающем сумматоре имеются переменные

aJ,

Синхронизация

а2, .",ал. Каждая перемен­

ная инициализируется нейтральным элементом (в данном случае

-

О). Когда метод

accurnulate () вызывается со значением v, одна из этих переменных автоматически об­
новляется следующим образом: дi

дi

=

ор

v,

где ор

операция накопления в ин­

-

фиксной форме записи. В данном примере в результате вызова метода accumulate ()
вычисляется сумма дi

+ v

а1

=

приводит к такому результаrу: а1

для некоторой величины
ор

всех накапливающих сумматоров а1

а2

+

ор

. . . ор an.
+ . . . + ап.

а2

А вызов метода get ()

i.

В данном примере это сумма

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

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

Имеются также классы DouЬleAdder и DouЬleAccumulator, которые действуют
аналогичным образом, только оперируют значениями типа douЫe.

12.4.11.

Взаимные блокировки

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

1.

Счет

1:

сумма

200

2.

Счет

2:

сумма

300 долл.

3.

Поток

1:

переводит сумму

300 долл.

4.

Поток

2:

переводит сумму

400

Как следует из рис.

долл.

12.4,

потоки

со счета

1 на

счет

2.

долл. со счета

2 на

счет

1.

1

и

2

полняется, поскольку остатков на счетах

явно блокируются. Ни один из них не вы­

1

и

2

недостаточно для выполнения тран­

закции. Может случиться так, что все потоки исполнения будут заблокированы, по­
скольку каждый из них будет ожидать пополнения счета. Такая ситуация называется
взаимной б.\окировкоit.
В рассматриваемой здесь программе взаимная блокировка не может произойти

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

100

счетов с общим балансом

100000

1000 долл.

А

долл., то как минимум

на одном счете в любой момент времени должна быть сумма больше

1000

долл. По­

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

Но если внести изменение в метод run (), исключив лимит

1000

долл. на тран­

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

10,

сконструируй­

те объект типа RunnaЬle со значением поля max, равным 2*INITIAL_BALANCE, и за­
пустите программу на выполнение. Она будет работать довольно долго, но в конеч­
ном итоге зависнет.

СОВЕТ. Когда программа зависнет, нажмите комбинацию клавиш . В итоге будет выведено
содержимое памяти с перечислением всех потоков исполнения. Для каждого потока исполнения

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

!Потоки!, приведенной на рис.

jconsole,
12.5.

как описано в главе

7,

и перейдите к панели

Threads

Глава

Параллелизм

12 •

,..

bank . accounts

Поток

1

1

200

2

300

1

Поток 2

1

1

Ьanlt. transfer ( l, 2, 300)
bank . wai t ()

1

bank . transfer (2 , 1 , 400)
1

bank. wai t ()

1

Рис.

12.it. Ситуация

взаимной блокировки

Другой способ создать взаимную блокировку

-

сделать i-й ноток исполнения

ответс1·венным за размещение денеж11ых средсп1 на i-м счете вмесrо их снятия с

i-ro

счета. В этом случае имеется вероятность, что все потоки исполнения набросятся
на один и тот же счет, и в каждом из них будет предпринята попытка с11ять дены·и

с этого счета. Попробуйте смоделировать подобную ситуацию. Для этого обратитесь
в программе

методу

метода

SynchBankTest к
trans fer ( ) поменяйте

местами параметры

run ()

из класса TransferRun naЫ e, а в вызове

f romAccount

и

t oAccount.

Посл е

запуска про1раммы вы почти сразу же обнаружите взаимную блокировку.
Взаимная блокировка может легко во:шикнуть и в том случае, если заменить метод

signalAll ()

на

signal ()

в программе

SynchBankTes t .

Сделав это, ны обнаружите,

что программа в конечном итоге зависнет. (И в этом случае лучше усrановить значение

10

консrанты

NACCOUNTS, чтобы как можно скорее добиться желаемого результата.) В
signalAll (), который извещает все потоки, ожидающие пополне­
метод signa l () разблокирует только один поток исполнения. Если этот по­

отличие от метода

ния счета,

ток не может продолжить свое исполнение, то все потоки могут оказаться заблокиро­
ванными. Рассмотрим следующий проСI'Ой сценарий создания взаимной блокировки .

1.

Счет

2.

Все прочие счета: сумма

1: 1990 долл.
990

3.

Поток

4.

Все про•~ие потоки : переводят сумму

1:

переводит сумму

долл. на каждом.

995 долл.

со счета

1

995 долл.

11а счет

2.

со своего счета на другой счет.

12.4.

Синхронизация



Dvoм•• / Memory Тhre~d•

"."1

1- - - - - - - - - -- - - - - - - - - - - - -- - - - - - -

~ '~di·····

25

20
13 :52

"" Nam@'. Тhread·O

Stat~ : \'/дmNG on B•n @•О•9~
Totol Ыocked . 2 Totol wa~ed' 59

~~з}i.z~r

S19nol D•spatcher

stock trace
1ava lan9.0b1oct.wait(t«>t111• Method )
1;r.;1 lon9 Ob 1 oct . ~•~ ( Ob1oct . J•ll8 485)

Threoo·l
Тhrt•d·2
'П>rtad · 3

Bank~ransfer{Ban~.1"'·"' : 29j

Тhre ~.4

TransferRunnaЬl• . run( TrensfttRunnab l • ./ itVili 2~ }
1ava lan9 .Тhread run (Тhr•ad .1 ";a:бlg )

Throod-5

Threod·5

[ Q•toct Dtodlock}
pid: 553 SynchBanl