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

Java. Библиотека профессионала, том 2. Расширенные средства программирования [Кей С. Хорстманн] (pdf) читать онлайн

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


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

БИБЛИОТЕКА ПРОФЕССИОНАЛА
Том

2.

Расширенные средства
программирования

ОДИННАДЦАТОЕ ИЗДАНИЕ

Java®
Библиотека профессионала
Том 2. Расширенные средства программирования

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

Core Java®
Volume 11 - Advanced Features

Eleventh Edition
Сау

S. Horstmann

е

Pearson
Boston • Columbus • lndianapolis • 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

Библиотека профессионала
Том 2. Расширенные средства
программирования

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

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

Москва

• Санкт-Петербург

2020

ББК

32.973.26-018.2.75
Х82

у дк

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

Зав. редакцией С.Н. Tpillljб
Перевод с английского и редакция И.В. Берштеl1нп

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

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

Java.

Библиотека

профессионала,

11ро1раммирования, 11-е изд.: Пер. с англ.

864

том

-

2.

Расширенные

средства

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

2020.

с.: ил.

- Парал. тит. англ.
SBN 978-5-907144-38-5 (рус., том 2)
ISBN 978-5-907144-30-9 (рус., многотом)
ББК

32.973.26-018.2.75

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

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

ские, вклю•1ая фотокопирование и зашкь на мапштный носитель, если на это нет письменного
разре111ею1я 11злател1,ства l'reпtice Hall, lпс.

Copyright © 2020 Ьу Dialektika Computer PuЬlisl1iпg Ltd.
Authorized J~ussiaп traпslatioп of the Eпglish editioп of Core fащ, Volume II: AdPm1ced Features,
11 th Editioп (ISBN 978-0-13-516631-4) © 2019 Pearsoп Educatioп !пс.
Portioпs copyright © 1996-2013 Oracle aпd/or its affiliates. All Rigl1ts Reserved.
This traпslatioп is puЬlished апd sold Ьу permissioп of Pearsoп Educatioп !пс" which owпs or
coпtrols all rights to puЫish апd sell the same.
All rights reserved. No part of this book may Ье reproduced or transmitted iп any form or Ьу any
meaпs, electroпic or mechanical, iпcludiпg photocopyiпg, recordiпg, or Ьу any information storage
or retrieval system, witlюut the prior written permission of the copyright owner and the PuЬlisher.
Научно-11011у.\ярное l13дан ие
Кей С. Хорстманн

Java.

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

2

Расширенные средства программирования
11-е издание
Под1111са1ю в печать

27.11.2019.

Формат 70х100/16.

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

69,66. Уч.-изд. л. 45,4.
300 экз. :~аказ № 10558.

Опю•1атано в АО "Первая Обра:щовая типография"
Филиал "Чеховск11i1 Печатный Двор"

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

ISBN 978-5-907144-38-5
ISBN 978-5-907144-30-9
ISBN 978-0-13-516631-4

195027,

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

30,

лит. А, пом.

(рус" том 2)
(рус., многотом)

©

(ашл.)

© Pearson Education Inc" 2019

848

ОСЮ "Диалектика", 2020,
перевод, оформление, макетирование

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

13

Глава

1. Потоки данных

19

Глава

2.

71

Глава

3. XML

Глава

4.

Работа в сети

235

Глава

5.

Работа с базами данных

287

Глава

6.

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

Глава

7.

Интернационализация

377

Глава

8.

Написание сценариев, компиляция и обработка аннотаций

435

Глава

9. Модульная система

Глава

1О.

Глава

11. Расширенные средства Swing

Глава

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

Ввод и вывод

163

API даты

и времени

на платформе

Java

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

Предметный указатель

353

493
521

и графика
методы

601
787
849

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

13

К читателю

От издательства

13
13
16
16
16
18

Глава

19

Краткий обзор книги
Условные обозначения
Примеры исходного кода
Благодарности

1.

Потоки данных

1.1. От итерации к потоковым операциям
1.2. Создание потока данных
1.3. Методы filter (), map () и flatMap ()
1.4. Извлечение подпотоков и объединение потоков данных
1.5. Другие операции преобразования потоков данных
1.6. Простые методы сведения
1.7. Тип Optional
1.7.1. Получение необязательных значений
1.7.2. Употребление необюательных значений
1.7.3. Конвейеризация необязательных значений
1.7.4. Как не следует обрабатывать необязательные значения
1.7.5. Формирование необязательных значений
1.7.6. Сочетание функций необязательных значений с методом flatMap ()
1.7.7. Преобра:ювание типа Optional в поток данных
1.8. Накопление результатов
1.9. Накопление результатов в отображениях
1.10. Группирование и разделение
1.11. Нисходящие коллекторы
1.12. Операции сведения
1.13. Потоки данных примитивных типов
1.14. Параллельные потоки данных

20
22
28
30
31
32
34
34
35
36
37
38
38
39
42
47
51
52
57
60
65

Глава

71

2.

Ввод и вывод

2.1. Потоки ввода-вывода
2.1.1. Чтение и запись байтов
2.1.2. Полный комплект потоков ввода-вывода
2.1.3. Сочетание фильтров потоков ввода-вывода
2.1.4. Ввод-вывод текста
2.1.5. Вывод текста
2.1.6. Ввод текста

71
72
75
79
82
83
85

Содержание

2.1.7. Сохранение объектов в текстовом формате
2.1.8. Кодировки символов
2.2. Чтение и запись двоичных данных
2.2.1. Интерфейсы Datalnput и DataOutput
2.2.2. Файлы с произвол1,ным доступом
2.2.3. ZIР-архивы
2.3. Потоки ввода-вывода и сериализация объектов
2.3.1. Сохранение и загрузка сериализируемых объектов
2.3.2. Представление о формате файлов для сериализации
2.3.3. Видоизменение исходного механизма сериализации
2.3.4. Сериализация одноэлементных множеств

объектов

86
90
92
92
95
99
102
102
107
113

2.3.5. Контроль версий
2.3.6. Применение сериализации для клонирования
2.4. Манипулирование файлами
2.4.1. Пути к файлам
2.4.2. Чтение и запись данных в файлы
2.4.3. Создание файлов и каталогов
2.4.4. Копирование, перемещение и удаление файлов
2.4.5. Получение сведений о файлах
2.4.6. Обход элементов каталога
2.4.7. Применение потоков каталогов
2.4.8. Системы ZIР-файлов
2.5. Файлы, отображаемые в памяти
2.5.1. Эффективность файлов, отображаемых в памяти
2.5.2. Структура буфера данных
2.6.3. Блокирование файлов
2.6. Peryлярные выражения
2.7.2. Совпадение со строкой
2.7.3. Обнаружение многих совпадений
2.7.4. Разбиение строк по разделителям
2.7.5. Замена совпадений

115
116
119
121
121
124
125
126
128
130
132
135
136
136
144
146
148
153
157
159
159

Глава З.

163

и типизированных перечислений

XML

3.1. Введение в ХМL
3.2. Струкrура ХМL-документа
3.3. Синтаксический анализ ХМL-документов
3.4. Проверка достоверности ХМL-документов
3.4.1. Определения типов докумеtпов
3.4.2. Схема ХМL-документов
3.4.3. Практический пример применения ХМL-документов
3.5. Поиск информации средствами XPath
3.6. Использование пространств имен
3.7. Потоковые синтаксические анализаторы
3.7.1. Применение SАХ-анализатора
3.7.2. Применение StАХ-анализатора
3.8. Формирование ХМL-документов
3.8.1. ХМL-документы без пространств имен
3.8.2. ХМL-документы с пространствами имен

164
166
169
178
180
187
190
196
201
204
204
209
213
214
214

Содержание

3.8.3. Запись ХМL-документов
3.8.4. Запись ХМL-документов средствами StAX
3.8.5. Пример формирования файла 11 формате SVG
3.9. Преобразование ХМL-документов языковыми средствами XSLT

215
217
222
224

Глава

235

4.

Работа в сети

4.1. Подключение к серверу
4.1.1. Применение утилиты telnet
4.1.2. Подключение к серверу из программы на Java
4.1.3. Время ожидания для сокетов
4.1.4. Межсетевые адреса
4.2. Реализация серверов
4.2.1. Сокеты сервера
4.2.2. Обслуживание многих клиентов
4.2.3. Полузакрьпие
4.2.4. Прерываемые сокеты
4.3. Получение данных из Интернета
4.3.1. URL и URI
4.3.2. Извлечение данных средствами класса URLConnection
4.3.3. Отправка данных формы
4.4. НТТР-клиент
4.5. Отправка электронной почты

235
235
238
240
241
243
243
247
250
251
258
258
260
267
276
283

Глава

287

5.

Работа с базами данных

5.1. Структура JDBC
5.1.1. Типы драйверов JDBC
5.1.2. Типичные примеры применения JDBC
5.2. Язык SQL
5.3. Конфигурирование JDBC
5.3.1. URL баз данных
5.3.2. Архинные JАR-файлы драйверов
5.3.3. Запуск ба:ш данных
5.3.4. Регистрация класса драйвера
5.3.5. Подключение к базе данных
5.4. Работа с операторами JDBC
5.4.1. Выполнение операторов SQL
5.4.2. Управление подключениями, операторами
и результирующими наборами

5.4.3. Анализ исключе11ий SQL
5.4.4. Запол11ение базы да11ных
5.5. Выполнение запросов
5.5.1. Подготовленные операторы для запросов
5.5.2. Чте11ие и запись больших объектов
5.5.3. Синтаксис переходов в SQL
5.5.4. Множественные результаты
5.5.5. Извлечение автоматически генерируемых ключей
5.6. Прокручиваемые и обновляемые результирующие наборы
5.6.1. Прокручиваемые результирующие наборы
5.6.2. Обновляемые результирующие наборы

288
288
290
291
296
296
297
297
298
299
302
302
305
306
309
312
313
320
321
323
324
324
325
327

Содержание

5.7. Наборы строк
5.7.1. Построение наборов строк
5.7.2. Кешируемые наборы строк
5.8. Метаданные
5.9. Транзакции
5.9.1. Программирование транзакций средствами JDBC
5.9.2. Точки сохранения
5.9.3. Групповые обновления
5.9.4. Расширенные типы данных SQL
5.10. Управление подключением к базам данных в неб-приложениях
и корпоративных приложениях

Глава

6.1.
6.2.
6.3.
6.4.
6.5.
6.6.
6.7.

6.

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

API даты

350
и времени

Временная шкала
Местные даты

Корректоры дат
Месrное время
Поясное время
Форматирование и синтаксический анализ даты и времени
Взаимодействие с унаследованным кодом

Глава

7.

331
331
332
335
345
345
346
346
349

Интернационализация

7.1. Региональные насrройки
7.1.1. Назначение региональных настроек
7.1.2. Указание региональных настроек
7.1.3. Региональные настройки по умолчанию
7.1.4. Огображаемые имена
7.2. Форматирование чисел
7.2.1. Форматирование числовых значений
7.2.2. Форматирование денежных сумм в разных валютах
7.3. Форматирование даты и времени
7.4. Сортировка и нормализация
7.5. Форматирование сообщений
7.5.1. Форматирование чисел и дат
7.5.2. Форматы выбора
7.6. Ввод-вывод тексrа
7.6.1. Текстовые файлы
7.6.2. Окончания строк
7.6.3. Консольный ввод-вывод
7.6.4. Протокольные файлы
7.6.5. Огметка порядка следования байтов в кодировке UTF-8
7.6.6. Кодирование символов в исходных файлах
7.7. Комплекты ресурсов
7.7.1. Обнаружение комплектов ресурсов
7.7.2. Файлы свойств
7.7.3. Классы комплектов ресурсов
7.8. Пример интернационализации прикладной программы

353
354
358
363
364
366
370
375

377
378
378
379
381
382
384
384
390
392
400
407
407
409
411
411
411
412
413
413
414
414
415
416
417
419

Содержание

Глава

8.

Написание сценариев, компиляция и обработка аннотаций

8.1. Написание сценариев для платформы Java
8.1.1. Получение интерпретатора сценариев
8.1.2. Выполнение сценарие11 и привязки
8.1.3. Переадресация 111юда-11ывода
8.1.4. Вы:юв функций и методов из сценариев
8.1.5. Компиляция сце11арие11
8.1.6. Пример со:ца11ия сце11'1рия для обработки

436
436
437

439
440
442
событий

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

8.2. Прикладной интерфейс API для компилятора
8.2.1. Вызов компилятора
8.2.2. Запуск заданий 11а компиляцию
8.2.3. Фиксация диагностики
8.2.4. Чтение исходных файлов из оперативной памяти
8.2.5. Запись байт-кодов в оперативную память
8.2.6. Пример динамического генерирования кода Java
8.3. Применение аннотаций
8.3.1. Введение в аннотации
8.3.2. Пример аннотирования обработчиков событий

8.5.

9.

449
450
450
451

453
459
460

Аннотирование объявлений

470

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

471

Аннотирование по ссылке

472

this

473

8.5.1. Аннотации для компиляции
8.5.2. Аннотации для управления ресурсами
8.5.3. Мета-аннотации
8.6. Обработка аннотаций на уровне исходного кода
8.6.1. Процессоры аннотаций
8.6.2. Прикладной интерфейс АР\ модели языка
8.6.3. Генерирование исходного кода с помощью аннотаций
8.7. Конструирование байт-кодов
8.7.1. Модификация файлов классов
Глава

448

Объявление аннотаций

Стандартные аннотации

8.7.2.

443
448

461
466
466
468

Синтаксис аннотаций
8.4.1. Интерфейсы а1111отаций

8.4.

8.4.2.
8.4.3.
8.4.4.
8.4.5.

435

474
475
476
478

479
479
480
483

483

Модификация байт-кодов во время загрузки

489

Модульная система на платформе

493

Java

494

9.1.
9.2.
9.3.

Понятие модуля

9.4.
9.5.

Требования модулей

498

Экспорт пакетов

500
503
505
508
510

Име11011ание модулей
Пример модульной программы

495

"Hello, Modular World!"

9.6. Модульные архивные JАR-файлы
9.7. Модули и рефлексивный доступ

9.8.
9.9.

Автоматические модули
Безымянные модули

496

Содержание

9.10.
9.11.
9.12.
9.13.
9.14.

Параметры командной сrроки для переноса прикладного кода
Переходные и сrатические требования
Уточненный экспорт и открытие модулей
Загрузка служб

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

Глава

1О.

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

521

10.1. Загрузчики классов
10.1.1. Процесс загрузки классов
10.1.2. Иерархия загрузчиков классов
10.1.3. Применение загрузчиков классов в качестве
10.1.4. Создание собственного загрузчика классов
10.1.5. Верификация байт-кода
10.2. Диспетчеры защиты и полномочия
10.2.1. Проверка полномочий
10.2.2. Организация защиты на платформе Java
10.2.3. Файлы правил защиты
10.2.4. Специальные полномочия
10.2.5. Реализация класса полномочий
10.3. Аутентификация пользователей
10.3.1. Каркас JAAS
10.3.2. Модули регистрации JAAS
10.4. Цифровые подписи
10.4.1. Свертки сообщений
10.4.2. Подписание сообщений
10.4.3. Верификация подписи
10.4.4. Проблема аутентификации
10.4.5. Подписание сертификатов
10.4.6. Запросы сертификатов
10.4.7. Подписание кода
10.5. Шифрование
10.5.1. Симметричные шифры
10.5.2. Генерирование ключей шифрования
10.5.3. Потоки шифрования
10.5.4. Шифрование открытым ключом
Глава

11. Расширенные средства Swing

511
512
514
514
517

пространств имен

и графика

11.1. Таблицы
11.1.1. Проста я таблица
11.1.2. Модели таблиц
11.1.3. Манипулирование строками и столбцами
11.1.4. Воспроиз11едение и редактиро11ание ячеек
11.2. Деревья
11.2.1. Простые деревья
11.2.2. Перечисление узлов дерева
11.2.3. Воспроизведение узлов дерева
11.2.4. Обработка событий в деревьях
11.2.5. Специальные модели деревье11

522
522
523
526
526
532
536
536
538
542
548
549
555
555
561
570
571
574
577
580
582
584
585
587
588
589
595
596

601

таблицы

601
602
606
610
626
639
640
657
659
662
669

Содержание

11.3. Расширенные средства AWT
11.3.1. Конвейер визуализации
11.3.2. Фигуры
11.3.3. Участки
11.3.4. Обводка
11.3.5. Раскраска
11.3.6. Преобразование координат
11.3.7. Отсечение
11.3.8. Прозрачность и композиция
11.4. Растровые изображения
11.4.1. Чтение и запись изображений
11.4.2. Манипулирование изображениями
11.5. Вывод изображений на печать
11.5.1. Вывод графики на печать
11.5.2. Многостраничная печать
11.5.3. Службы печати
11.5.4. Потоковые службы печати
11.5.5. Атрибуты печати

678
678
681
697
699
707
709
714
717
726
726
737
753
753
763
773
776
779

Глава

787

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

12.1. Вызов функции на С из программы на Java
12.2. Числовые параметры и возвращаемые значения
12.3. Строковые параметры
12.4. Доступ к полям
12.4.1. Доступ к полям экземпляра
12.4.2. Доступ к статическим полям
12.5. Кодирование сигнатур
12.6. Вызов методов на Java
12.6.1. Методы экземпляра
12.6.2. Статические методы
12.6.3. Конструкторы
12.6.4. Альтернативные вызовы методов
12.7. Доступ к элементам массивов
12.8. Обработка ошибок
12.9. Применение прикладного интерфейса API для вызовов
12.10. Практический пример обращения к реестру Windows
12.10.1. Общее представление о реестре Windows
12.10.2. Интерфейс для доступа к реестру на платформе Java
12.10.3. Реализация функций доступа к реестру
в виде платформенно-ориентированных методов

Предметный указатель

788
794
796
803
803
807
808
810
810
811
812
812
816
820
825
831
831
832
833

8,9

Предисловие

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

Java,

Java 11.

В первом томе рассматри­

а в этом томе речь пойдет о расширен­

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

Java

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

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

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

В главе

1

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

Java,

придающая

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

особые преимущества при оптимизации параллельных вычислений.
Глава

2

посвящена организации ввода-вывода. В языке

Java

весь ввод-вывод

осуществляется через так называемые потоки ввода-вывоiJа (не путать с потоками
данных, рассматриваемыми в главе

1). Такие

потоки позволяют единообразно об­

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

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

Предисловие
этой главы будут представлены долгожданные усовершенствования системы в1ю­
да-вывода в последних версиях
Основной темой главы

3

Java.

является

XML.

В ней показывается, каким образом

осуществляется синтаксический анализ ХМL-файлов, формируется ХМL-размет­
ка и выполняются ХSL-преобразования. В качестве примера демонстрируется

разметка компоновки Swing-фopмы в формате
ется также прикладной интерфейс

API XPath,

XML.

В этой главе рассматрива­

значител1,но упрощающий поиск

мелких подробностей в больших объемах данных формата

XML.
4 рассматривается прикладной интерфейс API для работы в сети.
Java чрезвычайно просто решаются сложные задачи сетевого програм­

В главе
В языке

мирования. В этой главе показывается, как устанавливаются сетевые соединения
с серверами, реализуются собственные серверы и организуется связь по сетевому
протоколу НТГР. Здесь также описывается новый НТГР-клиент.

5 посвящена программированию баз данных. Основное внимание в ней
JDBC - прикладному интерфейсу для организации доступа к базам дан­
приложений на Java, который позволяет прикладным программам на Java

Глава

уделяется
ных из

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

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

JDBC.

(Для рассмотрения всех средств интерфейса

JDBC

потребовалась бы отдель­

ная книга почти такого же объема, как и эта.) И в завершение главы приводятся

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

JNDI (Java Naming and Directory Interface - ин­
Java) и протоколе LDAP (Lightweight Directory

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

Access Protocol -

упрощенный протокол доступа к каталогам).

Ранее в библиотеках

Java

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

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

Java 8.

Поэтому в главе

6 поясняется,

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

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

В главе

7

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

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

Java

относится к тем немно­

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

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

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

грамм на

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

В главе

8

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

прикладные интерфейсы

API для сценариев и компилятора дают возможность
Java код, написанный на каком-нибудь языке сценариев,
например JavaScript или Groovy, и компилироват1, его в код Java. Аннотации по­
зволяют вводить в программу на Java произволшую информацию (иногда еще на­
вызывать в программе на

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

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

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

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

В rлаве
сии
мы

9 описывается модульная система на платформе java, внедренная в вер­
Java 9 для того, чтобы способствовать нормальной эволюции самой платфор­
и базовых библиотек Java. Эrа модульная система обеспечивает инкапсуляцию

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

long count = words.stream()
.filter(w -> w.length() > 12)
.count();
В последнем случае не нужно искать в цикле наглядного подтверждения опе­

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

Достаточно заменить метод

stream ()

на метод

parallelStream (),чтобы

ор­

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

операций фильтрации и подсчета слов, как показано ниже.

long count = words.parallelStream()
.filter(w -> w.length() > 12)
. count ();
Потоки данных действуют по принципу "что, а не как делать". В рассматрива­
емом здес1, примере кода мы описываем, что нужно сделать: получить длинные

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

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

1.1.

1.

От итерации к потоковым операциям

Поток данных не сохраняет свои элементы. Они могуг храниться в основной

коллекции или формироваться по требованию.

2.

Потоковые операции не изменяют их источник. Например, метод

fil ter ()

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

3.

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

всех слов, метод

fil ter ()

прекратит фильтрацию после пятого совпаде­

ния. Следовательно, потоки данных могуг быть бесконечными!
Вернемся к предыдущему примеру, чтобы рассмотреть его подробнее. Мето­

ды

stream () и parallelStream () выдают поток данных для списка слов words.
fil ter () возвращает другой поток данных, содержащий только тесло­
длина которых больше 12 букв. И, наконец, метод count () сводит этот поток

А метод
ва,

данных в конечный результат.

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

1. Создание потока данных.

2.

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

3.

-

возможно, в несколько этапов.

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

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

parallelStream

().Метод

1.1 поток данных создается
fil ter () преобразует его, а

методом
метод

полняет оконечную операцию.

Листинг

1
2
3
4
5

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

streams/CountLongWords. java

package streams;
/**
* @version 1.01 2018-05-01

* @author

Сау

Horstmann

*/

6
7

8
9
10
11
12
13
14
15
16
17
18

1.1.

import
import
import
import

java.io.*;
java.nio.charset.*;
java.nio.file.*;
java.util.*;

puЫic

class CountLongWords

{
puЫic

static void main(String[] args)
throws IOException

var contents = new String(Files.readAllBytes(

stream()
count () вы­

Глава

19
20
21

Потоки данных

1 •

Paths.get(" .. /gutenberg/al1ce30.txt")),
StandardCharsets.UTF 81;
List words = List.of(contents.split("\\PL+"));

22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

long count = О;
for (String w : words)
{
if (w.leпgth() > 12) count++;
Systern.out.println(count);
count = words.strearn()
.filter(w -> w.length() > 12) .count();
Systern.out.priпtlп(couпt);

count

=

words.parallelStrearn()
.filter(w -> w.length() > 12) .count();

Systern.out.println(couпt);

В следующем разделе будет показано, как создается поток данных. В трех по­

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

-

оконечные операции.

java.util.stream.Stream 8


Stream filter(Predicate дерево модели

DOM,

представляющей документ,

а затем вывести его содержимое по месту назначения. Подроб11ости такого под­
хода к формированию ХМL-документов обсуждаются в последующих разделах.

З.8.1. ХМL-документы без пространств имен
Чтобы построиП> древовидную структуру

DOM,

11ать сначала пустой документ с помощью метода

DocumentBuilder
Document doc

необходимо сформироиз класса

newDocument ()

следующим образом:

builder.newDocurnent();

=

Затем следует вызвать метод

createElement ()

и;1 класса

Document,

чтобы по­

строить элементы документа, как пока:1а1ю ниже.

Element rootElement = doc.createElement(rootName);
Element childElement = doc.createElement(childName);
Далее создаются текстовые у:1лы с помощью метода create'Гext.Node

Text textNode

=

():

doc.createTextNode(textContents);

З.8.2. ХМL-документы с пространствами имен
Если используются пространства имен, то процедура формирования ХМL-до­
кумента ока:швается несколько иной. Сначала фабрика построителей докумен­
тов устанавливается в режим управления пространствами имен, а затем создает-

01

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

DocumentBuilderFactory factory =
DocurnentBuilderFactory.newinstance();
factory.setNamespaceAware(true);
builder = factory.newDocumentBuilder();
Далее для со:1дания любых узлов вместо метода
метод

createElement ()

вызывается

createElementNS ():

String namespace = "http://www.wЗ.org/2000/svg";
Element rootElement = doc.createElementNS(namespace, "svg");
Если узел имеет уточненное имя с префиксом пространства имен, то любые
требующиеся атрибуты с префиксом
требуется ввести данные формата

SVG

xmlns

создаются автоматически. Так, если

в ХНТМL-документ, для этой цели можно

построить соответствующий элемент аналогично приведенному ниже.

Element svgElement = doc.createElement(namespace, "svg:svg")
Когда этот элемент записывается, он превращается в следующий элемент раз­
метки:



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

setAttributeNS ()

Element:

rootElement.setAttributeNS(namespace, qualifiedName, value);

3.8.

Формирование ХМL-документов

З.8.З. Запись ХМL-документов
Как ни странно, записать дерево модели

DOM

в поток вывода не так-то просто.

Для этой цели проще всего воспользоваться прикладным интерфейсом

XSLT

(ExtensiЬ\e

зования

Stylesheet Language Transformations ХМL-документов). Более подробно язык XSLT

API

языка

расширяемый язык преобра­
рассматривается в последнем

разделе этой главы, а до тех пор допустим, что приведенный ниже код каким-то

волшебным образом позволяет получить даш1ые, выводимые в формате

XML.

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

11

построить

объект холостого преобразования:

t =

Traпsformer

DOCTYPE в выводимые данные, сле­
SYSTEM и PUBLIC в качестве свойств вывода.

TraпsformerFactory.пewiпstaпce()

.пewTraпsformer();

11 установить свойства вывода, чтобы получить узел DOCTYPE:
t.setOutputProperty(OutputKeys.DOCTYPE SYSTEM,
systemidentif ier) ;
t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
puЫicideпtifier);

!/ установить отступ:
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty(OutputKeys.METHOD, "xml");
t.setOutputProperty(
"{http: / /xml. apache. org/xsl t) indent-amount", "2") ;
11 выполнить холостое преобразование и вывести
11 результат в файл:
t.transform(new DOMSource(doc),
пеw StreamResult(new FileOutputStream(file) ));
Еще один способ :ыписи ХМL-документов состоит в применении юттерфей­
са

LSSerializer. Для получения экземпляра класса, реализующего этот интер­

фейс, служит следующий фрагмент кода:

DOMimplementation impl = doc.getlmplemeпtation();
DOMimplemeпtationLS implLS = (DOMimplementationLS)
impl.getFeature("LS", "3.0");
LSSerializer ser = implLS.createLSSerializer();
Если требуется ввести пробелы и разрывы строк в документ, достаточно уста­
новить следующий флаг:

ser. getDomConf ig () . set Parameter ( "f ormat-pretty-pr int",
true);
И тогда преобразован документ в символьную строку не составит особого
труда:

String str

=

ser.writeToString(doc);

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

LSOutput следующим образом:

LSOutput out = implLS.createLSOutput();
out.setEncoding("UTF-8");

Глава Э

• XML

out.setByteStream(Files.newOutputStream(path) );
ser.write(doc, out);

javax.xml.parsers.Documentвuilder



1.4

Document newDocument()
Возвращает пустой документ.

org.wЗc.dom.Document

1.4

Element createElement (String

namв)

Element createElementNS (String uri,

String qname)

Создают элемент с заданным именем.



Text createTextNode (String data)
Создает текстовый узел с указанными данными.

org.wЗc.dom.Node



1.4

Node appendChild (Node child)
Присоединяет узел к списку его дочерних узлов. Возвращает присоединенный узел.

org.wЗc.dom.Element



void

setAttriЬute



void

setAttriЬuteNS

1.4

(String

namв,

String value)

(String uri, String

qnamв,

String value)

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

uri

должен принимать пустое

null.

javax.xml.transform.TransformerFactory 1.4


static TransformerFactory newlnstance()



Transformer newTransformer()

Возвращает экземпляр класса

TransformerFactory.

Возвращает экземпляр класса Transformer, выполняющий тождественное [холостое! пре­
образование, не предполагающее никаких действий.

3.8.

Формирование ХМL-документов

javax.xml.transform.Transformer 1.4
void setOutputProperty(String name, String value)



Задает свойство вывода. Перечень этих свойств можно найти по адресу
wЗс.

org/TR/xslt#output.

https: /

/www.

Ниже перечислены наиболее употребительные свойства вы­

вода.

doctype-puЬlic

Идентификатор PUВLIC, используемый в объявлении

DOCTYPE

doctype-system

Идентификатор SYSTEМ, используемый в объявлении

DOCTYPE

indent

Принимает значение

"yes" или "no"

method

Принимает значение

"xml", "html", "text"

или специальное строковое



значение

void transform(Source from, Result to)
Выполняет преобразование ХМL-документа.

javax.xml.transform.dom.DOMSource 1.4
DOМSource



(Node n)

Создает источник данных из заданного узла. Обычно параметр

n

обозначает узел документа.

javax.xml.transform.stream.StreamResult 1.4


StreamResult(File f)



StreamResult(OutputStream out)



StreamResult (Writer out)



StreamResult(String systemID)
Создают поток вывода результатов преобразования на основе указанного файла, потока вы­

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

3.8.4.

URL].

Запись ХМL-документов средствами

StAX

В предыдущем разделе было показано, как ХМL-документ формируется пу­
тем записи дерева модели

DOM.

Но если дерево модели

DOM

нигде больше не

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

StAX API

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

непосредственно в формате

XMLStreamWri ter

XML.

Для этого следует создать поток записи типа

из потока вывода типа

OutputStream,

как показано ниже.

XMLOutputFactory factory = XMLOutputFactory.newinstance();
XMLStreamWriter writer = factory.createXMLStreamWriter(out);
Для того чтобы создать и вывести заголовок ХМL-документа, необходимо вы­
звать сначала следующий метод:

writer.writeStartDocument()

Глава Э •

XML

а затем метод

writer.writeStartElement(name);
Далее, для вывода атрибуто11 следует вызвать приведенный ниже метод.

writer.writeAttribute(name, value);
Теперь можно вывести дочерние элементы разметки, снова вызвав метод

wri teStartElement

(),или записать символы, вызвав следующий метод:

writer.writeCharacters(text);
После записи всех дочерних узлов следует вызвать приведенный ниже метод,
который закроет текущий элемент разметки.

writer.writeEndElementll;
Чтобы :~аписать элемент ра:1метки без дочерних элементов (например, эле­
мент





Har ry Hac ker
SOOOO< / salary>



Tony Tes t er
40000

3.9.

Преобразование ХМL-документов языковыми средствами



Из этой ра:шетки желательно получить следующую НТМL-таблицу:

border="l">

Carl Cracker$75000.0l987-12-15


Harry Hacker$50000.01989-10-1


Tony Tester$40000.01990-3-15

0) path = Paths.get(args[OJ);
else path = Paths. get ( "transform", "makel1tml. xsl");
try (InputStream styleln =
Files.newinputStream(path))
var styleSource = new StreamSource(stylein);
Transformer t = TransformerFactory.newinstance()
.newTransformer(styleSource);
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty(OutputKeys.METHOD, "xml");
t.setOutputProperty("{http://xml.apache.org/xslt)"
+ "indent-amount", "2");
try (InputStream docln = Files.newinputStream(
Paths.get("transform", "employee.dat")))
t.transform(new SAXSource(new EmployeeReader(),
new InputSource(docin) ),
new StreamResult(System.out));

/**

Э. 9. Преобразование ХМL-документов языковыми средствами

55
56
57
58
59
60
61
62
63
64

65
66
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

*
*

Этот

класс

уведомляет

читает

о

однородньм

событиях

в

файл

employee.dat

SАХ-анализаторе,

и

как будто

* он сам выполнил синтаксический анализ ХМL-документа
*/
class EmployeeReader implemeпts XMLReader

private
puЫic

CoпtentHandler

handler;

void parse(InputSource source)
throws IOException, SAXException

InputStream stream = source.getByteStream();
var in = new BufferedReader(
new InputStreamReader(stream));
String rootElement = "staff";
var atts = new Attributeslmpl();
if (handler
null)
throw new SAXException 1"No content handler");
handler.startDocument();
handler.startElement("", rootElement,
rootElement, atts);
String line;
while ( ( line
in.readLine()) != null)
{

handler. startElement ( "", "employee",
"employee", atts 1;
var t = new StringTokenizer(line, "1");
handler.startElement("", "name", "name", atts);
String s = t.nextToken();
handler.characters(s.toCharArray(), О,
s. length 1) ) ;
handler. endElement ("", "name", "name");
handler. startElement {"", "salary", "salary",
atts);
s = t.nextToken();
handler.characters(s.toCharArray(), О,
s.length());
handler. endElement (" ", "salary", "salary");

97

98
99
100
101
102
103
104
105
106
107
108
109
110

atts.addAttribute("", "year", "year", "CDATA",
t.nextToken());
atts.addAttribute("", "month", "month", "CDATA",
t.nextToken());
atts.addAttribute("", "day", "day", "CDATA",
t.nextToken() 1;
handler.startElement("", "hiredate", "hiredate",
atts);
handler.endElement("", "hiredate", "hiredate");
atts.clear();
handler.eпdElement("",

"employee", "employee");

XSLT

Глава

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

3 • XML

handler. endElement ( "", rootElement, rootElement) ;
handler.endDocument();

puЫic

void setContentHandler(ContentHandler newValue)

{
handler = newValue;

puЫic

ContentHandler getContentHandler()

{
return handler;

//

следующие методы являются

всего лишь

//холостыми реализациями
puЫic
puЫic
puЫic

puЫic
puЫic
puЫic
puЬlic

puЫic
puЬlic
puЫic
puЫic

void parse(String systemid)
throws IOException, SAXException {)
void setErrorHandler (ErrorHandler handler) {)
ErrorHandler getErrorHandler () { return null;
void setDTDHandler (DTDHandler handler) {)
DTDHandler getDTDHandler () { return null; )
void setEntityResolver(
EntityResolver resolver) {)
EntityResolver getEntityResolver()
{ return null; )
void setProperty (String name, Object value) {)
Object getProperty(String name)
{ return null; )
void setFeature (String name, boolean value) {)
boolean getFeature(String name)
{ return false; )

javax.xml.transform.TransformerFactory 1.4


Transformer newTransformer ( Source
Возвращает экземпляр класса

stylвShввt)

Transformer,

считывающий таблицу стилей из указанного

источника.

javax.xml.transform.stream.StreamSource 1.4
Stream.Source (File f)


Stream.Source(InputStream in)



Stream.Source (Reader in)



Stream.Source (String systemID)
Создают потоковый источник данных из указанного файла, потока ввода, потока чтения или

системного идентификатора !обычно это относительный или абсолютный

URLJ.

З. 9. Преобразование ХМL-документов языковыми средствами

XSLT

javax.xml.transform.sax.SAXSource 1.4


SAXSource (XМLReader reader,

InputSource source)

Создает SАХ-источник, получающий вводимые данные из указанного источника, используя
заданный поток чтения для синтаксического анализа данных.

org. xml. sax. XМLReader 1 . 4


void

setContentнandler(ContentHandler

handler)

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



void parse (InputSource source)
Анализирует данные, вводимые из указанного источника, и передает обработчику содержи­

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

javax.xml.transform.dom.DOМResult



DOМResul t

1.4

(Node n)

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

n

указывается

узел документа.

org.xml.sax.helpers.Attributesimpl 1.4


void addAttribute(String uri,
type, String value)

String lname,

Вводит атрибут в коллекцию атрибутов. В качестве параметра
имя без префикса, а в качестве параметра
метр

type

"NМТОКЕN", "NМТОКЕNS",

"ENTITY",

String

lname указывается локальное

уточненное имя с префиксом. Пара­

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

"IDREFS",


qname -

String qname,

"CDATA", "ID", "IDREF",
"NOTATION".

"ENТITIES" ИЛИ

void clear ()
Удаляет все атрибуты из данной коллекции.

Этим примером завершается обсуждение особенностей поддержки

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

блиотеке
стях

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

XML

XML.

Для этого

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

Глава

3 • XML

потребностям, сохранят~, устойчиность с течением времени, а наши деловые
партнеры быть готовыми принимать от вас ХМL-документы. Решение всех этих
вопросов может оказат1,ся гораздо сложнее, чем умелое обращение с синтакси­
ческими анализаторами, определениями ОТО или преобра:юваниями, ныполня­

емыми средствами

XSLT.

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

Java.

Сначала мы рассмотрим основные положения о сетевых со­

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

тронной почты и Всемирной паутины.

ГЛАВА

Работа в сети
В этой главе ...
~

Подключение к серверу

~

Реализация серверов

~

Получение данных из Интернета

~

НТТР-клиент

~

Отправка электронной почты

Эта глава начинается с описания основных понятий для работы в сети, а за­

тем в ней рассматриваются примеры написания программ на

Java,

позволяющих

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

Java

и сбора данных

с веб-сервера.

4.1.

Подключение к серверу

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

программы на

4.1.1.

Применение утилиты

Утилита

telnet,

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

Java.

telnet

telnet

служит отличным инструментальным средством для отлад­

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

telnet.

Глава

4 •

Работа в сети

НА ЗАМЕТКУ! В

Windows

утилиту

telnet

необходимо активизировать. С этой целью открой­

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

Windows и установите флажок Клиент Telnet. Следует так­
Windows блокирует не которы е сетевые порты , которые будут

же иметь в виду, что брандмауэр

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

Утилитой

te l ne t

можно пользоваться не только для соединения с удаленным

компьютером. С ее помощью можно также юаимодействовать с ра :ыичными се­

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

telnet time-a.nist.gov 13
На рис.

4.1

приведен пример ответной реакции сервера, которая в режиме ко­

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

54276 07-06-25 21: 37: 31 50

О

О

659.

О

UTC (NIST ) *

-$ telnet time-a .nist .gov 13

Trying 129 .6.15 .28 . . .
Connected to time -a.nist .gov.
Escape character is •Л ]'.
57488 16-04-10 04 :23:00 50 0 0 610 .5 UTC(NIST) *
Connection closed Ьу foreign host.
-$

I

Рис.

4.1. Результат,

11олучаемый из службы учета времени дня

Что же в действительности произошло? Утилита

te lnet

подключилас1, к сер­

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

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

UNIX.

Указанный в этом приме­

ре сервер находится в Национальном институте стандартов и технологий США

(National lnstitute of Standards and Technology).

Его системное время синхрони ­

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

13.

4.1.
НА ЗАМЕТКУ! В сетевой терминологии порт

-

Подключение к серверу

это не какое-то конкретное физическое устрой­

ство, а абстрактное понятие, упрощающее представление о соединении сервера с клиентом
[рис. 4 2).

Сетевой
пакет

/

1111 [

-_____.,__,.,___
132.163.4.103

13

data

Клиент

Рис.

4.2. Схема

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

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

13.

При получении

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

13

на сервере активизируется соответствующий процесс

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

Когда сеанс связи с сервером через порт

13 начинается по команде t e ln e t
t ime - a . ni st . gov, сетевое программное обеспечение преобразует
"time-a. n is t. gov" в IР-адрес 129 . 6 .15. 28 . Затем оно посылает по это­

с параметром
строку

му адресу запрос на соединение с удаленным компьютером через порт

13.

После

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

1.

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

telnet horstmann.com 80

2.

Затем аккуратно и точно введите следующие строки, дважды нажав клавишу

в
GET /

конце:

НТТР/1.1

Host: horstmann.com
пустая

На рис.

стро ка

4.3

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

имеет уже знакомый вам вид страницы текста в формате

HTML,

te l n et .

Она

а именно на­

чальной страницы веб-сайта Кея Хорстманна. Именно так обычный веб-браузер

Глава

4 •

Работа в сети

получает искомые неб-страницы. Для запроса веб-страниц на сервере 011 приме­
няет сетевой протокол НТТР. Разумеется, браузер отображает данные и 11ам1юго
более удобном для чтения ниде, 'lем формат

HTML.

[

tetnet horstmann.com 80
67 . 218 . 118 . 65 .. .
Connected to horstmann.com.
Escape character is •Л J •.
GЕТ / НТТР/1.1
Host : hoгstmann.com

~$

~rying

НТТР/1 . 1 200 ОК
Date: Sun, 16 Apr 2616 64:36 :27 GМТ
Server: Apache/2.2.24 (Unix) mod sst/2.2.24 Open55L/0.9.8e-fips-rhets mod auth р
assthrough/2.1 mod_bwtimited/1 .4-mod_fcgid/2.3.б Sun-ONE-ASP/4.0.3
Last-Мodified: Тhu, 17 маг 2016 18:32:18 GМТ
ЕТаg: "2590elc-lc47-52e42d9a8fe8e•
~ccept-Ranges: bytes
Content-Length: 7239
Content-Type: text/htmt




Cay Horstmann's Ноте Page

Рис .

4.3. Досrуп

к НТТ Р- 11орту с 1юмощыо уп1литы

НА ЗАМЕТКУ! Пару ··ключ-значение " Host:

telnet

horstmann. com требуется указывать для под­

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

домены. Ее можн о не указывать, если на веб-сервере размещается единственный домен .

4.1 .2. Подключение

к серверу из программы на

Java

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

t el net.
емые

11

4.1,

выполняются те же действия, 'ITO и при испол1,:юнании утилиты

Она устаt~анливает соединение с сервером чере:1 порт и 11ы1юд11т пол уча­

ответ данные.

Листинг4.1. И с ходный код из фа йла

1
2
3
4
5
6
7

8
9

10
11

12
13
14

socket/SocketTest . java

package socket ;
impo r t
i mport
import
impo rt

.
. o. * ;
Java.i
java . net.* ;
java. n io . c harset. *;
java.uti l. *;

/* *
* В этой п ро гра мме устан а вли вае т ся
* с атомными час ами в г. Боулдере ,
* выв одит с я в ремя, п е р е да в аемое и з
* @ve r s i on 1 .22 20 18-03- 17
* @aut hor Сау Horstmann
*/

со к е тн ое
шт.

с о единени е

Кол орадо и

сер в ера

4.1.
15

puЫic

Подключение к серверу

class SocketTest

16 {
17
18

puЫic

static void main(String[] args)
throws IOException

19
20
21
22

try (var s = new Socket("time-a.nist.gov", 13);
var in = new Scanner(s.getinputStream(),
StandardCharsets.UTF 8))

23
24
25
26
27
28
29

while (in.hasNextLine())
{
String line = in.nextLine();
System.out.println(line);

30
31
В данной программе наибольший интерес представляют следующие две стро­
ки кода:

Socket s = new Socket("time-a.nist.gov", 13);
Scanner in = new Scanner(s.getinputStream(), "UTF-8"))
В первой строке кода открывается сокет. Сокст

-

это абстрактное пою1-

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

для обмена данными по сети. Конструктору объекта сокета передается адрес
удаленного сервера и номер порта. Если установит~, соединение не удает­
ся, генерируется исключение типа

UnknownHost.Exception,

вении каких-нибудь других затруднений

-

а при 1юзникно­

исключе11ие типа

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

Класс

ключение из суперкласса.

После открытия сокета метод
возвращает объект типа

getinputStream () из класса j ava. net. Socket
InputStream, который можно испол1,зовап, как любой

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

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

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

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

Класс

Socket

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

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

Java.

Пакет

j ava. net,

по существу,

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

Глава

4 •

Работа в сети

НА ЗАМЕТКУ! Здесь рассматривается только сетевой протокол ТСР ITгaпsmiss1on Сопtгоl
Pгotocol - протокол управления передачей). На платформе Java поддерживается также про­
токол UDP IUseг Datagгam Pгotocol - протокол пользовательских дейтаграмм), который мо­

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

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

java.net.Socket 1.0


Socket(String bost, int port)



InputStream getinputStream()



OutputStream getOutputStream()

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

Получают поток ввода для чтения данных из сокета или поток вывода для записи данных
в сокет.

4.1.З. Время ожидания дпя сонетов
Чтение данных из сокета продолжается до тех пор, пока данные доступны. Если
хост (т.е. сетевой узел) недоступен, прикладная программа будет ожидать очень
долго, и все будет зависеть от того, когда операционная система, под управлением
которой работает компьютер, определит момент завершения времени ожидания.
Для конкретной прикладной программы можно самостоятельно определить

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

метод

setSoTimeout

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

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

var s = new Socket( . . . );
11 истечение времени ожидания
s.setSoTimeout(lOOOO);

через

10

секунд:

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

SocketTimeoutException

по истечении времени ожидания

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

try
{

InputStream in = s.getinputStream();
11 читать данные из потока ввода in

4.1.

Подключение к серверу

catch (InterruptedIOException exception)
{
отреагировать

на

истечение

времени

ожидания

Что касается времени ожидания для сокетов, то остается еще одно затрудне­

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

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

Socket{String host, int port)
Это затруднение можно преодолеть, если сначала создать несоединяемый со­
кет, а затем установить соединение с ним, задав время ожидания:

Socket s = new Socket();
s.connect(new InetSocketAddress(host, port), timeout);
Если же пользователям требуется предоставить возможность прерывать сое­
динение с сокетом в любой момент, то далее, в разделе

4.2.4

поясняется, как этого

добиться.

java.net.Socket 1.0


Socket () 1 . 1



void connect (SocketAddress address) 1. 4

Создает сокет, который еще не соединен в данный момент времени.

Соединяет данный сокет по указанному адресу.



void connect (SocketAddress address, int

timeoutinМil.l.iseconds)

1. 4

Соединяет данный сокет по указанному адресу или осуществляет возврат, если заданный
промежуток времени истек.



void setSoTimeout (int

timвoutinМil.l.iseconds)

1.1

Задает время ожидания для чтения запросов в данном сокете. По истечении времени ожида­
ния возникает исключение типа

InterruptedIOException.

boolean isConnected () 1 . 4
Возвращает логическое значение



true,

если установлено соединение с сокетом.

true,

если разорвано соединение с сокетом.

boolean isClosed () 1 . 4
Возвращает логическое значение

4.1.4.

Межсетевые адреса

Как правило, нет особой нужды беспокоиться о межсетевых адресах в Интер­
нете

-

числовых адресах хостов, состоящих из четырех байтов (или из шестнад­

цати байтов

-

по протоколу IPvб), как, например,

12 9. 6. 15. 2 8.

Но если требу­

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

В пакете

j ava. net

InetAddress.

поддерживаются межсетевые адреса по протоколу IPvб,

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

Глава

4 •

Работа в сети

системы хоста. В частности, статический метод

getByName () но:шращает объект
InetAddress для хоста. Например, н следующей строке кода возвращает­
ся объект типа InetAddress, инкапсулирующий последовательность из четырех

типа

байтов

129.6.15.28:

InetAddress address = InetAddress.getByName("time-a.nist.gov");
Чтобы

получить

байты

межсетевого адреса, достаточно

вызвать

метод

getAddress () следующим образом:
byte[] addressBytes = address.getAddress();
Имена некоторых хостов с большим объемом трафика соответствуют нескол1>­
ким межсетевым адресам, что объясняется попыткой сбалансировать нагрузку.
Так, на момент написания данной книги имя хоста

google. сот соответствовало

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

getAllByName ():

InetAddress[] addresses = InetAddress.getAllByName(host);
Наконец, иногда требуется адрес локального хоста. Если вы просто запро­
сите адрес локального хоста, указав 1оса1 ho s t, то неизмешю получите в ответ

локальный петлевой адрес

127.

О. О.

1,

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

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

getLocalHost

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

ниже.

InetAddress address = InetAddress.getLocalHost();
В листинге

4.2

приведен пример простой программы, выводящей межсетевой

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

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

java inetAddress/InetAddressTest www.horstmann.com
Листинг

4.2.

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

1
2

package inetAddress;

3

import Java.io.*;
import java.net.*;
/**

4
5
6

*

В

этой

программе

inetAddress/InetAddressTest. java

демонстрируется

применение

7
* класса InetAddress. В качестве аргумента в командной
8
* строке следует указать имя хоста или запустить
9
* программу без аргументов, чтобы получить в ответ
10 * адрес локального хоста
11 * @version 1.02 2012-06-05
12 * @author Сау Horstmann
13 */
14 puЫic class InetAddressTest
15 {

4.2.
puЫic

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Реализация серверов

static void main(String[] args)
throws IOException

if (args.length > 0)
String host = args[O];
InetAddress[] addresses
InetAddress.getAllByName(host);
for (InetAddress а : addresses)
System.out.println(a);
else
InetAddress localHostAddress =
InetAddress.getLocalHost();
System.out.println(localHostAddress);

java.net.InetAddress 1.0
getвyName

(String host)



static InetAddress



static InetAddress [] getAl.lByName (String host)
Конструируют объект типа

или массив всех межсетевых адресов для задан­

InetAddress

ного имени хоста.

static InetAddress getLocalHost()



Конструирует объект типа

InetAddress



Ьуtе [] getAddress ()



String

для локального хоста.

Возвращает массив байтов, содержащий числовой адрес.
getнostAddress

()

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

String



"132 .163. 4 .102".

getнostName

()

Возвращает имя хоста.

4.2.

Реализация серверов

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

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

4.2.1.

Сокеты сервера

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

Глава
номер порта

4 •

Работа в сети

818 9,

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

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

818 9:

var s = new ServerSocket(8189);
В приведенной ниже строке кода серверной программе предписывается ожи­
дать подключения клиентов к заданному порту.

Socket incorning

=

s.accept();

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

accept ()

возвратит объект типа

Socket,

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

ниже фрагменте кода.

InputStrearn inStrearn = incorning.getinputStrearn();
OutputStrearn outStrearn = incorning.getOutputStrearn();
Все данные, направляемые в поток вывода серверной программы, поступают
в поток ввода клиентской программы. А все данные, направляемые в поток выво­
да из клиентской программы, поступают в поток ввода серверной программы. Во
всех примерах, приведенных в этой главе, обмен текстовыми данными осущест­

вляется через сокеты. Поэтому соответствующие потоки ввода-вывода через сокет
преобразуются в потоки сканирования (типа

Scanner)

и записи (типа

Wri ter)

следующим образом:

var in = new Scanner(inStrearn, "UTF-8");
var out = new PrintWriter(new OutputStrearnWriter(
outStrearn, "UTF-8"),
true /*автоматическая очистка*/);
Допустим, клиентская программа посылает следующее приветствие:

out.println("Hello! Enter

ВУЕ

to exit.");

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

telnet,

818 9

используется

это приветствие отображается на экране терминала.

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

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

String line = in.nextLine();
out.println("Echo: " + line);
if (line.trirn() .equals("BYE")) done = true;
По завершении сеанса связи открытый сокет закрывается следующим образом:

incorning.close();
Вот, собственно, и все, что делает данная программа. Любая серверная про­

грамма, например, веб-сервер, работающий по протоколу НТГР, выполняет ана­
логичный цикл следующих действий.

4.2.
1.

Реализация серверов

Получение из потока ввода входящих данных запроса на конкретную ин-

формацию от клие1пской программы.

2.

Расшифровка клиентского запроса.

3.

Сбор информации, запрашиваемой клиентом.

4.

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

В листинге

4.3

приведен весь исходный код описанного выше примера сервер­

ной программы.

Листинг 4.3. Исходный код из файла

1
2
3
4
5
6

server /EchoServer. j ava

package server;
import
import
import
import

java.io.*;
java.net.*;
java.nio.charset.*;
java.util.*;

7

8 /**
9
* В этой программе реализуется простой сервер,
10 * прослушивающий порт 8189 и посьmающий обратно
11 * клиенту все полученные от него данные
12 * client input.
13 * @version 1.22 2018-03-17
14 * @author Сау Horstmann
15 */
16 puЫic class EchoServer
17 {
puЫic static void main(String[] args)
18
19
throws IOException
20
//установить сокет на стороне сервера
21
22
try (var s = new ServerSocket(8189))
{
23
24
11 ожидать подключения клиента
25
try (Socket incoming = s.accept())
26
{
27
InputStream inStream
28
incoming.getinputStream();
29
OutputStream outStream =
30
incoming.getOutputStream();
31
try (var in
32
new Scanner(inStream,
StandardCharsets.UTF 8))
33
34
var out
new PrintWriter(
35
new OutputStreamWriter(
36
37
outStream, StandardCharsets.UTF_8),
true /* автоматическая очистка */);
38
39
out.println("Hello! Enter ВУЕ to exit.");
40
41

Глава

4 •

Работа в сети

11 п ере д а т ь обра тн о да нн ые ,
11 п олуче нны е от кл и е н та
var do ne = f a l se ;
whi l e ( !done && i n.has Next Line())

42
43
44
45
46
47
48
49

{

String line = in. nextLi ne() ;
out. pr in t l n( "E cho : " + l i ne ) ;
if (li ne . tr im () .equa l s ( " BY E" ))

dопе

true ;

50
51

52
53

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

tel net

рез порт

к локальному серверу

818 9.

localhost

(или по IР-адресу

1 27 . О

. О

. 1)

че­

Если ваш компьютер непосредстве11110 подключен к Интернету,

любой пол1, :юватель может получить доступ к данной серверной программе,

если ему и з вестен IР-адрес и номер порта . При подключении через этот порт

будет получено следующее сообщение (рис.

4.4):

Hello 1 Enter ВУЕ to exit. 1
~1.

fd• 111ow :i>rmmai

"'Ь•

1:1о 1р

-$ te1net 1oca1host 8189

Trying 127.0 .0 .1 . . .
Connected to 1oca1host .
Escape character is • л ] ' .
Не11о! Enter ВУЕ to exit .
НеНо SaHor!
Echo : Не11о Sai1or !
ВУЕ

Echo : ВУЕ
Connection c1osed
-$

Рис.

I

Ьу

4.1.. Сеанс свя з и

foreign host .

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

Введите любую фра зу и понаблюдайте за тем, как она будет получена обрат1ю в том же самом виде . Для отключения от сервера введите ВУЕ (все символы
в верхнем регистре) . В итоге :sавершится и серверная программа .

1

Пр и вет 1

Введ и те ВУЕ

( П о ка) ,

чтобы выйти из п ро г ра ммы.

4.2.

Реализация серверов

java.net.ServerSocket 1.0


ServerSocket (int port)



Socket accept ()

Создает сокет на стороне сервера, контролирующего указанный порт.

Ожидает соединения. Этот метод блокирует lт.е. переводит в режим ожидания) текущий поток
до тех пор, пока не будет установлено соединение. Возвращает объект типа

Socket,

через

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

void close ()
Закрывает сокет на стороне сервера.

4.2.2.

Обслуживание многих клиентов

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

подключения сра:~у нескольких клиентских

про­

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

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

accept ()

нение, т.е. в результате вызова метода

возвращается сокет, запускается

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

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

while (true)
{

Socket incoming = s.accept();
var r = new ThreadedEchoHandler(incoming);
var t
new Thread(r);
t.start();
Класс

run ()

ThreadedEchoHandler

реализует интерфейс RunnaЬle и в своем методе

поддерживает юаимодействие с клиентской программой:

class ThreadedEchoHandler implements

RunnaЫe

{
puЫic

void run()

(

try (InputStream inStream
OutputStream outStream
обработать

incoming.getinputStream();
incoming.getOutputStream() 1

полученный запрос

и отправить

ответ

Глава

Работа в сети

4 •

ca t ch( I OE xcepti on

е)

{
обработа ть

исключение

Когда 11011ый поток испол11е11ия запускается при каждом соединении , 11еско;11,­

ко клиентских программ могут одновременно подключаться к серверу. Это 11е­
трудно проверить, выполнив следующие деi1ст1н~я.

1.

Скомпилиру йте и запустите на выполнение серверную программу, исход­
ный код которой приведен в листи11ге

4.4.

2.

Откройте несколько окон утилиты

3.

Переходя из одного окна в дру гое, введите команды. В ито ге каждое отдель­
ное окно утилиты

te lne t

t elnet

(рис.

4.5).

будет юаимодействовать с серверной програм­

мой не:~ависимо от других окон.

4.

Чтобы ра:юрвать соединение и :~акрыть окно утилиты
комбинацию клавиш
!81T• r•ninal
fllt fdit ~tw i-rm1n.i

te l n e t ,

_ о х

Т.Ьs

l::::ttlp

г

-/books/ с j 8/code/v2ch03/ThreadedEchoSe rve r $ java ThreadedEchoSe rver
Spawning 1
Spawning 2
!• IТe1111l11'1I

f:ilt Edit

~.w

1

_ох

F.trmtn.t

ТаЬ.s

нажмите

.

tjelp

-$ tetnet tocathoot 8189
Try. • т.;.~;;,"г

,.

fil• fdn: ~•w Jtrminal т.ь.s 1::::telp
Esc;
Н t - $ tet net tocathoot 8189
н:t Trying 127 . 0.0 . 1" .
Echc Connected to tocathost .
. Escape character is
~1~ НеНо! Enter 8УЕ to exit.
~~с
8 НеНо Saitor !
Echc Echo: Не Но Saitor !
Con How а re you 1
-$ 1 Echo : How are you7

-

'1 1 Х

Con

1

"' ]

'

f

1

.

ВУЕ

Echo: ВУЕ
Connection ctosed
-$ о

1

1
Ьу

foreign host .
1

'

о:=

1

=
Рис.

L..5. Сеанс олн овре м еююй

с 11ю11 нес кольки х кли ентов с се рверо м

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

java. nio. Дополнительные сведения по данному в о пр о с у м ож но получить, обратившись
https : //www _ibm. com/developerworks/java/liЬrary/j-javaio/.

по адресу

4.2.
Листинг

1
2
3

4
5
6

t..t..

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

Реализация серверов

threaded/ThreadedEchoServer. java

package threaded;
import
import
import
import

java.io.*;
java.net.*;
java.nio.charset.*;
java.util.*;

7

8
9
10
11
12
13

14
15
16
17
18
19
20
21
22
23

/**

*
*

В

*

полученные

этой

программе

прослушивающий
от

реализуется многопоточньm

порт
всех

8189

и

передающий

сервер,

обратно

все

данные,

клиентов

* @author Сау Horstmann
* @version 1.23 2018-03-17
*/
puЫic class ThreadedEchoServer
{
puЫic static void main(String(] args )
{
try (var s = new ServerSocket(8189))
{
int i = 1;
while (true)

24
Socket incoming = s.accept();
System.out.println("Spawning " + i);
RunnaЫe r = new ThreadedEchoHandler(incoming);
var t = new Thread(r);
t.start();
i ++;

25
26
27
28
29
30
31
32
33

catch (IOException

е)

34
35
36

e.printStackTrace();

37

38
39

40 /**
41 * Этот класс обрабатывает данные, получаемые
42 * от клиента через одно сокетное соединение
43 */
44 class ThreadedEchoHandler implements RunnaЫe
45
46

private Socket incoming;

47
48
49

/**

50
51
52
53

Конструирует

сервером

обработчик

@param incomingSocket

Входящий

сокет

*/
puЫic

(

ThreadedEchoHandler(Socket incomingSocket)

Глава

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

4 •

Работа в сети

incoming = incom1ngSocket;

puЫic

void run()

{
try (InputStream inStream =
incoming.getinputStream();
OutputStream outStream =
incoming.getOutputStream();
var in = new Scanner(inStream,
StandardCharsets. UTF 8);
var out = new PrintWriter(
пеw OutputStreamWriter(outStream,
StandardCharsets.UTF 8),
true /* autoFlush */))
out.println("Hello! Enter

БУЕ

to exit.");

// передать обратно данные, полученные
var done = false;
while (!done && in.hasNextLine())
{
String line = in.nextLine();
out.printlп("Echo: " + liпe);
if (line.trim() .eq1ыls("BYE"))
done = true;
catch (IOException
{

от

клиента

е)

e.priпtStackTracel);

4.2.З. Полузакрытие
По.лу.~акрытuе обеспечинает 1юзможность прервать передачу данных на одной
стороне сокетноrо соединения, продолжая в то же время прием данных от дру­

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

дать. Если речь идет о фс:~йле, то

ero

закрытие, по существу, о:шачает завершение

передачи данных. Если же :1акрьпь сокет, то соединение с сервером будет немед­
лешю ра:юрвано.

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

чи данных запроса. При ::пом поток ввода остается открытым, позволяя получить
ответ от сервера. Код, реали:1ующий механизм полузакрытия на стороне клиен­
та, приведен ниже.

try (var socket = new Socket(host, port))
{

var in

=

new Scanner(socket.getinputStream(), "UTF-8");

4.2.

Реализация серверов

var writer = new PrintWriter(socket.getOutputStream() );
11 передать данные заnроса
writer.print ( . . . 1;
writer. flush ();
socket.shutdownOutput();
11 теперь сокет полузакрыт
11 принять данные ответа
while (in.hasNextLine() != null)
{

String line = in.nextLine();

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

не закроется поток вывода на другом конце соединения. Очевидно, что такой
подход применим только для служб однократного действия по сетевым протоко­
лам, подобным НТТР, где клиент устанавливает соединение с сервером, передает
запрос, получает ответ, после чего соединение ра::1рывается.

java.net.Socket 1.0
void shutdownOutput () 1. З



Устанавливает поток вывода в состояние завершения.



void shutdownlnput () 1 . З



Ьoolean



Ьoolean

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

isOutputShutdown () 1. 4

Возвращает логическое значение

Возвращает логическое значение

4.2.4.

true,

если вывод данных был остановлен.

islnputShutdown () 1. 4
true,

если ввод данных был остановлен.

Прерываемые сокеты

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

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

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

interrupt ().

Для прерывания сокетных операций служит класс
ляемый в пакете
образом:

j ava. nio.

Объект типа

SocketChannel, предостав­
SocketChannel создается следующим

Глава

4 •

Работа в сети

Soc ketChannel c han ne l = Soc ketChanne l. open(
new Ine t SocketAdd r ess (host, por t) ) ;
У канала отс утстuуют связа1111ые с ним потоки ввода-вы11ода. Вместо это­

го в канале предоставляются методы

read ( )

и

wr i t e (),

испо л ь зу ющие объ ­

екты типа

Buf f e r.

в главе

Эти методы объявм1ются в интерфейсах R eadaЬleBy t e C h a n nel

2.)

(Подробнее о буферах и:1 системы ввода-вывода

NIO

см .

и Wr i ta Ьl e B yt e C hann e l. Если же 11ет желания иметь дело с буферами, мя чте­
ния из канала типа

Scan ner.

So cket Chan n e l можно воспользовал,01 объектом типа
Sca n n e r предусмотрен следующи й конструктор

Для этой цели в К11ассе

с параметром типа R eadaЫe By t e C ha nnel:

var i n

=

new Scanne r( c hannel , St andardCha r set s . UTF 8 );

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

Cha n ne l s .ne wOu t pu tSt re arn() :
Out putStream ou tStream

=

Channe l s.newOutput St r eam(channe l ) ;

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

Если же поток исполнения будет прерван в процессе установления соединения,
чте11ия или записи, соответству ющая операция завершится генерированием ис­

ключе11ия .

В примере програ ммы, исходный код кото рой приведен в листише

4.5,

демон­

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

щий . Есл и щел кнуть на кно пке

-

блокиру ю ­

Cancel (Отмена ) во время выв ода первых десяти

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

Cancel

после передачи первы х десяти чисел, то пре­

рвется испол11ение только первого потока. Блокировка второго потока исполне­
ния будет продолжаться до тех пор, пока сервер не разорвет окончател ьно сое­

ди~1ение (рис.

.•
1

4.6).

11li e 1ruptiБTesoc k~ -- ...:. сНх

lnt;;ruptiыel 1

Blocking

1

Сш

lnterruptlЫe.

Read1ng
Read1ng
Reading
Reading
Readlng
Readlng
Readlng
Readlng
Reading
Reading
Reading

•1 j

"8

[

lnte rп1ptible socketтes t

11 ." "ti~[

"1

1tn11

-

- ---_

п 'х

:L:___;_•

,
J

Slocklng:
Reading 1
Reading 2
Read1ng З
Read1ng 4
Read1ng S
Readlng б
Read1ng 7
Reading 8
Read1ng 9
Read1ng 10
Read1ng

1
2
3
4

s
б

7
8
9

10
Channel closed

-

Рис.

-

1+.6.

Прерыв а ние сокета

4.2.

Реализация серверов

Листинг 4.5. Исходный код из файла interruptiЫe/InterruptiЫeSocketTest.

1

2
3
4
5
6
7
8

9

10
11
12
13
14
15
16
17
18
19
20
21

package
import
import
import
import
import
import
import
import

*
*

В

этой

сокета

программе
через

демонстрируется

прерывание

канал

* @author Сау Horstmann
* @version 1.05 2018-03-17
*/
puЬlic class InterruptiЫeSocketTest
{
puЫic

static void main(String[] args)

{

EventQueue.invokeLater( () ->

23
24
25
26

{

var frame

=

new

InterruptiЬleSocketFrame();

frame.setTitle("InterruptiЫeSocketTest");

frame.setDefaultCloseOperation(
JFrame.EXIT ON_CLOSE);

27

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

java.awt.*;
java.awt.event.*;
java.util.*;
java.net.*;
java.io.*;
java.nio.charset.*;
java.nio.channels.*;
javax.swing.*;

/**

22

33

interruptiЬle;

frame.setVisiЬle(true);

)) ;

class

InterruptiЫeSocketFrame

private
private
private
private
private
private
private
puЬlic

extends JFrame

Scanner in;
JButton interruptiЬleButton;
JButton ЫockingButton;
JButton cancelButton;
JTextArea messages;
TestServer server;
Thread connectThread;
InterruptiЫeSocketFrame()

{
var northPanel = new JPanel();
add(northPanel, BorderLayout.NORTH);

47

48
49
50
51
52
53
54

final int ТЕХТ ROWS = 20;
final int ТЕХТ COLUMNS = 60;
messages = new JTextArea(TEXT ROWS,
add(new JScrollPane(messages));

COLUMNS);

= new JButton("InterruptiЫe");
= new JButton("Blocking");

interruptiЫeButton
ЫockingButton

ТЕХТ

java

Глава

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

northPanel.add(interruptiЫeButton);
northPanel.add(ЬlockingButton);

interruptiЫeButton.addActionListener(event

97

98
99
100
101
102
103
104
105
106
107
108
109
110

->

{
interruptiЬleButton.setEnaЬled{false);
ЫockingButton.setEnaЬled(false);

cancelButton.setEnaЫed(true);

connectThread = new Thread( () ->
try
{
connectinterruptiЬly();

catch (IOException
{
messages.append(

е)

"\ninterruptiЫeSocketTest."

+

"connectlnterruptiЫy:

" +

)
));
connectThread.start();

78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

Работа в сети

4 •

));
ЫockingButton.addActionListener(event

->

(
interruptiЬleButton.setEnaЬled(false);
ЫockingButton.setEnaЫed(false);
cancelButton.setEnaЫed(true);

connectThread = new Thread( ()

->

try
{
connectBlocking();
catch (IOException
{
messages.append(

е)

"\ninterruptiЫeSocketTest."

+ "connectBlocking: " +
)
));

connectThread.start();
));
cancelButton = new JButton("Cancel");
cancelButton.setEnaЫed(false);

northPanel.add(cancelButton);
cancelButton.addActionListener(event ->
(
connectThread.interrupt();
cancelButton.setEnaЫed(false);

));
server = new TestServer();
new Thread(server).start();

е);

е);

4.2.
111

Реализация серверов

pack();

112

113
114
115
116
117
118
119
120
121
122
123
124
125
12 6
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

162
163
164
165
166

/**
* Соединяет

*

используя

с

проверяемым

прерываемый

сервером,

ввод-вывод

*/
void

puЫic

coпnectinterruptiЬly()

throws IOException

{
messages.append("InterruptiЫe:\n");

try (SocketChannel channel = SocketChannel
.open(new InetSocketAddress("localhost", 81891))
in = new Scanner(channel, StandardCharsets.UTF_B);
while ( 1 Thread.currentThread() .isint.errupted())
{
messages.append("Reading ");
if (in.hasNextLine())
{

String line = in.nextLine();
messages.append(line);
messages.append("\n");

finally
{
EventQueue.invokeLater( () ->
{
messages.append("Channel closed\n");
interruptiЬleButton.setEnaЫed(true);
ЫockingButton.setEnaЬled(true);

}) ;

/**
* Соединяет

* используя
*/
puЫic

с

проверяемым сервером,

блокирующий

ввод-вывод

void connectBlock1ng() throws IOException

{
messages.append("Blocking:\n");
try (var sock = new Socket("localhost", 8189))
{
in = new Scanner(sock.getinputStream(),
StandardCharsets.UTF 8);
while ( 1 Thread.currentThread() .isinU~rr\Jpte,i() 1
{
messages.append("Reading ");
if (in.hasNextLine())
{
String line = in.nextLine();
messages.append(line);
messages.append("\n"I;

Глава

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

4 •

Работа в сети

finally
{
EventQueue.invokeLater(() ->
{
messages.append("Socket closed\n");
interruptiЫeButton.setEnaЫed(true);
ЫockingButton.setEnaЫed(true);

}) ;

/**
*

Многопоточный

*

посьmающий

*

после

сервер,

прослушивающий

клиентам числа,

передачи

10

имитируя

порт

8189

и

зависание

чисел

*
*/
class TestServer implements
puЫic

RunnaЫe

void run()

{
try (var s = new ServerSocket(8189))
{
while (true)
{
Socket incoming = s.accept();
RunnaЫe r = new TestServerHandler(incoming);
new Thread(r) .start();

catch (IOException е)
{
messages.append("\nTestServer.run: "+

/**
* Этот

класс

обрабатывает

данные,

е);

получаемые

* сервером от клиента через одно сокетное соединение
*/
class TestServerHandler implements RunnaЫe
{
private Socket incoming;
private int counter;
/**

* Конструирует обработчик
* @param i Входящий сокет
*/
puЫic

TestServerHandler(Socket i)

{
incoming = i;

4.2.
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

puЫic

Реализация серверов

void run()

{
try
(
try
{
OutputStream outStream =
incoming.getOutputStream();
var out = new PrintWriter(
new OutputStreamWriter(outStream,
StandardCharsets.UTF 8),
true /* автоматическая очистка */);
while (counter < 1001
{
counter++;
if (counter ame,\l/ ре­

(URL)

и унифицированные uдентuфur-;аторы ресурсов

(URI).

В частности,

это лишь синтаксическая конструкция, содержащая ра:ыичные части

символьной строки, обозначающей веб-ресурс.

URL - это особая ра:шовидносп,
URI с исчерпывающими данными о местоположении ресурса.
такие URI, как, например, mail to: cay@hortsmann. сот, которые 11е

идентификатора
Имеются и

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

будь данные. Такой

URI

называется унифицированным имене.м ресурса

В классе URI из библиотеки

Java

(URN).

отсутствуют методы доступа к ресурсу по ука­

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

URL

позволяет открыть поток ввода-вывода для данного ресурса. Поэто­

му в классе

URL

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

мам, которые поддерживаются в библиотеке

Java, в том числе http:, https:
ftp: - для Интернета, fi le: - для локальной файловой системы, а также
j ar: - для обращения к архивным JАR-файлам.

и

4.3.
Синтаксический анализ

Получение данных иэ Интернета

непростая задача, поскольку идентификаторы

URI -

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

URI

с замысловатой структурой.

http:/google.com?q=Beach+Chalet
ftp://username:password@ftp.yourserver.com/puЬ/file.txt

В обозначении идентификаторов
тура

URI

URI

задаются правила их построения. Струк­

выглядит следующим образом:

[схема:]специальная_часть_схемы[#фрагмент]

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

#

служат в качестве разделителей. Если схема: присутствует как составная часть

в идентификаторе
Абсолютный

URI

URI,

то он называется абсолютным, а иначе

-

относительным.

называется непро3рачным, если специальная_ часть_ схемы не

начинается с косой черты

(/),

как, например, показано ниже.

mailto:cay@horstmann.com
Все абсолютные, непрозрачные

URI

и все относительные

URL

имеют иерархи­

ческую структуру. Например:

http://horstmann.com/index.html
.. / .. /java/net/Socket.html#Socket()
Составляющая специальная_ часть_ схемы иерархического

URI

имеет следу­

ющую структуру:

[//полномочия] [путь] [?запрос]

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

URI

серверов имеет приведенную ниже форму, где элемент порт

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

[ сведения_о_пользователе@] хост[: порт]
В документе

RFC 2396,

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

URI,

допускает­

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

URI

состоит в синтаксическом анализе отдельных

составляющих идентификатора. Они извлекаются с помощью перечисленных
ниже методов.

GetScheme 1)
getSchemeSpecificPart()
getAuthori ty ()
getUserinfo()
getHost 1)
getPort 1)
getPath 1)
getQuery 11
getFragmeпt ()
Другое назначение класса

URI

состоит в обработке абсолютных и относитель­

ных идентификаторов. Так, если имеются абсолютный и относительный иденти­

фикаторы

URI:

http://docs.mycompaпy.com/api/java/пet/ServerSocket.html

Глава

4 •

Работа в сети

и

.. / .. /java/net/Socket.html#Socket()
их можно объединить в абсолютный

URI

следующим образом:

http://docs.mycompany.com/api/java/net/Socket.html#Socket()
Такой процесс называется 11реобра.юванием адресов относительного

URI.

Обрат­

ный процесс называется 11реобра.юванием абсолютньzх адресов в относительные. На­
пример, имея ба3овыu

URI:

http://docs.mycompany.com/api
можно преобразоваТI> следующий абсолютный

URI:

http://docs.company.com/api/java/lang/String.html
в приведенный ниже относительный

URI.

java/lang/String.html
Для выполнения обоих видов преобразования в классе

URI

предусмотрены

два соответствующих метода:

relative
comЬined =

4.3.2.

base.relativize(combined);
base.resolve(relative);

Извлечение данных средствами класса URLConnection

Для получения дополнителы1ых сведений о веб-ресурсе следует воспол~,­
зоваться классом

URLConnection,

предоставляющим намного больше средств

управления доступом к неб-ресурсам, чем болеепростой класс
с объектом типа

URL.

Для работы

необходимо тщательно спланировать и выпол­

URLConnection

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

1.

Вызвать метод

типа

openConnection () из класса URL
URLConnection следующим образом:

URLConnection connection

2.

=

для получения объекта

url.openConnection();

Задать свойства запроса с помощью перечисленных ниже методов.

SetDoinput ()
setDoOutput ( 1
setifModifiedSince()
setUseCaches ()
setAllowUserinteraction()
setRequestProperty()
setConnectTimeout()
setReadTimeout ()

3.
4.

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

соединение

с

удаленным

ресурсом

с

помощью

метода

connect ():
connection.connect();

5.

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

4.3.
6.

После

подключения

к

Получение данных из Интернета

веб-серверу становятся

доступными

поля

за­

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

getHeaderFieldKey ()

и

getHeaderField ().

Кроме того, для удобства раз­

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

getContentType ()
getContentLength()
getContentEncoding()
getDate ()
getExpiration ()
getLastModified()

7.

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

get Inpu tStream (),

предоставляющий поток ввода для чтения данных. (Это

тот же поток ввода, который возвращается методом

са

URL.)

Существует также метод

getContent

openStream ()

из клас­

(),но он не такой удобный.

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

(text/
plain) или изображений (image/gif), придется воспользоваться классами
из пакета сот. sun. Кроме того, можно зарегистрировать собственные обра­



ботчики содержимого, но они в данной книге не рассматриваются .

URLConnection, ошибочно
getinputStream() и getOutpuStream() аналогичны одноименным
Socket. Это не совсем так. Класс URLConnection способен выполнять

НА ЗАМЕТКУ! Некоторые разработчики, пользующиеся классом
считают, что методы

методам из класса

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

Рассмотрим методы из класса

URLConnection

более подробно. В нем имеется

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

setDoinput ()

и

setDoOutput ().

По умолчанию при соединении предоставляется поток ввода для приема данных
с веб-сервера, но не поток вывода для передачи данных. Чтобы получить поток
вывода (например, с целью разместить данные на неб-сервере), необходимо сде­
лать следующий вызов:

connection.setDoOutput(true);
Далее можно установить ряд заголовков запроса и послать их веб-серверу
в составе единого запроса. Ниже приведен пример заголовков запроса.

GET www.server.com/index.html НТТР/1.0
Referer: http://www.somewhere.com/links.html
Proxy-Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Xll; U; Linux i686; en-US; rv:l.8.1.4)
Host: www.server.com
Accept: text/html, image/gif, image/jpeg, image/png, */*
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8
Cookie: orangemilano=l92218887821987
Метод

setifModifiedSince ()

служит для уведомления о том, что требуется

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

Глава

Работа в сети

4 •

Наконец, с помощью метода

setRequestProperty ()

можно установит~. пару

"имя-значение", имеющую определенный смысл для конкретного протокола.
Формат заголовка запроса по сетевому протоколу НТГР описан в документе

2616.

RFC

Некоторые его параметры не очень хорошо документированы, поэтому

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

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

1.

Составить символьную строку из имени пол1,:ювателя, двоеточия и пароля:

String input = username + ":" + password;

2.

Перекодировать полученную в итоге символ~.ную строку по алгоритму ко­

дирования

Base64,

как пока:ыно ниже. (Эгот алгоритм преобразует после­

довател~.ность байтов в последовательносл, символов в коде АSСП.)

Base64.Encoder encoder = Base64.getEncoder();
String encoding = encoder.encodeToString(
input.getBytes(StandardCharsets.UTF_8) );

3.

Вы:шать метод
и значением

setRequestProperty () с именем свойства "Authorization"
"Basic " + encoding, как показа1ю ниже.

connection.setRequestProperty("Authorization",
"Basic" + encoding);
СОВЕТ. Здесь рассматривается способ обращения к защищенной паролем веб-странице. Для
доступа к защищенному паролем ПР-файлу применяется совершенно другой подход. В этом
случае достаточно сформировать

URL

следующего вида:

ftp://ИНЯ"_noльзosaтeля:napoль@ftp.вaш_cepsep.com/puЬ/file.txt

После вызова метода

connect ()

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

та. Рассмотрим сначала способ перечисления всех полей :ыголовка. Создатели
рассматриваемого здесь класса посчитали нужным создать собственный способ
перебора полей. Так, в результате вызова приведенного ниже метода получается
11-й ключ заголовка, причем нумерация начинается с единицы! В итоге во:~вра­
щается пустое значение

null,

если /1 равно нулю или больше общего количества

полей заголовка.

String key = connection.getHeaderFieldKey(n);
Но

для

определения

количества

полей

не

предусмотрено

никако­

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

getHeaderFieldKey ()

до тех пор, пока не будет получено пустое значение

null.

Аналогично при вызове следующего метода во:шращается значение из 11-го поля:

String value
Метод

=

connection.getHeaderField(n);

getHeaderFields ()

возвращает объект типа Мар с полями :~аголовка:

Map headerFields =
connection.getHeaderFields();
В качестве примера ниже приведен ряд полей :ыголовка из типичного ответа
на запрос по сетевому протоколу НТГР.

4.3.

Получение данных из Интернета

Date: Wed, 27 Aug 2008 00:15:48 GMT
Server: Apache/2.2.2 (Unix)
Last-Modified: Sun, 22 Jun 2008 20:53:38 GMT
Accept-Ranges: bytes
Content-Length: 4813
Connection: close
Content-Type: text/html
НА ЗАМЕТКУ! Получить в ответ строку состояния [например, "НТТР/1.1 200 ОК"] можно,



сдел а в вызов

connection. getHeaderField (О)

ил и

headerFields . get (null) .

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

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

к соответствующим числовым типам по мере необходимости. Все эти удобные
методы перечислены в табл.

4.1.

В методах, возвращающих л~ачения типа

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

4.1. Удобные

1

января

long,

1970

г.

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

Имя поля (ключа)

Имя метода

Возвращаемое значение

Date
Expires
Last-Modified
Content-Length

getDate
getExpiration
getLastмodif ied
getContentLength

long
long
long
int
String
String

Content-Тype

getContentТype

Content-Encoding

getContentEncoding

В примере программы из листинга

4.6 предоставляется возможность поэкспе­
URL. Запустив программу, вы можете указать
URL, имя пользователя и пароль:

риментироват1, с соединениями по
в командной строке конкретный

java urlConnection.URLConnectionTest
польэова~ель

http://www.вaш_cepвep.com

пароль

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




Все ключи и значения из полей заголовка.

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



Первые



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

Листинг4.6. Исходный код из файла

1

4.1).

urlConnection/URLConnectionTest. java

package urlConnection;

2

3
4
5
6

import
import
import
import

java.io.*;
java.net.*;
java.nio.charset.*;
java.util.*;

7

8
9

/**
* В

этой

программе

устанавливается

соединение

по

Глава

4 •

Работа в сети

10 * заданному URL и отображаются данные заголовка из
11 * получаемого ответа, а также первые 10 строк
12 * запрашиваемых данных. Для этого в командной строке
13 * следует указать конкретный URL, дополнительно имя
14 * пользователя и пароль (для элементарной аутентификации
15 * по сетевому протоколу HTTPI
16 * @version 1.12 2018-03-17
17 * @author Сау Horstmann
18 */
19 puЫic class URLConnectionTest
20 {
puЬlic static void main(String[] args)
21
{
22
try
23
{
24
String urlName;
25
if (args.length > 0) urlName = args[O];
26
else urlName = "http://horstmann.com";
27
28
var url = new URL(urlName);
29
URLConnection connection = url.openConnection();
30
31
32
//установить имя пользователя и пароль, если они
33
// указаны в командной строке
34
35
if largs.length > 21
36
{
37
Striпg userпame = args[l];
38
Striпg password = args[2];
39
Striпg iпput = username + ":" + password;
40
Base64.Eпcoder encoder = Base64.getEпcoder();
41
Striпg encodiпg = eпcoder.eпcodeToString(
42
input.getBytes(StandardCharsets.UTF_8) 1;
43
coппection. setRequestProperty ( "Authorization",
44
"Basic" + eпcoding);
45
46
47
connectioп.connect();
48
49
// вывести поля заголовка
50
51
Map headers =
52
connectioп.getHeaderFields{);
53
for (Map.Entry entry
54
headers.entrySet())
55
Striпg key = eпtry.getKey();
56
57
for (Striпg value : entry.getValue() 1
58
System.out.println{key + ": "+ value);
59
60
61
// вывести значения полей заголовка,
62
//используя удобные методы
63
64
System.out.println("----------");
65
System.out.printlп("getCoпtentType: "
66
+ connectioп.getContentType());

4.3.

Получение данных из Интернета

System.out.println("getContentLength: "
+ connection.getContentLength() );
System.out.println("getContentEncoding: "
+ connection.getContentEncoding() );
System.out.println("getDate: "
+ connection.getDate() );
System.out.println("getExpiration: "
+ connection.getExpiration() );
System.out.println("getLastModifed: "
+ connection.getLastModified() );
System.out.println("----------");

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

String encoding = connection.getContentEncoding();
if (encoding == null) encoding = "UTF-8";
try (var in = new Scanner(
connection.getinputStream(), encoding))
//
//

вывести первые десять
запрашиваемого

строк

содержимого

for (int п = 1; in.hasNextLine() && п О ? args[O]
: "post/post.properties";
var props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get(propsFilename)))

String propsFilename

props. load ( in) ;

27

28
29
30
31
32
33
34
35
36
37
38

props.remove("url") .toString();
String urlString
props.remove("User-Agent");
Object userAgent
props.remove("redirects");
Object redirects
CookieHandler.setDefault(new CookieManager(null,
CookiePolicy.ACCEPT_ALL));
String result = doPost(new URL(urlString), props,
null? null : userAgent.toString(),
userAgent
redirects == null ? -1 : Integer.parseint(
redirects.toString()) );
System.out.println(result);

39

40
41
42
43
44
45
46
47
48
49

50
51
52
53
54

/**
* Сделать НТТР-запрос по команде POST
* @param url Конкретный URL для отправки запроса
* @param nameValuePairs Параметры запроса
* @param userAgent Пользовательский посредник или
пустое значение null, если это
*

*

по

посредник

* @param redirects
*
*

Количество

*

производится

переадресаций
значение

умолчанию

последующих

-1,

вручную или
если

переадресация

автоматически

* @return Данные, возвращаемые из сервера
*/
puЫic static String doPost(URL url,

Глава

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

4 •

Работа в сети

Map nameValuePairs,
String userAgent, int redirects)
throws IOException
var connection = (HttpURLConnection)
url.openConnection();
if (userAgent 1= null)
connection.setRequestProperty(
"User-Agent", userAgent);
if (redirects >= 0)
connection.setinstanceFollowRedirects(false);
connection.setDoOutput(true);
try (var out = new PrintWriter(
connection.g~tOutputStream()

))

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

var first = true;
for (Map.Entry pair
nameValuePairs.entrySet())
if (first) first = false;
else out.print('&');
String name = pair.getKey() .toString();
String value = pair.getValue().toString();
out.print(name);
out.print('=');
out.print(URLEncoder.encode(value,
StandardCharsets.UTF 8) );

String encoding = connection.getContentEncoding();
if (encoding == null) encoding = "UTF-8";
if (redirects > 0)
{
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection
.НТТР MOVED PERM
1 1 responseCode == HttpURLConnection
. НТТР MOVED ТЕМР
1 1 responseCode == HttpURLConnection
.НТТР SEE OTHER)
String location

connection
.getHeaderField("Location");
if (location != null)
{
URL base = connection.getURL();
connection.disconnect();
return doPost(new URL(base, location),
nameValuePairs, userAgent,
redirects - 1);

else if (redirects

0)

4.3.
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

Получение данных из Интернета

throw new IOException("Too many redirects");

var response = new StringBuilder();
try (var in = new Scanner(
connection.getinputStream(), encoding))
while (in.hasNextLine())
{
response.append(in.nextLine() );
response.append("\n");

catch (IOException е)
{
InputStream err = connection.getErrorStream();
if (err == null) throw е;
try (var in = new Scanner(err))
{
response.append(in.nextLine() );
response.append("\n");

return response.toString();

java. net. HttpURLConnection 1. О


InputStream getErrorStream()
Возвращает поток ввода, из которого читаются сообщения сервера об ошибках.

java.net.URLEncoder 1.0


static String encode (String s,

String encoding)

Возвращает строку s, закодированную в формате

URL

1. 4

с помощью заданной кодировки сим­

волов. !Рекомендуется указывать кодировку "UTF-8 11 .I При кодировании в формате URL
символы

'A'-'Z', 'a'-'z', '0'-'9'. '-',' ','.'и,_, оставляются без изменения.
Пробелы заменяются знаками '+',а все остальНЬ1е символы - последовательностями коди­
рованных байтов в форме "%ХУ'', где ОхХУ- шестнадцатеричное значение байта.

java.net.URLDecoder 1.2


static string decode (String s, String encoding)
Возвращает форму строки s, закодированной в формате
заданной кодировки символов.

URL

1.4

и декодированной с помощью

Глава

4.4.

4 •

Работа в сети

НТТР-клиент

Класс

URLConnection

был разработан еще до того, как сете1юй протокол НТТР

стал универсальным для Интернета. В этом классе поддерживается целый ряд се­
тевых протоколов, хотя поддержка протокола НТТР в нем реализована не очень

удобно. Когда было принято решение о поддержке сетевого протокола НТТР/2,
то стало ясно, что лучше предоставить современный клиентский интерфейс вме­

сто того, чтобы переделывать уже существующий прикладной интерфейс
Так, в классе

АР! для поддержки сетевого протокола НТТР/2. Начиная с версии

Ht tpClient

API.

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

HttpClient

входит в состав пакета

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

Java 11

класс

j ava. net. ht tp.

Java 9 и 1О прикладные программы следует запускать на выполне­

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

--add-modules

jdk.incuЬator.httpclient

В прикладном интерфейсе АР! для НТТР-клие1rrа предоставляется более про­
стой механизм подключения к веб-серверу, чем в классе

URLConnection,

где этот

процесс дотошно выполняется в течение целого ряда стадий. НТТР-клиент, реали­
зуемый средствами класса

HttpClient,

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

от неб-сервера. Чтобы получить такой клиент, достаточно сделать следующий вызов:

HttpClient client = HttpClient.newHttpClient()
Если требуется сконфигурировать клиент, то можно восполь:юваться при­
кладным интерфейсом

API

его построителя, как показано ниже.

HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
Подобным образом получается построитель, вызываются методы для специ­
алыюй настройки создаваемого клиента, а затем вызывается метод

build ()

с це­

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

По такому же шаблону построения составляются запросы. В качестве приме­
ра ниже демонстрируется составление НТТР-запроса по команде

GET.

HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("http://horstmann.com"))
.GET ()

.build ();
Универсальный идентификатор ресурса
нозначен

URL.

Но в

Java

(URI)

предоставляется класс

в сетевом протоколе Н1ТР рав­

URL

с методами, фактически уста­

навливающими соединение с веб-ресурсом по заданному

URI

URL,

тогда как класс

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

фрагмент и т.д.).
Для составления НТТР-запроса по команде POST требуется "издатель тела за­
проса", где запрашиваемые данные преобразуются в пересылаемые данные. Име­
ются издатели тела :1апроса для символьных строк, массивов байтов и файлов.

4.4.
Так, если запрос составляется в формате

НТТР-клиент

JSON, издателю его тела достаточно
JSON, как показано ниже.

пре­

доставить символьную строку в формате

HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(url))
. header ( "Content-Type", "application/j son")
.POST(HttpRequest.BodyPuЬlishers

.ofString(jsonString))
.build();
К сожалению, в рассматриваемом здесь прикладном интерфейсе АР! не под­
держивается требующееся форматирование общеупотребительных типов со­
держимого запросов. В приведенном далее примере программы из листинга

4.8

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

формы и выгрузки файлов.

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

BodyHandlers. ofString ()

HttpResponse.

следующим образом:

HttpResponse response = client.send(request,
HttpResponse.BodyHandlers.ofString() );
Класс

Ht tpResponse

является обобщенным, а параметр его типа обозначает

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

дующим образом:

String bodyString = response.body();
Имеются и другие обработчики тела ответа, получающие ответ в виде массива
байтов или потока ввода. В частности, метод

BodyHandlers. ofFile ( filePa th)

возвращает обработчик, сохраняющий отнет н заданном

файле, а

метод

BodyHandlers. ofFileDownload (directoryPath) сохраняет ответ в заданном
каталоге, используя имя файла из заголовка Content-Disposi tion. Наконец,
обработчик, возвращаемый из метода BodyHandlers. discarding (),просто от­
вергает полученный ответ.

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

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

JSON,

API.

Так, если ответ получается в фор­

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

библиотека, поддерживающая обработку данных формата
В объекте типа

HttpResponse

JSON.

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

ки ответов:

int status = response.statusCode();
HttpHeaders responseHeaders = response.headers();
Объекты типа

HttpHeaders

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

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

Map headerMap = responseHeaders.map();
Если же требуется значение для конкретного ключа и заранее известно, что

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

firstValue (),

Глава

4 •

Работа в сети

как показано ниже. В ответ получается конкретное значение, а если оно не пре­
доставлено

-

пустое необязательное значение.

Optional lastModified =
headerMap.firstValue("Last-Modified");
Огветы можно обрабатывать и асинхронно. Для этого при построении клиен­
та предоставляется исполнитель:

ExecutorService executor = Executors.newCachedThreadPool();
HttpClient client = HttpClient.newBuilder()
.executor(executor) .build();
Сначала

составляется

sendAsync (},

запрос,

а

затем

для

клиента

вызывается

метод

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

ствие типа CompletaЫeFuture, где Т

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

12

-

тип обработчика

API

для завершаемых

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

HttpRequest request = HttpRequest.newBuilder() .uri(uri)
.GET() .build();
client.sendAsync(request,
HttpResponse.BodyHandlers.ofString())
thenAccept(response -> . . . );
СОВЕТ. Чтобы активизировать режим протоколирования для НТТР-клиента типа
достаточно ввести следующую строку кода в файл

net.properties

HttpClient.
JDK:

свойств комплекта

jdk.httpclient.HttpClient.log=all
Вместо параметра all можно указать разделяемый запятыми список параметров headers,
requests, content, errors, ssl, trace и frames, а после них дополнительно - пара­
метры :control, :data, :window или :all, но без пробелов.
После этого можно установить уровень протоколирования

httpclient. HttpClient, введя, например,
properties свойств комплекта JDK:

INFO для регистратора jdk.
logging.

следующую строку кода в файл

jdk.httpclient.HttpClient.level=INFO

Листинг 4.8. Исходный код из файла

1

client/HttpClientTest. java

package client;

2

3
4
5
6
7
8

import
import
import
import
import
import

java.io.*;
java.math.*;
java.net.*;
java.nio.charset.*;
java.nio.file.*;
java.util.*;

9

10
11
12
13

class

14

{

15

import java.net.http.*;
import java.net.http.HttpRequest.*;
MoreBodyPuЬlishers

puЫic

static

BodyPuЬlisher

ofFormData(

4.4.
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
67

68
69
70

Map data)
var first = true;
var builder = new StringBuilder();
for (Map.Entry entry
data.entrySet())
l f (first) first = false;
else builder.append("&");
builder.append(URLEncoder.encode(
entry.getKey() .toString(),
StandardCharsets.UTF 8));
builder.append("=");
builder.append(URLEncoder.encode(
entry.getValue() .toString(),
StandardCharsets.UTF 8) );

return

BodyPuЫishers.ofString(builder.toString());

private static byte[] bytes(String s)
{ return s.getBytes(StandardCharsets.UTF 8); )
puЫic

static BodyPuЬlisher ofMimeMultipartData(
Map data, String boundary)
throws IOException

var byteArrays = new ArrayList();
byte[] separator = bytes("--" + boundary
+ "\nContent-Disposition: form-data; name=");
for (Map.Entry entry :
data.entrySet())
byteArrays.add(separator);
if (entry.getValue() instanceof Path)
{
var path = (Path) entry.getValue();
String mimeType = Files.probeContentType(path);
byteArrays.add(bytes("\"" + entry.getKey()
+ "\"; filename=\"" + path.getFileName()
+ "\"\nContent-Type: "
+ mimeType + "\n\n") );
byteArrays.add(Files.reac!AllBytes(path));
else
byteArrays.add(bytes("\"" + entry.getKey()
+ "\"\n\n" + entry.getValue() + "\n"));
byteArrays.add(bytes("--" + boundary + "--"));
return BodyPuЬlishers.ofByteArrays(byteArrays);

puЫic

static

BodyPuЬlisher ofSimpleJSON(
Map data)

71

72

var builder = new StringBuilder();

НПР-клиент

Глава

73
74
75
76
77
78

4 •

Работа в сети

builder.append("{");
var first = true;
for (Map.Entry entry
data.entrySet())
if (first) first = false;
else
builder.append(",");
builder.append(jsonEscape(entry.getKey()
.toString()))
. append ( " : ")
.append(jsonEscape(entry.getValue()
. toString ()));

79
80
81

82
83
83

84
85
86
87
88

builder.append("}");
return BodyPuЬlishers.ofString(builder.toString());

89
90
91

private static Map replacements =
Map.of('\b', "\\Ь", '\f', "\\f", \n', "'\\n",
'\r', "\\r", '\t', "\\t", '"', "\\\"",
'\\', "\\\\");

92
93
94
95
96
97
98
99

private static StringBuilder jsonEscape(String str)

100
101
102

char ch = str.charAt(i);
String replacement = replacements.get(ch);
if (replacement == null) result.append(ch);
else result.append(replacement);

(

var result
for (int i

new StringBuilder("\"");
О; i < str.length(); i++)

{

103
104

result.append("\"");
return result;

105
106

107
108
109

110
111
112
113
114
115
116

117
118
119

120
121
122
123

124
126
127
128
129

puЬlic

class HttpClientTest

(
puЫic

static void main(String[] args)
throws IOException, URISyntaxException,
InterruptedException

System.setProperty("jdk.httpclient.HttpClient.log",
"headers,errors");
String propsFilename = args.length >О ? args[O]
"client/post.properties";
Path propsPath = Paths.get(propsFilename);
var props = new Properties();
try (InputStream in =
Files.newinputStream(propsPath))
props. load (in);
String urlString = "" + props.remove("url");
String contentType

4.4.
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

НТТР-клмент

+ props.remove("Content-Type");
if (contentType. equals ( "multipart/ form-data"))
{
var generator = new Random();
String boundary = new Biginteger(256, generator)
. toString ();
contentType += ";boundary=" + boundary;
props.replaceAll( (k, v) ->
v.toString() .startsWith("file://")
? propsPath.getParent()
.resolve(Paths.get(v.toString()
.substring(7)))
v);
String result = doPost(urlString,
contentType, props);
System.out.println(result);

puЫic

static String doPost(String url,
String contentType, Map data)
throws IOException, URISyntaxException,
InterruptedException

HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
BodyPuЫisher puЫisher

= null;
if (contentType.startsWith("multipart/form-data"))
{
String boundary = contentType.substring(
contentType. last IndexOf ( "=") + 1) ;
puЫisher = MoreBodyPuЬlishers
.ofMimeMultipartData(data, boundary);

else if (contentType.equals(
"application/x-www-form-urlencoded"))
puЫisher = MoreBodyPuЬlishers.ofFormData(data);
else
{
contentType = "application/json";
puЫisher = MoreBodyPuЬlishers.ofSimpleJSON(data);

HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(url))
.header("Content-Type", contentType)
.POST(puЬlisher)

.build();
HttpResponse response = client.send(
request, HttpResponse.BodyHandlers.ofString() );
return response.body();

Глава

4 •

Работа в сети

java.net.http.HttpClient 11



static HttpClient newHttpClient ()
Возвращает объект типа HttpClient с конфигурацией
static HttpClient.Builder newBuilder()





НТТР-клиента по умолчанию.

Возвращает построитель НТТР-клиентов, представленных объектами типа



HttpClient.
HttpResponse send(HttpRequest request, HttpResponse.
BodyHandler responseBodyHandl.er)


sendAsync(HttpRequest
HttpResponse .BodyHandler responseBodyHandl.er)

CompletaЬleFuture

rвquest,

Составляют синхронный и асинхронный запрос и обрабатывают тело получаемого ответа

с помощью заданного обработчика.

java.net.http.HttpClient.Builder 11


HttpClient build ()
Возвращает объект типа

HttpClient

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

строителем НТТР-клиентов.



HttpClient.Builder followRedirects(HttpClient.Redirect policy)
Устанавливает правило переадресации, определяемое одной из следующих констант из пере­

числения HttpClient. Redirect: ALWAYS, NEVER или NORМAL !отклонять переадресацию
только из сетевого протокола НТТРS в протокол НТТР].



HttpClient.Builder executor(Executor executor)
Устанавливает исполнитель асинхронных запросов.

java.net.http.HttpRequest 11


HttpRequest.Builder newBuilder()
Возвращает построитель НТТР-запросов, представленных объектами типа

HttpRequest.

java.net.http.HttpRequest.Builder 11


HttpRequest build()
Возвращает объект типа

HttpRequest

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

строителем НТТР-запросов.



HttpRequest.Builder uri(URI uri)



HttpRequest.Builder header(String name, String value)

Устанавливает

URI

для данного запроса.

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

4.5.
j ava . net. h t tp . Ht tpReques t. Builder 11





Отправка электронной почты

{окончание}

HttpRequest. Builder GET ()
BttpRequest.Builder DELETE()
HttpRequest.Builder POST(HttpRequest.BodyPuЬlisher bodyPuЬlisher)
HttpRequest.Builder PUТ(HttpRequest.BodyPuЬlisher bodyPuЬlisher)
Устанавливают метод доступа и тело для данного запроса.

java.net.http.HttpResponse 11



т

body()

Возвращает тело данного ответа.



int statusCode ()



HttpHeaders headers ()

Возвращает код состояния для данного ответа.
Возвращает заголовки ответа.

java.net.http.HttpHeaders 11


мap

шар()

Возвращает отображение типа мар данных заголовков.



Optional firstValue(Strinq name)
Возвращает первое значение по имени, указанному в данных заголовках, если таковое
имеется.

4.5.

Отправка электронной почты

В прошлом для отправки электронной почты досrаточно было написать про­

25, который
SMTP (Simple Mail Transport

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

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

Protocol -

просrой протокол передачи почты), описывающего формат электрон­

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

SMTP,
1.

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

Открыть сокет на своем компьютере, подключенном к Интернету, как по­
ка:ыно ниже.

var s = new Socket("mail.yourserver.com", 25);
11 номер порта 25 соответствует протоколу SMTP
var out = new PrintWriter(s.getOutputStream(),"UTF-8");

2.

Направит~, в поток вывода следующие данные:

HELO

хост

МAIL

FROM:

отправителя

адрес

отправителя

Глава

4 •

Работа в сети

RCPT ТО:
DATA
Subject:

адрес

(пустая

строка)

почтовое
(любое

получателя

тема
сообщение

количество

строк)

QUIT
В спецификации сетевого протокола

SMTP

(документ

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

RFC 821) требуется, что­
/r и /n. Первоначально

SМТР-серверы исправно направляли электронную почту от любого адресата. Но
когда навязчивые сообщения наводнили Интернет, большинство этих серверов
было оснащено встроенными проверками и принимали запросы только по тем

IР-адресам, которым они доверяют. Аутентификация обычно происходит через
безопасные сокетные соединения.

Реализовать алгоритмы подобной аутентификации вручную

-

дело непро­

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

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

JavaMail API для отправки сообщений электронной почты из про­
Java. С этой целью загрузите данный прикладной интерфейс по адре­

https://javaee.github.io/javamail/

и разархивируйте его на жесткий

диск своего компьютера.

Чтобы воспользоваться прикладным интерфейсом

JavaMail

АР!, необходимо

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

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

сервера

GMail.

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

мере программы из листинга

4.9.

mail.transport.protocol=smtps
mail.smtps.auth=true
mail.smtps.host=smtp.gmail.com
mail.smtps.user=cayhorstmann@gmail.com
Из соображений безопасности пароль не вводится в файл свойств и предла­
гается для ввода вручную. После чтения из файла свойств сеанс почтовой связи
устанавливается следующим образом:

Session mailSession = Session.getDefaultinstance(props);
Затем составляется почтовое сообщение с указанием требуемого отправителя,
получателя, темы и текста самого сообщения:

MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(from) );
message.addRecipient(RecipientType.TO,
new InternetAddress(to) 1;
message.setSubject(subject);
message.setText(builder.toStr1ng());
Далее почтовое сообщение отправляется следующим образом:

Transport tr = mailSession.getTransport();
tr.connect(null, password);
tr.sendMessage(message, message.getAllRecipients());
tr.close();

4.5.

Отправка электронной почты

Рассматриваемая здесь программа читает почтовое сообщение из текстового

файла в приведенном ниже формате.
Отправитель

Получатель
Тема

Текст сообщения

(любое количество

строк)

Кроме упомянутого выше прикладного интерфейса

JavaMail API,

для выпол­

нения данной программы потребуется архивный JАR-файл каркаса

Activation Framework,

JavaBeans

который можно загрузить по адресу

https: / /www. oracle.
com/technetwork/ j ava/j avase/downloads/ index-13504 6. html#download или из
центрального хранилища Maven Central по адресу https: / /mvnreposi tory. сот/
arti f act/ j avax. acti va tion/ acti va tion. Затем выполните следующую команду:
java -classpath .:javax.mail.jar:activation-1.1.1.jar
path/to/message.txt
На момент написания данной книги почтовый сервер

GMail

не проверял до­

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

president@whi tehouse. gov

очередного приглашения на официальный прием, организуемый на лужайке пе­
ред Белым домом.)
СОВЕТ. Если вам не удастся выяснить причину, по которой соединение с почтовым сервером
не действует, сделайте следующий вызов и проверьте почтовые сообщения:

mailSession.setDebug(true);
Кроме того, обратитесь за полезными советами на веб-страницу

JavaMail API FAQ !Часто за­

даваемые вопросы по прикладному программному интерфейсу JavaMail API FAQ), доступную
по адресу

ht tps: / / j avaee. g i thub. io/ j avamail /FAQ.

Листинг 4.9. Исходный код из файла

1
2
3
4
5
6
7

8
9
10

mail/MailTest. java

package mai l;
import
import
import
import
import
import
import

java.io.*;
java.nio.charset.*;
java.nio.file.*;
java.util.*;
javax.mail.*;
javax.mail.internet.*;
javax.mail.internet.MimeMessage.RecipientType;

11 /**

12 * В этой программе демонстрируется применение
13 * прикладного интерфейса JavaMail API для отправки
14 * сообщений по электронной почте
15 * @author Сау Horstmann
16 * @version 1.01 2018-03-17
17 */
18 puЫic class MailTest
19 !

Глава

20
21
22
23
24
25
26
77
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 }

puЫic

4 •

Работа в сети

static void main(String[] args)
throws MessagingException, IOException

var props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get ("mail", "mail.properties"J))
props.load(in);
List lines = Files.readAllLines(
Paths.get(args[O]), StandardCharsets.UTF 8);
String from = lines.get(O);
String to = lines.get(l);
String subject = lines.get(2);
var builder = new StringBuilder();
for (int i = 3; i < lines.size(); i++)
builder.append(lines.get(i));
builder.append("\n");

Console console = System.console();
var password =
new String(console.readPassword("Password: ") );
Session mailSession =
Session.getDefaultinstance(props);
// mailSession.setDebug(true);
var message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(from));
message.addRecipient(RecipientType.TO,
new InternetAddress(to));
message.setSubject(subject);
message.setText(builder.toString() );
Transport tr = mailSession.getTransport();
try
{
tr.connect(null, password);
tr.sendMessage(message,
message.getAllRecipients() );
f inally
{
tr.close();

В этой главе было показано, как на

Java

пишется исходный код программ

для сетевых клиентов и серверов и как организуется сбор данных с неб-серверов.

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

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

JDBC

АР!.

Java,

ис­

ГЛАВА

Работа с базами данных
В этой главе ...
~ Структура

JDBC

~

Язык

~

Конфигурирование

~

Работа с операторами

~

Выполнение запросов

~

Прокручиваемые и обновляемые результирующие наборы

~

Наборы строк

~

Метаданные

SQL
JDBC
JDBC

~ Транзакции
~

Расширенные типы данных

SQL

~ Управление подключением к базам данных в веб- и корпоративных
приложениях

В

1996

году компания

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

(JDBC).

API

Sun Microsystems

выпустила первую версию приклад­

для организации доступа из программ на

Java

к базам дан­

Этот прикладной интерфейс позволяет соедишm,ся с базой данных,

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

(Structured Query Language - SQL).

Язык

SQL

фактически стал стандартным сред­

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

JDBC

JDBC

стал одним

11 библиотеке

Java.

неоднократно обновлялся. На момент написа­

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

став версии

APJ

JDBC 4.3,

включенная в со­

Java 9.

В этой главе рассматриваются принципы, положенные 11 основу прикладного

интерфейса

JDBC.

Из нее вы узнаете (а возможно, лиш1, вспомните) о языке

SQL,

Глава

Работа с базами данных

5 •

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

В ней будут также рассмотрены примеры применения интерфейса

JDBC,

демон­

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

НА ЗАМЕТКУ! Как заявляют в компании Огасlе,
ние

Java Database Connectivity.

JDBC -

это торговая марка, а не сокраще­

Она была придумана по аналогии с обозначением

ODBC

стан­

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

предложен корпорацией

5.1.

Структура

Создатели

Microsoft

и затем внедрен в стандарт

JDBC

Java

с самого начала осознавали потенциальные преимущества дан­

ного языка для работы с базами данных. С

ширением стандартной библиотеки
средствами

SQL.

SQL.

1995

года они начали работать над рас­

для организации доступа к базам данных

Java

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

Java,

которые

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

Java,

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

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

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

Java

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

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

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

Java

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

SQL,

а также диспетчер

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

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

JDBC основана на вес1,ма
ODBC, разработанного в корпорации Microsoft.
ODBC положен общий принцип: программы, на­

Подобная организация прикладного интерфейса

удачной модели интерфейса

В основу интерфейсов

JDBC

и

писанные в соответствии с требованиями прикладного интерфейса АР!, способ­
ны взаимодействовать с диспетчером драйверов

JDBC,

который, в свою очередь,

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

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

5.1.1.

JDBC API.

Типы драйверов

Каждый драйвер



JDBC

JDBC

Драйвер типа

1.

относится к одному из перечисленных ниже типов.

Преобразует интерфейс

JDBC в ODBC и для взаимодей­
ODBC. Один такой драйвер был
названием мост f DBC/ODBC. Но для его

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

включен в первые версии

Java

под

применения требуется установить и настроить соответствующим образом

драйвер

ODBC.

В первом выпуске

JDBC

этот мост предполагалось исполь:ю­

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

5.1.

Структура

JDBC

В настоящее время уже имеется достаточное количество более удачных

драйверов, поэтому пользоваться мостом



Драйвер типа

2.

Написан частично

JDBC/ODBC не рекомендуется.
на Java и отчасти использует платфор­

менно-ориентированный код для взаимодействия с клиентским приклад­

ным интерфейсом

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

помимо библиотеки

специфический для конкретной платформы.



Драйвер типа
ки

Java,

3.

Разрабатывается только на основе клиентской библиоте­

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

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

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



Драйвер типа
на

Java,

4.

Представляет собой библиотеку, написанную только

для приведения запросов

JDBC

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

кретной базы данных.
НА ЗАМЕТКУ! Спецификация прикладного интерфейса

JDBC доступна

для загрузки по адресу

https://jcp.org/en/jsr/detail?id=221.
Большинство поставщиков баз данных предоставляют драйверы типа

3 или 4.

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

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

поставщиками баз данных.

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

JDBC

можно сформулировать следу­

ющим образом.



Разработчики пишут программы на

Java, пользуясь
SQL (или его

данных стандартными средствами языка

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

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



Java.

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

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

JavaOne

в мае

1996

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

Micгosystems указали на ряд следующих причин отказа от модели




Sun

ODBC.

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

Java

основан на применении большого количества простых

и интуитивно понятных методов.



Основана на использовании указателей типа
отсутствующих в



void* и других элементов языка С,

Java.

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

Java.

Глава

5.1.2.

5 •

Работа с базами данных

Типичные примеры применения

JDBC

Соглас1ю традиционной модели " клиент-сервер" граф11ческ11i1 поль:ювате11ь­
ский шперфеiiс (ГПИ) реализуется на стороне клнента, а ба:1а да1111ы х распола­
гается на стороне сервера (рис.

5.1 ).

В лом случае драйвер

JDBC

ра : шертывО && sel скалярную функцию,
следует вставить ее стандартное имя и аргументы, как показано ниже. Полныi1

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

JDBC.

{fn left(?, 20))
( fn user ( 1 }
Хранимой называется такая процедура, которая выполняется в базе да1111ых
и написана на специальном языке для конкретной базы данных. Для вызова хра­
нимой процедуры служит переход

cal1.

Если у процедуры отсутствуют пара­

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

{call PROCl (?, ?) }
{call PROC2}
{call ? = РRОСЗ(?)}
Внешнее соединение двух таблиц не требует, чтобы строки из каждой таблицы
совпадали по условию соединения. Например, в приведенном ниже запросе ука­
заны книги, для которых столбец PuЬlisher_Id не имеет совпадений в таблице
PuЬlishers, причем пустые значения

NULL

обо:шачают отсутствие совпадений.

SELECT * FROM {oj Books LEFT OUTER JOIN PuЬlishers
ON Books.PuЬlisher Id = PuЫisher.PuЬlisher Id}
-

-

Чтобы включить в запрос и:~дательства бе:~ совпадающих книг, может потре­
боваться предложение

и другое

-

RIGHT OUTER JOIN, а чтобы возвратить по запросу и то
FULL OUTER JOIN. Синтаксис переходов требуется

предложение

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

5.5.
Наконец, знаки

Выполнение запросов

и % имеют специальное назначение в операции LIKE, обо­

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

приведенной ниже конструкцией, где знак

а последовательность символов

!_

_,

можно воспользоваться

определен как символ перехода,

!

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

... WHERE? LIKE % % {escape '' '}
1

5.5.4.

Множественные результаты

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

1.

Вызвать метод

2.

Получить первый результат или подсчет обновлений.

3.

Повторить вызов метода

execute ()

для выполнения оператора

getMoreResul ts (),

SQL.

чтобы перейти к следую­

щему ре:~ультирующему набору.

4.

Завершить процедуру, если больше не остается результирующих набо­
ров или подсчетов обновлений.

Методы

execute ()

и

getMoreResul ts ()

возвращают логическое значение

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

true,
Метод

get.UpdateCount ()

возвращает значение

-1,

если следующим звеном в це­

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

boolean isResult = stat.execute(command);
boolean done = false;
while (!done)
{

if (isResult)
{

ResultSet result = stat.getResultSet();
сделать

в

что-нибудь

переменной

с полученным результатом

result

else
int updateCount = stat.getUpdateCount();
if (updateCount >= 0)
сделать

что-нибудь

в переменной

с подсчетом обновлений

updateCount

else
done = true;
if (!done) isResult

stat.getMoreResults();

Глава

5 •

Работа с базами данных

java.sql.Statement 1.1


boolean

qetмoreResults

()

boolean

qetмoreResults

(int current) 6

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

SQL. Параметр current при­
нимает значение одной из следующих констант: CLOSE CURRENТ RESULT lпо умолчанию!,
I

showTaЬle(

(String)

taЫeNames.getSelecteditem(),
add(taЫeNames,

BorderLayout.NORTH);

conn) );

5.8.
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

addWindowListener(new WindowAdapter()
{
puЫic void windowClosing(WindowEvent event)
{
try
{
if (conn 1= null) conn.close();
catch (SQLException ех)
{
for (ThrowaЫe t : ех)
t.printStackTrace();
}
}) ;
var buttonPanel = new JPanel();
add(buttonPanel, BorderLayout.SOUTH);
previousButton = new JButton("Previous");
previousButton.addActionListener(
event -> showPreviousRow());
buttonPanel.add(previousButton);
nextButton = new JButton("Next");
nextButton.addActionListener(
event -> showNextRow());
buttonPanel.add(nextButton);
deleteButton = new JButton("Delete");
deleteButton.addActionListener(
event -> deleteRow());
buttonPanel.add(deleteButton);
saveButton = new JButton("Save");
saveButton.addActionListener(
event -> saveChanges() );
buttonPanel.add(saveButton);
if (taЬleNames.getitemCotшt() > 0)
showTaЬle(taЬleNames.getitemAt(O),

/**
* Подготавливает

*

таблицы и

* @param

поля

первую ее

для

void

Подключение

к

showTaЬle(String

базе

показа

новой

строку

taЬleName Имя отображаемой

* @param conn
*/
puЬlic

текстовые

отображает

conn);

таблицы

даннь~

taЬleName,

Connection conn)
try (Statement stat
conn.createStatement();
ResultSet result
stat.executeQuery(
"SELECT * FROM " + taЬleName))
//

получить

результирующий набор и

Метаданные

Глава

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

5 •

Работа с базами данных

скопировать

//

его

в

кешируемьШ

//результирующий набор

RowSetFactory factory =
RowSetProvider.пewFactory();

crs = factory.createCachedRowSet();
crs.setTaЬleName(taЫeName);

crs.populate(result);

!= пull) remove(scrollPaпe);
= пеw DataPaпel(crs);
scrollPaпe = пеw JScrollPaпe(dataPaпel);
add(scrollPane, BorderLayout.CENTER);
pack();
showNextRow();
if

(scrollPaпe

dataPaпel

catch
(
for

(SQLExceptioп
(ThrowaЫe

ех)

t :

ех)

t.priпtStackTrace();

/**
* Осуществляет
*/

переход

к

предыдущей

строке

таблицы

void showPreviousRow()

puЫic

(
try
(
if (crs == пull 1 1 crs. isFirst ()) return;
crs.previous();
dataPanel.showRow(crs);
catch (SQLException
for

(ThrowaЫe

t :

ех)
ех)

t.priпtStackTrace();

/**

* Осуществляет переход к следующей
*/
puЫic void showNextRow()
(
try
(
if (crs==пull 11 crs.isLast())
crs.пext();

dataPaпel.showRow(crs);

catch
(
for

(SQLExceptioп
(ThrowaЫe

t :

ех)
ех)

t.priпtStackTrace();

строке

таблицы

returп;

5.8.
195
196
197
198
199
200
201
202
203
204
205
20 6
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250

/**
* Удаляет строку из текущей таблицы
*/
puЫic void deleteRow()
{
if (crs == null) return;
new SwingWorker()
{
puЫic Void doinBackground() throws SQLException
{

crs.deleteRow();
crs.acceptChanges(conn);
if (crs.isAfterLast())
if (!crs.last()) crs = null;
return null;
puЫic

void done()

{
dataPanel.showRow(crs);
}.execute();

/**
* Сохраняет

все

внесенные

изменения

*/
puЫic

void saveChanges()

{
if (crs == null) return;
new SwingWorker()
{
puЫic Void doinBackground() throws SQLException
{
dataPanel.setRow(crs);
crs.acceptChanges(conn);
return null;
}.execute();
private void readDatabaseProperties()
throws IOException
props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get("database.properties") 1)
props.load(in);
String drivers = props.getProperty(
"jdbc.drivers");
if (drivers != null)
System.setProperty("jdbc.drivers", drivers);

Метаданные

Глава

5 •

Работа с базами данных

/**
251
* Получает сведения о подключении к базе данных из
252
253
* свойств, задаваемых в файле database.properties,
254
* и на их основании подключается к базе данных
255
* @return Подключение к базе данных
256
*/
private Connection getConnection()
257
throws SQLException
258
259
String url = props.getProperty("jdbc.url");
260
props.getProperty(
261
String username
"jdbc.username");
262
props.getProperty(
String password
263
"jdbc.password");
2 64
265
return DriverManager.getConnection(url,
266
username, password);
267
268
269
270
271 /**
272 * Панель для отображения содержимого
273 * результирующего набора
*/
274
275 class DataPanel extends JPanel
276 (
private java.util.List fields;
277
276
/**
277
* Конструирует панель для отображения данных
278
* @param rs Результирующий набор, содержимое
279
которого отображается на данной панели
280
*/
281
puЫic DataPanel(RowSet rs) throws SQLException
282
{
283
fields = new ArrayList();
284
setLayout(new GridBagLayout());
285
var gbc = new GridBagConstraints();
286
gbc.gridwidth = 1;
287
gbc.gridheight = 1;
288
289
ResultSetMetaData rsmd = rs.getMetaData();
290
for (int i = 1; i того сш1ска расположена группа к1юпок-перек11ючателей, в котороi1 можно

ныбрап, способ форматирования чисел, денежных сумм или числовых вел11ч1111

н процентах. После выбора ноной регионалыюй настройки или способа форма­
тирования чисел число в текстовом поле автоматически переформатируето1.

Просмотрев л11w1, несколько вариантов формат11рованш1 ч11сел

11

денежных

сумм в зависимости от выбранных репю11алы1ых настроек, пол1. :ювате111,, несо­

мнешю, составит ясное представление о разнообразии сущесп1ующих форма­
тов чисел. В тексто1юм поле можно ввести любое число и щелкнул, на кнопке

Parse,

в резул1.тате чего будет вызва11 метод

parse () ,

выполш1ющий синтаксиче­

ский анализ введенной символыюй строки. При удкра­
" Par.se erro r"

не. В пропtв1юм случае в текстовом поле появляется сообщение
(Ошибка синтаксического анализа).

О

[

Number (!, Curren(\1 U Percent

Рше ]\Ш_._4-=_5-6~-,_7-=_s-=_€-:_-:_-:_=_-::::_~~--------~r
Рис.

7.1.

Рабочее ок110 нро1раммы NumЬerFormatTest

Как следует из листиша

7.1,

исходный код рассматриваемой :iдecr. программы

имеет доволыю простую структуру. Сначала в конструкторе вы:.1ывается метод

N11mberF'orma t . g etAva ilaЫ e L o ca le s (). Затем для каждого в1ца поддержи­
ваемых региональных настроек вы:~ывается метод getD isplayName () ,а во :тра­
щаемые ·:>тим методом ре:~ультаты вводятся в список. (Симво11ы1ые строкн 11е
сортируются; подробнее об этом реч1, пойдет в разделе

7.4.)

Во1к11i1 раз, когда

по111,зовател1, выбирает другие регионалы1ые настройки или способ форматиро­
вания чисел, со:1дается новый форматирующий объект и 061ю1ияется содерж11мое текстового поля. Если по111,зовате111, щелкнет 11а кнопке
метод

p arse

Parse,

то вызывается

(),преобразующий в число 01м1юлы1ую строку в соответствии с вы­

бранными регио11алы1ыми настройками.
НА ЗАМЕТКУ! Длs~ чтениs~ локализованных целых чисел, а та кже чисел с плаваю щей точкой
можно воспользоватьсs~ классом

Scanner,

вызвав метод

useLocale ()

из этого класса

длs~ установки региональных настроек.

Листинг

7.1.

Исходный код из файла numЬerFormat/NumЬerFormatTest. java

1

package

2
3
4
S
6

import
import
import
import

numЬer Format;

java.awt .*;
j a va.aw t . eve nt.*;
j ava .t e xt.*;
java .util. * ;

7.2.

Форматирование чисел

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
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

import javax.swing.*;
/**
* В

*

этой

программе

при разных

демонстрируется

региональных

форматирование

чисел

настройках

* @version 1.15 2018-05-01
* @author Сау Horstmann

*/
puЫic

NumЬerFormatTest

class

{
puЫic

static void main(String[] args)

{
EventQueue.invokeLater(() ->
{
var frame = new NumЬerFormatFrame();
frame. setTi tle ( "NumЬerFormatTest");
frame.setDefaultCloseOperation(
JFrame.EXIT ON CLOSE);
frame.setVisiЬle(true);

));
)
)
/**

*

Этот

*

выбора

способа

*
*

список

для

поле

*

а

*

анализа

фрейм содержит

для

также

*/
class

кнопки-переключатели для

форматирования

выбора

региональных

отображения

кнопку для

NumЬerFormatFrame

комбинированный

настроек,

отформатированного

активизации

содержимого

чисел,

текстового

текстовое
числа,

синтаксического
поля

extends JFrame

private Locale[] locales;
private douЫe currentNumЬer;
private JComЬoBox localeComЬo
new JComЬoBox();
private JButton parseButton =
new JButton("Parse");
private JTextField numЬerText
new JTextField(30);
private JRadioButton numЬerRadioButton
new JRadioButton("Number");
private JRadioButton currencyRadioButton
new JRadioButton("Currency");
private JRadioButton percentRadioButton =
new JRadioButton("Percent");
private ButtonGroup rbGroup = new ButtonGroup();
private NumЬerFormat currentNumЬerFormat;
puЫic

NumberFormatFrame()

{
setLayout(new GridBagLayout());
ActionListener listener

=

event -> updateDisplay();

Глава

7 •

Интернационализация

var р = new JPanel();
addRadioButton(p, numЬerRadioButton,
rbGroup, l1stener);
addRadioButton(p, currencyRadioButton,
rbGroup, listener);
addRadioButton(p, percentRadioButton,
rbGroup, listener);

64
65
66
67
68
69

70
71
73
74
75
76

add(new JLabel("Locale:"I,
new GBC(O, 0) .setAnchor(GBC.EAST) );
add(p, new GBC(l, 11);
add(parseButton, new GBC(O, 2) .setinsets(2) 1;
add(localeComЬo, new GBC(l, 0) .setAnchor(GBC.WEST));

77

add(numЬerText,

78
79
80
81
82
83
84
85
86
87
88

new GBC(l, 2) .setFill (GBC.HORIZONTAL));
locales = (Locale[J 1
NumberFormat.getAvailaЫeLocales() .clone();
Arrays.sort(locales,
Comparator.comparing(Locale: :getDisplayName) );
for (Locale loc : locales)

72

localeComЬo.additem(loc.getDisplayName());

localeCombo.setSelecteditem(
Locale. getDefaul t ( 1 . getDisplayName ( 1);
currentNumЬer = 123456.78;
updateDisplay();

89

90
91
92
93
94

localeCombo.addActionListener(listener);
parseButton.addActionListener(event ->
{

String s =
try

95
96

m1mЬerText. getтext

{

97

Number n =

98
99

currentNumЬer

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

1). trim ();

currentNumЬerFormat.parse(s);

= n.douЫeValue();
updateDisplay();
catch (ParseException

е)

{

numberText.setText(e.getMessage());
)

) 1;
pack ( 1;
/**
* Вводит
*

@param

*

кнопки-переключатели

р

в

контейнер

Контейнер для размещения
кнопок-переключателей

* @param Ь Кнопка-переключатель
* @param g Группа кнопок-переключателей
* @param listener Приемник событий от

*

кнопок-переключателей

*/
puЫic

void addRadioButton(Container р,
JRadioButton Ь, ButtonGroup g,
ActionListener listener)

7.2.
121
122
12 3
12 4
125
12 6
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
14 9
150

b.setSelected(g.getButtonCount()
b.addActionListener(listener);

0);

g.add(Ь);
p.add(Ь);

/**
* Обновляет

*

в

отображаемое

соответствии

с

число и

форматирует

пользовательскими

его

установками

*/
puЬlic

void updateDisplay()

(
Locale currentLocale =
locales[localeComЬo.getSelectedindex()];
currentNumЬerForrnat

= null;

(numЬerRadioButton.isSelected())

if

currentNumЬerForrnat

=

NumЬerFormat

.getNumЬerinstance(currentLocale);

else if (currencyRadioButton.isSelected())
currentNumЬerFormat = NumberFormat
.getCurrencyinstance(currentLocale);
else if (percentRadioButton.isSelected())
currentNumЬerForrnat = NumberFormat
.getPercentinstance(currentLocale);
String forrnatted = currentNumЬerFormat
.format(currentNumЬer);

numЬerText.setText(formatted);

java.text.NumЬerFormat



Форматирование чисел

static Locale []

1.1

qetAvailaЬleLocales

()

Возвращает массив объектов типа Locale, для которых доступны форматирующие объекты
типа NumЬerFormat.






static

NumЬerFormat

static

NumЬerFormat

qetNumЬerinstance(Locale

static

NumЬerFormat

qetCurrencyinstance()

static

NumЬerFormat

qetCurrencyinstance(Locale 1)



static

NumЬerFormat

qetPercentinstance()



static

NumЬerFormat

qetPercentinstance(Locale 1)

qetNumЬerinstance()

1)

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



Strinq format (douЫe



Strinq format (lonq

х)

х)

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

Глава

7 •

Интернационализация

j ava. text. NumЬerForma t



NwnЬer

1 .1

/окончание/

parse (String s)

Возвращает число, получаемое в результате синтаксического анализа символьной строки.
Это число может иметь тип

Long

или DouЫe, а символьная строка не должна начинаться

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

ParseException.
void setParseintegerOnly(boolean
boolean isParseintegerOnly ()
чение типа




Ь)

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



void setGroupingUsed(boolean
boolean isGroupingUsed ()

Ь)

Устанавливают или получают признак, указывающий на то, что данный форматирующий объ­

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

100, ООО) в анализируемых числах.
void set:МinimumintegerDigits(int n)
int get:МinimumintegerDigits()
void setмaximumintegerDigits(int n)
int getмaximumintegerDigits()
void set:МinimwnFractionDigits(int n)
int get:МinimumFractionDigi ts ()
void setмaximumFractionDigits(int n)
int getмaximumFractionDigits()













Устанавливают или получают максимальное или минимальное количество цифр в целой или
дробной части числа.

7.2.2.

Форматирование денежных сумм в разных валютах

Для форматирования

денежных сумм

служит

метод

Numbe r Fo rma t.

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

getCurrencyinstance (),

рующий объект только для одной валюты. Допустим, для американского :ыказ­

чика выписывается счет-фактура, где одни суммы представлены в долларах США,
а другие в евро. Для решения этой задачи нельзя просто воспользоваться двумя

форматирующими объектами, как показа1ю ниже. Счет, в котором фигурируют
такие суммы, как
при

$100,

ООО и

представлении сумм

100.

ОООС, будет выглядеть не совсем обычно. Ведь

в евро для

точка, а сумм в долларах США

NumberFormat dollarFormatter

-

разделения

групп

разрядов используется

:ыпятая.

=

NumЬerFormat.getCurrencylnstance(Locale.US);

NumberFormat euroFormatter

=

NumЬerFormat.getCurrencyinstance(Locale.GERMANY);

Для управления форматированием денежных сумм в ра:шых валютах лучше
воспользоваться классом

Currency.

Сначала получается объект типа

Currency,

7.2.
мя чего статическому методу

Форматирование чисел

Currency. getinstance () передается идентифи­
setCurrency () мя каждого форматиру­

катор валюты. Затем вызывается метод

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

euroFormatter =

Nl1mЬerFormat

NumЬerFormat.getCurrencyinstance(Locale.US);

euroFormatter.setCurrency(Currency.getinstance("EUR") );
Идентификаторы налют определяются по стандарту

org/iso-4217-currency-codes .html).
Таблица

7.3.

ISO 4217 (https: / /www. iso.
7.3.

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

Идентификаторы валют

Валюта

Идентификатор

Доллар США

USD

Евро

EUR

Ашлийский фунт

GBP

Яrюнская иена

JPY

Китайский юань

CNY

Индийская рупия

INR

Российский рубль

RUВ

java. util. Currency 1. 4
static Currency getinstance (String currencyCode)


static Currency getinstance (Locale locale)
Возвращают экземпляр класса
дарту

150 4217

Currency,

соответствующий заданному коду валюты по стан­

или стране, указанной в текущих региональных настройках.



String toString ()



String getCurrencyCode ()



String getNumericCode () 7



getNumericCodeAsString () 9
Получают код текущей валюты по стандарту

String

getSymЬol

()

String

getSymЬol

(Locale locale)

150 4217.

Получают форматирующий знак текущей валюты в соответствии с текущими или заданными

региональными настройками. Например, доллар США

US$


[USDI может обозначаться как $ или

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

int getDefaultFractionDigits()
Получает принятое по умолчанию количество цифр в дробной части денежной суммы, указан­
ной в текущей валюте.



static Set

getAvailaЫeCurrencies

Получает все имеющиеся валюты.

() 7

Глава

7.3.

7 •

Интернационализация

Форматирование даты и времени

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



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



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



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



Следует учитывать часовые пояса.

Для форматирования даты и времени применяется класс

DateTimeFormatter.

Затем выбирается один из четырех стилей форматирования, перечисленных
в табл.

7.4.

Далее получается средство форматирования следующим образом:

11 Один из стилей форматирования FormatStyle.SHORT,
11 FormatStyle.MEDIUM, ...
FormatStyle style = ... ;
DateTimeFormatter dateFormatter
DateTimeFormatter.ofLocalizedDate(style);
DateTimeFormatter timeFormatter =
DateTimeFormatter.ofLocalizedTime(style);
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofLocalizedDateTime(style);
11 или DateTimeFormatter
11
.ofLocalizedDateTime(stylel, style2)
Таблица

7.4.

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

Стиль

Дата

Время

SHORT

7/16/69
Jul 16, 1969
July 16, 1969

9:32 АМ
9:32: 00

МEDIUМ

LONG

9: 32: 00
9: 32: 00

АМ
АМ

для класса

FULL

Wednesday,
July 16, 1969

EDT

с учетом региональных настроек

МSZ с учетом региональных настроек

en-US,
de-DE (только

ZonedDateTime)

9: 32: 00 АМ EDT с учетом региональных настроек en-US,
9: 32 Uhr мsz с учетом региональных настроек de-DE
(только для класса ZonedDateTime)

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

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

wi thLocale ()

следующим образом:

DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDate(style) .withLocale(locale);
Теперь можно отформатировать местную дату (объект типа
местное время и дату (объект типа

LocalTime)
но ниже.

LocalDateTime),

или поясное время и дату (объект типа

LocalDate),

местное время (объект типа

ZonedDateTime),

как показа­

7.3.

Форматирование даты и времени

ZonedDateTime appointment = ... ,
String formatted = formatter.format(appointment);
НА ЗАМЕТКУ! В данном случае применяется класс

DateTimeFormatter из пакета java.
java. text. DateFormatter, внедренный еще
объектами типа Date и Calendar.

Имеется также устаревший класс

time.

в версии

Java 1.1

для манипулирования

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

служит один

из

статических

LocalDateTime, LocalTime

или

методов parse ()
ZonedDateTime:

LocalTime time = Loca1Time.parse("9:32
Но методы

parse ()

в

классах

LocalDate,

formatter);

АМ",

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

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

варительной обработки. Например, средство форматирования даты и времени
в кратком стиле для Соединенных Штатов способно проанализировать символь­

"9: 32

ную строку

АМ", но не строку

"9: 32АМ"

или

"9: 32 am".

ВНИМАНИЕ! Средства форматирования дат подвергают синтаксическому анализу несуще­



ствующие даты вроде

31

ноября, корректируя их по последней дате в данном месяце.

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

getDisplayName ()

из перечислений

DayOfWeek

и

Month,

как показано ниже.

for (Month m : Month.values())
System.out.println(m.getDisplayName(textStyle, locale) +" ");
Стили

форматирования

STANDALONE

текста

перечислены

пример, январь по-фински обозначается как

"tammikuu"
Таблица

7.5.

в

табл.

7.5.

Стили

типа

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

"tammikuuta"

в самой дате, но как

за ее пределами или отдельно.

Стили форматирования текста, представленные константами из перечисления

java.time.format.TextStyle
Пример

Стиль

FULL /

FULL_ STANDALONE

SHORT /
NARROW /

SHORT_STANDALONE
NARROW STANDALONE

January
Jan
J

НА ЗАМЕТКУ! Первым днем недели может быть суббота, воскресенье или понедельник в за­
висимости от конкретных региональных настроек. Выяснить первый день недели с учетом
региональных настроек можно следующим образом:

DayOfWeek first = WeekFields.of (locale) .getFirstDayOfWeek();

Глава

7 •

Интернационализация

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

DateFormat

7.2,

де­

11а практике. Эта программа по­

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

в разных странах форматируются дата и время. На рис.

7.2

пока зано рабочее

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

Как видите, даты вы1юдято1 11а экран в правильном формате для китайских реги­
ональных настроек .

••-.::;



::а

.

Localo Chlnes• (ChlnвJ

Oate Lon1_ j~201~5я б 8
Tlme Short 1 • 1•.f4:Зб
Date and time Full

Рис.

\• 1

~

--

~

- -- -

-

Parse
- -' P-a r-se_ ,

-

Т~20lб!!'5Яб8 lila!E 1"f0411

22

{

var frame = new DateTime f ormatt er Frame ( ) ;
frame.setTitle(" DateFo rmatTest " ) ;
frame.setDe faultCl oseOpera ti on(
J frame .EXIT_ON_CLOSE);

23
24
25
26
27
28

frame.setVis iЬl e(true ) ;

));

29

30
31

32
33

/* *

*

Э тот фрейм со д ержит

комбиниро в а нные

с пи с к и для

да т

java

7.З. Форматирование даты и времени

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
77
78
79
80
81
82
83
84
85
86
87
88
89

*
*

выбора

*

отформатированных даты и

*
*
*

синтаксического

региональных

времени,

полей и

текстовые

настроек и
поля для

времени,

а

также

кнопки

содержимого

текстовых

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

нестрогой

интерпретации

анализа

форматов даты и

отображения

вводимых дат и

времени

*/
class DateTimeFormatterFrame extends JFrame
private
private
private
private
private
private
private
private
private
private
private
private
private
private
private

private

Locale[] locales;
LocalDate currentDate;
LocalTime currentTime;
ZonedDateTime currentDateTime;
DateTimeFormatter currentDateFormat;
DateTimeFormatter currentTimeFormat;
DateTimeFormatter currentDateTimeFormat;
JComЬoBox

localeComЬo

new JComЬoBox();
JButton dateParseButton
new JButton("Parse");
JButton timeParseButton
new JButton("Parse");
JButton dateTimeParseButton
new JButton("Parse");
JTextField dateText = new JTextField(30);
JTextField timeText = new JTextField(30);
JTextField dateTimeText = new JTextField(30);
EnumComЬo dateStyleComЬo =
new EnumComЬo ( FormatStyle. class, "Short",
"Medium", "Long", "Full");
EnumComЬo

new

timeStyleComЬo

EnumComЬo(FormatStyle.class,

"Short", "Medium");
private

EnumComЬo

new

puЬlic

dateTimeStyleComЬo

EnumComЬo(FormatStyle.class,

"Short",
"Medium", "Long", "Full");

DateTimeFormatterFrame()

{
setLayout(new GridBagLayout());
add(new JLabel("Locale"),
new GBC(O, 0) .setAnchor(GBC.EAST) );
add(localeComЬo, new GBC(l, О, 2, 1)
.setAnchor(GBC.WEST) );
add(new JLabel("Date"),
new GBC(O, 1) .setAnchor(GBC.EAST));
add(dateStyleComЬo,

new GBC(l, 1) .setAnchor(GBC.WEST));
add(dateText,
new GBC(2, 1, 2, 1) .setFill(GBC.HORIZONTAL));
add(dateParseButton,
new GBC(4, 1) .setAnchor(GBC.WEST) );
add(new JLabel("Time"J,
new GBC(O, 2) .setAnchor(GBC.EAST) );

Глава

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
124
125
12 6
127
128
129
130
131
132
133
134
135
136
137
138
139
140
142
143
144
145
146

7 •

Интернационализация

add(timeStyleComЬo,

new GBC(l, 2) .setAnchor(GBC.WEST));
add(timeText,
new GBC(2, 2, 2, 1) .setFill(GBC.HORIZONTAL) );
add(timeParseButton,
new GBC(4, 2) .setAnchor(GBC.WEST) );
add(new JLabel("Date and time"),
new GBC(O, 3) .setAnchor(GBC.EAST));
add(dateTimeStyleComЬo,

new GBC(l, 3) .setAnchor(GBC.WEST));
add(dateTimeText,
new GBC(2, 3, 2, 1) .setFill(GBC.HORIZONTAL) );
add(dateTimeParseButton,
new GBC(4, 3) .setAnchor(GBC.WEST));
locales = (Locale[])
Locale. getAvailaЬleLocales () . clone ();
Arrays.sort(locales, Comparator.comparing(
Locale: :getDisplayName) );
for (Locale loc : locales)
localeComЬo.additem(loc.getDisplayName());

localeCombo.setSelecteditem(
Locale. getDefaul t () . getDisplayName () ) ;
currentDate = LocalDate.now();
currentTime = LocalTime.now();
currentDateTime = ZonedDateTime.now();
updateDisplay();
ActionListener listener = event -> updateDisplay();
localeCombo.addActionListener(listener);
dateStyleComЬo.addActionListener(listener);

timeStyleCombo.addActionListener(listener);
dateTimeStyleComЬo.addActionListener(listener);

addAction (dateParseButton, () ->
{
currentDate = LocalDate.parse(
dateText.getText () .trim(),
currentDateFormat);
));
addAction (timeParseButton, () ->
(
currentTime = LocalTime.parse(
timeText.getText () .trim(),
currentTimeFormat);
));
addAction (dateTimeParseButton, () ->
(
currentDateTime = ZonedDateTime.parse(
dateTimeText.getText () .trim(),
currentDateTimeFormat);
});
pack();

7.3.
147
148
14 9
150
151
152
153

/**
* Добавляет заданное действие к экранной кнопке,
* по завершении обновляет отображение
* @param button Экранная кнопка, к которой

154
155
156
157
158
159
160
161
162
163

Форматирование даты и времени

*

добавляется действие

* @param action
*

Действие,
на

выполняемое

экранной

а

при щелчке

кнопке

*/
puЫic

void addAction(JButton button,

RunnaЫe

action)

{

button.addActionListener(event ->
{

try
{

action.run();
updateDisplay();

164
165
166
167
168
169
170
171
172

173
174
175

catch (Exception

JOptionPane.showMessageDialog(
null, e.getMessage() );
)
)) ;

/**
* Обновляет

* их
*/

17 6
177
178
179
180
181

182
183

184
185
186
187
188
189
190
191

192
193
194

195
196
197
198
199
200

201

е)

{

puЫic

в

отображаемые

соответствии

с

дату и

время

и

форматирует

пользовательскими установками

void updateDisplay()

{
Locale currentLocale

=

locales[localeComЬo.getSelectedindex()

];
FormatStyle dateStyle = dateStyleComЬo.getValue();
currentDateFormat =
DateTimeFormatter.ofLocalizedDate(dateStyle)
.withLocale(currentLocale);
dateText.setText(currentDateFormat
.format (currentDate));
FormatStyle timeStyle = timeStyleComЬo.getValue();
currentTimeFormat =
DateTimeFormatter.ofLocalizedTime(timeStyle)
.withLocale(currentLocale);
timeText.setText(
currentTimeFormat.format(currentTime) );
FormatStyle dateTimeStyle =
dateTimeStyleComЬo.getValue();

currentDateTimeFormat =
DateTimeFormatter.ofLocalizedDateTime(
dateTimeStyle) .withLocale(currentLocale);
dateTimeText.setText(currentDateTimeFormat
.format(currentDateTime) );

Глава

7 •

Интернационализация

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

щелкнуть на кнопке

Parse

(Произвести синтаксический анализ). В рассматривае­

мом здесь примере программы используется вспомогателы1ый класс EnumComЬo,

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

7.3.

Он служит для заполнения ком­

Short, Medium

и

Long,

а также для ав­

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

FormatStyle. SHORT, FormatStyle .MEDIUM или FormatStyle. LONG. Чтобы не пи­
сать повторяющийся код, в данном случае применяется рефлексия. Выбранный
пользователем вариант преобразуется в верхний регистр, пробелы заменяются
символами

подчеркивания,

после чего

определяется

значение

в

статическом

поле с полученным в итоге именем. (Более подробно рефлексия рассматривается
в главе

5

Листинг

7.3.

1

2
3
4
5
6
7
8
9

10
11

12
13

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

Исходный код из файла dateFormat/EnumComЬo. java

package dateFormat;
import java.util.*;
import javax.swing.*;
/**
* Комбинированный список для выбора среди значений
* статических полей, имена которых задаются в
* конструкторе вспомогательного класса
* @version 1.15 2016-05-06
* @author Сау Horstmann
*/
puЫic class EnumComЬo extends JComЬoBox

14 {

15
16
17
18
19
20
21
22

private Map

ОТНОСЯЩИХСЯ
EnumComЬo(Class

К ТИПУ Т

cl, String ... labels)

for (String label : labels)
{
String name = label.toUpperCase()
. replace ( ' '
' ' );
try
{
java.lang.reflect.Field f = cl.getField(name);
@SuppressWarnings ( "unchecked")
Tvalue= (Т) f.get(cl);
taЫe.put(label, value);

7.3.
38

catch (Exception

39

(

40
41
42
43
44
45
46
47
48
49
50
51
52

Форматирование даты и времени

е)

label = "(" + label + ")";
null);

taЫe.put(label,

additem(label);
setSelecteditem(labels[OJ );

/**
* Возвращает значение поля, выбранного
* @return Значение статического поля
*/
puЫic т getValue()
(
return taЫe.get(getSelecteditem());

53

54
55

пользователем

56
java.time.foпnat.DateTimeFormatter

В



static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle)



static DateTimeFormatter ofLocalizedTime (l!'ormatStyle dateStyle)



static DateTimeFormatter ofLocalizedDateTime(FormatStyle
da teTimeStyle)



static DateTimeFormatter ofLocalizedDate(FormatStyle
FormatStyle timeStyle)
Возвращают экземпляры типа

DateTimeFormatter для

dateStylв,

форматирования дат, времени или

того и другого с учетом заданных стилей.



DateTimeFormatter withLocale(Locale locale)
Возвращает копию данного средства форматирования вместе с заданными региональными
настройками.



String format (TemporalAccessor temporal)
Возвращает символьную строку, получающуюся в результате форматирования заданных даты
и времени.

java.time.LocalDate В
java.time.LocalTime 8
java.time.LocalDateTime
java.time.ZonedDateTime


static

Ххх

В
В

parse (CharSequence text, DateTimeFormatter .f"ormatter)

Производит синтаксический анализ заданной символьной строки и возвращает описанную
в ней местную дату или время в виде объекта типа

или

ZonedDateTime.

LocalDate, LocalTime, LocalDateTime
DateTimeParseException при неу­

Генерирует исключение типа

дачном исходе синтаксического анализа.

Гпава

7.4.

7 •

Интернацнонапизация

Сортировка и нормализация

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

String.

с пользователями. В методе

дировке

cornpareTo ()

из

К сожалению, этот метод не совсем годится для взаимодействия

UTF-16,

cornpareTo ()

применяются строковые значения в ко­

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

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

cornpareTo ()

таким образом:

Athens
Zulu
аЫе

zebra
Engstrom
При упорядочении словаря приходится учитывать регистр букв, но совсем не­
обязательно ударение. Для англоязычного пользователя приведенный выше пе­
речень слов должен быть упорядочен следующим образом:
аЫе

Engstrom
Athens
zebra
Zulu
Но такой порядок следования слов неприемлем для шведскоязычного поль­

зователя. Ведь в шведском языке буква А отличается от буквы А и поэтому сорти­
руется после буквы

Z!

Это означает, что для шведскоязычного пользователя упо­

мянутый выше перечень слов должен быть отсортирован следующим образом:
аЫе

Athens
zebra
Zulu
Angstrom
Чтобы получить компаратор с учетом региональных настроек, следует вызвать
метод

Collator. getinstance (),как показано ниже. Класс Collator реализует
Cornparator, поэтому объект типа Collator можно передать методу
List. sort (Cornpara tor), чтобы отсортировап, символьные строки.
11 Класс Collator реализует интерфейс Comparator:
Collator coll = Collator.getinstance(locale);
words.sort(coll);
интерфейс

Для средств сортировки предусмотрены четыре уровня избирательности: пер­
востепенный, второстененный, третьесте11еннь1й и идентичный. Например, в ан­

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

-

-

вто­

третьестепенным.

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

Collator. PRIМARY.
Colla tor. SECONDARY, то будут учтены

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

7.6.

7.4.

Сортировка и нормализация

Таблица 7.6. Сортировка с разными уровнями избирательности [английские региональные
настройки)
Первостепенный уровень

Angstrom
АЫе

=

аЫе

Третьестепенный уровень

Второстепенный уровень

= Angstrom

Angstrom
АЫе

=

*

Angstrom

Angstrom

аЫе

АЫе

Если же установлен уровень избирательности

*

*

Angstrom

аЫе

Collator. IDENTICAL,

то отли­

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

Иногда символ или последовательность символов могут быть описаны не

только в Юникоде. Например, символу А в Юникоде соответствует код U+OOC5.
С другой стороны, его можно представить в виде последовательности символов А

(код

U+0065)

и

0

тельность букв

гатура

ffi"

(кружок сверху; код U+ОЗОА). Еще удивительнее, что последова­

"ffi"

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

с кодом U+FBOЗ. (Можно, конечно, спорить, что это вопрос представ­

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

символов в Юникоде, но правила установлены не нами.)
В стандарте на Юникод определяются четыре формы норма.лUJации символь­

ных строк (О, КО, С и КС; подробнее об этом см. по адресу

org/reports/trl5/tr15-23.html).

http://www.unicode.

Две из этих форм используются для сортировки.

В форме нормализации О символы с ударением раскладываются на составляю­

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

0



А в формах нормализации КС и КО на составляющие расклады­

ваются такие символы, как лигатура

ffi

или знак торговой марки ти.

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

Collator.NO_DECOMPOSITION,

то символь­

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

Collator. CANONICAL _DECOMPOSITION,

определяющее

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

Таблица

7.7.

7.7.

Сортировка в разных режимах разложения на составляющие

Беэ разложения

Каноническое разложение

на составляющие

на составляющие

Полное разложение
на составляющие

А

А

""=

тм

Глава

7 •

Интернационализация

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

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

getCollationKey ()

возвращает объект типа

CollationKey,

используемый

для ускорения всех последующих операций сравнения.

String а = . . . ;
CollationKey аКеу = coll.getCollationKey(a);

11

быстрое сравнение:

if(aKey.compareTo(coll.getCollationKey(b)) == 0)
Наконец, символьные строки иногда требуется преобразовать в их нормали:ю­
ванные формы, не прибегая к сортировке. Такая потребность возникает, напри­
мер, при сохранении символы1ых строк в базе данных или при юаимодействии

с другой программой. Для этой цели служит класс

java. text. Normalizer,

вы­

полняющий процесс нормализации, как показано ниже. Нормализо11анная стро­

ка содержит десяп, символов. Символы А и Ь заменяются последовательностями
символов "А 0

"

и "Ь".

String name = "Angstrom";

11 использовать форму нормализации D:
String normalized = Normalizer.normalize(
name, Normalizer.Form.NFD);
Тем не менее это обычно не самая лучшая форма для хранения и передачи
символьных строк. В форме нормализации С сначала 11ыполняется разложение
на составляющие, а затем в установленном порядке присоединяются ударения.

В соответсrвии с рекомендациями консорциума WЗС такой режим является наи­
более предпочтительным для передачи данных через Интернет.

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

7.4,

по:шоляет экспе­

риментировать с разными видами сортировки. Досrаточно ввести слово в тексто­
вом поле и щелкнуть на кнопке

Add,

чтобы добавить введенное слово в список.

Список сортируется заново после добавления в него каждого слова, изменения
региональных настроек (в раскрывающемся списке Locale), уровня избиратель­
ности сортировки (в раскрывающемся списке
на составляющие (в раскрывающемся списке

Strength) или режима разложения
Decomposition). Знак равенства (=)

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

7.3).

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

US English, то
(Norway, Nynorsk) окажутся выше в дан­
настройки Norwegian (Norway), несмотря на то,

мую здесь программу при стандартных региональных настройках

региональные настройки

Norwegian

ном списке, чем региональные

что значение знака запятой в Юникоде больше, чем значение :шака закрываю­
щей скобки.

7.4.

tcompos1t1on.Full Dtcomposition

__ I дdd [_ -

-

Сортировка и нормализация



=---~~

аые

=

Рис.

АЬlе

7.3.

Ра60•1ее окно 1!ро1раммы

Листинг 7.4. Исходный код из файла

1

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

import
import
import
import
import

java . a wt.*;
java.awt.event.*;
java. tex t.*;
java.t1t il.*;
java .u t il.List;

i mport

JЗ vax .s w i n g.*;

/ **

* В этой про грамме демон с триру е1'ся сортировка
* симsольн ых с трок при выборе разных ре гион альных
* настроек
* @ve rsi o n 1.1 6 2018-05-0 1
* @a uthor Сау Horstmann

15
16

*/

17

18

puЫi c

19

(

c la ss Co llatio nTes t

20

puЫic

21

{



24
25
26

collation/CollationTest. java

package co ll a ti o n;

14

22

CollationTest

s tati c vo id main (String [] args)

Event Que ue .invokeLater (( ) - >
(
var frame = ne w Co ll a t 1onFrame(J;
fr ame . setTit l e ("Colla t i o nT es t" ) ;
fr a me . setDefau lt Cl oseOpera t ion(

Глава

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
77
78
79
80
81
82

7 •

Интернационализация

JFrame.EXIT ON CLOSE);
frame.setVisiЬle(true);

}) ;

/**
* Этот фрейм содержит
* выбора региональных

*

сортировки и

*

текстовое

*
*

строк,

а

режимов

поле
также

отсортированных

и

комбинированные
настроек,

разложения

на

кнопку для ввода

текстовую область
символьных

списки для

уровня

избирательности

составляющие,

новых
для

символьных

перечисления

строк

*/
class CollationFrame extends JFrame
{
private Collator collator = Collator.getinstance(
Locale.getDefault());
private List strings = new ArrayList();
private Collator currentCollator;
private Locale[] locales;
private JComЬoBox localeComЬo
new JComЬoBox();
private JTextField newWord = new JTextField(20);
private JTextArea sortedWords = new JTextArea(20, 20);
private JButton addButton = new JButton("Add");
private EnumComЬo strengthComЬo =
new EnumComЬo (Collator. class, "Primary",
"Secondary", "Tertiary",
"Identical");
private EnumComЬo decompositionComЬo =
new EnumCombo(Collator.class,
"Canonical Decomposition",
"Full Decomposition",
"No Decomposition");
puЫic

CollationFrame()

{
setLayout(new GridBagLayout() );
add(new JLabel("Locale"),
new GBC(O, 0) .setAnchor(GBC.EAST) );
add(new JLabel("Strength"),
new GBC(O, 1) .setAnchor(GBC.EAST) );
add(new JLabel("Decomposition"),
new GBC (О, 2) . setAnchor (GBC. EAST) ) ;
add(addButton, new GBC(O, 3) .setAnchor(GBC.EAST));
add(localeComЬo,

new GBC(l, 0) .setAnchor(GBC.WEST));
add(strengthCombo,
new GBC(l, 1) .setAnchor(GBC.WEST));
add(decompositionCombo,
new GBC(l, 2) .setAnchor(GBC.WEST));
add (newWord, new GBC (1, 3). setFill (GBC.HORIZONTAL));
add(new JScrollPane(sortedWords),
new GBC(O, 4, 2, l).setFill(GBC.BOTH));

7.4.
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
124
125
12 6
127
128
129
130
131
132
133
134
135
136
137
138

locales

=

(Locale[))

Сортировка и нормализация

Collator.getAvailaЫeLocales()

.clone 1);
Arrays.sort(locales, (11, 12) ->
collator.compare(ll.getDisplayName(),
12.getDisplayName()));
for (Locale loc : locales)
localeComЬo.additem(loc.getDisplayName() 1;
localeComЬo.setSelecteditem(

Locale. get Def aul t 11 . get DisplayName 11 1 ;
strings.addl"America");
strings.add("aЫe");

strings.add("Zulu");
strings.add("zebra");
strings. add (" \uOOC5ngstr\uOOF6m" 1;
strings.add("A\u030angstro\u0308m");
strings.add("Angstrom"I;
strings.add("AЫe");

strings.add("office");
strings.add("o\uFB03ce");
strings.add("Java\u2122"1;
strings.add("JavaTM");
updateDisplay();
addButton.addActionListener(event->
{
strings.add(newWord.getText() 1;
updateDisplay();
}) ;
ActionListener listener

=

event -> updateDisplay();

localeComЬo.addActionListener(listener);
strengthComЬo.addActionListener(listener);

decompositionCombo.addAct1onListener(listener);
pack();

/**
* Обновляет

* в
*/

отображаемые

соответствии

puЬlic

с

строки

и

сортирует

пользовательскими

их

установками

void updateDisplayll

{
Locale currentLocale =
locales[localeComЬo.getSelectedlndex()
localeComЬo.setLocale(currentLocale);

currentCollator =
Collator.getinstance(currentLocale);
currentCollator.setStrength(
strengthComЬo.getValue());

currentCollator.setDecomposition(
decompositionCombo.getValue() );
strings.sort(currentCollator);

);

Глава

139
140
141
142
143
144
145
146
147
148
149
150
151

7 •

Интернационализация

sortedWords.setText("");
for (int i
О; i < strings.size(); i++)
{
String s
strings.get(i);
if (i >О && currentCollator.compare(s,
strings.get(i - 1)) == 0)
sortedWords.append("= ");
sortedWords.append(s + "\n");
pack();

java.text.Collator 1.1



s ta tic Locale []

qetAvailaЬleLocales

Возвращает массив объектов типа
типа

Locale,

()

для которых существуют сортирующие объекты

Collator.



static Collator qetinstance()



static Collator qetinstance (Locale 1)



int compare (Strinq

Возвращают объект типа

Collator для
а,

Strinq

текущих или заданных региональных настроек.
Ы

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



boolean equals (Strinq

а,

Возвращает логическое значение
че




-

логическое значение

Strinq
true,

Ь)

если строки а и Ь считаются одинаковыми. а ина­

false.

void setStrenqth (int strength)
int qetStrenqth()
Устанавливают или получают уровень избирательности сортировки. Чем выше уровень из­
бирательности, тем больше вероятность того, что сортируемые слова будут признаны разны­

ми. Поддерживаются следующие уровни избирательности сортировки:

Collator. SECONDARY



И

Collator. PRIМARY,

Collator. ТERTIARY.

void setDecomposition (int decomp)
int qetDecompositon ()
Устанавливают или получают режим разложения на составляющие при сортировке символь­
ных строк. Чем выше степень разложения на составляющие, тем строже выполняется сравне­

ние сортируемых символьных строк. Поддерживаются следующие режимы разложения на со­
ставляющие:
И



Collator .NO_DECOМPOSITION, Collator. CANONICAL_DECOМPOSITION
Collator. FULL DECOМPOSITION.

CollationКey

qetCollationКey(Strinq

а)

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

7.5.

Форматирование сообщений

java.text.CollationKey 1.1


int compareTo(CollationKey

Ь)

Возвращает отрицательное значение, если данный ключ сортировки предшествует ключу Ь;
нулевое значение, если ключи одинаковы; или положительное значение, если данный ключ
следует за ключом Ь.

java.text.Normalizer 6
static String normalize (CharSequence str, Normalizer. Form form)
Возвращает нормализованную форму символьной строки

str. Параметр form может прини­

мать одно из следующих значений: ND, NКD, NC или NКС.

7.5.

Форматирование сообщений

В состав библиотеки

Java

входит класс

MessageFormat для форматирования

текста, содержащего фрагменты с переменными. Этот механизм подобен форма­
тированию с помощью метода

printf

(),но он действует с учетом региональных

настроек, а также форматов чисел и дат. В последующих разделах этот механизм
рассматривается более подробно.

7.5.1.

Форматирование чисел и дат

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

{2), а {0) destroyed {1) houses
caused {3} of damage."

"Оп

апd

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

MessageFormat. format ()

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

значений переменных может быть произведена следующим образом:
Striпg

msg = Messagerormat.format(
{2), а (0) destroyed {1) houses апd
caused {3} of damage.", "hurricaпe", 99,
пеw GregoriaпCaleпdar(l999, О, 1) .getTime(),
"Оп

В данном

примере заполнитель

"hнrricane", запол11ител1,

{1} -

{О}

10.ОЕВ);

замещается

числовым значением

строковым

99

значением

и т.д. А в результате

подстановки получается следующая текстовая строка:

Оп

1/1/99 12:00 АМ, а hurricaпe destroyed 99 houses and
caused 100,000,000 of damage.
Результат для начала неплохой, но вряд ли может устроить полностью. В част­

ности, время 12:

00

АМ отображать не следует, а сумму ущерба от урагана нужно

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

Глава

7 •

Интернационализация

{2,date,long},

"Оп

caused

а

{О}

destroyed {1} houses
of damage."

апd

{3,numЬer,currency}

В результате очередной подстановки получается следующая строка:

January 1, 1999, а hurricaпe destroyed 99 houses
caused $100,000,000 of damage.

Оп

апd

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

time
date
choice
Если указан тип numЬer, то допускаются следующие стили:
iпteger
curreпcy

perceпt

В качестве стиля может быть также ука3ан шаблон числового формата, напри­
мер $,##О. (Подробнее об этом см. в документации на класс
Для типа

time

или

date

Decimal Forma t.)

может быть указан один из следующих стилей:

short
medium
loпg

full
Аналогично числам, в качестве стиля может быть указан шаблон даты, напри­
мер ггг-мм-дд. (Допустимые форматы подробно рассматриваются в документа­
ции на класс



SimpleDateFormat.)

ВНИМАНИЕ! Статический метод

format ()

форматирует значения с учетом текущих регио­

нальных настроек. Форматировать сообщения средствами класса

MessageFormat

с учетом

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

метод с переменным числом аргументов. Поэтому форматируемые значения придется разме­
стить в массиве

var mf

=

Striпg

msg =

Object [],

как показано ниже.

loc);
Object[] { значения});

пеw MessageFormat(patterп,

mf.format(пew

java.text.MessageFormat 1.1


Locale getLocale {)
Устанавливают или получают региональные настройки для заполнителей в сообщении.
Региональные настройки пригодны только для последующих шаблонов, задаваемых с помо­
щью метода



applyPattern ().

static String format(String pattern, Object ... args)
Форматирует символьную строку по шаблону ра ttern, подставляя вместо заполнителей

{i}

объекты из массива

args [i].

7.5.

Форматирование сообщений

java. text.MessageFormat 1. 1 {окончание}
StringBuffer format(Object args, StringBuffer result,
FieldPosition pos)



Форматирует шаблон данного объекта типа

MessageFormat. Параметр args принима­

ет массив объектов. Форматируемая строка добавляется к значению параметра
которое затем возвращается. Если параметр

pos

resul t,

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

new
FieldPosi tion (MessageForma t. Field. ARGUМENT) , его свойства beginindex
и endindex устанавливаются в соответствии с расположением текста, который подставля­
ется вместо заполнителя { 1}. Если же сведения о расположении подстановочного текста не
важны, в качестве параметра pos следует задать пустое значение null.

java.text.Format 1.1


String format (Object obj)
Форматирует заданный объект по правилам, определяемым текущим форматирующим объек­
том. С этой целью делается следующий вызов:

format (obj,

new StringBuffer () ,

new Field Posi tion ( 1) ) . toString () .

7.5.2.

Форматы выбора

Вернемся к шаблону из предыдущего раздела, чтобы рассмотреть его подробнее:

"On {2},

а

{0} destroyed {1} houses and caused {3} of damage."

Если вместо заполнителя {О

ставить строковое значение

},

обозначающего вид стихийного бедствия, под­

"earthquake"

(землетрясение), то получится следу­

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

используемых артиклей:

On January 1, 1999,

а

earthquake destroyed . . .

Для устранения этой грамматической ошибки артикль а придется ввести в за­
полнитель {О} следующим образом:

"On {2}, {0} destroyed (1} houses and caused {3} of damage."
Теперь вместо заполнителя {О} будет подставлен текст "а

"an

earthquake ".

hurricane"

или

Такой способ особенно удобен для перевода сообщений

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

"{0} zerstbrte am {2} {1} H!user und richtete einen Schaden
von {3} an."
В этом случае заполнитель будет заменяться грамматически правильными со­
четаниями артикля и имени существительного, например

и

"Ein

Wirbelsturrн"

"Eine Naturkatastrophe".
Теперь рассмотрим заполнитель

{1}.

Если стихийное бедствие оказалось не

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

ние

1.

Но и в этом случае получится предложение с нарушением правил англий­

ской грамматики:

On January 1, 1999,

а

mudslide destroyed 1 houses and . . .

Глава

7 •

Интернационализация

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

no houses
one house
2 houses
Именно для этой цели и был внедрен формат выбора типа

choice.

В соответ­

ствии с этим форматом задаето1 последователыюст1, пар значений, каждая и:1 ко­

торых содержит нижний предел и фор.чатирующую строку. Нижний предел и фор­
матирующая строка разделяются знаком

знак

1.

#,

а для разделения пар значений служит

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

{1},

в котором формат выбора выде­

лен полужирным. Резул1,таты форматирования текста сообщения в зависимости
от :шачения, подставляемого вместо заполнителя

{1},

представлены в табл.

7.8.

{1, choice, O#no housesll#one housel2#{1} houses}
Таблица

7.8.

Текст сообщения, отформатированный по выбору

{1}

Результат

о

"no houses"
"one house"
113 houses"
"no houses"

1
3
-1

А зачем в форматирующей строке дважды ука:1ьшается заполнитель

{1}?

Ког­

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

2,

возвращаето1 символьная строка"

{1} houses".

Эта строка формати­

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

7.8,

первый предел

в классе МessaqeForшat вообще игнорируется. Синтаксис форматирования сообщений мог
бы быть более понятным, если бы пределы указывались между выбираемыми вариантами,
например, следующим образом:

no houses 111 one house 12 1{1} houses
11 более понятньм, но не действующий
С помощью :шака

< можно указать,

формат

что предлагаемый вариант должен быть вы­

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

#

можно также ука:швать знак

:::; (\ u2 2 6 4

в Юникоде). По желанию

можно даже ука:1ать для пер но го значения нижний предел равным -оо

(- \ u2 21 Е

в Юникоде), как пока:1а110 ниже.

-oosQО;l'"!Т--~ &4 Ба1апс.е $ 588. 949 бЭ
fli~ бS 6а1~е SSSS, 3:97 11
~
~
~
~

....J
;
А

r,
i

66 Balance S.520, t&J 96
67 &lanc~ S.&83, 175 Зt
6З Вa:l!Sl"ICe

S44J .334 1)8

69

S4•J3, SSIJ 78

6а1мс~

1

~

"i:J &alэnce f360, 728 32
it.~ 7J БаllУКе SЗtS ,164 -.
~ :Z Баlм~>:е t2б6 SSl 97

t.9e

:з 6alanc~

jil:ge

:ч Ба111nо.:е

S:1З. 980

62

$166 .929 бS

Ago!

iS tial~ или
типом

MIME.

java.x. script. ScriptEngineFactory 6



List qetNames()
List qetExtensions()



List

qetltimeТypes

()

Получают имена, расширени>~ файлов сценариев и типы

MIME,

по которым известна данна>~

фабрика.

8.1.2.

Выполнение сценариев и привязки

Получив интерпретатор, можно приступить к вызову сценария:

Object result = engine.eval(scriptString);
Если сценарий хранится в файле, необходимо открыть поток чтения типа

Reader

и сделать следующий вызов:

Object result

=

engine.eval(reader);

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

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

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

172 9.

engine.eval("n = 1728");
Object result = engine.eval("n + l");

Глава В



Написание сценариев. номпипяция и обработка аннотаций

НА ЗАМЕТКУ! Чтобы выяснить, безопасно ли параллельное выполнение сценариев во многих
потоках, достаточно сделать следующий вызов:

Object param = factory.getParameter("THREADING");
В итоге возвращается одно из перечисленных ниже значений.
параллельное выполнение небезопасно.



null:



"МULTITНREADED": параллельное выполнение безопасно. Результаты исполнения одного
потока могут быть доступны для другого потока.



"TНREAD-ISOLAТED": то же, ЧТО и значение "МULTIТНREADED", НО только для каждого
потока исполнения поддерживаются разные привязки переменных.



"STATELESS":

то же, что и значение "TНREAD-ISOLAТED", но только сценарии не могут

изменять привязки переменных.

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

Java.

Рассмо­

трим в качестве примера следующие операторы:

engine.put(k, 1728);
Object result = engine.eval("k + 1");
Код сценария читает определение объекта

k из

привязок в области видимости

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

у

Java

и зачастую посредством более простого, чем

синтаксиса. Например:

Java,

engine.put(b, new JButton() );
engine.eval("b.text = 'Ok'");
С другой стороны, можно извлекать значения переменных, привя:ынных опе­
раторами сценария, как показано ниже.

engine.eval("n = 1728");
Object result = engine.get("n");
Кроме области видимости интерпретатора сценариев, существует и глобаль­
ная область видимости. /lюбые привязки, которые вводятся в объект типа

EngineManager,

Script

становятся видимыми для всех и1rrерпретаторов сценариев.

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

в область видимости интерпретатора сценариев, их можно накапливать в объ­
екте типа

Bindings

и передавать методу

eval

(),как пока:ыно ниже. Это очень

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

eval ().

Bindings scope = engine.createBindings();
scope.put(b, new JButton() );
engine.eval(scriptString, scope);
НА ЗАМЕТКУ! Безусловно, может возникнуть потребность иметь и другие области видимости,
отличающиеся как от глобальной области видимости, так и от области видимости интерпре­

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

ScriptContext,

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

видимости. Каждая такая область видимости должна снабжаться целочисленным номером,

8.1.

Написание сценариев дnя платформы

Java

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

ром.

IB стандартной библиотеке доступен только класс SimpleScriptContext, но он пред­

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

сценариев.!

javax. script. ScriptEngine 6


Object eval (String script)



Object eval (Reader reader)



Object eval (String script, Bindings



Object eval (Reader

readвr,

Bindings

Ьindings)
Ьindings)

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



Object get (String key)



void put (String

kву,

Object value)

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



Bindings createBindings()
Создает пустой объект типа

Bindings,

пригодный для данного интерпретатора сценариев.

javax.script.ScriptEngineManager 6


Object get (String key)



void put (String

kву,

Object value)

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

javax.script.Bindings 6


Object get (String key)



void put (String

kву,

Object value)

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

Bindings.

8.1.З. Переадресация ввода-вывода
Стандартный ввод-вывод можно переадресовывать в сценарии, вызывая метод

setReader ()

или

setWri ter ()

соответственно в контексте сценария, как пока­

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

JavaScript,

как

print ()

или

println (),

направляются объ­

writer.

var writer = new StringWriter();
engine.getContext() .setWriter(new PrintWriter(writer, true) );

Глава
Методы

8 •

Написание сценариев, компиляция и обработка аннотаций

setReader ()

и

setWri ter ()

воздейсrвуют только на стандартные исrоч­

ники ввода-вывода данных в интерпретаторе сценариев. Например, при выполне­
нии приведенного ниже кода в сценарии

JavaScript перенаправлен будет только
Nashom ничего неизвестно о стандартном
метода setReader () ничего не даст.

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

println ( "Hello");
java.lang.System.out.println("World");

javax. script. ScriptEngine 6
ScriptContext getContext()
Получает стандартный контекст сценариев для данного механизма.

javax.script.ScriptContext 6


Reader getReader ()
void setReader (Reader reader)
Writer getWriter ()
void setWri ter (Wri ter wri ter)
Writer getErrorWriter()
void setErrorWri ter (Wri ter wri ter)







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

8.1.4.

Вызов функций и методов из сценариев

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

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

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

Nashom. Чтобы вызвать функ­
invokeFunction (),указав

цию из сценария, достаточно обратиться к методу

в нем имя и параметры требуемой функции:

11 определить функцию приветствия в JavaScript
engine.eval("function greet(how, whom)
{ return how + ', ' + whom + ' 1 ' } " ) ;
11 вызвать эту функцию с аргументами "Hello", "World"
result = ( (InvocaЬle) engine) .invokeFunction("greet",
"Hello", "World");
Если же язык сценариев является объектно-ориентированным, можно вызвать
метод

11

invokeMethod ()

определить

следующим образом:

класс

Greeter в JavaScript:
engine.eval("function Greeter(how) { this.how

how } ");

8.1.

Написание сценариев дпя платформы

Java

engine.eval("Greeter.prototype.welcome ="
+ " function (whom) "
+ "{ return this.how + ', ' + whom + '1' 1");
11 получить экземпляр:
Object уо = engine.eval("new Greeter('Yo')");
11 вызвать метод приветствия для экземпляра:
result = ( (InvocaЬle) engine) .invokeMethod(yo,
"welcome", "World");
НА ЗАМЕТКУ! Подробнее об определении классов в JavaScгipt см. в книге

Good Parts Дугласа

JavaScript-The

Крокфорда !Douglas Cгockfoгd, издательство O"Reilly, 2008 г.1. 1

НА ЗАМЕТКУ! Даже если механизм сценариев не реализует интерфейс InvocaЫe,
метод все равно можно вызвать не зависящим от конкретного языка образом. Метод
getмethodCallSyntax

()

из класса

ScriptEngineFactory формирует символьную
eval (). Но все параметры этого метода долж­

строку, которую можно затем передать методу

ны быть привязаны к именам, в то время как метод invokeМethod О может вызываться
с произвольными значениями параметров.

Можно пойти еще дальше и запросить интерпретатор сценариев реализовать

интерфейс

Java.

В этом случае появится возможность вызывать функции и мето­

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

Java

для вызова методов. И хотя это зави­

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

из интерфейса достаточно предоставить соответствующую функцию. Рассмо­
трим в качестве примера следующий интерфейс
puЬlic

Java:

interface Greeter

{

String greet(String whom);
Если определить глобальную функцию с тем же именем в

Nashom,

ее можно

вызвать через следующий интерфейс:

11 определить функцию приветствия в JavaScript:
engine.eval("function welcome(whom)"
+ " { return 'Hello, ' + whom + '!' 1");
11 получить объект Java и вызвать метод Java:
Greeter g = ( ( InvocaЬle) engine) .getinterface (Greeter.class);
result = g.welcome("World");
В объектно-ориентированном языке сценариев доступ к классу из сценария
можно получить через соответствующий интерфейс

Java.

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

JavaScript
Greeter g

вызывается в синтаксисе языка

В следующем приме­

SimpleGreeter

из языка

Java:

( (InvocaЬle) engine)
.getinterface(yo, Greeter.class);
result = g.welcome("World");
=

1 В русском 11ереводе эта книга вышла IIOД названием

тельстве "Питер", СПб.

2012

г.

favaScript.

Cu,\t>Hl>le стороны в изда­

Глава В



Написание сценариев, компиляция и обработка аннотаций

Таким образом, интерфейс InvocaЫe оказывается удобным в том случае,
если требуется вызвап, код сценария из кода

Java,

не особенно разбираясь в син­

таксисе языка сценариев.

javax.script.InvocaЫe

6



Object invokeFunction(String name, Object ... parameters)



Object invokeМethod(Object implicitEarameter, String
Object. . . explici tE&rllZ/leters)





пате,

Вызывают функцию или метод с указанным именем, передавая заданные параметры.
Т

getinterface (Сlавв iface)

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





Т

getinterface (Object implicitEarameter, Class iface)

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

методов заданного объекта.

В.1.5. Компиляция сценариев
Некоторые интерпретаторы сценариев способны компилировать код сценария

в промежуточную форму дл~I более эффективного выполнения. Такие интерпре­

таторы реализуют и1перфейс CompilaЫe. В следующем примере кода демонстри­
руется компилирование и вычисление кода, содержащегося в файле сценария:

var reader = new FileReader("myscript.js");
CompiledScript script = null;
if (engine implements CompilaЬle)
CompiledScript script =
( (CompilaЬle) engine) .compile(reader);
После компиляции сценария можно перейти к его выполнению. В приведен­

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

-

исходного сцена­

рия, если окажется, что механизм сценариев не поддерживает компиляцию. Без­
условно, компилировап, сценарий нужно лишь в том случае, если его требуется
ВЫПОЛНИТ!> повторно.

if (script != null)
script.eval();
else
engine.eval(reader);

javax.script.CompilaЫв



6

CompiledScript compile (String script)
CompiledScript compile (Reader reader)
Компилируют сценарий, задаваемый символьной строкой или потоком чтения.

В.1. Написание сценариев дпя платформы

Java

javax.script.CompiledScript 6



Object eval ()
Object eval(Bindings bindings)
Вычисляют данный сценарий.

8.1.6.

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

Чтобы продемонстрировать возможности прикладного интерфейса

API

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

задавать обработчики событий на избранном языке сценариев.

Проанализируйте исходный код, приведенный в листинге

8.1.

В этом коде

средства создания сценариев вводятся в класс произвольного фрейма. По умол­
чанию это класс

ButtonFrame

из листинга

8.2,

аналогичный по своим функциям

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



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



отсутствуют обработчики событий.

name;

Требующиеся обработчики событий определяются в файле свойств, а каждое
свойство определяется в следующей форме:
имяКомпонента.имяСобытия

=

кодСценария

Так, если пользователь выбирает язык сценариев

обработчики событий предоставляются в файле j

JavaScript,

требующиеся

s. properties

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

приведенным

Groovy

и

R.

yellowButton.action=panel.background = java.awt.Color.YELLOW
java.awt.Color.BLUE
redButton.action=panel.background = java.awt.Color.RED

ЫueButton.action=panel.background =

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

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

JavaScript.

Далее выполняется обработка сценария

ini t. язык,

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

если таковой имеется. И

R и Scheme

нуждаются в ряде гро­

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

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

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

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

Глава

8 •

Написание сценариев, компиляция и обработка аннотаций

посвященный прокси-объектам в главе

6

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

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

engine.eval(scriptCode);
Остановимся подробнее на обработке событий от кнопки выбора желтого

цвета фона (объекте

yellowButton). При обработке приведенной ниже
JButton под именем "yellowButton".

строки

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

yellowButton.action=panel.background = java.awt.Color.YELLOW
Далее к этому компоненту присоединяется объект типа
тодом

actionPerformed (),

ствами

ActionListener

с ме­

который выполняет сценарий, если он создан сред­

Nashorn:

panel.background = java.awt.Color.YELLOW
Интерпретатор

сценариев содержит привязку,

которая

связывает

имя

"panel" с объектом типа JPanel. Когда наступает событие, выполняется метод
setBackground () для этого объекта, а в итоге изменяется цвет фона панели.
Запустить рассматриваемую здесь программу с обработчиками событий из
сценария

JavaScript

можно, выполнив команду

java ScriptTest
А для того чтобы использовать обработчики событий из сценария

Groovy,

нужно выполнить такую команду:

java -classpath . :groovy/lib/\* ScriptTest groovy
где

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

groovy -

R по

проекту

Groovy.

Для реализации языка

архивные JАR-файлы для библиотеки

Renjin

претатора сценариев

Renjin

Renjin Studio

и интер­

следует включить в путь к соответствующим классам.

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

www. renj in.

org /downloads. html.
В рассматриваемом здесь примере программы демонстрируется применение

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

Java.

Желающие могут пойти еще дальше и описать такой интер­

фейс с помощью ХМL-файла, как было показано в главе

3.

В этом случае данная

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

XML,

а также

поведением, определяемым на языке сценариев. Это очень похоже на среду соз­
дания динамических серверных сценариев и динамических НТМL-страниц.

Листинг

1
2
3

4
5
6
7
8
9

8.1.

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

script/ScriptTest. java

package script;
import
import
import
import
import
import
import

java.awt. *;
java.beans. *;
java.io. *;
java.lang.reflect. *;
java.util.*;
javax.script. *;
javax.swing. *;

8.1.

Написание сценариев дnя платформы

10
11

/**
* @version 1.03 2018-05-01
* @author Сау Horstmann

12
13
14
15

puЫic

16

{

17
18
19
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
35
36

*/

class ScriptTest
static void main(String[] args)

puЫic

{
EventQueue.invokeLater( () ->
{
try
{
var manager = new ScriptEngineManager();
String language;
if (args.length == 0)
{

System. out .println ( "AvailaЫe factories: ");
for (ScriptEngineFactory factory :
manager.getEngineFactories())
System.out.println(factory.getEngineName());
language

=

else language

"nashorn";
=

args[OJ;

38

final ScriptEngine engine
manager.getEngineByName(language);
if (engine == null)

39

{

37

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

System.err.println("No engine for "
+ 1 anguage) ;
System.exit(l);

final String frameClassName = args.length < 2
? "buttonsl.ButtonFrame" : args[l];
(JFrame) Class
.forName(frameClassName)
.getConstructor() .newlnstance();
InputStream in = frame.getClass()
.getResourceAsStream("init." + language);
i f (in ! = null)
engine.eval(new InputStreamReader(in));
var components =
new HashMap();
getComponentBindings(frame, components);
components.forEach( (name, с) ->
engine.put(name, с));
var frame

var events = new Properties();
in = frame.getClass() .getResourceAsStream(
language + ".properties");
events.load(in);

Java

Глава

8 •

Написание сценариев, компиляция м обработка аннотаций

66
67
68
69
70
71
72

for (Object

73

frame.setTitle("ScriptTest");
frame.setDefaultCloseOperation(
JFrame.EXIT ON CLOSE);

: events.keySet())

String[] s = ((String) e).split("\\.");
addListener(s[OJ, s[l],
(String) events.get(e),
engine, components);

74
75
76
77
78
79
80
81
82
83

frame.setVisiЬle(true);

catch (ReflectiveOperationException
1
IOException 1 ScriptException
1 IntrospectionException ех)
ex.printStackTrace();
)

84
85

86
87
88
89
90
91
92
93
94
95

е

{

));

/**
* Собирает все именованные компоненты
* @param с Компонент
* @param namedComponents Отображение,

*

вводятся

*

и

их

все

в

контейнер

в

которое

компоненты

имена

*/
private static void getComponentBindings(Component
Map namedComponents)

с,

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

String name = c.getName();
if (name != null) { namedComponents.put(name, с); )
if (с instanceof Container)
{
for (Component child :
( (Container) с) .getComponents())
getComponentBindings(child, namedComponents);

/**

* Вводит в объект приемник событий, метод которого
* выполняет сценарий
* @param beanName Имя компонента JavaBeans,
*
в который вводится приемник событий
* @param eventName Имя компонента JavaBeans,
"action" (действие)
"change" (изменение)

*

например,

*

или

* @param scriptCode Выполняемь!Й код сценария
* @param engine Интерпретатор, выполняющий
код

сценария

* @param bindings Привязки для выполнения сценария
* @throws Исключение типа IntrospectionException
*/
private static void addListener(String beanName,

8.1.
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

Написание сценариев дпя платформы

String eventName, final String зcriptCode,
ScriptEngine engine, Map components)
throws ReflectiveOperationException,
IntrospectionException
Object bean = components.get(beanName);
EventSetDescriptor descriptor =
getEventSetDescriptor(bean, eventName);
if (descriptor == null) return;
descriptor.getAddListenerMethod()
.invoke(bean, Proxy.newProxyinstance(
null, new Class[]
{ descriptor.getListenerType()
(proxy, method, args) ->
{
engine.eval(scriptCode);
return null;
)1) ;

),

private static EventSetDescriptor
getEventSetDescriptor(
Object bean, String eventName)
throws IntrospectionException
for (EventSetDescriptor descriptor :
Introspector.getBeaninfo(bean.getClass())
.getEventSetDescriptors())
i f (descr iptor. getName () . equals ( eventName) 1
return descriptor;
return null;

Листинг

8.2.

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

1
2

package buttonsl;

3

import javax.swing.*;

buttonsl/ButtonFrame. java

4

5
6
7
8

/**

9

*/

* Фрейм с панелью кнопок
* @version 1.00 2007-11-02
* @author

Сау

Horstmann

10 puЫic class ButtonFrame extends JFrame
11 {
private static final int DEFAULT WIDTH = 300;
12
13
private static final int DEFAULT HEIGHT = 200;
14
15
private JPanel panel;
16
private JButton yellowButton;
17
private JButton ЫueButton;
18
private JButton redButton;

Java

Глава В

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

puЫic



Написание сценариев. компиляция и обработка аннотаций

ButtonFrarne()

{
setSize(DEFAULT_WIDTH, DEFAULT HEIGHT);
panel = new JPanel();
panel.setNarne("panel");
add(panel);
yellowButton = new JButton("Yellow");
yellowButton.setNarne("yellowButton");
ЫueButton = new JButton("Blue");
ЫueButton.setNarne("ЬlueButton");

redButton = new JButton("Red");
redButton.setNarne("redButton");
panel.add(yellowButton);
panel.add(ЬlueButton);

panel.add(redButton);

8.2.

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

API

для компилятора

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

Java.

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

обучения программированию на

Java,

а также инструментальные средства, авто­

матизирующие процессы тестирования и построения прикладного кода. Еще од­

ним тому примером служит обработка веб-страниц типа
встроенными операторами

8.2.1.

JSP (JavaServer Pages)

со

Java.

Вызов компилятора

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

примере кода. Получаемое в итоге нулевое значение переменной

resul t

указы­

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

JavaCornpiler cornpiler =
ToolProvider.getSysternJavaCompiler();
OutputStream outStream = . . . ;
OutputStream errStream = ... ;
int result
compiler.run(null, outStream, errStream,
"-sourcepath", "src", "Test.java");
Все выводимые данные и сообщения об ошибках компилятор направляет
в указанные потоки вывода. В качестве параметров метода
вать и пустое значение
вывода

System. out

и

run () можно указы­
null. В данном случае используются стандартные потоки
System. err. Первый параметр метода run () обозначает

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

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

метод

(null). Сам же
run () наследуется из обобщенного интерфейса Tool, допускающего при­

менение инструментальных средств для чтения вводимых данных.

8.2.
Остальные параметры метода
ло бы передать утилите

j avac,

run ()

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

API

для компилятора

являются аргументами, которые следова­

если бы этот метод вызывался из командной стро­

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

8.2.2.

Запуск заданий на компиляцию

С помощью объекта типа

CompilationTask

можно получить еще больший

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

получить

CompilationTask,

задание

на

компиляцию

в

необходимо получить сначала объект

виде

объекта

compiler,

типа

как было по­

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

JavaCompiler.CompilationTask task = compiler.getTask(
/!если указано значение null этого параметра,
// то используется поток вывода System.err:
errorWr i ter,
//если указано значение null этого параметра,
//то используется

стандартньШ диспетчер файлов:

fileManager,
// если указано значение null этого параметра,
// то используется поток вывода System.err:
diagnostics,
//если

конкретное

/!указано,

значение

он принимает

этого параметра

пустое

значение

не

null:

options,
//этот

параметр

//если

конкретное

//указано,

служит для
значение

он принимает

обработки аннотаций;
этого параметра не

пустое

значение

null:

classes,
sources);
Три последних параметра в приведенном выше вызове являются экземпляра­
ми типа IteraЫe. Например, последовательность параметров компиляции мо­
жет быть задана следующим образом:
IteraЫe

options = List.of ("-d", "bin");

В качестве параметра
пляров типа

sources указывается итератор типа IteraЫe
,JavaFileObj ect, представляющих исходные файлы. Если

экзем­
требу­

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

StandardJavaFileManager

getJavaFileObjects ():

StandardJavaFileManager f ileManager =
compiler.getStandardFileManager(null, null, null);
IteraЬle sources =
fileManager.getJavaFileObjectsFromStrings(
List .of ( "Filel. java", "File2. java" 11;
JavaCompiler.CompilationTask task = compiler.getTask(
null, null, null, options, null, sources);

Глава

Написание сценариев. компиляция и обработка аннотаций

8 •

НА ЗАМЕТКУ! Параметр

classes служит лишь для обработки аннотаций. И в этом случае не­
task. processors (annotationProcessors) со списком
Processor. Характерный пример обработки аннотаций приведен в разделе 8.6.

обходимо также сделать вызов

объектов типа

Метод

ge t

Та s

k ()

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

ет процесс компиляции.

Класс

Compi 1 а t i onTas k

реализует

интерфейс

CallaЫe. Объект этого класса можно передать исполнителю типа

Execu torService

для параллельного исполнения или же сделать синхронный

вызов, как показано ниже.

Boolean success

=

task.call();

8.2.З. Фиксация диагностики
Для приема появляющихся сообщений об ошибках устанавливается приемник

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

DiagnosticListener.

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

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

Diagnostic. В частности, интерфейс DiagnosticListener
DiagnosticCollector, где собираются все диагностические

данные для просмотра и анализа по завершении компиляции, как демонстриру­
ется в следующем примере кода:

DiagnosticCollector collector =
new DiagnosticCollector();
compiler.getTask(null, fileManager, collector, null,
null, sources) .call();
for (Diagnostic