Оглавление
Предисловие
Часть 1. Язык Java
Глава 2. Краткий обзор Java
Глава 3. Типы данных, переменные и массивы
Глава 4 . Операции
Глава 5. Управляющие операторы
Глава 6. Введение в классы
Глава 7. Подробное рассмотрение классов и методов
Глава 8. Наследование
Глава 9. Пакеты и интерфейсы
Глава 10. Обработка исключений
Глава 11. Многопоточное программирование
Глава 13. Ввод-вывод, аплеты в прочие вопросы
Глава 14. Обобщения
Глава 15. Лямбда·выражения
Часть 2. Библиотека Java
Глава 17. Пакет java. lang
Глава 18. Пакет java. util, часть 1. Collections Framework
Глава 19. Пакет java. util, часть 2. Прочие служебные классы
Глава 20. Пакет java. io для ввода-вывода
Глава 21. Система ввода·вывода NIO
Глава 22. Работа в сети
Глава 23. Класс Applet
Глава 24. Обработка событий
Глава 25. Введение в библиотеку АWТ: работа с окнами, графикой и текстом
Глава 26. Применение элементов управления, диспетчеров компоновки и меню из библиотеки АWТ
Глава 27. Изображения
Глава 28. Утилиты параллелизма
Глава 29. Потоковый API
Глава 30. Регулярные выражения и другие пакеты
Часть 3. Введение в программирование ГПИ средствами Swing
Глава 32. Исследование библиотеки Swing
Глава 33. Введение в меню Swing
Часть 4. Введение в программирование ГПИ средствами JavaFX
Глава 35. Элементы управления JаvаFХ
Глава 36. Введение в меню JavaFX
Часть 5. Применение Java
Глава 38. Введение в сервлеты
Приложение
Предметный указатель
Текст
                    Java8
Полностью обновлено с учетом
версии Java SE 8 (JDK 8)
Полное руководство
Девятое издание
Исчерпывающее и незаменимое учебное пособие по написанию,
компилированию и выполнению современных программ на Java
Герберт Шилдт


ORACLE OraekPress полное Java 8 руководство � девятое издание
ORACLE The Complete Reference Oracle Press"" Java Ninth Edition Herbert Schildt New York • Chicago • San Francisco • Athens • London • Madrid Mexico City • Milan • New Delhi • Singapore • Sydney • Toronto
ORACLE Oracle Pres s "' Полное Java 8 руководство девятое издание Герберт Шилдт • Издательский дом �вильямс" Москва •Санкт-Петербург• Киев 2015
ББК 32.973.26 -0 18.2 .75 Ш57 УДК 681 .3.07 Издател ьский дом "Вильяме" Зав. редакцией С.Н . Тfrигуб Перевод с английског о и редакция И.В. Берштейна По общим вопросам обращайте сь в И здател ьский дом " Вил ьяме" п о адресу: info@williamspuЫishing.com, http://ww w .williamspublishing.com Шилдт, Ге рберт. Ш57 Java 8. Полное руководство; 9-е изд.: Пер. с англ. - М. : ООО "И.Д. Вильяме", 2015. - 1376 с. : ил. - Парал. тит. англ. ISBN 978-5 -8459-1918-2 (рус.) ББК 32.973.26-018.2 .75 Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механиче­ ские, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства McG1·aw-Hill Education. Authorized translatioп from the English language edition puЫished Ьу McGraw-Hill Education, Copyright © 2014. Al l rights reseived. Except as peпnitted under the Copyiight Act of 1976, по part of this puЫication тау Ье reproduced or distributed in any fom1 or Ьу any means, or stored in а database or 1-etrieval system, without the p1ior written peпnission of Publisher, with the exception that the program listings may Ье entered, stoI"ed, and executed in а computer system, but they тау not Ье I"eproduced for publication. 01-acle andjava are registered trademarks ofOracle Corporation and/or its affiliates. Russian language edition puЫished Ьу Williams PuЫishing House according to the Agreement with R&I Enterprises Iпternationa\, Copyright © 2015 На учно-популярное издание Герберт Шилдт Java 8. Полное руководство 9-еиздание Л итературный редактор Ве рстка Худ ожествен ный редактор Корректор И.А . Попова О.В. Мишутина Е.П. Дынник Л.А . Гордиенко Подписано в печать 14.01.2015. Формат 70xl00/ 16 Гарнитура 1imes. Усл. печ. л. 86 ,0 . Уч. - изд. л. 79,2 Тираж 1000 экз. Заказ No 267 Оrпечатано способом ролевой струйной печати в ОАО •Первая Образцовая типография• Филиал •Чеховский Печатный Двор• 142300, Московская область, r. Чехов, ул. Полиграфистов, д.1 ООО "И . Д. Вильяме", 127055, г. Москва, ул. Лесная, д . 43 , стр. 1 ISBN 978-5-8459-1918-2 (рус.) ISBN 978-0 -07-180855-2 (англ.) ©Издательский дом "Вильяме" , 2015 © McGraw-Hill Education, 2014
Оглавление Предисловие Часть 1. Язык Java Глава 1. История и развитие языкаJаvа Глава 2. Краткий обзорJava Глава 3. Типы данных, переменные и массивы Глава 4 . Операции Глава 5. Управляющие операторы Глава 6. Введение в классы Глава 7. Подробное рассмотрение классов и методов Глава 8. Наследование Глава 9. Пакеты и интерфейсы Глава 10. Обработка исключений Глава 11. Многопоточное программирование Глава 12. Перечисления, автоупаковка и аннотации (метаданные) Глава 13. Ввод-вывод, аплеты в прочие вопросы Глава 14. Обобщения Глава 15. Лямбда·выражения Часть 11. Библиотека Java Глава 16. Обработка символьных строк Глава 17. Пакет java. lang Глава 18. Пакет java. uti l, часть 1. Collections Framework Глава 19. Пакет java. util, часть 11. Прочие служебные классы Глава 20. Пакет java. io для ввода-вывода Глава 21. Система ввода·вывода NIO Глава 22. Работа в сети Глава 23. Класс Applet Глава 24. Обработка событий Глава 25. Введение в библиотеку АWТ: работа с окнами, графикой и текстом Глава 26. Применение элементов управления, диспетчеров компоновки и меню из библиотеки АWТ Глава 27. Изображения Глава 28. Утилиты парал л елизма Глава 29. Потоковый API Глава 30. Регулярные выражения и другие пакеты Часть 111. Введение в программирование ГПИ средствами Swing Глава 31. Введение в библиотеку Swing Глава 32. Исследование библиотеки Swing Глава 33. Введение в меню Swing Часть IV. Введение в программирование ГПИ средствами JavaFX Глава 34. Введение вJavaFX Глава 35. Элементы управленияJаvаFХ Тhава 36. Введение в менюJavaFX Часть V. Применение Java Глава 37. КомпонентыJаvа Beans Глава 38. Введение в сервлеты Приложение Применение документирующих комментариев вJava Предметный указатель 28 31 33 53 75 103 125 155 177 209 235 263 285 317 355 395 437 467 469 497 563 651 717 769 811 833 855 885 923 975 1005 1061 1089 1119 1121 1143 1175 1209 1211 1233 1281 1309 1311 1323 1347 1347 1355
Содержание Предисловие Часть 1. Язык Java Глава 1. История и развитие языка J ava Прои(:хождение J ava 3арож.дение современного программиро ван ия: я:iык С Следующий этап: язык С++ Предносылки к созданию . Jаvа Со:щание языкаJava Свя:iь с языком С# Каким обра:юм языкjаvа изменил И11тt�р11ст Лнлеты нaJava Ьеао11ас нопъ Пе1>е11<1с имость Чудо J<t\•a: байт-код Ссрвлеты: сер верные програ ммы нaJava Отл ичительные особенности Java П)ЮП'О '\-d ( >бъсктная ориентиро ваннопъ 1 lадсж ность М 110\'О\ IОТОЧНО('ТJ, Архитектурная 11ейтра.11ь ность Ин тсрпретируемость и высокая прои:шодителыюсть Распрсi�t·л снность Ли11ам ичног1ъ Эволюция языкаJаvа Верс ия Ja\'a SE 8 Ку;шгура нововведений Глава 2. Краткий обзорjаvа Объектно-ориентирован ное 11рограмми1><1ван ис }1,ве методики Лбстраю�ия 'I)ш прющипа ООП 28 31 33 ;�3 34 35 37 37 39 40 40 41 41 41 43 43 44 44 44 45 45 46 46 46 46 50 51 53 53 5�� 54 54
С одержание 7 Первый пример простой программы 60 Ввод кода программы 61 Компиляция программы 61 Подробный анализ первого примера программы 62 Второй пример короткой программы 65 Два управляющих оператора 66 Условный оператор i f 67 Оператор цикла for 68 Использование блоков кода 69 Вопросы лексики 70 Пробелы 71 Идентификаторы 71 Литералы 71 Комментарии 72 Разделители 72 Ключевые словаjаvа 72 Библиотеки классов Java 73 Глава 3. Типы данных, переменные и массивы 75 Jаvа-строго ти пизированный язык 75 Примити вные типы 75 Целые числа 76 ТипЬуtе 77 Тип short 77 Тип int 77 Тип long 78 Числа с плавающей точкой 78 Тип float 79 ТипdоuЫе 79 Символы 80 Логические значения 81 Подробнее о литералах 82 Целочисленные литералы 82 Литералы с плавающей точкой 84 Логические литералы 85 Символьные лите ралы 85 Строковые лите рал ы 86 Переменные 86 Объявление переменной 86 Динамическая инициализация 87 Область и срок действия переменных 87 Преобразование и приведение типов 90 Автоматическое преобразование типов в Java 90 Приведение несовместимых типов 91 Автоматическое продвижение типов в выражениях 92 Правила продвижения ти пов 93 Массивы 94 Одномерные массивы 94 Многомерные массивы 97 Альтернативный синтакс ис объявления массивов 1О1 Введение в символьные строки 1О1 Замечание по поводу указателей для программирующих на С/С++ 102
8 Содержание Глава 4 . Операции Арифметические операции Основные арифметические операции Операция деления по модулю Составные арифметические операции с присваиванием Операции инкремента и декремента Поразрядные операции Поразрядные логические операции Сдвиг влево Сдвиг вправо Беззнаковый сдвиг вправо Поразрядные составные операции с присваиванием Операции отношения Логические операции Укороченные логические операции Операция присваивания Те рнарная операция ? Предшествование операций Применение круглых скобок Глава 5. Управляющие операторы Операторы выбора Условный оператор i f Оператор switch Операторы цикла Цикл while Цикл do-while Цикл for Вложенные циклы Операторы перехода Применение оператора break Применение оператора continue Оператор return Глава 6. Введение в классы Основы классов Общая форма класса Простой класс Объявление объектов Подробное рассмотрение оператора new Присваивание переменным ссылок на объекты Введение в методы Ввод метода в класс Вох Возврат значений Ввод метода, принимающего параметры Конструкторы Параметризированные конструкторы Ключевое слово this Сокрытие переменной экземпляра Сборка "мусора" Метод finalize () Класс Stack 103 103 104 105 105 106 108 109 111 113 114 116 117 118 120 120 121 122 123 125 125 125 128 133 133 135 138 147 147 148 151 153 155 155 155 156 159 160 161 162 163 164 166 168 170 171 172 172 173 174
Содержание 9 Глава 7. Подробное рассмотрение классов и методов 177 Перегрузка методов Перегрузка конструкторов Применение объектов в качестве параметров Подробное рассмотрение особенностей передачи аргументов Возврат объектов Рекурсия Введение в управление доступом Ключевое слово static Ключевое слово final Еще раз о массивах Вложенные и внутренние классы Краткий обзор класса String Применение аргументов ко мандной строки Аргументы переменной длины Перегрузка методов с аргументами переменной дл ины Аргументы переменной дл ины и неоднозначность 177 180 182 184 186 187 189 193 194 195 197 200 202 203 206 207 Глава 8. Наследование 209 Основы наследования 209 Доступ к чл енам класса и наследование 211 Более практический пример 212 Переменная суперкласса может ссылаться на объект подкласса 213 Ключевое слово super 214 Вызов конструкторов суперкласса с помощью ключевого слова super 215 Другое применение ключевого слова super 218 Создание многоуровневой иерархии 219 Порядок вызова конструкторов 222 Переопределение методов 223 Динамическая диспетчеризация методов 225 Назначение переопределенных методов 226 Применение переопределения методо в 227 Применение абстрактных классов 228 Кл ючевое слово final в сочетании с наследованием 231 Предотвращение переопределения с помощью ключевого слова final 231 Предотвращения наследования с помощью ключевого слова final 232 Класс Ob j ect 233 Глава 9. Пакеты и интерфейсы 235 Пакеты 235 Определение пакета 236 Поиск пакетов и переменная окружения CLASSPATH 236 Краткий пример пакета 237 Защита доступа 238 Пример защиты доступа 239 Импорт пакетов 242 Инте рфейсы 244
1О Содержание Объя вление интерфейса 245 Реализация интерфейсов 246 Вложе нные интерфейсы 249 Применение интерфейсов 250 Переменные в интерфейсах 253 Расширение инте рфейсов 255 Методы по умолчанию 255 Основы применения методов по умолчанию 257 Более практический пример 258 Вопросы множественного наследования 259 Применение статических методов в инте рфейсе 260 Заключительные соображения по поводу пакетов и интерфейсов 261 Глава 10. Обработка ис ключений 263 Основы обработки исключений 263 Ти пы исключений 264 Необрабатываемые исключения 265 Использование блоков операторов try и catch 266 Вывод описания исключения 268 Применение нескольких операторов catch 268 Вложенные операторы try 270 Оператор throw 272 Оператор throws 273 Оператор finally 275 Встроенные в Java исключения 276 Создание собственных подклассов исключений 278 Цепочки исключений 280 Недавно внедренные средства для обработки исключений 282 Применение исключений 284 Глава 11. Мн огопоточное программирование 285 Модель потоков испол нения вjava 286 Приоритеты потоков 287 Синхронизация 288 Обмен сообщениями 289 Класс Thread и инте рфейс RunnaЫe 289 Главный поток исполнения 290 Создание потока исполнения 291 Реализация интерфейса RunnaЫe 292 Расширение класса Thread 294 Выбор способа создания потоков исполнения 295 Создание многих потоков исполнения 295 Применение методов isAlive () и join () 297 Приоритеты потоков исполнения 299 Синхронизация 300 Применение синхронизированных методов 301 Оператор synchronized 303 Взаимодействие потоков испол нения 304 Взаимная бло кировка 309
С одержание 11 Приостановка, возобновление и остановка потоков исполнения 311 Получение состояния потока исполнения 313 Применение многопоточности 315 Глава 12. ПеречисленИJI, автоупаковка и аннотации (метаданные) 317 Перечисления 317 Основные положения о перечислениях 317 Методы values ()и valueOf () 319 Перечисления вJava относятся к типам классов 321 Перечисления наследуются от класса Enum 323 Еще од ин пример перечисления 325 Оболочки типов 326 Класс Character 327 Класс Boolean 327 Оболочки числовых типов 327 Автоупаковка 328 Автоупаковка и методы 329 Автоупаковка и автораспаковка в выражениях 330 Автоупаковка и распаковка значений из классов Boolean и Character 332 Автоупаковка и автораспаковка помогают предотвратить ошибки 333 Предупреждение 333 Аннотации (метаданные) 334 Основы аннотирования программ 334 Правила уд ержания аннотаций 335 Получение аннотаций во время выполнения с помощью рефлекс ии 336 Второй пример применения рефлексии 338 Получение всех аннотаций 339 Инте рфейс AnnotatedElement 341 Использование значений по умолчанию 341 Аннотации-маркеры 343 Одночленные аннотации 343 Встроенные аннотации 345 Ти повые аннотации 347 Повторяющиеся аннотации 352 Некоторые ограничения на аннотации 353 Глава 13. Ввод-вывод, аплеты и прочие вопросы 355 Основы ввода-вывода 355 Потоки ввода-вывода 356 Потоки ввода-вывода байтов и символов 356 Предоп ределенные потоки ввода-вывода 359 Чтение данных, вводимых с консоли 359 Чтение символов 360 Чтение символьных строк 361 Запись данных, выводимых на консоль 362 Класс PrintWriter 363 Чтение и запись данных в файлы 364 Автоматическое закрытие файла 370 Основы создания аплетов 374
12 Содержание Модификаторы доступа transient и volatile 377 Применение оператора instanceof 378 Модификатор доступа strictfp 380 Платформенно-ориентирован ные методы 381 Трудности , связанные с платформенно-ориентированными методами 384 Применение ключевого слова assert 385 Параметры включения и отключения режима проверки утверждений 388 Статический импорт 388 Вызов перегружаемых конструкторов по ссылке this () 391 Компактные профилиjаvа API 393 Глава 14. Обобщения 395 Что такое обобщения 396 Простой пример обобщения 396 Обобщения действуют только со ссылочными типами 400 Обобщенные типы различаются по аргументам типа 400 Каким образом обобщения повышают типовую безопасность 401 Обобщенный класс с двумя параметрами типа 403 Общая форма обобщенного класса 404 Ограниченные типы 405 Применение метасимвольных аргументов 407 Огран иченные метасимвольные аргументы 410 Создание обобщенного метода 414 Обобщенные конструкторы 417 Обобщенные интерфейсы 417 Базовые типы и унаследованный код 420 Иерархии обобщенных классов 422 Применение обобщенного суперкласса 422 Обобщенный подкласс 424 Сравнение ти пов в обобщенной иерархии во время выполнения 425 Приведение типов 428 Переопределение методов в обобщенном классе 428 Выведение типов и обобщения 429 Стирание 431 Мостовые методы 431 Ошибки неоднозначности 433 Некоторые ограничения, присущие обобщениям 434 Получить экземпляр по параметру типа нельзя 434 Огран ичения на статические члены 435 Ограничения на обобщенные массивы 435 Огран ичения на обобщенные исключения 436 Глава 15. Лямбда-выражения 437 Введение в лямбда-выражения 438 Основные положения о лямбда-выражениях 438 Функциональные инте рфейсы 439 Некоторые примеры лямбда-выражений 440 Блочные лямбда-выражения 444 Обобщенные функциональные интерфейсы 446 Передача лямбда-выражений в качестве аргументов 447 Лямбда-выражения и исключения 450
Содержание 1З Лямбда-выражения и захват переменных 451 Ссылки на методы 453 Ссылки на статические методы 453 Ссылки на методы экземпляра 454 Ссылки на обобщенные методы 458 Ссылки на конструкторы 460 Предопределенные функциональные инте рфейсы 465 Часть 11. Библиотека Java 467 Глава 16. Обраб отка символьных строк 469 Конструкторы символьных строк 470 Дл ина символьной строки 472 Специальные строковые операции 472 Строко вые литералы 472 Сцепление строк 473 Сцепление символьных строк с другими типами данных 473 Преобразование символьных строк и метод toString () 474 Извлечение символов 475 Метод charAt () 475 Метод getChars () 476 Метод getBytes () 476 Метод toCharArray () 477 Сравнение символьных строк 477 Методы equals () и equalsignoreCase () 477 Метод regionMatches () 478 Методы startsWith () и endsWith () 478 Метод equals () в сравнении с операцией== 479 Метод compareTo () 480 Поиск в символьных строках 481 Видоизменение символьных строк 482 Метод substring () 483 Метод concat ( ) 484 Метод replace () 484 Метод trim () 484 Преобразование дан ных методом valueOf () 485 Изменение регистра символов в строке 486 Соеди нение символ ьных строк 487 Допол н ительные методы из класса String 487 Класс StringBuffer 489 Конструкторы класса StringBuffer 489 Методы length () и capacity () 490 Метод ensureCapacity () 490 Метод setLength () 491 Методы charAt () и setCharAt () 491 Метод getChars () 492 Метод append () 492 Метод insert () 493 Метод reverse () 493 Методы delete ( ) и deleteCharAt () 494
1' Содержание Метод replace ( ) Метод substring () Дополнительные методы из класса StringBuffer Класс StringBuilder · · Тhава 17. Пакет java. lang Оболочки прииити�Н1П!11mо'в Класс NumЬer Классы DouЫe и Float Методы isinfinite () � ..�::?�а�(} Классы Byte, Short, Integtэr И Long Класс Character До полнения класса Character для подцержки кодовых точек в Юиикоде Класс Boolean КлaccVoid Класс Prooeee Класс Runtime Управление памятью Выполнение других программ Класс ProcessBuilder Класс System Измерение времени выполнения программы методом currentTimeMills () Применение метода arraycopy () Свойства окружения Класс Obj ect Применение метода clcme ( ) и интерфейса CloneaЫe Класс Class Класс ClassLoader КлассМаth Тригонометрические функции Экс поненциальные функции Функции округления Прочие методы из класса Маth Класс StrictMath Класс Compiler Классы Thread, ThreadGroup � и,нтерфейс RunnaЫe Интерфейс RunnaЫe Класс Thread Класс ThreadGroup Классы ThreadLocal и InheritaЫeThreadLocal Класс Package Класс RuntimePermission Класс ThrowaЫe Класс SecurityManager Класс StackTraceElement Класс Enum Класс ClassValue Интea:йcCharSequence Инте йс ComparaЫe Инте йс AppendaЬle 494 495 495 496 497 498 49� 498 503 503 514 517 519 520 520 521 523 524 525 528 530 531 532 532 533 535 538 539 539 540 540 542 54 4 545 545 545 545 548 553 553 555 555 555 555 556 557 558 558 559
С одержание 15 Интерфейс IteraЫe 559 Интерфейс ReadaЫe 560 Интерфейс AutoCloseaЫe 560 ИнтepфeйcThread.UncaughtExceptionHandler 560 Подпакеты из пакета вjava.lang 561 Пакетjava.lang. annotation 561 Пакетjava.lang. instrument 561 Пакетjava.lang.invoke 561 Пaкeтjava.lang.management 561 Пакетjava.lang.re f 562 Пакетjava.lang.reflect 562 Глава 18. Пакет java. util, часть 1. Collections Framework 563 Краткий обзор коллекций 564 Изменения каркаса коллекций в версииJDК 5 566 Обобщения коренным образом изменили каркас коллекций 566 В средствах автоматической упаковки используются примитивные типы данных 567 Цикл for в стиле for each 567 Интерфейсы коллекций 567 Интерфейс Collection 568 Интерфейс List 571 Интерфейс Set 573 Интерфейс SortedSet 573 Интерфейс NavigaЬleSet 574 Интерфейс Queue 576 Интерфейс Dequeue 577 Классы коллекций 579 Класс ArrayList 580 Класс LinkedList 584 Класс HashSet 585 Класс LinkedHashSet 587 Класс TreeSet 587 Класс PriorityQueue 588 Класс ArrayDeque 589 Класс EnumSet 590 Доступ к коллекциям через итератор 591 Применение интерфейса Iterator 593 Цикл for в стиле for each как альтернатива итераторам 595 Итераторы-разделители 596 Сохранение объектов пользовательских классов в коллекциях 599 Интерфейс RandomAccess 600 Обращение с отображениями 601 Интерфейсы отображений 601 Классы отображений 609 Компараторы 614 Применение компараторов 617 Алгоритмы коллекций 622 Массивы 629 Унаследованные классы и интерфейсы 634
ИнтepфeйcEnurneration Класс Vector Класс Stack Класс Dictionary Класс Properties Применение методов store () и load () ЗакточитеD�Rые с:о о6раже ния по поводу коллекций :j:, 635 635 639 641 645 649 650 Тhава 19. Пакет java. ui:il, часть 11. Прочие служебные массы 651 '�ii Клас с StringTokenizer 651 Класс Bi tSet 653 Класс�-��.1 1. ���J)j)μЬle. ОрЦоnа1 J;nt и Qpt.i,onalLoдg - �56 Класс Date 659 Класс Calendar , 661 Класс GregorianCalenda:r 664 Класс Timё-Zohe 666 Класс SimpleT"imeZone 667 Класс Locale 668 Класс Randorn 670 Класс ObservaЫe 672 Интерфейс Observer 673 Пример аабцщеиия за объектами 674 Класс1� �1 Timer и TimerTask 676 Класс Currency 678 КлaccFormatter 680 Конструкторы класса Forrnatter 680 Методы изклиса Forrnatter 681 Основы форматирования 682 Форматкро881 11 1 е строк и символов 684 Форматирование чисел 684 Форматирование арекеви и даты 685 СпецифюСаrорwфQрJ�мта%nи %% 687 Указание llJl ll llifВJDI Н oй ширины поля 687 УIСаЗаНИе � 689 Применение признаков формата 689 Выравнквавне выводимых данных 690 Признаки пробе.il а ,+ , Ои( 691 Признак запятой 692 ПризаЬ 692 Прописные формы спецификаторов формата 692 ПрИ1i:�h�'wидеЬ'аJЯ7м'&га 693 Закрытие объепа типа Forrnattеr 694 Аналоr фунJСЦИирrintf (} вJava 695 Класс Scanner 695 Конструкторы класса Scanner 695 Некоторые примеры применения класса Scanner 700 Установка разделителей 704 Прочие средства uacca Scanner 705 Классы ResourceBundle, ListResourceBundle и PropertyResourceBundle ' 706
Содержание 17 Пр очие служеб ные кла ссы и интерфей сы 710 Пa кeты java.util.concurrent ,java.util.concurrent. atomic ,java.util.concurrent.locks 712 Па ке т java.util. function 712 Па ке т java.util. jar 716 Па кет java.util.logging 716 Па кет java.util.prefs 716 Па кет java.util.regex 716 Па ке т java.util.spi 716 Па кет java.util.stream 716 Па кет java.util.zip 716 Глава 20. Пакет java. io для ввода-вывода 717 Кла ссы и интерфей сы ввода -вывода 718 Кла ссFile 718 Каталоги 722 Пр именение интерфей са FilenameFilter 723 Ал ьтер на ти вный метод listFiles () 724 Со здан ие катало гов 724 Ин терфей сы AutoCloseaЫe ,CloseaЫe и FlushaЫe 724 Исключения ввода -вывода 725 Два сп особа закрытия пото ка ввода -вывода 726 Кла ссы потоков ввода -вывода 727 По токи ввода -вывода байтов 728 Кла сс InputStream 728 Кла ссOutputStream 729 Кла ссFileinputStream 730 Кла ссFileOutputStream 732 Кла сс ByteArrayinputStream 734 Кла сс ByteArrayOutputStream 736 Фильтруе мые пото ки ввода -выво да байтов 737 Буферизова нные потоки вво да-выво да байтов 737 По токи ввода -вывод а си мв олов 748 Кла ссReader 749 Кла ссWriter 750 Кла ссFileReader 751 Кла ссFileWriter 751 Кла сс CharArrayReader 752 Кла ссCharArrayWriter 754 Кл асс BufferedReader 755 Кла сс BufferedWriter 756 Кла сс PushbackReader 757 Кла сс PrintWriter 758 Кла сс Console 759 Се риализация 761 Ин терфей сSerializaЫe 762 Инте рфей с ExternalizaЫe 762 Ин терфей сObjectOutput 763 Кла ссObjectOutputStream 763 Интерфей сObject Input 764
18 Содержа н ие Класс ObjectlnputStream 765 Пр имер сериализации 767 Пр еимущес тва по токов ввода -вывода 768 Глава 21. Система ввода-вывода NIO 769 Клас сы сис темы ввода -вывода NIO 769 Ос новные положения о сис теме ввода -вывода NIO 770 Буфера 770 Кан алы 772 Наборы символов и селе кторы 774 Ус овершенс твования в системе NIO, начиная с версииJDК 7 774 Ин те рфейс Path 774 Кла сс Files 776 Кла сс Paths 779 Ин терфейсы атрибу тов файлов 780 Кла ссы FileSystem, FileSystems и FileStore 782 Пр именение сис темы ввода -вывода NIO 782 Пр именение сис темы NIO для ка нально го ввода -вывода 783 Пр именение системы NIO для по токово го ввода -вывода 793 Пр именение сис темы ввода -вывода NIO для операций в файловой сис теме 796 Пр имеры ор ганизации ка нально го ввода -вывода до версии JDК 7 804 Чт ение из файла до версии JDК 7 804 За пись в файл версии до JDK 7 807 Глава 22. Работа в сети 811 Основыработы в сети 811 Се те вые классы и ин терфейсы 813 Кла сс InetAddress 813 Фа бричные методы 814 Ме тоды экземпляра 815 Клас сы Inet4Address и InetбAddress 815 Кли ен тские со кеты по протоколу ТСР/IP 816 Кла сс URL 819 Кла сс URLConnection 821 Кла сс HttpURLConnection 824 Класс URI 826 Со оkiе -ф айлы 826 Се рверные со кеты по про токолу ТСР/IP 827 Де йта граммы 827 Кла сс DatagramSocket 828 Кла сс DatagramPacket 829 Пр имер обработ ки дейта грамм 830 Глава 23. Класс Applet 833 Два ти па апле тов 833 Ос новы разрабо тки апле тов 834 Кла сс Applet 835 Стру ктура апле тов 837 Ске ле т аплета 838 Ин ициализация и пре кращение рабо ты апле та 839
Содержание 19 Пе реопределение метода update () 840 Простые методы воспроизведения аплетов 841 За прос на повторное воспроизведение 843 Пр остой аплет с баннером 844 Пр именениестро ки сос тояни я 846 НТМL-дескриптор APPLET 847 Пе редача параметров апле там 848 Ус овер шенствование аплета, воспроизводящего баннер 850 Ме тоды getDocшnent Base () и getCode Base ( ) 851 Ин те рфейс AppletContext и метод showDocument () 852 Ин те рфейс AudioClip 854 Инте рфейс AppletStub 854 Ко нсольный вывод 854 Глава 24. Обработка событий 855 Два механизма обрабо тки событий 855 Мо дель деле гирования событий 856 Со быти я 856 Источники событий 857 Пр иемни ки событий 857 Кла ссы событий 858 Кла сс Action Event 859 Кла сс Adjustment Event 860 Кл асс Component Event 861 Кла сс Container Event 861 Кла сс Focus Event 862 Кла сс Input Event 863 Кл асс Item Event 864 Кл асс Key Event 864 Кла сс Mouse Event 865 Кла сс MouseWheel Event 867 Кла сс Text Event 868 Кла сс Window Event 868 Ис точни ки событий 870 Ин терфейсы приемников событий 870 Ин терфейс ActionListener 871 Ин те рфейс AdjustmentListener 871 Ин те рфейс ComponentListener 872 Ин те рфейс ContainerListener 872 Ин терфейс FocusListener 872 Ин те рфейс ItemListener 872 Ин те рфейс KeyListener 872 Ин те рфейс MouseListener 873 Интерфейс MouseMotionListener 873 Ин терф ейс MouseWheelListener 873 Ин те рфейс TextListener 873 Ин терфейс WindowFocusListener 873 Ин терфейс WindowListener 873 Пр именение модели деле гирования событий 874 Об работка событий от мы ши 874 Об работка событий от клавиа туры 877
20 Содержание Кл ассы адаптеров 880 Внугренние кла ссы 882 Ан они мные внугренние кл ассы 883 Глава 25. Введение в библиотеку АWТ: работа с окнами, графикой и текстом 885 Кл ассы библиоте ки АWГ 886 Основные положения об окнах 888 Кл асс Component 889 Кл асс Container 889 Кл асс Panel 889 Кл асс Window 890 Кл асс Frame 890 Кл асс Canvas 890 Работа с обра мляющи ми окна ми 890 Установ ка раз меров окна 891 Со кр ытие и отображение окна 891 Установ ка за головка окна 891 За кр ыт ие обра мляюще го окна 891 Со здание обра мляюще го окна в аплете , по строенно м на основе библиоте ки АWГ 892 Об работк а со бытий в обра мляюще м окне 894 Со здание оконной прикладной про гра ммы 898 От обр ажение инфор мации в окне 899 Поддержка графики 900 Ри сован ие линий 900 Ри сование пря моу го льни ков 900 Ри сование эллип сов и окруж но стей 901 Ри сование дуг 901 Ри сование мн огоу гольни ков 901 Де мо нстрация ме тодов ри сования 902 Из менение раз меров гр афики 902 Работа с цвето м 904 Методыиз класса Color 905 Уста нов ка режи ма ри сования 907 Работа со шрифта ми 909 Оп ределение до ступных шрифтов 910 Со зда ние и выбор шрифта 911 По лучение св едений о шрифте 913 Уп равление фор матирование м выводи мого те кста 914 От ображение мн огострочно го те кста 915 Це нтров ка те кста 917 Выравни вание мн огострочно го те кста 918 Глава 26. Применение элементов управления, диспетчеров компоновки и меню из библиотеки АWТ 923 Основн ые положения об эл ементах управления 924 Ввод и уд аление эл ементов управления 924 Реа гирование на эл емент ы управления 925 Исключение ти па HeadlessException 925
С одержани е 21 Метки 925 Экранные кнопки 926 Обработка событий от кнопок 927 Флажки 930 Обработка событий от флажков 931 Кнопки-переключатели 932 Элементы управления выбором 934 Обработка событий от раскрывающихся списков 935 Использование списков 936 Обработка событий от списков 938 Управление полосами прокругки 939 Обработка событий от полос прокругки 941 Текстовые поля 943 Обработка событий в текстовых полях 944 Текстовые области 945 Диспетчеры компоновки 947 Класс FlowLayout 948 Класс BorderLayout 950 Вставки 952 Класс GridLayout 953 Класс CardLayout 954 Класс GridBagLayout 957 Меню и строки меню 962 Диалоговые окна 968 Диалоговые окна выбора файлов 972 О переопределении метода paint () 973 Глава 27. Изображения 975 Форматы файлов 975 Основы работы с изображениями: создание, загрузка и отображение 976 Создание объекта класса Image 976 Загрузка изображения 977 Воспроизведение изображения 977 Интерфейс ImageObserver 979 Двойная буферизация 980 Класс MediaTracker 983 Интерфейс ImageProducer 986 Класс MemoryimageSource 986 Интерфейс ImageConsumer 988 Класс PixelGrabber 988 Класс ImageFilter 991 Класс CropimageFil ter 991 Фильтр класса RGBimageFil te r 992 Дополнительные классы для формирования изображений 1004 Глава 28. Утилиты параллелизма 1005 Пакеты параллельного API 1006 Пaкeтjava.util.concu rrent.atomic 1008 Пакет java. util.concurrent.locks 1008 Применение объектов синхронизации 1008
22 с.,. .. .. Класс Semaphore 1008 КлaccCountDownLatch 1014 Класс CyclicBarrier 1016 Класс Exchanger 1018 Класс Phaser 1020 Применение JКПшвите.u 1028 Простое llpю&ep JSCJIQ1UfитeлЯ 1029 ПрвменеJ1 11е �k�C'allab].e и Future 1031 ПеречиСJ1евие TimeUni t lOM ПapaмeJIЬl lJile JCOJ JJie xци и 1035 БлQDlpoВlal 1036 Аrомар1Ще оnераци и 1039 Пара.цельное програм ми рование средствами Fork/Join Framework 1040 Основные массы Fork/jmn ·F\!arnework 1041 Стра� "pa aAC"ui Иuаствуй" 1045 Первый 11роСТQЙ дрвмер вилочного соединения 1047 ВJu uaиe )'ро11 118 llapiJIJleJUIЗМЗ 1049 Пр11Мер.црм ме -.е1U1ВJtl laC ca Recursi veTask<V> 1053 Acивxpc»l lJIQe a,Ji i1 110JD1 eвиe задач 1055 Onleиa Зl ltJU' Чl l 1056 Определевие состо•ния завершения задачи 1056 Перезапуааад;аЧ1 1 1056 Предмет д;uьвеiЦuеrо изучения 1057 Реаомен.а аци и �.О'QiОС1 1'1'е.1 1 но вилочного соединения 1059 УТИJIИТЫ �в сравнении страдJtционным подходом к :мвогоза,рчвости вjava 1059 'IЛава 29. Потокоаwй API 1001 Основные поло.ения о поrоках данных 1061 ПотоковыеивтерфеiсЬl·i1 · 1062 Получение1Ю'1'Оаuи иых 1065 Простойпримерпотокадан ных 106 6 Операци и свцен ня 1070 Пара.1 11 1 еJ1ЬНЫепотокидан н ых 1073 Оrобрuквие 1075 Нuоплеиие 1079 ИтераrорыиJЮТОКНда1 1ИЬ1Х 1083 Примевевве итератора в потоке данных 1083 ПрИке1 1 евие итератора-разделителя 1085 Дальнейшее� IIOТOJWвoro API 1087 Тhава 30. Реrу.яарвые вwражеВИJ J и другие пакеты 1089 I1ахетЬ1избаэо11ОrоAPI 1089 Обработка реrулярЩIХ выражений 1092 Класс Pa't'tern· 1092 Класс Matcher 1092 Синтцсис реrут�рных выраженИй · 1'J93 Примеры, деы9истрирующие совпадениеt шаблоном 1094 Два варианта сопОСТС1JU1еnИя с шаблоном 1100 Дальнейшее изучение реrулярnых. вьmажеиий 1100 Рефлексия ·' ' · .--:-� ·· 1101
Содержани е 23 Уд але нн ый вызов методов 1105 Пр остое приложение "кл иент -сервер ", использующее ме ханизм RMI 1105 Фо рмати рование дат ы и времени средствами пакета j ava.text 1109 Кла сс DateFormat 1109 Класс SimpledateFormat 1111 API дат ы и времени, внедренн ый в версииJDK 8 1113 Ос новн ые классы дат ы и времени 1113 Фо рматирование даты и времени 1115 Си нта ксичес кий анализ си мвольн ых стро к дат ы и времени · 1117 Дальнейшее из учение па кета j аvа . time 1118 Часть 111. Введение в программирование ГПИ средствами Swing 1119 Глава 31. Введение в библиотеку Swing 1121 Пр оисхожде ние библ иоте ки Swing 1121 По строение библиоте ки Swing на основе библиотеки АWГ 1122 Гл авн ые особенности библиоте ки Swing 1122 Ле гковесн ые ко мпонент ы Swing 1123 Под ключае мый стиль оформления 1123 Св язь с архите ктурой MVC 1124 Ко мпоненты и ко нтейнеры 1125 Компонент ы 1125 Контейнер ы 1126 Па нели ко нтейнеров верхне го уровня 1126 Па кет ы библиоте ки Swing 1127 Пр остое Swing-приложение 1127 Об работ ка соб ытий 1132 Со зда ние Swing-aпл eтa 1135 Рисование средствами Swing 1138 Ос нов ы рисования 1138 Вычисление области рисования 1139 Пр имер рисован ия 1140 Глава 32. Исследование библиотеки Swing 1143 Класс ы JLabel и Imageicon 1143 Кл асс JTextField 1145 Кноп ки из библиоте ки Swing 1147 Кл асс JButton 1148 Кла сс JToggleButton 1150 Фл ажки 1153 Кноп ки -п ереключатели 1155 Класс JTabbedPane 1157 Кла сс JScrollPane 1160 Класс JList 1162 Класс JComboBox 1166 Де ревья 1168 Кл асс JTаЫе 1172 Ос новн ые положения о меню 1175
2' Содержани е Глава 33. Введение в меню Swing 1175 Кр ат кий обзор кла ссов JМenu Bar , JМenu и JМenu Item 1177 Клас с JMenu Bar 1177 Клас с JMenu 1178 Клас с JMenuitem 1180 Создание гл авного меню 1180 Вв од мнемоники и опера тивных клавиш в ме ню 1184 Вв од изображений и всплывающих подс казо к в пу нкты ме ню 1187 Кл ассыJRadio ButtonMenuitem и JCheck BoxMenuitem 1188 Со здание вс плываю щего ме ню 1190 Со зда ние па нели ин ст руме нтов 1193 Де йствия 1196 Со ставле ние око нч атель но го вариа нта про граммы MenuDemo 1202 Дал ьнейшее изуче ние библиотеки Swing 1208 Часть IV. Введение в программирование ГПИ средствами JavaFX 1209 Глава 34. Введение вJavaFX 1211 Ос но вные по нятия Jа vаFХ 12 12 Па кетыJаvаFХ 1212 Кла ссы подмостков и сце ны 1213 Узлыи графы сцены 1213 Компо новки 1213 Клас с приложения и методы его жизненно го цикла 12 14 За пускJаvа FХ-приложе ния 12 14 Скел етJа vаFХ-пр ило же ния 12 15 Компиляция и выполнениеJаvаFХ-п риложения 1218 Пото к испол нения приложе ния 12 19 Ме т ка -про стейший элеме нт управле ния вJavaFX 12 19 Пр имене ние кнопок и со бытий 122 1 Ос новы обработ ки событий вJa vaFX 1 222 Эл еме нт управления экранной кноп кой 1223 Де мо нстрация обр аботки со бытий на примере экра нных кнопо к 1224 Рисов ание не посредстве нно на холсте 1227 Глава 35. Элементы управленияJavaFX 1233 Класс ы Image и ImageView 1233 Вв од изобр ажения в метку 1236 Пр име нение изображе ния в экранной кн оп ке 1238 Кл ас с Toggle Button 1240 Кла сс Radio Button 1243 Об работ ка со бытий изменения в гр уппе кн опо к-переключателей 1246 Др угой способ управле ния кноп ками -переключ ателями 1248 Кла сс Check Box 125 1 Кл асс ListView 1254 Пред ставление сп ис ка с полосами прокрутки 1258 Активизация режим а од новреме нно го выбора не скольких элеме нтов из сп иска 1258 Кл асс СоmЬоВох 1260 Класс TextField 1263 Класс ScrollPane 1265 Кла сс TreeView 1269
С оде ржание 25 Эффекты и преобразования 1273 Эффекты 1274 Преобразования 1275 Демонстрация эффектов и преобразований 1276 Ввод всплывающих подсказок 1279 Отключение элементов управления 1280 Глава 36. Введение в менюJavaFX 1281 Основные положения о меню 1281 Краткий обзор классов MenuBar, Menu и Menuitem 1283 Класс MenuBar 1283 Класс Menu 1284 Класс Menuitem 1285 Создание главного меню 1286 Ввод мнемоники и оперативных клавиш в меню 1291 Ввод изображений в пункты меню 1293 Классы RadioMenuitem и CheckMenuitem 1294 Создание контекстного меню 1296 Создание панели инструментов 1300 Составление окончательного варианта приложения MenuDemo 1302 Дальнейшее изучениеJаvаFХ 1308 Часть V. Применение Java 1309 Глава 37. КомпонентыJаvа Beans 1311 Общее представление о компонентахJava Beans 1311 Преимущества компонентовjаvа Beans 1312 Самоанализ 1312 Шаблоны проектирования для свойств компонентовJava Beans 1313 Шаблоны проектирования для событий 1314 Методы и шаблоны проектирования 1315 Применение интерфейса Beaninfo 1315 Привязанные и ограниченные свойства 1315 Сохраняемость компонентовJava Beans 1316 Настройщики 1316 Прикладной программный интерфейсJаvа Beans API 1317 Класс Introspector 1319 Класс PropertyDescriptor 1319 Класс EventSetDescriptor 1319 Класс MethodDescriptor 1319 Пример компонентаjаvа Bean 1320 Глава 38. Введение в сервлеты 1323 Предпосылки для разработки сервлетов 1323 Жизненный цикл сервлета 1324 Варианты разработки сервлетов 1325 Применение контейнера сервлетов Tomcat 1325 Простой пример сервлета 1327 Создание и компиляция исходного кода сервлета 1327 Запуск контейнера сервлетов Tomcat на выполнение 1328 Запуск веб-браузера и запрос сервлета 1328
26 Содержани е Пр икладной про гра ммный интерф ейс Servlet API Па кет j avax. servlet Ин терф ейс Servlet Ин терф ей с Servlet Con fig Ин терфейс Servlet Context Инте рфейс Servlet Request Ин те рфейс Servlet Responce Кла сс GenericServlet Кла сс ServletinputStream Кла сс ServletOutputStream Кла сс Servlet Exception Ввод пара метров сервлета Па кет j avax. servlet. http Ин терфейс HttpServlet Request Ин терф ейс HttpServlet Response Инте рфейс HttpSession Кла сс Cookie Класс HttpServlet Об работка НТТР -запросов и ответов НТТР Об работка НТТР-запросов типа GET Об работка НТТР -запросов типа POS T Пр именение соо kiе -ф айлов От слежива ние сеансов св язи Приложение А. Применение документирующих комментариев вJava Де скриптор ы утилиты javadoc Дескриптор $author Де скрип тор { @code} Де скриптор @deprecated Де скриптор { @doc Root} Дескриптор @exception Дескриптор { @inheritDoc} Дескриптор {@link} Де скриптор {@linkplain} Дескриптор { @literal} Дескриптор @param Де скриптор @return Дескриптор @see Дескриптор @serial Дескриптор @serialData Де скриптор @serialField Де скрип тор @since Дескр ип тор @throws Дескрипто р {@value} Де скриптор @version Об щая фор ма документирую щих ком ментариев Результаты , выводи мыеутилитой javadoc Пр имер при менения до кументирующих ко мментари ев Предметный указатель 1329 1329 1330 1330 1331 1331 1332 1333 1333 1333 1333 1334 1335 1336 1337 1338 1339 1340 1341 1341 1342 1343 1346 1347 1347 1348 1349 1349 1349 1349 1349 1350 1350 1350 1350 1350 1350 1351 1351 1351 1351 1352 1352 1352 1352 1353 1353 1355
'" Об авто ре Герберт Шилдт являет ся автором мно гочи сленных кн иг по про граммирова­ нию, пользующих ся большим успехом у чи тателей в течение почти трех де сяти­ лети й, а также признанным авторитетом по язы куJa va. Его кн иги продают ся мил ­ лионными тир ажами и переведены на мно гие язы ки мира. Его перу принадлежит немало кн иг по Ja va, в том чи сл е Ja va: руководство для начинающих, ja va: методики програм м ир ования Ши.лдта, SWING: руководство для начинающих, Искусство програм­ мирования наJa va, а также настоящее издан ие. Он написал немало кн иги по дру гим язы кам про граммирования , включая С, С++ и С#. Ин тере суя сь всеми аспе ктами вы числительной тех ни ки, Ге рберт уд еляет основное внимание язы кам про гр ам­ ми рования и, в ча стно сти , ко мпиля торам , интерпретаторам и язы кам упра вле ­ ния роботами. Он та кже проя вляет большой интере с к ст андартизации язы ков. Ге рберт окон чил Иллиной ски й универ ситет, получив обе ст епени - ба калавра и ма гистра. По дробнее об авторе можно узнать, по сетив его веб -сайт по адре су www .Herb S childt . com. О научном редакторе Д·р Дзиви Ковард реда ктировал все издания этой кн иги. Он ввел понятие се рв­ ле тов Jаvа в первую вер сию платформыJа vа ЕЕ, внедрил веб -службы на платформе Java МЕ, со ставил общую ст рате ги ю и пла н разработ ки вер сии jаvа SE 7. Он так­ же заложил основы техноло гии Jа vаFХ, а со всем недавно разработал са мое кр уп­ ное допол нение к ст андарту Java ЕЕ 7 и пр икладному про граммно му интер фей су Ja va WeЬSocket API . Д-р Ковард обладает необычайно обширными знаниями все х аспе ктов техноло гииJа vа: от про граммирования нaJava и до разработ ки пр иклад­ н ых программных интерфейсов API вместе сопытными специалистам и в данной обла сти , а та кже мно голетним опытом работы в испол нител ьном ко митете Ja va Community Process. Он та кже являет ся автором кн и ги Ja vaWebSocket Programmiпg и го товящей ся к изданию кни ги поJa va ЕЕ. Д -р Ковард окончил Оксфорд ский уни­ ве рситет, получив сте пени ба калавра , ма гистра и до ктора математиче ских нау к.
28 П редисловие П ред исловие Java - оди н из самых важн ых и широко применяемых языков программирова­ ния в мире на протяжении многих лет. В отличие от некоторых других языков программирования, вл ияниеJ ava не только не уменьшилось со временем, а, наобо­ рот, возросло. С момента первого выпуска он выдвинулся на передний край про­ граммирования приложений для Интернета. И каждая последующая версия лишь укрепляла эту позицию. НынеJava по-прежнему остается первым и самым лучшим языком для разработки веб-ориентированных приложений. Проще говоря , баль­ шая часть современного кода написана нa java. И это свидетельствует об особом значении языкаjаvа для программирования. Основная причина ycпexajava - его гибкость. Начиная с первой верс ии 1.0, этот язык непрерывно адаптируется к изменениям в среде программирования и подхо­ дам к написанию программ. А самое гл авное - он не просто следует тенде нциям в программировании, а помогает их создматъ. СпособностьJava адаптироваться к бы­ стрым изменениям в вычислительной технике служит основной причиной , по кото­ рой этот язык программирования продолжается оставаться столь успешным. Со времени публ икации первого издания этой книги в 1996 году она претерпе­ ла немало изменений , которые отражал и последовательное развитие языка Java. Настоящее, девятое, издание обновлено по версииjаvа SE 8 (JDK 8). А это означает, что оно содержит немало нового материала, поскольку в версии jаvа SE 8 появился ряд новых языковых средств. Наиболее важными из них являются лямбда-выраже­ ния , вводящие совершенно новый синтаксис и существенно повышающие вырази­ тельную силу языка. Вследствие особого значения лямбда-выражений им посвящена отдельная гл ава, а примеры их применения приведены и в других гл авах. Внедрение лямбда-выражений повлекло за собой появление других новых языковых средств. К их числу относится библиотека потоков ввода-вывода, входящая в пакет j ava . util . stream и поддерживающая конвейерные операции с данными. Этой библи­ отеке также посвящена отдельная гл ава. Еще одним нововведением является метод по умолчанию, позволяющий вводить в интерфейс функциональные возможности, используемые по умолчанию. А такие средства, как повторяющиеся и типовые анно­ тации, дополнительно повышают эффективностьjаvа. В версии jаvа SE 8 сделаны также значительные ус овершенствования в библиотеке Java API , и некоторые из них описываются в этой книге. Еще одним важным дополнением настоящего издания служит описание JavaFX - новой технологии Java для построения графического пользовательско­ го инте рфейса (ГПИ) приложений нa java. В перспективе JаvаFХ отводится суще­ ственная роль в разработке приложений нa java, и поэтому этой технологии по­ священы три отдел ьные гл авы. Это означает, что программирующим на Java про­ сто необходимо овладеть тех нологией JavaFX. Еще одна гл ава книги посвящена разработке меню средствами Swing. Несмотря на то что технология jаvаFХ может в перспективе полностью заменить технологию Swing, на момент выхода настоя­ щего издания Swing по-прежнему широко применяется для построения ГПИ при­ ложений на Java , и поэтому подробное рассмотрение этой технологии в настоя­ щем издании вполне обоснованно. И наконец, в отдел ьных гл авах упоминаются многочисленные мелкие усовершенствования в версии jаvа SE 8.
Книга для всех программистов Предисловие 29 Эта книга предназначена для всех категорий программистов: от начинающих до опытных. Начинающий программист найдет в ней подробные пошаговые опи­ сания и немало полезных примеров написания кода на Java , а углубленное рас­ смотрение более сложных функций и библиотекjаvа должно привлечь внимание опытных программистов. Для обеих категорий читателей в книге указаны дей­ ствующие ресурсы и полезные ссылки. Структура кн иги Эта книга служит исчерпывающим справочным пособием по языкуJava, в кото­ ром описываются его синтаксис , ключевые слова и основополагающие принципы программирования. В ней рассматривается также значительная часть библиотеки Java API . Книга разделена на пять частей, каждая из которых посвящена отдел ьно­ му аспекту среды программированияjаvа. В части 1 представлено подробное учебное пособие по языкуJava. Она начинается с рассмотрения таких основных понятий, как типы данных, операции, управляющие операторы и классы. Затем описываются правила наследования , пакеты, интерфей­ сы, обработка исключений и многопоточная обработка. Далее рассматриваются ан­ нотации, перечисления, автоупаковка, обобщения, операции ввода-вывода и аплеты. А заключительная гл ава этой части посвящена лямбда-выражениям, которые, как упо­ миналось ранее, являются самым важным нововведением в версииjаvа SE 8. В части 11 описываются основные компоненты стандартной библиотеки Java API. В ней обсуждаются следующие во просы: символьные строки, операции вво­ да-вывода, работа в сети , стандартные утилиты , каркас коллекций Collections Framework, аплеты , библиотека АWГ, обработка событий, формирование изобра­ жен ий, параллельная обработка (включая каркас Fork/Join Framework) , регуля р­ ные выражения и новая библиотека потоков ввода-вывода. Часть 111 состоит из трех гл ав, посвященных технологии Swi ng, а часть IV - из такого же количества гл ав , посвященных технологииJаvаFХ. Часть V состоит из двух гл ав с примерами практического примененияJava. Сначала в ней рассматривается технологияjаvа Beans, а затем представлены сервлеты. Исходный код примеров, доступный в Интернете Помните , что исходный код всех примеров, приведенных в этой книге, до­ ступен на веб-сайте издательства Oracle Press по адресу http://www .mhprofes­ sion al . com /product .php ?isbn=0 071808558 ил и с сайта Издател ьского дома "Вильяме" по адресу: http: / / archive .williamspuЫishing . com/ cgi-Ьin/ ma terials . cgi ?isbn= 978-5 -8459-1918-2 .
30 П редисловие Особые бл агода рно сти Выражаю особую благодарность Патрику Нотону (Patrick Naughton) , Джо О'Нилу (Joe O'Neil) и Дэнни Коварду (Danny Coward ). Патрик Нотон был одним из создателей языка Jаvа. Он помог мне в написании первого издания этой книги. Значительная часть материала гл ав 20, 22 и 27 была предоставлена Патриком. Его проницательность, опыт и энергия в огромной сте­ пени способствовали успеху этой книги. При подготовке второго и третьего издан ий этой книги Джо О'Нил предоста­ вил исходные черновые материалы, которые послужили основанием для написа­ ния гл ав 30, 32, 37 и 38. Джо помогал мне при написании нескольких книг, и я вы­ соко ценю его вклад. Дэнни Ковард выполнил научное редактирование книги . Он принимал участие в работе над несколькими моими книгами, и его полезные советы , поучительные замечания и дельные предложения всегда были ценными и достойными большой признател ьности . Гер берт Шилдт Допол нител ь ная л ите ратура Настоя щее издание открывает серию книг ·по програм мирован ию нa java, на­ писанных Ге рбертом Шилдтом. Ниже перечислены другие книги этого автора, ко­ то рые могут вас заинтересовать. • Ja va: методики прогр ам м ир ования Шил дта. И.Д . "Вильяме", 2008 г. • Ja va: руководство для на'Чинающих, 6-е изд. И.Д . "Вильяме", 2015 г. • SWING: руководство для на-чинающих. И.Д . "Вильяме", 2007 г. • Искусство програм м ир ования на]аvа. И .Д . "Вильяме", 2005 г.
ЧАСТЬ 1ГЛАВА 1 История и развитие языка Java ГЛАВА 2 Краткий обзор Java ГЛАВА З Типы данных, переменные и массивы ГЛАВА 4 Операции ГЛАВА 5 Управляющие операторы ГЛАВА 6 Введ ение в кла ссы ГЛАВА 7 Подробное рассмотрение кл ассов и методов ГЛАВА 8 Наслед ование ГЛАВА 9 Пакеты и инте рфейсы ГЛАВА 10 Обработка исключений ГЛ АВА 11 Многопоточ ное прогр аммирование ГЛ АВА 12 Перечисления, автоупа ковка и аннота ции !мета данные) ГЛАВА 13 Ввод-вывод, аплеты и про чие вопросы ГЛ АВА 14 Обобщения ГЛАВА 15 Ля мбда-выражения Язык Java
1 История и развитие языка Java Чтобы дос конально изучить язык программирования jаvа, следует понять при­ чины его создания , факторы, обусловившие его формирование, а также унасле­ дованные им особенности. Подобно другим уд ачным языкам программирования, предшествовавшим Java, этот язык сочетает в себе лучшие элементы из своего богатого наследия и новаторские концепции, применение которых обусловлено его особым положением. В то время как остальные гл авы этой книги посвящены практичес ки м вопросам программирования нa Java, в том числе его синтаксису, библиотекам и приложениям, в этой гл аве поясняется , как и почему был разра­ ботан этот язык, что делает его столь важн ым и как он развивался за годы своего существован ия. Несмотря на то что язык Java неразрывно связан с Инте рнетом, важно пом­ нить, что это , прежде всего , язык программирования . Разработка и усовершен­ ствование языков программирования обусловлены двумя основными причинами: • адаптация к изменяющимся средам и областям применения; • реализация улуч шений и усовершенствований в области программирования. Как будет показано в этой гл аве , разработка языка jаvа почти в равной мере была обусловлена обеими этими причинами. П роисхождение Java Язык Jаvа те сно связан с языком С++, который, в свою очередь, является пря­ мым наследником языка С. Многие особенности Jаvа унаследованы от обоих этих языков. От С языкJаvа унаследовал свой синтаксис, а многие его объектно-ориен­ тированные свойства были перенесены из С++. Собственно говоря , ряд определя­ ющих характеристик языка Java был перенесен - или разработан в ответ на воз­ никшие потребности - из его предшественников. Более то го , созданиеjаvа свои­ ми ко рнями уходит гл убоко в процесс усовершенствования и адаптации, который происходит в языках программирования на протяжении нескол ьких последних десятилети й. Поэтому в этом разделе рассматривается последовател ьность со-
3, Часть 1. Язы к Java бытий и факторов, приведш их к появлению J ava. Как станет ясно в дальнейшем, каждое новшество в архитектуре языка, как правило, было обусловлено необхо­ димостью найти решение той или иной основной проблемы, которую не могли разрешить существовавшие до этого языки. И Java не является исключением из этого правила. За рождение современного п рогра ммирова ния : язы к С Язык С буквально потряс компьютерный мир. Его вл ияние нельзя недооцени­ вать, поскольку он полностью изменил подход к программирован ию. Создание языка С было прямым следствием потребности в структурированном, эффектив­ ном и высокоуро вневом языке , который мог бы заменить код ассемблера в процес­ се создания систе мных программ. Как вы, вероятно, знаете, при проектировании языка программирования часто приходится находить компромиссы между: • простотой использования и предоставляемыми возможностями; • безопас ностью и эффективностью; • усто йчивостью и расширяемостью. До появления языка С программистам, как правило, приходилось выбирать между языками , которые позволял и оптимизировать тот или иной ряд характе­ ристик. Та к, на языке FORTRAN можно было писать достаточно эффективные программы для научных вычислений, но он не очень подходил для написания системного кода. Аналогично язык BASIC был очень прост в изучении, но у него было не очень много фун кциональных возможностей, а недостаточная структу­ рированность ставила под сомнение его полезность для написания крупных про­ грамм. На языке ассемблера можно писать очень эффективные программы, но его трудно изучать и эффективно использовать. Более того , отладка ассемблерного кода может оказаться весьма сложной задачей. Еще одна усложнявшая дело проблема состояла в том, что первые языки про­ граммирования вроде BASIC, COBOL и FORTRAN были разработаны без учета принципов структурирования. Вместо этого основными средствами управления программой в них были операторы безусловного перехода GOTO. В результате программы, написанные на этих языках, проявляли тенденцию к появлению так называемого "макаронного кода" - множества запутанных переходов и условных ветвей , кото рые делали программу едва ли доступной для понимания. И хотя язы­ ки наподобие Pascal структурированы, они не были предназначены для написания эффективных программ и лишены ряда важных средств, необходимых для напи­ сания разнообразных программ. (В частности , применять Pascal для написания системного кода было нецелесообразно, принимая во внимание наличие несколь­ ких стандартных диалектов этого языка.) Та ким образом, до изобретения языка С, несмотря на затраченные усилия, ни одному языку не уд авалось разрешить существовавшие в то время противоречия в программировании. Вместе с тем потребность в таком языке становилась все бо­ лее насущной. В начале 1970-х годов началас ь компьютерная революция, и потреб­ ность в программном обеспечении быстро превысила возможности программи-
Гл ава 1. История и развитие языка Java 35 стов по его созданию. В академических кругах большие усилия были приложены к созданию более совершенного языка программирования. Но в то же время стало все больше ощущаться вл ияние еще одного, очень важного фактора. Компьютеры, наконец, получили достаточно широкое распространение , чтобы была достигну­ та "критическая масса" . Компьютерное оборудо вание больше не находилось за за­ крытыми дверями. Впервые программисты получили буквально неограниченный доступ к своим вычислительным машинам. Это дало свободу для экспериментиро­ вания. Программисты смогли также приступить к созданию своих собственных инструментальных средств. Накануне создания С произошел качественный ска­ чок в области языков программирования. Язык С, изобретенный и впервые реализованный Деннисом Ритчи на мини­ эвм DEC PDP-11, работавшей под управлением операционной системы UNIX. явился результатом процесса разработки, начавшегося с предшествующего ему языка BCPL, разработанного Мартином Ричардсом. BCPL оказал влияние на язык, получивший название В, который бьш изобретен Кеном То мпсоном и в начале 1970-х гг. привел к появлению языка С. В течение долгих лет факти ческим стан­ дартом языка С была его версия, которая поставлялась вместе с операционной си­ стемой UNIX (она описана в книге ЯЗъtх програм м ир ования С Брайана Кернигана и Денниса Ритчи , 2-е издание, И.Д . "Вилямс", 2007 г. ) . Язык С был формально стан­ дартизован в декабре 1989 г., когда Национальный институт стандартизаци и США (American National Standards Institute -ANSI) принял стандарт на С. Многие считают создание С началом современного этапа развития языков про­ граммирования. Этот язык ус пешно соединил проти воречивые свойства, которые доставляли столько неприятностей в предыдущих языках. В итоге появился мощ­ ный , эффективный, структур ированный язык, изучение которого было сравни­ тел ьно простым. Кроме того, ему бьша присуща еще одна, почти непостижимая особенность: он бьш разработан специалъно для программистов. До появления С языки программирован ия проектировались в основном в качестве академических упражнений или бюрократическими организациями. Иное дело - язык С. О н был спроектирован, реализован и разработан практикующ ими программиста ми и от­ ражал их подход к программированию. Его функции были отлажены, проверены и многократно переработаны людьми, которые действительно пользовались этим языком. Та ким образом, получился язык, который сразу же понравился програм­ мистам. И действительно, язык С быстро приобрел много приверженцев, кото­ рые едва ли не молились на него. Благодаря этому С получил быстрое и широкое признание среди программистов. Короче говоря , С - это язык, разработанный программистами для программистов. Как станет ясно в дальнейшем, эту замеча­ тельную особенность унаследовали языкjаvа. Следующий этап: язык С++ В конце 1970-х-начале 1980-х гг. язык С стал господствующим языком про­ граммирования и продолжает широко применяться до сих пор. А если С - удач­ ный и удо бный язык, то может возникнуть вопрос: чем обусловлена потребность в каком-то другом языке? Ответ состоит в постоянно растущей С1Wжности про­ грамм. На протяжении всей истории развития программирования постоянно
36 Часть 1. Язы к Java растущая сложность программ порождала потребность в более совершенных спо­ собах преодоления их сложности . Язык С++ явился ответом на эту потребность. Чтобы лучше понять, почему потребность преодоления сложности программ яв­ ляется гл авной побудител ьной причиной создания языка С++ , рассмотрим следу­ ющие факторы. С момента изобретения компьютеров подходы к программированию корен­ ным образом изменились. Когда компьютеры только появились, программирова­ ние осушествлялось изменением двоичных машинных инструкций вручную с па­ нели управления компьютера. До тех пор, пока длина программ не превышала не­ скольких сотен инструкций, этот подход был вполне приемлем. В связи с разрас­ танием программ был изобретен язык ассемблера, который позволил программи­ стам работать с более крупными и все более сложными программами, используя символьные представления машинных инструкций. По мере того как программы продолжали увеличиваться в объеме, появились языки высокого уровня, которые предоставили программистам дополнительные средства для преодоления слож­ ности програм м. Первым языком программирования, который получил широкое распростране­ ние, бьи, конечно же, FORТRAN. Хотя он и стал первым впечатляющим этапом в программировании, его вряд ли можно считать языком, который способствует созданию ясных и простых для понимания программ. 1960-е годы ознаменовались зарождением структурного програм м ир ования. Эта методика программирования наи­ более ярко проявилась в таких языках, как С. Пользуясь структурированными язы­ ками, программисты впервые получили возможность без особых затруднений соз­ давать программы средней сложности. Но и методика структурного программиро­ вания уже не позволяла программистам справиться со сложными проектами, когда они достигали определенных масштабов. К началу 1980-х. сложность многих про­ ектов начала превышать предел, позволявший справиться с ними, применяя струк­ тур ный подход. Для решения этой проблемы бьиа изобретена новая методика про­ граммирования, получившая название об&ектн�тированного профам м ирования (ООП). Объектно-ориентированное программирование подробно рассматрива� ся в последующих гл авах, а здесь приводится лишь краткое его определение: ООП - это методика программирования , которая помогает организовывать сложные про­ граммы, применяя принципы наследования, инкапсуляции и полиморфизма. Из всего сказанного выше можно сделать следующий вывод: несмотря на то , что С является одним из лучших в мире языков программирования , существует предел его способности справляться со сложностью программ. Как только раз­ меры программы превышают определенную величину, она становится слишком сложной, чтобы ее можно было охватить как единое целое. То чная величина этого предела зависит как от структуры самой программы, так и от подходов, исполь­ зуемых программистом, но начиная с определенного момента любая программа становится слишком сложной для понимания и внесения изменений, а следова­ тельно, неуправляемой. Язык С++ предоставил возможности , которые позволили программистам преодолеть этот порог сложности , чтобы понимать крупные про­ граммы и управлять ими. Язык С++ бьи изобретен Бьярне Страуструпом (Bjarne Stroustrup) в 1979 г. , когда он работал в компании Bell LaЬoratories в городе Мюррей-Хилл, шт. Нью-
Глава 1.История и развитие языка Java 37 Джерси. Вначале Страуструп назвал новый язык "С with Classes" (С с классами). Но в 1983 г. это название бьvю изменено на С++. Язык С++ расширяет функцио­ нальные возможности языка С, добавляя в него объектно-ориентированные свой­ ства. А поскольку язык С++ построен на основе С, то в нем поддерживаются все функциональные возможности , свойства и преимущества С. Это обстоятельство явилось гл авной причиной успешного распространения С++ в качестве языка про­ граммирования. Изобретение языка С++ не бьvю попыткой создать совершенно новый язык программирования. Напротив, все усилия были направлены на усо­ ве ршенствование уже существующего очень уд ачного языка. П редпос ылки к с озданию Java К концу 1980-х и в начале 1990-х гг. объектно-ориентированное программиро­ ва ние на С++ стало основной методикой программирования. И в течение некото­ рого, хотя и непродолжительного периода времени казалось, что программисты наконец изобрели идеальный язык. Ведь язык С++ сочетал в себе высокую эффек­ тивность и стилистические элементы языка С наряду с объектно-ориентирован­ ным подходо м, и поэтому его можно было использовать для создания самых раз­ ных программ. Но, как и прежде, уже вызревали факторы, которые должны были в оче редной раз стимулировать развитие языков программирования . Прошло еще несколько лет, и развитие Всемирной паутины и Интернета достигло "критиче­ ской масс ы", что и привело к еще одной революции в программировании. Создан ие я зыка Java Начало разработке языка jаvа было положено в 1991 г. Джеймсом Го слингом (James Gosling) , Патриком Нотоном (Patrick Naughton), Крисом Уа ртом (Chris Warth), Эдом Франком (Ed Fran k) и Майком Шериданом (Mike Sheridaп), работав­ шими в компан ии Sun Microsystems, Inc. Разработка первой рабочей версии заняла 18 месяцев. Вначале язык получил название "Oak" (Дуб) , но в 1995 г. он был пере­ именован в 'Java". Между первой реализацией языка Oak в конце 1992 г. и публич­ ным объявлением о создании jаvа весной 1995 г. в разработке и развитии этого языка приняли участие немало других специалистов. В частности , Бил л Джой (Bill Joy) , Артур ван Хофф (Arthur van Hoff) , Джонатан Пэйн (Jonathan Payne ), Франк Йелл ин (Frank Ye llin) и Тим Линдхольм (Тlm Lindholm) внесли основной вклад в развитие исходного прототипа J ava. Как ни странно, первоначальной побудител ьной причиной для создания языка Java послужил совсем не Инте рнет, а потребность в независящем от кон кретной платформы (т.е . архитектурно нейтральном) языке, который можно было бы ис­ пользовать для создания программного обеспечения, встраиваемого в различные быто вые электронные устройства, в том числе микроволновые печи и устройства дистанционного управления. Не трудно догадаться, что в контроллерах таких устройств применяются разнотипные процессоры. Но трудность применения С и С++ (как и большинства других языков) состоит в том, что написанные на них программы должны компилироваться для конкретной платформы. И хотя про-
38 Часть 1. Язык Java граммы на С++ могут быть скомпилированы практически для любого типа про­ цессора, тем не менее, для этого потребуется наличие полного компилятора С++, предназначенного для данного конкретного процессора. Но дело в то м, что соз­ дание компиляторов обходится дорого и отнимает немало времени. Поэтому тре­ бовалось более простое и экономически выгодное решение. Пытаясь найти такое решение, Гос л инг и другие разработчики начали работу над переносимым , не за­ висящим от конкретной платформы языком, который можно было бы использо­ вать для создания кода, пригодного для выполнения на разнотипных процессорах в различных средах. И в ко нечном итоге их усилия привели к создан ию языкаJava. Примерно в то время , когда определялись основные характеристики языка Java, появился второй, еще более важный фактор, который должен был сыграть решающую роль в судьбе этого языка. И этим вторым фактором, конечно же , стала Всемирная паутина (веб). Если бы формирование веб не происходило почти одно­ временно с реализацией языка Java, он мог бы остаться полезным, но незамечен­ ным языком программирования бытовых электронных устройств. Но с появлени­ ем веб языкjаvа вышел на передний край разработки языков программирования, поскольку среда веб также нуждалась в переносимых программах. С самых первых шагов своей карьеры большинству программистов приходится твердо усваивать, что переносимость программ столь же недостижима, сколь и же­ лательна. Несмотря на то что потребность в средствах для создания эффективных, переносимых (не зависящих от конкретной платформы) программ существовала в программировании с самого начала, она отодвигалась на задний план другими, более насущными , задачами. Более того, бмьшая часть отрасли вычислительной техники была разделена на три конкурирующих лагеря (lntel, Macintosh и UNIX), и поэтому большинство программистов оставались на своих закрепленных аппа· ратно-программных рубежах, что несколько снижало потребность в переносимом коде. Те м не менее с появлением Интернета и веб старая проблема переносимости возникла снова, а ее решение стало еще более актуал ьным. Ведь Интернет пред­ ставляет собой разнообразную и распределенную сетевую среду с разнотипными компьютерами, операционными системами и процессорами. Несмотря на то что к Интернету подключены разнотипные платфор мы, пользователям желательно, чтобы на всех этих платформах можно было выполнять одинаковые программы. То , что в начале было неприятной, но не слишком насущной задачей, стало по­ требностью первостепенной важности. К 1993 г. членам группы разработчиков Java стало очевидно, что пробле­ мы переносимости , часто возникающие при создании кода, предназначенного для встраивания в контроллеры, возникают также и при попытках создания кода для Интернета. Фактически та же проблема, для решения которой в мелком мас­ штабе предназначался язык Java, была актуальна в большем масштабе и в среде Интернета. Понимание этого обстоятельства вынудило разработчиков java пере­ нести свое внимание с бьгrовой электроники на программирование для Интернета. Таким образом, Интернет обеспечил крупномасштабный успех Java, несмотря на то , что потребность в архитектурно нейтральном языке программирования по­ служила для этого своего рода "первоначальной искрой". Как упоминалось ранее , языкJava наследует многие изсвоих характеристик от язы­ ков С и С++. Это сделано намеренно. РазработчикиJava знали, что использование зна-
Гл ава 1. Истори я и раз витие язы ка Java 39 комого синтаксиса С и повторение объектно-ориентированных свойств С++ доткно бьmо сделать их язык привлекательным для миллионов, имеющих опыт программи­ рования на С/С++. Помимо внешнего сходства, вjava исполь.зуется ряд других харак­ терных особенностей, которые способствовали успеху языков С и С++. Во-первых, язык jаvа бьm разработан, проверен и усовершенствован людьми, имевшими солид­ ный практический опыт программирования. Этот язык разработан с учетом потреб­ ностей и опыта его создателей. Та ким образом, Java - это язык для программистов. Во-вторых, Java целостен и логически непротиворечив. В-третьих, Java предоставля­ ет программисrу полный контроль над программой, если не учитывать ограничения, накладываемые средой Интернета. Если программирование вьmолняется грамотно, это непосредственно отражается на самих программах. В равной степени справедли­ во и обратное. Иначе говоря, Java - это язык не для обучающихся программирова­ нию, а для тех, кто занимается им профессионально. Из-за сходства характеристик языков Java и С некоторые склонны считать Java просто "версией языка С++ для Интернета". Но это серьезное заблуждение. Языку Java присущи значительные практические и концепrуальные отличия . Язык С++ дей­ ствительно оказал влияние на характеристики jаvа, но Java не является усовершен­ ствован ной версией С++. Например , Java несовместим с языком С++. Разумеется , у него немало сходств с языком С++, и в исходном коде программы нaJava програм­ мирующий на С++ будет чувствовать себя почти как дома. Вместе с тем язык jаvа предназначен не для замены С++, а для решения одних задач , тогда как язык С++ ­ для решения других. Оба этих языка будут еще долго сосуществовать. Как отмечалось в начале гл авы, развитие языков программирования обусловле­ но двумя причинами: адаптацией к изменениям в среде и реализацией новых идей в области программирования. Изменения в среде , которые побудили к разработке языка, подобного Java, вызвали потребность в независящих от платформы про­ гра ммах, предназначенных для распространения в Интернете. Но языкjаvа изме­ няет также подход к написанию программ. В частности , языкjаvа углубил и усовер­ шенствовал объектно-ориентированный подход, использованный в С++, добавил в него поддержку многопоточной обработки и предоставил библиотеку, которая упростила досrуп к Интернету. Но столь поразительный ycпex Java обусловлен не отдел ьными его особенностями , а их совокупностью как языка в целом. Он явил­ ся прекрасным ответом на потребности в то время лишь зарождающейся среды в высшей степени распределенных вычислительных систем. В области разработ­ ки программ для Интернета язык jаvа стал тем, чем язык С стал для системного программирования: революционной силой, которая изменила мир. Связь с языком С# Многообразие и большие возможности языка jаvа продолжают оказывать вли­ яние на всю разработку языков программирования. Многие из его новаторских характеристик, конструкций и концепций становятся неотъемлемой частью осно­ вы любого нового языка. Yc пex java слишком значителен, чтобы его можно бьmо игнорировать. Вероятно, наиболее наглядным примером вл ияния языкаjаvа на программиро­ ва ние служит язы к С#. Он создан в корпорации Microsoft для поддержки платфор-
40 Часть 1. Язы к Java мы .NET Framework и тес но связан с Java. В частности , в обоих этих языках исполь­ зуется од ин и тот же общий синтаксис, поддерживается распределенное програм­ мирование и применяется од на и та же объектная модель. Разумеется, у Java и С# имеется целый ряд отличий, но в целом они внешне очень похожи. Ныне такое "перекрестное опьшение"Java и С# служит наилучшим доказательством того , что языкJаvа коренным образом изменил представление о языках программирования и их применении. Каким образом я зык Java измен ил И нте рнет Интернет способствовал выдвижению Java на передний край программирова­ ния, а языкJаvа, в свою очередь, оказал очень сильное влияние на Интернет. Язык Java не только упростил создание программ для Интернета в целом, но и обусло­ вил появление нового типа прикладных программ, предназначенных для работы в сети и получивших название аплетов, которые изменили понятие содержимого сетевой среды. Кроме того , язык jаvа позволил решить две наиболее острые про­ бл емы программирования , связанные с Интернетом: переносимость и безопас­ ность. Рассмотрим каждую из этих проблем в отдел ьности. Аппеты на Java Аплет - это особый вид прикладной программы на Java, предназначенный для передачи через Интернет и автоматического выполнения в совместимом сJava веб-браузере . Более то го, аплет загружается по требованию, не требуя дальнейше­ го взаимодействия с пользователем. Если пользователь щелкает кнопкой мыши на ссьшке , которая содержит аплет, он автоматически загружается и запускается в браузере. Аплеты создаются в виде небольших программ. Как правило, они слу­ жат для отображения данных, предоставляемых сервером, обработки действий пользователя или выполнения локально, а не на сервере, таких простых функций, как вычисление процентов по кредитам. По существу, аплет позволяет перенести ряд функций со стороны сервера на сторону клиента. Появление аплетов изменило характер программирования приложений для Инте рнета, поскольку они расширили совокупность объектов, которые мож­ но свободно перемещать в киберпространстве . В общем, между сервером и кли­ ентом передаются две крупные категории объектов: пассивные данные и динами­ чески акти визируемые программы. Например, чтение сообщений электронной почты подразумевает просмотр пассивных данных. Даже при загрузке программы ее код по-прежнему остается пассивными дан ными до тех пор, пока он не начнет выполняться . И напротив, аплет представляет собой динамическую , автоматиче­ ски выполняющуюся прикладную программу. Такая программа является акти вным агентом на клиентской машине, хотя она инициируется сервером. Но за привлекательностью динамических сетевых программ скрываются се­ рьезные трудности обеспечения безопасности и переносимости. Очевидно, что клиентскую машину нужно обезопасить от нанесения ей ущерба программой , кото­ рая сначала загружается в нее, а затем автоматически запускается на выполнение.
Гл ава 1. История и развитие язы ка Java ,, Кроме того , такая программа должна быть в состоянии выполняться в различных вычислительных средах и под управлением разных операционных систем. Как станет ясно в дальнейшем, эти трудности эффекти вно и изящно решаются вjava. Рассмотрим их подробнее. Без опас ность Вам , вероятно , известно, что каждая загрузка обычной программы сопряжена с риском, поскольку загружае мый код может содержать вирус, "троянский конь" или вредоносный код. Дело в том, что вредоносный код может сделать свое чер­ ное дело , если получит несанкционированный доступ к системным ресурсам. Например, просматривая содержимое локальной файловой системы компьютера, вирусная программа может собирать конфиденциальную информацию вроде но­ меров кредитных карточек, сведений о состоянии банковских счетов и паролей. Для безопас ной загрузки и выполнения аплетовJava на клиентской машине нужно было предотвратить подобные атаки со стороны аплетов. Java обеспечивает такую за щиту, ограничивая действие аплета исполняющей средой jаvа и не предоставляя ему доступ к другим частям операционной системы компьютера. (Способы достичь этого рассматриваются далее .) Возможность за­ гружать аплеты в полной уверенности , что это не нанесет системе никакого вреда и не нарушит ее безопасность, многие специалисты и пользователи считают наи­ более новаторским аспектом J ava. П ер енос имость Переносимость - основная особенность Инте рнета, поскольку эта гл обальная сеть соединяет вместе множество разнотипных ко мпьютеров и операционных систем. Чтобы программа нa java могла выполняться практически на любом ком­ пьютере, подключенном к Интернету, требуется каким-то образом обеспечить ее выполнение в разных системах. В частности , один и тот же аплет должен иметь возможность загружаться и выполняться на широком спектре процессоров, опе­ рационных систем и браузеров, подключенных к Инте рнету. А создавать разные ве рсии аплетов для разнотипных компьютеров совершенно нерационально. Один и тот же код должен работать на всех компьютерах. Поэтому требовался какой-то механизм для создания переносимого исполняемого кода. Как станет ясно в даль­ не йшем , тот же самый механизм , который обеспечивает безопасность, способ­ ствует и созданию переносимых программ. Чудо Java : байт- код Основная особенность Java , которая позволяет решать описанные выше про­ блемы обеспечения безопасности и переносимости программ, состоит в том , что компилятор Java выдает не исполняемый код , а так называемый ба йт-код - в выс­ шей степени оптимизированный набор инструкций, предназначенных для выпол­ нения в исполняющей системе Java , называемой виртуалъной машиной ]аvа Qava
'2 Часть 1. Язык Java Virtual Machine - JVM). Собственно го воря, первоначальная версия виртуальной машины JVМ разрабатывалась в качестве интерпретатора ба йт-кода. Это может вызывать недоумение, поскольку для обеспечения максимальной производитель­ ности компиляторы многих современных языов программирования призваны создавать исполняемый код. Но то , что программа нajava интерпретируется вир­ туал ьной машинойJVМ , как раз помогает решить основные проблемы разработки программ для Интернета . И вот почему. Тр ансляция программы Java в байт-код значительно упрощает ее выполнение в разноти пных средах, поскольку на каждой платформе необходимо реализовать только виртуальную машину JVМ. Если в отдельной системе имеется исполняю­ щий пакет, в ней можно выполнять любую программу на Java. Следует, однако, иметь в виду, что все виртуальные машины JVМ на разных платформах, несмотря на некоторые отличия и особенности их реализации, способны правильно ин­ терпретировать один и тот же байт-код. Если бы программа нajava ко мпилирова­ лась в машинозависимый код , то для каждого типа процессоров, подключенных к Интернету, должны были бы существовать отдел ьные версии одной и той же программы. Ясно, что такое решение неприемлемо. Та ким образом, организация выполнения байт-кода виртуальной машинойJVМ - простейший способ создания по-настоящему переносимых программ. То т факт, что программа нaJava выполняется виртуальной машиной JVМ, спо­ собствует также повышению ее безопасности . Виртуальная машина JVМ управля­ ет выполнением программы, поэтому она может изолировать программу и воспре­ пятствовать возникновению побочных эффектов от ее выполнения за пределами данной системы. Как станет ясно в дальнейшем, ряд ограничений, существующих в языке jаvа , также способствует повышению безопас ности . В общем, когда программа компилируется в промежуточную форму, а затем ин­ терпретируется виртуальной машинойJVM , она выполняется медленнее, чем если бы она была скомпилирована в исполняемый код. Но в Java это отличие в произ­ водительности не слишком заметно. Байт-код существенно оптимизирован , и по­ этому его применение позволяет виртуальной машинеJVМ выполнять программы значительно быстрее , чем следовало ожидать. Язык Java бьш задуман как интерпретируемый, но ничто не препятствует ему оперативно выполнять компиляцию байт-кода в машинозависимый код для повы­ шения производител ьности. Поэтому вскоре после выпускаjаvа появилась техно­ логия HotSpot, которая предоставляет динамu'Ческuй компилятор (или так называ­ емый рт-компилятор) байт-кода. Если динамический компилятор входит в состав виртуальной машины JVМ , то избранные фрагменты байт-кода компилируются в исполняемый код по частям, в реальном времени и по требованию. Важно по­ нимать, что одновременная компиляция всей программы jаvа в исполняемый код нецелесообразна, посколькуJava производит различные проверки , которые могут быть сделаны только во время выполнения. Вместо этого динамический компи­ лятор ко мпилирует код во время выполнения по мере надобности . Более того , ко мпилируются не все фрагменты байт-кода, а только те , которым компиляция принесет выгоду, а остальной код просто интерпретируется. Те м не менее прин­ цип динамической компиляции обеспечивает значительное повышение произ­ водител ьности . Даже при динамической компиляции байт-кода характеристики
Гл ава 1 . История и развитие яэь1ка Java 'З переносимости и безопасности сохраняются, поскольку вирrуальная машинаJVМ по-прежнему отвечает за целостность исполняющей среды . Сервл еты : серверные программы на Java Как ни полезны аплеты , они решают лишь половину задачи в архитекrуре "клиент-сервер". Вскоре после появления языка jаvа стало очевидно, что он мо­ жет пригодиться и на серверах. В результате появились сервлеты. Сервлст - это небольшая прикладная программа, выполняемая на сервере. Подобно тому как аплеты динамически расширяют фун кциональные возможности веб-браузе ра, сервлеты динамически расширяют функциональные возможности веб-сервера. Та ким образом, с появлением сервлетов язык jаvа охватил обе стороны соедине­ ния "клиент-сервер". Сервлеты служат для создания динамически генерируемого содержимого, ко­ торое затем предоставляется кл иенrу. Например, интерактивный склад может использовать сервлет для поиска стоимости товара в базе данных. Затем инфор­ мация о цене используется для динамического создания веб-страницы, отправля­ емой браузеру. И хотя динамически создаваемое содержимое доступно и с помо­ щью таких механизмов , как CGI (Common Gateway Interface - интерфейс общего шлюза), применение сервлетов дает ряд преимуществ , в том числе повышение производительности. Подобно в сем программам на Java , сервлеты ко мпилируются в байт-код и вы­ полн яются вирrуальной машиной JVМ , поэтому они в высшей степени перено­ симы. Следовательно, один и тот же сервлет может применяться в различных серверных средах. Единственным необходимым усло вием для этого является под­ де ржка на сервере вирrуальной машиныJVM и контейнера для сервлета. Отл и ч ител ьные особенности Java Рассмотрение истории создания и развития языка jаvа было бы неполным без описания особенностей, характерных для Java. Основным фактором, обусловив­ шим изобретение Java, стала потребность в обеспечении переносимости и без­ опасности, но свою роль в формировании окончател ьной верс ии языка сыграли и другие факторы. Гр уппа разработчиков обобщила основные понятия Java и со­ ставила следую щий перечень его особенностей: • простота; • безопасность; • переносимость; • объектная ориентированность; • надежность; • мноrопоточность; • архитекrурная нейтральность;
'' • • • Часть 1. Язы к Java интерпретируемость; высокая производительность; рас п ределенность; • динамичность. Мы уже рассмотрели такие особенности, как безопасность и переносимость. А те перь поясним значение остальных элементов этого списка. П ростота Язык jаvа был задуман как простой в изучении и эффективный в употреблении профессиональными программистами. Овладеть языком Java те м, у кого имеется некоторый опыт программирования , не составит особого труда. Если же вы уже знакомы с основными принципами объектно-ориентированного программирова­ ния, то изуч ить jаvа вам будет еще проще. А от тех, кто имеет опыт программиро­ вания на С++, переход кjava вообще потребует минимум ус илий. Языкjаvа наследу­ ет синтаксис С/С++ и многие объектно-ориентированные свойства С++ , поэтому для большинства программистов изучениеjаvа не составит бол ьших трудностей. О бъектная ориентированность Хотя предшественники языка jаvа и оказали вл ияние на его архитектуру и син­ таксис, при его проектировании не ставилась задача совместимости по исходному коду с каки м-нибудь другим языком. Это позволило группе разработчиков созда­ вать Java, по существу, с чистого листа. Одним из следствий этого явился четкий, практичный, прагматичный подход к объектам. Помимо того , что .! ! Зык jаvа поза­ имствовал свойства многих уд ачных объектно-программных сред, разработанных на протяжении нескольких последних десятилети й, в нем уд алось достичь золотой середины между строгим соблюдением принципа "все элементы программы явля­ ются объектами" и более прагматичного принципа "прочь с дороги". Объектная модель Java проста и легко рас ширяема. В то же время такие элементарные типы данных, как целочисленные, сохраня ются в виде высокопроизводительных ком­ понентов, не являющихся объектами. Н адежность МногоIU1атформенная среда веб предъявляет к программам повышенные тре· бования , поскольку они должны надежно выполняться в разнотипных системах. Поэтому способность создавать надежные программы была одн им из гл авных прио­ ритетов при разработкеjаvа. Для обеспечения надежности вjava накладывается ряд ограничений в нескольких наиболее важных областях, что вынуждает программи­ стов выявлять ошибки на ранних этапах разработки программы. В то же время Java избавляет от необходимости беспокоиться по поводу многих наиболее часто ветре· чающихся ошибок программирования. А поскольку Java - строго типизированный язык, то проверка кода выполняется во время компиляции. Но проверка кода де-
Гл ава 1 . История и раз витие язы ка Java '5 лается и во время выполнения. В результате многие трудно обнаруживаемые про­ граммные ошибки, которые часто приводят к возникновению с трудом воспроизво­ димых ситуаций во время выполнения, попросrу невозможны в программе нa Java. Предсказуемость кода в разных ситуациях - одна из ос новных особенностейJаvа. Чтобы понять, каким образом достигается надежность программ нa Java, рас­ смотрим две основные причины программных сбоев: ошибки управления памя­ тью и неправильная обработка исключений (т.е . ошибки при выполнении) . В тра­ диционных средах создания программ управление памятью - сложная и трудоем­ кая задача. Например, в среде С/С++ программист должен вручную резервировать и освобождать всю динамически распределяемую память. Иногда это ведет к воз­ никновению трудностей, поскольку программисты забывают освободить ранее зарезервированную память или, что еще хуже , пытаются освободить область па­ мяти , все еще используемую другой частью кода. Java полностью исключает такие ситуации, автоматически управляя резервированием и освобожде нием памяти. (Освобождение оперативной памяти полностью выполняется автоматически, по­ скол ькуJava предоставляет средства сборки неиспользуемых объектов в "мусор".) В тради ционных средах условия для исключений часто возникают в таких ситуа­ циях, как делен ие на нуль ил и отсутствие искомого файла, а управление ими долж­ но осуществляться с помощью громоздких и трудных для понимания конструкций. Java облегчает выполнение этой задач и, предлагая объектно-ориентированный механизм обработки исключений. В грамотно написанной программе нajava все ошибки при выполнении могут (и должны) обрабатываться самой программой. Многопоточность Язык Java был разработан в ответ на потребность создавать интерактивные сетевые программы. Для этой цели в Java поддерживается написание много­ поточных программ, способных одновременно выполнять многие действия. Исполняющая система Jаvа содержит изящное, но вместе с тем сложное решение задачи синхронизации многих процессов, которое позволяет строить устойчиво работающие интерактивные системы. Простой подход к организации многопо­ точной обработки , реализованный вJava , позволяет программистам сосредоточи­ вать основное внимание на конкретном поведении программы, а не на создании многозадач ной подсистемы. Арх итектурная н е йтральность Основной задачей , которую ставили перед собой разработчики Jаvа, было обе­ спечение долговечности и переносимости кода. Одной из гл авных трудностей, стоявших перед разработчиками, когда они создавалиJаvа, было отсутствие вся­ ких гаранти й, что код , написанный сегодня, будет ус пешно выполняться завтра - даже на од ном и том же компьютере. Операционные системы и процессоры по­ стоянно совершенствуются, и любые изменения в основных систе мных ресурсах могут стать причиной неработоспособности программ. Пытаясь каким-то образом изменить это положение, разработчики приняли ряд жестких решений в самом языке и виртуальной машине Java. Они поставили перед собой следующую цель:
'6 Часть 1. Язы к Java "написано однажды, выполняется везде , в любое время и всегда". И эта цель была в значительной степени достигнуга. И нтерпретируе мость и в ы сокая произ водител ь ность Как упоминалось ранее , выполняя компиля цию программ в промежуточное представление, называемое байт-кодом, Java позволяет создавать межпла'Iфор­ менные программы. Та кой код может выполняться в любой системе , на которой реализована виртуальная машина JVМ. С первых же попыток разработать меж­ платформенные решения удал ось достичь поставленной цели, хотя и за счет сни­ жения производительности. Как пояснялось ранее, байт-код Java бьт тщательно разработан таким образом, чтобы его можно было с высокой эффективностью преобразовывать непосредственно в машинозависимый код на конкретной плат­ форме с помощью динамического компилятора. Исполняющие системыjаvа, обе­ спечивающие такую возможность, сохраняют все преимущества кода, не завися­ щего от конкретной платформы. Рас предел е нно сть Язык jаvа предназначен для распределенной среды Интернета, поскольку он поддерживает семейство сетевых протоколов ТСР/IP. По существу, обращение к ресурсу по унифицированному указателю информационного ресурса (URL) мало чем отличается от обращения к файлу. В Java поддерживается также удаленнъtй вы­ зов методов (RМI - Re mote Method Invocation ). Та кая возможность позволяет вы­ зывать методы из программ через сеть. Дина мич ность Программы на Java содержат значительный объем данных динамического ти па, используемых для проверки полномочий и разрешения доступа к объек­ там во время выполнения . Это позволяет безопас но и рационально выполнять динамическое связывание кода. Данное обстоятельство исключительно важно для устойчивости среды Java, где небольшие фрагменты байт-кода могут динами­ чески обновляться в действующей системе. Эволюция языка Java Первоначальная версия Java не представляла собой ничего особенно револю­ ционного , но она и не ознаменовала завершение периода быстрого совершенство­ вания этого языка. В отличие от большинства других систем программирования, совершенствование которых происходило понемногу и постепенно, языкjаvа про­ должает стремител ьно развиваться. Вскоре после выпуска версии jаvа 1.0 разра­ ботчики уже создали версиюJava 1.1 . Внедренные в этой версии фун кциональные возможности оказались более значительными и существенными, чем можно бьто ожидать, судя по увеличению дополнительного номера версии. В новой версии
Глава 1.История и развитие языка Java '7 разработчики добавили много новых библиотечных элементов , переопределили механизм обработки событий и изменили конфигурацию многих библиотечных средств из версии 1.0 . Кроме того , они отказались от нескольких средств , перво­ начально определенных в Java 1.0, но признанных затем устаревшими. Таким обра­ зом , в версииjаvа 1.1 были внедрены новые характерные свойства и в то же время исключены некоторые свойства, определенные в первоначальной спецификации. Следующей основной стала версия jаvа 2, где номер "2" означает второе поко­ ление . Создание версии jаvа 2 стало знаменательным событием, означавшим на­ чало "современной эпохи"Java. Первой версииjаvа 2 был присвоен номер 1.2, что может показаться несколько странным. Дело в то м, что вначале номер относил­ ся к внутре ннему номеру версии библиотек Jаvа , но затем он был распространен на всю версию в целом. С появлением версииJаvа 2 компания Sun Microsystems на­ чала выпускать программное обеспечение Java в виде пакета J2SE (Java 2 Platform Standard Edition - Стандартная версия платформыJаvа 2) , и с тех пор номера вер­ сий стали присваиваться именно этому программному продукту. В версии Java 2 была добавлена поддержка ряда новых средств , в том числе Swing и Collections Framework. Кроме того , были усовершенствованы виртуальная ма шина JVМ и различные инструментальные средства программирования . В то же время версия Java 2 содержала некоторые не рекомендованные к употребле­ нию средства. Наибольшие изменения претерпел класс Thread, где методы sus­ pend (), resume () и stop ( ) стали рекомендованными к употреблению в многопо­ точном программировании. Версия J2SE 1.3 стала первой серьезной модернизацией первоначальной вер­ сии Java J2SE. Эта модернизация состояла, гл авным образом, в расширении су­ ществующих фун кциональных возможностей и "уплотнении" среды разработки . В общем случае программы, написанные для версий 1.2 и 1.3, совместимы по ис­ ходному коду. И хотя изменений в версии 1.3 оказалось меньше, чем в трех пред­ шествующих основных версиях, это не сделало ее менее важной. Совершенствование языка Java было продолжено в версии J2SE 1.4 . Эта вер­ сия содержала несколько важных усовершенствований и добавлений. Например, в нее были добавлены новое ключевое слово assert, цепочки исключений и под­ система ввода-вывода на основе ка налов. Изменения были внесены и в каркас Collections Framework, а также в классы, предназначенные для работы в сети . В эту ве рсию было также внесено много мелких изменений. Несмотря на значительное количество новых функциональных возможностей, версия 1.4 почти полностью сохранила совместимость по исходному коду с предшествующими ве рсиями. В следующую версию Java, названную J2SE 5, был внесен ряд революционных изменений. В отличие от большинства предшествующих обновлений Java, кото­ рые предоставляли важные, но постепенные ус овершенствования, в версииJ2SЕ 5 была коренным образом расширена область, возможности и пределы применения языка. Чтобы стало понятнее, насколько существенными оказались изменения , внес енные в версии J2SE 5, ниже перечислены лишь самые основные из новых функциональных возможностейJаvа в данной версии. • Обобщения. • Аннотации.
48 Часть 1. Язык Java • Автоупаковка и автораспаковка. • Перечисления. • Ус овершенствован ный цикл for в стиле f or each. • Аргументы переменной длины (varargs). • Стати ческий импорт. • Форматированный ввод-вывод. • Ут илиты параллел ьной обработки . В этом перечне не указаны незначительные изменения или постепенные усо­ вершенствования. Каждый пункт перечня представлял значительное дополнение языка jаvа. Одни из них, в том числе обобщения , усовершенствованный цикл for и аргументы переменной длины, представляли новые синтаксические эл ементы , а друrие, например автоупаковка и автораспаковка, изменяли семантику языка. Аннотации придали программированию совершенно новые черты . В любом слу­ чае влияние всех этих дополнений вышло за рамки их прямого действия. Они пол­ ностью изменили сам характер языка jаvа. Значение новых фун кциональных возможностей нашло отражение в присвоен­ ном данной версии номере - "5". Если следовать привычной логике, то следующим номером версииjаvа должен был быть 1.5 . Однако новые средства оказались столь значительными, что переход от версии 1.4 к ве рсии 1.5 не отражал бы масштабы внесенных изменений. Поэтому в компан ии Sun Microsystems решили присвоить новой версии номер 5, чтобы тем самым подчеркнуть значимость этого события . Поэтому версия данного программного продукта была названаJ2SЕ 5, а комплект разработчика - JDK 5. Те м не менее для сохранения единообразия в ко мпании Sun Microsystems решили использовать номер 1.5 в качестве внутреннего номера версии, называемого также номером версии разработки. Цифра 5 в обозначении версии называется номером версии продукта. Следующая версия получила название Java SE 6. С выходом этой версии в ком­ пании Sun Microsystems решили в очередной раз изменить название платформы Java. В названии была опущена цифра 2. Та ким образом, данная платформа те перь называется сокращенно ja va SE, а официально - Ja va Platfarm , Standaтd Edition 6 (ПлатформаJаvа, стандартная версия 6) . Комплект разработчика был названJDК 6. Как и в обозначении версии J2SЕ 5, цифра 6 в названии jаvа SE 6 означает номер верс ии продукта. Внутренним номером разработки этой версии является 1.6. Версия jаvа SE 6 была построена на основе версии J2SЕ 5 с рядом дальнейших усовершенствований. Она не содержала дополнений к числу основных языковых средств Java, но расширяла библиотеки API, добавляя несколько новых пакетов и предоставляя ряд усовершенствований в исполняющей системе . В этой версии было сделано еще несколько усовершенствован ий и внесено несколько дополне­ ний. В целом версия Java SE 6 призвана закрепить достижения в J2SE 5. Далее был а выпущена версия Jаvа SE 7 с комплектом разработчика Jаvа JDК 7 и внутренним номером версии 1.7. Это был первый гл авный выпускjаvа с тех пор, как компания Sun Microsystems была приобретена ко мпанией Oracle (этот процесс начался в апреле 2009 года и завершился в январе 2010 года) . Версия jаvа SE 7 со-
Глава 1 . История и развитие языка Java 1.9 держит много новых средств , включая существенные дополнения языка и библи­ отек Java API. В данной версии была усовершенствована исполняющая система Java, а также включена в нее помержка других языков, кроме Java, но програм­ мирующих нajava больше интересовали дополнения , внесенные в сам язык и его библиотеку. Новые языковые средства были разработаны в рамках проекта Pro:ject Coin. Этот проект должен был обозначать многие, так называемые "мелкие" из­ менения вjava, которые предполагалось включить вJDK 7, хотя эффект от всех этих новых "мелких" новшеств весьма вел ик с точки зрения их воздействия на код. В действительности для большинства программистов эти изменения могут стать самым важным нововведением в Java SE 7. Ниже перечислены новые языковые средства, внедренные вJDK 7. • Расширение типа String возможностями управлять ветвлением в операто- ре switch. • Двоичные целочисленные литерал ы. • Символы подчеркивания в числовых литералах. • Расширение оператора try, называемое оператором try с ресурсами, обеспечи­ вающим автоматическое управление ресурсами. (Например, потоки ввода-вы­ вода моrуг теперь быть закрьrгы автоматически, когда они больше не нужны .) • Выводимость типов (с помощью ромбовидного оператора <>) при создании обобщенного экземпляра. • Ул учшенная обработка исключений, благодаря которой два исключения или больше могут быть обработаны в одном блоке оператора catch (много­ кратный перехват) , а также улучшенный контроль соответствия типов по­ вторно ге нерируемых исключений. • Ул учшение предупреждений компилятора, связанных с некоторыми типами методов с переменным количеством аргументов. И хотя это нельзя считать из­ менением в синтаксисе, оно дает бальший контроль над предупреждениями. Несмотря на то что средства, внедренные в рамках проекта Project Coin, счи­ таются мелкими изменениями в языке Java, в целом они тем не менее дают мно­ го больше преимуществ, чем подразумевает само понятие "мелкие". В частности , оператор try с ресурсами оказывает существенное влия ние на порядок написания кода, опирающегося на потоки ввода-вывода. Кроме того , возможность использо­ вать объекты типа String в операторе switch оказалась долгожданным усовер­ шенствованием, позволившим во многом упростить код. В версии Java SE 7 внесено несколько дополнений в библиотеку Java API. Важнейшими из них являются усовершенствования каркаса NIO Framework и до­ полнения каркаса Fork/Join Framework. Новая система ввода-вывода NIO (перво­ начально называвшаяся New !/ О) была внедрена в версии jаvа 1.4 . Но изменения, внесенные в версии jаvа SE 7, значительно расширили возможности этой систе­ мы, причем настолько, что она зачастую обозначается как N/0.2 . Каркас Fork/Join Framework обеспечивает важную помержку парал л ельного программирования. Те рмином пара.мелъное програм м ир ование обычно обозначают­ ся технологии, повышающие эффекти вность применения компьютеров с несколь-
50 Часть 1. Яэwк Java кими процессорами, вклJОЧаа многоядерные системы. Преимуще ства, которые д�uот ._ногоядернwе системы , поаволяют в перспективе значительно повысить nро�заодительнqст�. nрогрЮfи, Каркас Fork/Join Framework п озволяет решать эа• дачи парал л ельно1V, nрогрвм;vирования в следующих областях: • упрощение сdсtаВ:Лейня й использования заданий, которые мо �уг выпоЛ� пяться пара.iше.пi.нQ; . · 1 - t"t • автоматw.Jес�ое цспол�цание нескольких процессоров. ' Следовательно , с ndмdIЦЬid каркаса Fork/Join Frarnework можно создавать при­ ЛОЖения, в кoтOpr.tk авtоматИЧесtси используются процессоры , досТупнЫ:е ' в ис­ полняющей среде. Разумеется, не все алгоритмы оказываются парал л ельными, но � JJЭНIP', которъ�е т,щц>вым:и все же ЯВJLЯются , позволяют добиться существенных преимуществ в скорости выполнения � Версия Java SE 8 -' (� В новейщеw ;выпуске ,Р. .п St:,,8 с 1юмплектом разработчикаJ DК 8 и внугренним щ>иером версщ�, ЦJ. , ,nроц:ющли значительн ые усовершенствования бл;агодаря внедрению лямбд.а-в ыражений - нового языкового средства с далеко Идущft:М по- 1•'·_. - - :_. ' .- . ' .;·-� ·' . �- '.'.- '1. ,. - . . 1 " сл'еДСтвиЯми'. Лямбда-вьiражения позволяют совершенно иначе подходить к про- граммированию и написанию кода. Как поясняется более п одробно в главе 15, ймб,ца·выражений внm:ят вJava возможности функционального проrраммир0ва­ RИ•; · В процессе nр0гра)�(М4'рОоаНЮ1 .1 1Я мбда·выражен ия способны упрости1Ъ и со­ кратить объем иоrодного tto.l(a; требующегося для создания определенных. кон­ струкций, в том числе некоторых типов анонимных классов. Кроме того , лямбда­ JJ�ения вводм:'J' » язы� Jlовый оператор (->) и эле мецт си нтакс иса. И наконец, JЦибда-аwр4'Же1ЩЯ по�rа�т . сщr.ранить живость и проворство язьпщ, которые ПОJlЬЗОвате.ли У?К�Ч� � "�JWоlо( .О�ТЬ от Java. Внедрение лямбда-выражений оказало обширное вл ияние и на библиотекиJava, кЬторые бы.iJи дdrЮлнёюJ йЬisЬtми ·средствами , выгодно испол ьзующими лямбда­ в'бiраженИя. к наиболее is� новшествам в библиотеке Jаvа относится новый npit:iuiМitoй hpc>ti(ja:Ммнili* mtri!pфeйc API потоков ввода-вывода, входящий в па" к�'j ava . uti1 . st re;am; 'B1Эroм' rфi'fRmi д нoм инrерфейсе API, оптимизированном с'учетом лямбда..tьфЗ.ЖенИй, поддерживаются конвейерные операции с данным'и . lще одним ваЖнЬlм новiП�с'thЬм является пакет j ava . util . function, в котором определен целый ряд фун:кЦiаmал:ьнъtх интерфейсов, обеспечивающих дополниrе.Jiь­ нУю поддержtсу ·лямбда-1\ьt�ажёний. В библиотеке Java API внедрены и другие , ме- нее значительные cpe�t'ri ia ; liмеЮщие отношение к лямбда-выражениям. . ·' 'Еще одiю нqвовведе�е; на�ЙннЬе лямбда-выражениями, касается интерфейсов. В Вер<:·ии .JDK 8 h:оявRла(:ь !iозможно'стЬ определять реализацию по умолчанию метО-: да; объяв.Jiенноrо в'��Йtе; Если конкретная реализация такого меТода отсут­ ствует, то используеТся eroрёаЛизация по умолчанию в интерфейсе. Таки м образрм, Обесnечиваетс:я поdеrtенн0е развитие интерфейсов , поскольку новый метод может бЬlть в�ден в инtеj>фейс, Н'е ii:apymaя существующий " код. Это новшество позволЯ-
Гл ава 1.История и развитие языка Java 51 ет также рационализировать реализацию интерфейса при наличии подходящих средств, доступных по умолчанию. К числу других средств, появившихся в JDK 8, от­ носятся новый прикладной программный интерфейс API для обработки даты и вре­ мени, типовые аннотации и возможность использовать парал л ельную обработку при сортировке массива. Кроме того, в составJDK 8 включена поддержкаJavaFX 8 - последней версии нового каркаса приложенийJаvа с ГПИ. Предполагается, что кap­ кac JavaFX станет неотъемлемой частью практически всех приложений jаvа и в к<r печном итоге заменит Swing для разработки большинства проектов на основе ГПИ. Введение вjavaFX представлено в части IV данной книги. Подводя краткий итог, можно сказать, чтоJava SE 8 является основной верси­ ей , значительно расширяющей возможности Java и изменяющей порядок написа­ ния кода на этом языке. Последствия выпуска этой версии еще долго будуг оказы­ вать вл ияние на все области применения Jаvа. И это действительно очень важное обновление Java. Материал настоящего издания был обновлен таким образом, чтобы отражать переход к версииjаvа SE 8 со всеми новыми средствами, модификациями и допол­ нениями, обозначенными в соответствующих местах книги. Культура нововведений С самого начала язык Java оказался в центре культуры нововведений. Его первоначальная версия изменила подход к программированию для Интернета. Виртуальная маш ина Java (JVМ) и байт-код совершенно изменили представление о безопасности и переносимости. Аплеты (а вслед за ними и сервлеты) вдохнули жизнь в веб. Процесс Java Community Process ОСР) изменил способ внедрения н<r вых идей в язык. Область применения Java никогда не оставалась без изменений в течение длительного периода времени. И Java SE 8 остается на момент выхода этого издания из печати самой последней версией в непрекращающемся динамич­ ном развитииJаvа.
2 Краткий обзор Java Как и во всех остальных языках программирования, элементы Java существуют не сами посебе. Они скорее действуют совместно, образуя язык в целом. Но такая их вза­ имосвязанность может затруднять описание какоГОJ J'О одного аспекта jаvа, не затра­ гивая ряда других. Зачастую для понимания одного языкового средства необходимо знать другое средство. Поэтому в этой гл аве предста ал ен краткий обзор ряда основ­ ных языковых средств Java. Приведенный в ней материал послужит отправной точ­ кой, •rгобы создавать и понимать простые проrраммы. Большинство рассмотренных в этой гл аве вопросов будуг подробнее обсуждаться в остальных гл авах данной части. Объектн о-ор иенти р ованное прогр аммирование Объектно-ориентированное программирование (ООП) составляет основу Java. По существу, все программы на Java являются в какой-то степени объектно­ ориентированными. Язык Java связан с ООП настолько тес но , что прежде чем приступить к написанию на нем даже простейших программ, следует вначале оз­ накомиться с основными принципами ООП. Поэтому начнем с расс мотрения тео­ ретических вопросов ООП. Две м етодики Все компьютерные программы состоят из двух элементов: кода и данных. Более того, программа концептуально может быть организована вокруг своего кода или своих данных. Иными словами , организация одних программ определяется те м, "что происходит" , а других - те м, "на что оказывается вл ияние''. Существуют две методи ки создания программ. Первая из них называется моделъю, ориентированной. на nр О'Цессы и характеризует программу как последовательность линейных шагов (т.е . кода). Модель, ориентированную на процессы, можно рассматривать в каче­ стве кода, воздействующего на данные. Та кая модель довольно успешно применяется в про цедур ных языках вроде С. Но, как отмечалось в гл аве 1, подобный подход по­ рождает ряд трудностей в связи с увеличением размеров и сложности программ. С целью преодолеть увеличение сложности программ бьша начата разработ­ ка подхода, называемого об'6е/Сm1ю-ариентированным програм м ир ованием. Объектно­ ориентированное программирование позволяет организовать программу вокруг
5' Часть 1. Язы к Java ее данных (т.е. объектов) и набора вполне определенных интерфейсов с этими да нными. Объектно�риентированную программу можно охарактеризовать как даннш, управляющие доступам к коi!у. Как будет показано далее, передавая функции управления данными, можно получить несколько организационных преимуществ. Абстракция Важным элементом ООП является абстракция. Человеку свойственно представ­ лять сложные явления и объекты, прибегая к абстракции. Например, люди пред­ ставляют себе автомобиль не в виде набора десятков тысяч отдельных деталей, а в виде совершенно определенного объекта, имеющего свое особое поведение. Эта абстракция позволяет не задумываться о сложности деталей , составляющих автомобиль, скажем, при поездке в магазин. Можно не обращать внимания на под­ робности работы двигателя, коробки передач и тормозной системы. Вместо этого объект можно использовать как единое целое. Эффективным средством применения абстракции служат иерархические клас­ сификации. Это позволяет упрощать семантику сложных систем , разбивая их на более управляемые части . Внешне автомобиль выглядит единым объектом. Но стоит заглянуть внутрь, как становится ясно, что он состоит из нескольких под­ систем: рулевого управления, тормозов, аудиосистемы, привязных ремней, обо­ гревателя , навигатора и т. п. Каждая из этих подсистем, в свою очередь, собрана из более специализированных узлов. Например, аудиосистема состоит из радиопри­ емника, проигрывателя компакт-дисков и/или аудиокассет. Суть всего сказанного состоит в том, что структуру автомобиля (или любой другой сложной системы) можно описать с помощью иерархических абстракций. Иерархические абстракции сложных систем можно применять и к компьютер­ ным программам. Благодаря абстракции данные традиционной , ориентирован­ ной на процессы, программы можно преобразовать в составляющие ее объекты, а последовательность этапов процесса - в совокупность сообщений, передавае­ мых между этими объектами. Та ким образом, каждый из этих объектов описывает свое особое поведение. Эти объекты можно считать ко нкретными сущностями, реагирующими на сообщения , предписывающие им въtnал.нитъ конкретное действие. В этом, собственно, и состоит вся суть ООП. Принципы ООП лежат как в основе языкаJava, так и восприятия мира человеком. Важно понимать, каким образом эти принципы реализуются в програм мах . Какстанет ясно в дальнейшем, ООП является еще одной, но более эффективной и естественной методикой создания программ, способных пережить неизбежные изменения, сопро­ вождающие жизненный цикл любого крупного программного проекта, включая за­ рождение общего замысла, развитие и созревание. Например, при наличии тщатель­ но определенных объектов и ясных, надежных интерфейсов с этими объектам можно безбоязненно и без особого труда извлекать или заменять части старой системы. Три принци па ООП Все языки объектн�риентированного программирования предоставляют ме­ ханизмы, облегчающие реализацию объектн�риентированной модели. Этими
Гл ава 2. Краткий обзор Java 55 механ измами являются инкапсуля ция, наследование и полиморфизм . Рассмотрим эти принципы ООП в отдел ьности . Инкапсуляция Механ изм , связывающий код и данные, которыми он манипулирует, защищая оба эти компонента от внешнего вмешательства и злоупотреблений, называется июсап­ суля цшй. Инкапсуляцию можно считать защитной оболочкой , которая предохраня­ ет код и дан ные от произвольного досrупа со стороны другого кода, находящегося снаружи оболочки . Доступ к коду и данным, находящимся внутри оболочки , строго контролируется тщател ьно определенным интерфейсом. Чтобы провести анало­ гию с реальным миром, рассмотрим автоматическую коробку передач автомобиля. Она инкапсулирует немало сведений об автомобиле, в том числе величину ускоре­ ния , крутизну поверхности , по которой совершается движение, а также положение рычага переключения скоростей. Пользователь (в данном случае водитель) может оказывать вл ияние на эту сложную инкапсуляцию только одним способом: переме­ щая рычаг переключения скоростей. На коробку передач нельзя воздействовать, например, с помощью индикатора поворота или дворников. Та ким образом, рычаг переключения скоростей является строго определенным, а по существу, единствен­ ным, инте рфейсом с коробкой передач. Более то го , происходящее внутри коробки передач не влияет на объекты, находящиеся вне ее. Например , переключение пере­ дач не включает фары! Функция автоматического переключения передач инкапсу­ лирована, и поэтому десятки изготовителей автомобилей могут реализовать ее как угодно. Но с точки зрения водителя все эти коробки передач работают одинаково. Аналогичный принцип можно применять и в программировании. Сильная сторона инкапсулированного кода состоит в следующем: всем известно, как получить доступ к нему, а следовательно , его можно ис пользовать независимо о подробностей реали­ зации и не опасаясь неожиданных побочных эффектов. Основу инкапсуляции вJava составляет класс. Подробнее класс ы будут рассмотре­ ны в последующих гл авах, а до тех пор полезно дать хотя бы краткое их описание. Класс определяет структуру и поведение (данные и код), которые будут совместно использоваться набором объектов. Каждый объект данного класса содержит струк­ туру и поведение, которые определены классом, как если бы объект был "отлит" в форме класса. Поэтому иногда объекты называют экземпляра.ми 'К.!Lасса. Та ким об­ разом, класс - это логическая конструкция , а объект - ее физическое воплощение. При создан ии класса определяются код и данные, которые образуют этот класс . Совместно эти элементы называются 11ЛСНа.ми класса. В частности , определенные в классе данные называются пере.мт т ъtмwч.ле н а.ми, или пере.штнъtми экземпляра, а код, оперирующий данными, - методами-"1ЛСНа.ми, или просто методами. (То, что програм­ мирующие нa Java называют методами, программирующие на С/С++ называют tfrунк­ циями.) В программах, правильно написанных наJava, методы определяют, каким об­ разом используются переменные-члены. Это означает, что поведение и интерфейс класса определяются методами , оперирующими данными его экземпляра. Поскольку назначение класса состоит в инкапсул яции сложной структуры про­ граммы, существуют механизмы сокрытия сложной структуры реализации в самом классе. Каждый метод или переменная в классе могут быть помечены как закрытые или открытые. Откръtтъtй интерфейс класса представляет все, что должны ил и мо-
56 Часть 1. ЯзыкJava гут знать внешние пользователи класса. Закрытъtе методы и данные могут быть до­ ступны только для кода, который является членом данного класса. Следовательно, любой другой код, не являющийся членом данного класса, не может получать доступ к закрытому методу или переменной. Закрытые члены класса доступны другим ча­ стям программы только через открытые методы класса, и благодаря этому исключа­ ется возможность выполнения неправомерных действий. Это , ко нечно, означает, что открытый интерфейс должен быть тщательно спроектирован и не должен рас­ крывать лишние подробности внугреннего механизма работы класса (рис 2. 1). о Открытые переменные экземпляра (не рекомендуются) L� Открытые методы Закрытые методы Закрытые переменные экземпляра Класс \/д / д д А� L.1о оо Рис. 2.1 . Инкапсуляция: открытые методы можно использовать дл я защиты закрытых да нных Наследование Процесс, в результате которого один объект получает свойства другого, назы­ вается нас:ледованием. Это очень важный принцип ООП, поскольку наследование обеспечивает принцип иерархической классификации. Как отмечалось ранее, большинство знаний становятся доступными для ус воения благодаря иерархиче­ ской (т.е. нисходящей) классификации. Например, золотистый ретривер - часть классификации собак, которая , в свою очередь, относится к классу .млекопитающих, а тот - к еще большему классу животнъtх. Без иерархий каждый объект должен был бы явно определять все свои характеристики. Но благодаря наследованию объект должен определять только те из них, которые делают его особым в классе. Объект может наследовать общие атрибуты от своего родительского объекта. Та ким об-
Гл ава 2. Краткий обзор Java 57 разом, механизм наследования позволяет сделать один объект частным случаем более общего случая. Рассмотрим этот механизм подробнее. Как правило, большинство людей воспринимают окружающий мир в виде ие­ рархически связанных между собой объектов, подобных животным, млекопитаю­ щим и собакам. Если требуется привести абстрактное описание животных, можно сказать, что они обладают определенными свойствами: размеры, уровень интел­ лекта и костная система. Животным присущи также определенные особенности поведения: они едят, дышат и спят. Та кое описание свойств и поведения составля­ ет определение 'К.Ласса животных. Если бы потребовалось описать более конкретный класс животных , например млекопитающих, следовало бы указать более кон кретные свойства, в частности тип зубов и молочных желез. Та кое определение называется подклассом животн ых, которые относятся к суперклассу (родительскому классу) млекопитающих. А по­ скольку млекопитающие - лишь более то чно определенные животные, то они на­ rмдуют все свойства животных. Подкласс нижнего уровня uepapxuu 'К.Лассов наследу­ ет все свойства каждого из его родительских классов (рис. 2.2). ( Лабрадор � ,----'- - --._ (n�р-ес_м_ы.,._ка_ю_щ_иеся �� �( �( ,----"- - -._ ( �_.__ п _ Уд _ е_ л_и _ _,) Золотистый ретри вер Волчьи Кошачьи ) Рис. 2.2 . Иерархия кл ассов жи вотн ых ) Наследование связано также с инкапсуля цией. Если отдел ьн ый класс инкапсули­ рует определенные свойства, то любой его подкласс будет иметь те же самые свой­ ства плюс любые дополнительные, определяющие его специализацию (рис. 2.3). Благодаря этому ключевому принципу сложность объектно-ориентированных про­ грамм нарастает в арифмети ческой, а не геометрической прогрессии. Новый под­ класс наследует атрибуты всех своих родительских классов и поэтому не содержит непредсказуемые взаимодействия с большей частью остального кода системы.
58 Часть 1. Язык Java Лабрадор Возраст Пол Вес Охотничьи навыки Вес Млекопитающие Домашние Лабрадор Количество детенышей Период беременности Охотничьи навыки Длина хвоста Дворовые/комнатные Животные Собачьи Охотничьи Период беременности Го нчая? .Наличие родословной? Го нчая? Выученная для утиной охоты? Наличие родословной? Ри с. 2.3 . Лабрадор полностью насл едует инкапсулированные свойства всех родител ьских кл ассов животн ых
Полиморфизм Гл ава 2. Краткий обзор Java 59 Полиморфизм (от греч. "много форм") - эт о принцип ООП, позволяющий ис­ пользоват ь один и тот же интерфейс для обще го класса дейст вий . Каж дое дейс твие завис ит от конкрет ной сиrуации. Расс мотрим в качестве примера ст ек, действую­ щий как спис ок обратного мага зинного типа. Допус тим, в программе тре бу ютс я стеки тре х типов: для це лочис ле нных значе ний , для чис ловых значе ний с плаваю­ щейточкой и для символов. Алгоритм ре ализации кажд ого из эт их стеков ос тае тс я не изме нным, нес мотря на отличия в данных , которые в них хранятс я. В языке, не явл яющемся объектно-орие нтированным, дл я обраще ния со стеком приuuюсь бы создават ь три разных ряда служебных программ под от дельными именами. А вJava, благодаря принципу полиморфизма, для обращения состеком мож но определить общ ий ряд служе бных программ под одними и те ми же общими именами. В более общем смыс ле принцип полиморфизма не ре дко выражается фр аз ой "один инт ерфейс, не сколь ко мет одов" . Эт о означает, что можно разработать об­ щий инт ерфейс для группы связанных вместе дейс твий. Такой подход позволяе т уменьшить с ложнос ть программы, пос коль ку один и тот же инте рфейс служ ит для указания общего класса действий. А выбор конкретного действия (т. е. мет ода) де­ лаетс я применитель но к каж дой сиrуации и входит в обязанности компилятора. Эт о избавляе т программис та от не обходимости де лать такой выбор вручную. Ему нужно лишь помнить об обще м интерфейсе и правиль но применять его. Ес ли продолж ить аналогию с собаками, то можно сказать, что собачье обон я­ ние - полиморфное свой с тво. Ес ли собака почувс твует запах кошк и, она залае т и погонитс я за ней . А ес ли собака почувс тву ет запах своего корма, то у нее нач­ нетс я слюноотде ление, и она пос пешит к своей мис ке. В обоих случаях де йс твуе т одно и то же чу вс тво обоняния. Отличие лишь в том, чт о именно издает запах, т. е . в типе данных, воздействующих на нос собак и! Эт от общий принцип можно ре а­ лизовать, применив его к методам в программе нa jav a. Совместное п рим енение п олиморфизма, и нка п суля ции и наследовани я Если принципы полиморфизма, инкапсу ляции и нас ле дования применяютс я правильно, то они образуют совмес тно среду программирования, подде рживаю­ щую разработ ку более ус той чивых и мас штабируемых программ, чем в том случае, когда приме няетс я модель, ориентированная на процесс ы. Тщате ль но продуман­ ная иерархия класс ов служ ит прочным ос нованием для многок ратног о ис поль зо­ вания кода, на разработку и проверку котор ого были затраче ны вре мя и ус и лия . Инкапс уляция позволяет возвращатьс я к ранее созданным ре ализациям, не нару­ шая код, завис ящий от отк рытого интерфейс а применяемых в приложении клас ­ сов. А полиморфизм позволяе т создавать понятный, практичный, удобочитае мый и устойчивый код. Из двух приве де нных ране е примеров из реаль ной жизни пример с автомобиля­ ми более полно иллюс трирует возможнос ти ООП. Ес ли пример с собаками вполне подходит для расс мотрения ООП с точки зре ния нас ле дования, то приме р с авто­ мобилями имеет боль ше общего с программами. Сад ясь за ру ль различных ти пов
60 Часть 1. Язы к Java (под классов) автомобилей, все водители пользуются наследованием. Независимо от того , является ли автомобиль школьным автобусом, легковым, спорти вным ав­ томобилем или семейным микроавтобусом, все водители смогут легко найти руль, тормоза , педаль акселератора и пользоваться ими. Немного повозившись с рыча­ гом переключения передач , большинство людей могут даже оценить отличия руч­ ной коробки передач от автоматической, поскольку они имеют ясное представле­ ние об общем родительском классе этих объектов - системе передач . Пользуясь автомобилями, люди постоянно взаимодействуют с их инкапсули­ рованными характеристиками. Педали тормоза и газа скрывают невероятную сложность соответствующих объектов за настолько простым интерфейсом, что для управления этими объектами достаточно нажать ступней педаль! Конкретная реализация двигателя , тип тормозов и размер шин не оказывают никакого влия­ ния на порядок взаимодействия с определением класса педалей. И након ец, полиморфизм ясно отражает способность изготовителей автомо­ билей предлагать большое разнообразие вариантов, по сути , одного и того же средства передвижения. Та к, на автомобиле могут быть установлены система тор­ мозов с защитой от бло кировки или традиционные то рмоза, рулевая система с ги­ дроус ил ителем или с реечной переда чей и 4-, 6- или 8-цилиндровые двигатели. Но в любом случае придется нажать на педаль то рмоза, чтобы остановиться , вращать руль, чтобы повернуть , и нажать на педаль акселератора, чтобы автомобиль дви­ гался быстрее. Один и тот же интерфейс может быть использован для управления самыми разн ыми реализациями. Как видите , благодаря совместному применению принципов инкапсуля ции, наследован ия и полиморфизма отдел ьные детали уд ается превратить в объект, назы ваемый автомобилем. Это же отн осится и к компьютерным программам. Принципы ООП позволяют составить связную , надежную , сопровождаемую про­ грамму из многих отдел ьных частей. Как отмечалось в начале этого раздела, каждая программа нajava является объ­ ектно-ориентированной. То чнее говоря , в каждой программе нaJava применяют­ ся принципы инкапсуляции, наследования И полиморфизма. На первый взгляд может показаться , что не все эти принципы проявляются в коротких примерах программ, приведе нных в остальной части этой гл авы и ряде последующих гл ав, тем не менее они в них присутствуют. Как станет ясно в дальнейшем, многие язы­ ковые средства Java являются составной частью встроенных библиотек классов, в которых широко применяются принципы инкапсуляции, наследования и поли­ морфизма. П ервый пример просто й про гр аммы А те перь, когда разъяснены самые основы объектно-ориентированного харак­ тера Jаvа, рассмотрим несколько практических примеров программ, написанных на этом языке. Начнем с компиляции и запуска короткого примера программы, об­ суждаемого в этом разделе. Оказывается, что эта задача не та к проста, как может показаться на первый взгляд.
/* Это простая программа на Java . Глава 2. Краткий обзор Java 61 Присвоить исходному файлу имя "Example .java " */ class Example { // Эта программа начинае тся с выз ова ме тода шain () puЫ ic static vo id ma in (String args[] ) { · System.out . print ln ("Пpocтaя программа на Java.") ; На заметку! Здесь и далее испол ьзуется стандартн ый комплект разработчи ка Java SE 8 Developer's Kit (JDK 8), предоставляемый компанией Oracle. Есл и же для написания программ наJava при­ меняется инте грированная среда разработки (ИСР), то для ко мпиляции и выполнения про­ грамм может потребоваться другая процедура. В та ком сл учае обращайтесь за справкой к до­ кумента ции на применяемую ИСР. Ввод кода программы Для большинства языков программирования имя файла, который содержит исход­ ный код программы, не имеет значения. Но в Java дело обстоит иначе. Прежде всего следует твердо усвоить, что исходному файлу очень важно присвоить имя. В данном пр имере исходному файлу должно быть присвоено Example . java . И вот почему. В Java исходный файл официально называется единицей кoмnШIJlцuu. Он, среди прочего , представляет собой текстовый файл , содержащий определения одного или нескольких классов. (Буд ем пока что пользоваться исходными файлами, со­ держащими только один класс .) Компилятор Java требует, чтобы исходный файл имел рас ширение . java . Как следует из исходного кода рассматриваемого здесь примера программы, определенный в ней класс также называется Example. И это не случай но. В Java весь код должен размещаться в классе. По принятому соглашению имя гл авного класса должно совпадать с именем файла, содержащего исходн ый код программы. Кроме того , написание имени исходного файла должно то чно соответствовать имени гл авного класса, включая строчные и прописные буквы . Дело в том, что в коде Java учитывается регистр символов. На первый взгляд, соглашение о стро­ гом соответствии имен файлов и классов может показаться произвольным. Но на самом деле оно упрощает сопровождение и организацию программ. Компиляци я п рогр а ммы Чтобы скомпилировать программу Exarnple, запустите компилятор (javac), указав имя исходного файла в командной строке следующим образом: С:\>j avac Ехашрlе . java Компилятор javac создаст файл Example.class, содержащий версию байт­ кода. Как пояснялось ранее, байт-код Java является промежуточным представле­ нием программы, содержащим инструкци и, которые будет выполнять виртуаль­ ная машинаJVМ. Следовательно, компилятор javac выдает результат, который не является непос редственно исполняемым кодом.
62 Часть 1. Язы к Java Чтобы выполнить программу, следует воспользоваться загрузчиком приложе­ нийjаvа, который называется java. Ему нужно передать имя класса Example в ка­ честве аргумента командной строки , как показано ниже. C:\>j ava Example Выполнение данной программы приведет к выводу на экран следующего ре­ зультата: Простая программа на Java . В процессе компиляции исходного кода каждый отдел ьный класс поме щается в собственный выходной файл, называемый по имени класса и получающий рас­ ширение . class. Поэтому исходным файлам программ на Java целесообразно присваивать имена, совпадающие с именами классов, которые содержатся в фай­ лах с расширением . class. При запуске загрузчика приложений java описанным выше способом в командной строке на самом деле указывается имя класса, кото­ рый нужно выполнить. Загрузчик приложений автоматически будет искать файл с указанным именем и расширением . clas s. И если он найдет такой файл, то вы­ полнит код, содержащийся в указанном классе. П одробный анали з перво го примера про граммы Хотя сама программа Example . java небольшая , с ней связано несколько важных особенностей, характерных для всех программ на Java. Проанализируем подробно каждую часть этой программы. Начинается эта программа со следующих строк: /* Это пр остая пр ограмма на Java . Присвоить исходному файлу имя "Exaaple .java " */ Эти строки кода содержат комментарий. Подобно большинству других языков программирования , Java позволяет вставлять примечания к коду программы в ее исходный файл . Компилятор игнорирует содержимое комментариев. Эти ком­ ментарии описывают или поясняют действия программы для тех, кто просма­ трuвает ее исходный код. В данном случае комментарий описывает программу и напоминает, что исходному файлу должно быть присвоено имя Exaaple . java . Разумеется, в реальных прикладных программах комментарии служат гл авным об­ разом для пояснения работы отдельных частей программы или действий, выпол­ няемых отдел ьными языковыми средствами. В Java поддерживаются три вида комментариев. Комментарий, приведенный в начале программы , называется многострач,нъLМ. Этот вид комментариев должен на­ чинаться с символов / * и оканчиваться символами * /. Весь текст, расположенный между этими двумя парами символов, игнорируется компилятором. Как следует из его назван ия, многострочный комментарий может содержать несколько строк. Перейдем к следующей строке кода анализируе мой здесь программы. Ниже по­ казано, как она выглядит. class Example { В этой строке кода ключевое слово class служит для объя вления вновь опреде­ ляемого класса, а Example - в качестве идентификатора, обозначающего имя клас-
Глава 2. Краткий обзор Java 63 са. Все определение класса, в том числе его членов, должно располагаться между открывающей ( {) и закрывающей (}) фигурными скобками. Мы не станем пока что останавливаться на особенностях реализации класса. Отметим только, что в среде J ava все действия программы выполняются в пределах класса. В этом и со­ стоит одна из причин, по кото рым все программы нa java явля ются (по крайней мере, частично) объектно-ориентированными. Следующая строка кода данной программы содержит однострочный коммен­ тарий: 11 Эта программа начинается с вызова ме тода 11 1a in () Это вто рой вид комментар иев, поддерживаемых в Java. Он называется одна­ страчнъrм комментарием и нач инается с символов / /, а завершается символом кон­ ца строки. Как правило , программисты пользуются многострочными коммента­ риями для вставки длинных примечаний, а однострочными - для коротких, по­ строчных описаний. Тр ети й вид комментариев , называемый документирующим, будет рассмотрен далее в разделе "Комментарии". Перейдем к следующей строке кода анализируемой здесь программы. Ниже по­ ка зано, как она выглядит. puЬlic static vo id ma in (String args [ ] ) { Эта строка кода начинается с объявл ения метода ma in ().Как следует из пред­ шествующего ей ком ментария, выполнение программы начинается именно с этой строки кода. Выполнение всех прикладных программ нajava начинается с вызова метода ma in ().Мы не станем пока что разъяснять подробно назначение каждого элемента этой строки, потому что для этого требуется ясное представление о под­ ходе к инкапсуля ции, принятом вjava. Но поскольку эта строка кода присутствует в большинстве примеров из данной части книги, то проанализируем ее вкратце. Ключевое слово puЫ ic является модифико:тором доступа, который дает про­ граммисту возможность управлять видимостью членов класса. Когда члену кл асса предшествует кл ючевое слово puЬ lic, этот член доступен из кода за пределами класса, где он определен. (Совершенно противоположное обозначает ключевое слово priva te - оно не разрешает доступ к члену класса из кода за пределами дан­ ного класса. ) В данном случае метод ma in () должен быть определен как pu Ыic, поскольку при запуске программы он должен вызываться из кода за пределами его класса. Ключевое слово s ta tic позволяет вызывать метод ma in () без полу­ чения экземпляра класса. Это необходимо потому, что метод ma in () вызывается виртуал ьной машиной JVМ перед созданием любых объектов. А ключевое слово vo id просто сообщает компилято ру, что метод main () не возвращает никаких значений. Как будет показано далее , методы могуг также возвращать конкретные значен ия. Если это краткое пояснение покажется вам не совсем понятным, не от­ чаивайтесь, пос кольку упомянутые здесь понятия и языковые средства Jаvа будут подробно рассматриваться в последующих гл авах . Как указывалось выше, метод ma in ( ) вызывается при запуске прикладных про­ грамм нa Java . Следует, однако , иметь в виду, что вjava учитывается регистр сим­ волов. Следовательно, имя Ma in не равнозначно имени ma in. Следует также иметь в виду, что компилятор Java скомпилирует классы, в которых отсутствует метод
6' Часть 1. Язы к Java ma in (),но загрузчик приложений (java ) не сможет выполнить код таких классов. Та к, если вместо имени ma in ввести имя Ma in, компилятор все равно скомпилиру­ ет программу, но загрузчик приложений java выдаст сообщение об ошибке, по­ скольку ему не удал ось обнаружить метод ma in (). Для передачи любой информации, требующейся методу, служат переменные, ука­ зываемые в скобках вслед за именем метода. Эти переменные называются параметра­ ми. Если параметры не требуются методу, то указываются пустые скобки. У метода ma in ( ) имеется единственный, хотя и довольно сложный параметр. Та к, в выраже­ нии String args [] объявляется параметр args, обозначающий массив экземпляров класса String. ( Массшrы -это коллекции похожих объектов.) В объектах типа String хранятся символьные строки. В данном случае параметр args принимает любые арrу­ менты командной строки, присутствующие во время выполнения программы. В дан­ ной программе эта информация , вводимая из командной строки, не используется, но в других, рассматриваемых далее примерах программ она будет применяться. Последним элементом в рассматриваемой здесь строке кода оказывается сим­ вол открывающей фигурной скобки ( {). Он обозначает начало тела метода ma in ( ) . Весь код, составляющий тело метода, должен располагаться между открывающей и закрывающей фигурными скобками в определении этого метода. Еще один важный момент: метод ma in () служит всего лишь началом програм­ мы. Сложная программа может включать в себя десятки классов, но только один из них должен содержать метод ma in ( ) , чтобы программу можно было запустить на выполнение. Но в некоторых случаях метод ma in ( ) вообще не требуется, на­ пример, при создании аплетов - прикладных программ нa Java, внедряемых в веб­ браузеры. Метод ma in () в аплетах не требуется потому, что для их запуска на вы­ полнение применяются другие средства. Перейдем к следующей строке кода анализируемой здесь программы. Ниже по­ казано, как она выглядит. Следует также иметь в виду, что эта строка кода находит­ сявтелеметодаmain(). System. out . println ( "Пpocтaя программа на Java.") ; В этой строке кода на экран выводится текстовая строка "Простая программа на Java ." с последующим переходом на новую строку. На самом деле вывод текста на экран выполняется встроенным методом println (). В данном случае метод print ln () отображает переданную ему текстовую строку. Как будет по­ казано далее, с помощью этого метода можно выводить и другие типы данных. Анализируемая здесь строка кода начинается с обозначения стандартного потока вывода Sy stem .out. Это слишком сложная языковая конструкция , чтобы ее мож­ но было просто объяснить на данной стадии изучения Java, но вкратце Sys tem обозначает предопределенный класс, предоставляющий доступ к систе ме, а out - поток вывода , связанный с консолью . Нетрудно догадаться, что в реальных программах нa Java консольный вывод­ ввод применяется редко. Многие современные вычислител ьные среды по своему характеру являются оконными и графическими, поэтому консольный ввод-вывод зачастую применяется в простых служе бных и демонстрационных программах. В дальнейшем будут рассмотрены другие способы ввода-вывода данных в Java, а до тех пор будут применяться методы консольного ввода-вывода.
Глава 2. Краткий обзор Java 65 Обратите внимание на то, что оператор, в котором вызывается метод println () , завершается точкой с запятой. В языке Java все операторы обычно должны окан­ чиваться этим символом. Причина отсугствия точки с запятой в конце остальных строк кода программы состоит в том, что формально они не являются операторами. Первый символ } завершает метод ma in (), а последний символ } - определение класса Example. Второй пример короткой программы Вероятно, ни одно другое понятие не является для языка программирования столь важным, как понятие переменных. Как вы, вероятно, знаете , перемен ная - это именованная ячейка памяти , которой может быть присвоено значение в про­ грамме. Во время выполнения программы зн ачение переменной может изме­ няться. В сл едующем примере программы демонстрируются способы объявления переменной и присвоения ей значения. Этот пример иллюстрирует также неко­ торые новые аспекты консольного вывода. Как следует из комментариев в начале программы, ее исходному файлу следует присвоить имя Ехашрlе2 . java. /* Это е ще один короткий пример программы . Присвоить исходному файлу имя "Exaiaple2 .java " */ class Example2 { puЫ ic static void ma in (String args[J ) { int num; // в этой строке кода объявляется переменная с име нем nwn num = 100; // в этой строке кода переменной nwn 11 присваивается значение 100 System.out . println ("Этo переменная num : " + num) ; num=num*2; System . out .print ("З нaчeниe переменной num * 2 равно ") ; System. out .println (num ) ; Выполнение данной программы приведет к выводу на экран следующего ре­ зультата: Э т о переменная num : 100 Значение переменной num * 2 равно 200 Рассмотрим подробнее получение такого результата. Ниже приведена строка кода из рассматриваемой здесь программы, которая еще не встречалась в преды­ дущем примере. int num ; // в этой строке кода объявляется переменная с име нем nwn В этой строке кода объявляется целочисленная переменная num. В Java, как и в большинстве других языков программирован ия, требуется, чтобы перемен­ ные бьии объявлены до их применения. Ниже приведена общая форма объявле­ ния переменных. r:rиn 11НJ1_перенен н ой;
66 Часть 1. Язы к Java В этом объявлении тип обозначает ко нкретный тип объявляемой переменной, а имя_ переменной - задан ное имя переменной. Если требуется объявить несколь­ ко переменных заданного типа, это можно сделать в виде разделенного запятыми списка имен переменных. В Java определен целый ряд типов дан ных, в том числе целочисленный, символьный и числовой с плавающей точкой. Ключевое слово int обозначает целочисленный тип. В приведенной ниже строке кода из рассма­ три ваемого здесь примера программы переменной num присваивается значение 100. ВJava операция присваивания обозначается одиночным знаком равенства. num = 100; // в э той строке кода переме нной nUl ll присваивается значение 100 В следующей строке кода выводится значение переменной num, которому пред­ шествует текстовая строка " Это переменная num : " : System. out .println ("Этo переменная num : " + num) ; В этом операторе знак + присоединяет значения переменной num в конце пред­ шествующей ему текстовой строки , а затем выводится результирующая строка. (На самом деле значение переменной num сначала преобразуется из целочислен­ ного в строковый эквивалент, а затем объединяется с предшествующей строкой. Подробнее этот процесс описывается далее в книге.) Такой подход можно обоб­ щить. С помощью операции + в одном вызове метода println ( ) можно объеди­ нить нужное кол ичество символьных строк. В следующей строке кода из рассматриваемого здесь примера программы пе­ ременной num присваивается хранящееся в ней значение, умноженное на 2. Как и в большинстве других языков программирования, вJava знак * обозначает ариф­ метическую операцию умножения. После выполнения этой строки кода перемен­ ная num будет содержать значение 200. Ниже приведены две следую щие строки кода из рассматриваемого здесь при­ мера программы. System. out .print ("З нaчeниe переменной num * 2 равно ") ; System.out . println (num) ; В них выполняется ряд новых действий. В частности , метод print () вызывает­ ся для вывода текстовой строки " Значение переменной num * 2 равно " . После этой строки не следует символ новой строки. Так им образом, следующий результат будет выводиться в той же самой строке. Метод print ( ) действует аналогично методу println (),заисключением того, что после каждого вызова он не выводит символ новой строки. А теперь рассмотрим вызов метода println (). Обратите внимание нато,tr ro имя переменной num указывается буквально. Методы print ( ) и println ( ) могут служить для вывода значений любых встроенных в Java типов данных. Два управл яющих операто ра Хотя управляющие операторы подробно описываются в гл аве 5, в этом разделе будут вкратце рассмотрены два управляющих оператора, чтобы было понятно их назн ачение в примерах программ, приведенных в гл авах 3 и 4. Кроме того , они по­ служат хорошей иллюстрацией важного аспектаjаvа - бл оков кода.
Гл ава 2. Краткий обзор Java 67 Условный оператор iE Оператор i f действует подобно уСJ J овному оператору в любом другом языке программирования. Более того, его синтаксис такой же, как и условных операто­ ров if в языках С, С++ и С#. Простейшая форма этого оператора выглядит СJ J еду­ ющим образом: if (усло"е) aaepa!l'op ; где условие обозначает логическое выражение. Если условие истинно, то опера­ тор выполняется. А если условие ложно , то оператор пропускается. Рассмотрим следующую строку кода: if (num < 100) System. o ut . println ( "num ме ньше 100" ); Есл и в данной строке кода переменная num содержит значение меньше 100, то условное выражение истинно и вызывается метод println (). А если пере­ м ен ная num содержит значение, большее ил и равное 100, то вызов метода println () пропускается. Как будет показано в гл аве 4, в языке Java определен полный набор операций сравнения , которые можно использовать в условном выражении. Некоторые из них перечислены в табл. 2 .1 . Таблица 2.1. Некоторые операции сравнения Операция Значение < > Меньше Больше Равно Следует иметь в виду, что проверка на равенство обозначается двойным знаком равенства (=) . Ниже приведен пример программы, демонстрирующий примене­ ние условного оператора i f. /* Продемонстрировать применение условного оператора if . Присвоить исходному файлу имя "IfSaiaple .java" */ class IfSamp le { puЫic static void ma in (String args[] ) { int х, у; х=10; у=20; if (x <у) System.out . println ("x ме ньше у" ); х=х*2; if(x == у) System.out . println ("x теперь равно у" ); х=х*2; if (x >у) System. o ut . println ("x теперь больше у" ) ; 11 этот оператор не будет ниче го выводить if (x == у) System.out . println ("вы не увидите этого" };
68 Часть 1. Язы к Java Эта программа выводит следующий результат: х меньше у х теперь равно у х теперь больше у Обратите внимание на еще одну особенность данного примера программы. В строке кода int х, у; объявляются две переменные - х и у. Для этого испол ьзуется список, разделяе­ мый запяты ми. Оператор цикла for Из имеющегося опыта программирования вам , должно быть, известно, что операторы цикла являются важной составной частью практически любого языка программирования. И языкJаvа не является в это м отношении исключением. Как будет показано в гл аве 5, вJava поддерживаются разнообразные операторы цикла. И, вероятно, наиболее универсальным среди них является оператор цикла for. Ниже приведена простейшая форма этого оператора. for (1UfJlfцяаля.эаЦJ1 1 я; усло:вне ; 1f!1'ераЦJ1 1 я) onepa!l'op; В этой чаще всего встречающейся форме инициа лиза ция обозначает началь­ ное значение переменной управления циклом, а условие - логическое выраже­ ние для проверки значения переменной управления циклом. Если результат про­ верки условия истинен, то выполнение цикла for продолжается . А если результат этой проверки ложен, то выполнение цикла прекращается. Выражение итера ция определяет порядок изменения переменной управления циклом на каждом его шаге. В приведенном ниже кратком примере программы демонстрируется приме­ нение оператора цикла for. /* Продемонстрировать приме нение цикла for . Присвоить исходн ому файлу имя "ForTest .java " */ clas s ForTest { puЫic static void main (String args [] ) { int х; for(x = О; x<lO; х = x+l) System. out . println ("З нaчeниe х: "+х) ; Эта программа выводит следующий результат: Значение х: О Значение х: 1 Значение х 2 Значение х 3 Значение х 4
Значение х: Значение х: Значение х: Знач ение х: Знач ение х: 5 6 7 8 9 Гл ава 2. Краткий обзор Java 69 В дан ном примере х служит переменной управления циклом. В инициализиру­ ющей части цикла for ей присваивается начальное нулевое значение. В начале каждого шага цикла, включая и первый, выполняется проверка условия х < 10. Если результат этой проверки истинен , в программе вызывается метод prin t ln ( ) , а затем выполняется итерационная часть цикла for. Процесс продолжается до тех пор, пока результат проверки условия не окажется ложным. Следует заметить, что в программах, профессионально написанных на Java, итерационная часть цикла почти никогда не встречается в том виде , в каком она представлена в данном примере. Иными словами, операция вроде следующей в та­ ких программах встречаются крайне редко: х=х+1; Дело в том, что в Java предоставляется специальная , более эффективная опе­ рация инкремента ++ (т.е . два знака "плюс" подряд) . Операция инкремента увели­ чивает значение операнда на единицу. Используя эту операцию, предшествующее выражение можно переписать так: х++ ; Та ким образо м, оператор цикла for из предыдущего примера программы мож­ но переписать следующим образом: for(x = О; x<lO; х++) Можете проверить выполнение этого цикла и убедиться, что он действует точ­ но так же, как и в предыдущем примере . В Java предоставляется также операция декремента --, которая уменьшает значение операнда на ед иницу. Исп ол ьзование бл оков кода В языке Java два и более оператора допускается группиро вать в маки кода, на­ зываемые также кодовъLМи маками. С этой целью операто ры заключают в фигур­ ные скобки . Сразу после своего создания бл ок кода становится логической еди­ ницей, которую можно использовать в тех же местах, где и отдел ьные операторы. Например, блок кода может служить в качестве адресата для операторов if и for. Рассмотрим следующий условный оператор i f: if(x < у) { // начало блока х у; у=О; 11 конец блока Если значение переменной х в данном примере меньше у, то выполняются оба оператора, находя щиеся в блоке кода. Оба эти оператора образуют логическую единицу, и поэтому выполнение одного из них невозможно без выполнения друго­ го в блоке кода. Та ким образом, всякий раз , когда требуется логически связать два или более оператора, создается блок кода.
70 Часть 1. Язык Java Рассмотрим еще один пример. В следующей программе блок кода служит в ка­ честве адресата для оператора цикла for: /* Продемонс трировать приме нение блока кода . Присвоить исходному файлу имя "BlockTest.java " */ class BlockTest { puЬlic static void main (String args[] ) { int х, у; у=20; // адресатом этого оператора цикла служи т блок кода for(х = О; x<lO; х++) { Syзtem.out . println ("Знaчeниe х: "+х) ; System. o ut . println ("Зн aчeниe у: "+у ); у=у -2; Эта программа выводит следующий результат: Значение х: О Значение у: 20 Значение х: 1 Значение у: 18 Значение х: 2 Значение у: 16 Значение х: 3 Значение у: 14 Значение х: 4 Значение у: 12 Значение х: 5 Значение у: 10 Значение х: 6 Значение у: 8 Значение х: 7 Значение у: 6 Значение х: 8 Значение у: 4 Значение х: 9 Значение у: 2 В данном случае адресатом оператора цикла for служит блок кода, а не единствен­ ный оператор. Таким образом, на каждом шаге цикла будуг выполняться три операто­ ра из блока кода. Об этом свидетельствует и результат выполнения программы. Как будет показано в последующих гл авах, блоки кода обладают дополнитель­ ными свойствами и областями применения. Но их основное назначение - созда­ ние логически неразрывных единиц кода. Вопросы ле ксики А теперь, когда было рассмотрено несколько кратких примеров программ нajava , настало время для более формального описания самых основных элемен-
Глава 2. Краткий обзор Java 71 тов языка. Исходный текст программ наJava состоит из совокупности пробелов, идентификаторов, литералов, комментариев, операторов, разделителей и ключе­ вых слов. Операторы рассматриваются в следующей гл аве, а остальные элементы вкратце описываются в последующих разделах этой гл авы . П робелы Java - язык свободной формы. Это означает, что при написании программы не нужно следовать каким-то специальным правилам в отношении отступов. Например, программу Example можно было бы написать в одной строке ил и лю­ бым другим способом. Единственное обязательное требование - наличие, по мень­ шей мере , одного пробела между всеми лексемами, которые еще не разграничены оператором или разделителем. В языкеJava пробелами считаются символы про­ бела, табуля ции или новой строки . Идентификаторы Для именования классов, методов и переменных служат идентификаторы. Идентификатором может быть любая последовательность строчных и прописных букв , цифр или символов подчеркивания и денежной еди ницы. (Знак денежной ед иницы не предназнач ается для общего использован ия.) Идентификаторы не должны начинаться с цифры, чтобы компилятор не путал их с числовыми кон­ стантам и. Напомним еще раз, что в Java учитывается регистр символов, и поэтому VAL UE и Va lue считаются разн ыми идентификаторами. Ниже приведено несколь­ ко примеров допустимых идентификаторов. IAvgTemp lcount la4 1$test lthis is ok А следующие идентификаторы недопустимы: l 2count lhigh-temp !Not /ok На заметку! Начиная с версии JDK 8, сам знак подчеркивания не рекомендуется употреблять в качестве идентифи катора. Л итерал ы В Java постоя нное значение задается его литералъным представлением. В каче­ стве примера ниже показано несколько литералов. 198.6 1 •х• l"This is а test" Первый литерал в данном примере обозначает целочисленное значение, следу­ ющий - числовое значение с плавающей точкой, третий - символьную константу, а последний - строковое значение. Литерал можно использовать везде, где допу­ стимо применение значений дан ного типа.
72 Часть 1. Язы к Java Комментарии Как отмечалось ранее, вjava определены три вида комментариев. Два из них (одно- и многострочные комментарии) уже встречались в рассмотренных ранее примерах программ. А третий вид комментариев называется документирующим. Этот вид ко мментариев служит для создания НТМL-файла документации на про­ грамму. Документи рующий комментарий начинается с символ ов /** и оканчи­ вается символами * /. Подробнее документирующие комментарии описываются в приложении к книге. Раздел ител и В Java допускается применение нескольких символов в качестве разделител ей. Чаще всего в качестве разделителя употребляется точка с запятой. Но, как следует из приведенных ранее примеров программ, точка с запятой употребляется также для завершения строк операторов. Символы, допустимые в качестве разделите­ лей , перечислены в табл. 2 .2 . Табл ица 2.2. Символ ы, допусти мые в качестве раздел ителей Символ Н азвание Н азначе ние () Круглые скобки {} Фи гурные скобки Уп отребляются дл я передачи сп исков параметров в определениях и вызовах методов. Применяются также для обозначе ния операции п риведения типов и пред­ шествования операторов в выражениях , употребляе мых в уп равляющих операторах Употребляются дл я указания значений автоматически инициализируемых массивов, а также для определения блоков кода, кл ассов , методов и локальных областей дей­ ствия [] Квадратные скобки Употребляются дл я объявления типов массивов, а также п ри обращении к эле м е нтам массивов То чка с запятой Завершает операторы Зап ятая То чка Разделяет последовательный ряд идентификаторов в объ­ явлениях п еременных. П риме няются также для создания цепочек операторов в операторе цикла for Употребляется для отдел ения имен пакетов от п одпаке­ тов и классов, а также для отдел ения переменной ил и метода от ссылочной переменной Кл ю чевые слова Java В настоящее время в языке Java определено 50 ключевых слов (табл . 2.3), ко­ то рые вместе с синтаксисом операторов и разделителей образуют основу языка Java. Их нельзя использовать ни в качестве идентификаторов, ни для обозначения имен переменных, классов или методов.
Та блица 2.3. Ключевые слова Java abstract cont inue for assert default got o boolean do if break douЫ e implement s byte else import case enurn instanceof catch extends int cha r final interface class finally long const float nat ive Гл ава 2. Краткий обзор Java 73 new switch pac kaqe synchroni zed private this prot ected throw puЫic throws return transient short try static void s trict fp volat ile supe r wh ile Кроме ключевых слов, в Java зарезервированы также слова true , false и nul1. Они представляют значения, определенные в спецификации языка jаvа. Их нель­ ая использовать для обозначения имен переменных, классов и т. п . Библиотеки классовJava В приведенных в этой гдаве примерах программ использовались два встроен­ ных методаjаvа: println ( ) и print ().Как отмечалось ранее, эти методы дОС1)'П­ ны по ссьmке Sys tem. out на класс S ystem, который является стандартиым: вjа� и автоматически включается в прикладные програм м ы. В более широком смы� среда Java опирается на несколько встроенных библиотек классов, содержаl'ЦЮf многие встроенные методы, обеспечивающие подцержку таких операций, ках. ввод-вывод, обработка символьных строк, работа в сети и отображение графИIСВ. Стандартные классы обеспечивают также подцержку ШИ. Та ким образом , среда Java в целом состоит из самого языка Java и его стандартных l(ЛаСсов. Как станет ясно в дальнейшем, библиотеки классов предоставляют большую часть функциQ­ нальн ых возможностей среды jаvа. В действительности стать програм м ирующим нa java означает научиться пользоваться стандартными l(Jlaccaми java. В последу­ ющих гл авах этоii части описание различных элементов стандартных библиотеч­ ных классов и методов приводится по мере надобности, а в части 11 библиотеки классов описаны более подробно.
3 Типы данных, переменные и массивы В этой гл аве рассматриваются три самых основных элементаJ ava: типы данных, переменные и масс ивы. Как и во всех современных языках программирования, в Java поддерживается несколько типов данных. Их можно применять для объяв­ ления переменных и создания массивов. Как станет ясно в дальнейшем, вjava при­ меняется простой, эффективный и связный подход к этим языковым средствам . Java - строго типизированный язык Прежде всего следует заметить, что Java - строго типизированный язык. Именно этим объясняется безопасность и надежность программ нajava. Выясним, что же это означает. Во-первых, каждая переменная и каждое выражение имеет конкретный ти п, и каждый тип строго определен. Во-вторых, все операции при­ сваивания, как явные, так и через параметры, передаваемые при вызове методо в, проверяются на соответствие типов. В Java отсутствуют средства автоматическо­ го приведения или преобразования конфликтующих типов, как это имеет место в некоторых языках программирования. Ко мпилятор Java проверяет все выраже­ ния и параметры на соответствие типов. Любые несоответствия типов считаются ошибками , кото рые должны быть исправлены до завершения компиляции класса. Примитивные типы В языке jаvа определены восемь примитивнъtх типов данных: byte, short, int, long, char, float, douЫe и boolean. Примитивные типы называют также пр о­ сmъLМи, и в данной книге употребляются оба эти термина. Примитивные типы можно разделить на следующие четыре группы. • Целые числа. Эта группа включает в себя типы дан ных byte, short, int и long, представляющие целые числа со знаком. • Числа с плавающей точкой. Эта группа включает в себя типы дан ных f 1 оа t и douЫe, представляющие числа с то чностью до определенного знака по­ сле десятичной то чки.
76 Часть1.ЯзыкJava • Символы. Эта группа включает в себя тип дан ных char, представляющий символы, например буквы и цифры, из определенного набора. • Логические значения. Эта группа вкл ючает в себя тип данных boolean, специально предназначенный для представления логических истинных и ложных значений. Эти типы данных можно использовать непосредственно или для создания соб­ ственных типов классов. Та ким образом, они служат основанием для всех других типов данных, которые могут быть создан ы. Примитивные типы представляют одиночные значения, а не сложные объ­ екты . Язык Java является полностью объектно-ориентирован ным, кроме прими­ тивных типов данных. Они аналогичны простым типам данных, которые можно встретить в большинстве других не объектно-ориентированных языков програм­ мирования. Эта особенность Java объясняется стремлением обеспечить макси­ мальную эффективность. Превращение примитивных типов в объекты привело бы к слишком заметному снижению производительности. Примитивные типы определены таким образом, чтобы обладать явно выража­ емым диапазоном допустимых значений и математически строгим поведением. Языки типа С и С++ допускают варьирование дл ины целочисленных значений в зав исимости от требован ий исполняющей среды. Но в Java дело обстоит ина­ че. В связи с требованием переносимости программ на Java все типы данных об­ ладают строго определенным диапазоном допустимых значен ий. Например, зна­ чения типа int всегда оказываются 32-разрядными, независимо от ко нкретной платформы. Это позволяет создавать программы, которые гарантированно будут выполняться в любой машинной архитектуре без специалъного перi?Носа. В некото­ рых средах строгое указание дл ины целых чисел может приводить к незначитель­ ному снижению производительности , но это требование абсолютно необходимо для переносимости программ. Рассмотрим каждый из примитивных типов дан­ ных в отдельности. Целые числа Для целых чисел в языкеJаvа определены четыре типа: byte , short, int и long. Все эти типы данных представляют целочисленные значения со знаком: как поло­ жительные, так и отрицател ьные. В Java не поддерживаются только положитель­ ные целочисленные значения без знака. Во многих других языках программиро­ вания поддерживаются целочисленные значения как со знаком, так и без знака, но разработчики Jаvа посчитали целочисленные значения без знака ненужными. В частности , они решили, что понятие числовых значений без з1tа1са служило в ос­ новном для того , чтобы обозначить состоя ние старшего бита, определяющего знак целочисленного значения. Как будет показано в гл аве 4, в Java управление состо­ янием старшего бита осуществляется иначе: с помощью специальной операции "сдвига вправо без знака". Благодаря этому отпадает потребность в целочислен­ ном типе дан ных без знака.
Глава 3. Типы данных, переменные и массивы 77 Дл ина целочисленного типа означает не занимаемый объем памяти , а скорее поведение, определяемое им для переменных и выражений данного типа. В испол­ няющей среде J ava может быть использована любая дл ина, при условии , что типы данных ведут себя так , как они объявлены. Как показано в табл. 3 .1, дл ина и диа­ пазон допустимых значений целочисленных типов данных изменяются в широких пределах. Табл ица Э. 1. Дл ина и диапазон до пусти мых значений целочисленных ти пов да нных Наименование Длина в битах Диапазон допустимых эначениА long 64 от -9223372036854775808 до 9223372036854775807 int 32 от -2 147483648 до 2147483647 short 16 от -32768 до 32767 byte 8 ОТ -128 ДО 127 А теперь рассмотрим каждый из целочисленных типов в отдельности . Tи nbyte Наименьшим по дл ине является целочисленный тип byte. Это В-разряд­ ный тип данных со знаком и диапазоном допустимых значений от -128 до 127. Переменные типа byte особенно уд обны для работы с потоками ввода-вывода дан­ ных в сети или файлах. Они уд обны также при манипулирован ии необработанны­ ми двоичными данными, которые могут и не быть непосредственно совместимы с другими встроенными типами дан ных в J ava. Для объявления переменных типа byte служит ключевое слово by te . Например, в следующей строке кода объявляются две переменные Ь и с типа byte: byte Ь, с; Ти п short Тип short представляет 16-разрядные целочисленные значения со знаком в пре­ делах от -32768 до 32767. Этот тип данных применяется вJava реже всех осталь­ ных. Ниже приведены некоторые примеры объявления переменных типа short. short s; short t; Тип int Наиболее часто употребляемым целочисленным типом является int. Это тип 32-разрядных целочисленных значений со знаком в пределах от -2147483648 до 2147483647. Среди прочего переменные типа int зачастую используются для управления циклами и индексирования массивов. На первый взгляд может по­ казаться, что пользоваться ти пом byte или short эффективнее, чем типом int,
78 Часть 1. Язы к Java в тех случаях, когда не требуется более широкий диапазон допустимых значений, предоставляемый последним, но в действительности это не всегда так. Дело в том, что при указании значений типа byte и short в выражениях их тип продвшаетrя к типу int при вычислении выражения. (Под робнее о продвижении типов речь пойдет далее в гл аве.) Поэтому тип int зачасrую оказывается наиболее подходя­ щим для обращения с целочисленными значениями. Ти п long Этот тип 64-разрядных целочисленных значений со знаком уд обен в тех ситу­ ациях, когда дл ины типа int недостаточно для хранения требуемого значения. Диапазон допусти мых значений типа long достаточно велик, что делает его удоб­ ным для обращения с большими целыми числами. Ниже приведен пример про­ граммы, которая вычисляет количество миль, проходимых лучом света за указан­ ное число дней . 11 Вычислить расстояние , проходимо е светом, 11 исполь зуя переме нные типа lonq class Light { puЫic static void maiп (String args []) { int ligh tspeed; long days ; long seconds ; long distance ; // приблизительная скорость света , миль в се кунду lightspeed = 186000; days = 1000; // указать количества дней seconds = days * 24 * 60 * 60 ; // преобразовать в секунды distance = lightspeed * seconds ; // вычислить расстояние System . out .print ("Зa " + days) ; System .out .print (" дней свет пройдет около ") ; System. out . println (distance + " мил ь.") ; Эта программа выводит следующий результат: За 1000 дней свет пройде т около 16070400000000 мил ь . Очевидно, что такой результат не поместился бы в переменной типа int. Числа с плавающей точкой Числа с плавающей точкой, называемые также действителъными числами, ис­ пользуются при вычислении выражений, которые требуют результата с точно­ стью до определенного знака после десятичной то чки. Например, вычисление квадратного корня или трансцендентных функций вроде синуса или косинуса приводит к резул ьтату, который требует применения числового типа с плавающей
Глава 3. Типы данных, переменные и массивы 79 точкой. В Java реализован стандартный (в соответствии с нормой IEEE-754) ряд ти пов и операций над числами с плавающей то чкой. Существуют два числовых типа с плавающей точкой : float и douЫe, которые соответственно представля­ ют числа одинарной и двойной точности . Их дл ина и диапазон допустимых значе­ ний приведены в табл.3.2. Таблица 3.2. Дл ина и диапазон допусти мых знач ений ч исловых типов с пл авающей точ кой Наименование Длина в битах douЫe 64 float 32 Приблиэительнь1А диапазон допустимых эначениii i от 4.9е- 324 до 1.8 е+308 от 1.4 е -045до З.4 е+ОЗ8 Рассмотрим каждый из этих числовых типов с плавающей точкой в отдельности . Ти п float Этот тип определяет числовое значение с пл авающей точкой одинарной точно­ сти , для хранения которого в операти вной памяти требуется 32 бита. В некоторых процессорах обработка числовых значений с плавающей то чкой од инарной точ­ ности выполняется быстрее и требует в два раза меньше памяти , чем обработка аналогичных значений двойной точности . Но если числовые значения сл ишком вел ики или слишком малы, то тип данных float не обеспечивает требуемую точ­ ность вычислений. Этот тип данных удо бен в тех случаях, когда требуется число­ вое значение с дробной частью, но без особой точности . Например, значение типа float может быть удо бно для представления денежных сумм в долларах и центах. Ниже приведен пример объявления переменных типа float. float hi ghtemp, lowtemp; Ти п douЬle Для хранения числовых значений с плавающей точкой двойной точности , как обозначает ключевое слово douЬle, в оперативной памяти требуется 64 бита. На самом деле в некоторых современных процессорах, оптимизирован ных для вы­ полнения математических расчетов с высокой скоростью, обработка значений двойной точности выполняется быстрее , чем обработка значений одинарной точ­ ности. Все трансцендентные математические функции наподобие sin (), cos () и sqrt () возвращают значения типа douЫe. Рациональнее вс его пользоваться типом douЫe, когда требуется сохранять точность многократно повторяющихся вычислений или манипул ировать большими числ ами. Ниже приведен краткий пример программы, где переменные типа douЫe ис­ пользуются для вычисления площади круга. 11 Вычисли ть площадь кру га class Area { puЫ ic static void ma in (String args[] ) { douЫe pi, r, а;
80 Часть 1. Язык Java r = 10.8; pi = 3 .1416; а=pi*r* 11 радиус окружн ости 11 приблизительное значение числа пи r; // вычисли ть площадь круга System. out .println ( 11Плoщaдь круга равна 11 + а) ; Символы Для хранения символов вJava используется тип данных char. Но тем, у кого имеется опыт программирования на С/С++, следует иметь в виду, что тип char в Java не равнозначен типу char в С ил и С++. Есл и в С/С++ тип char является целочисленным и имеет длину 8 бит, то в Java дело обстоит иначе. Для представ­ ления символ ов типа char в Java используется кодировка в Юникод (Unicode) , определя ющем полный набор международных символов на всех известных языках мира. Юникод ун ифицирует десятки наборов символов, в том числе латинский, греческий , арабский алфавит, кириллицу, иврит, японские и тайские иероглифы и многие другие символы. Для хранения этих символов требуется 16 бит, и поэто­ му вjava тип char является 16-разрядн ым. Диапазон допустимых значений этого ти па составляет от О до 65536. Отрицательных значений типа char не существу­ ет. Стандартный набор символов, известный как код ASCII, содержит значения от О до 127, а расширенный 8-разрядный набор символов ISO-Latin- 1 - значения от О до 255. А поскольку язык Jаvа предназначен для написания программ, кото­ рые можно применять во всем мире, употребление в нем кодировки в Юникоде для представления символов вполне обоснованно. Разумеется, пользоваться коди­ ровкой в Юни коде для локализации программ на таких языках, как английский, не­ мецкий, испанский или фран цузский , не совсем эффективно, поскольку для пред­ ставления символов из алфавита этих языков достаточно и 8 бит. Но это та цена, которую приходится платить за переносимость программ в гл обальном масштабе. На заметку! Подробнее о кодировке Юникод см. по адресу http : //www . unicode . org. Использование переменных типа char демонстрируется в следующем примере программы: 11 Продемонстрировать приме нение типа данных char class CharDemo { puЫ ic static vo id ma in (String args[] ) { char chl, ch2 ; chl 88; // код симв ола Х ch2 'У'; System. out .print ("chl и ch2 : 11 ); System. out . println (chl + 11 11 + ch2) ; Эта программа выводит следующий результат: chlиch2:ХУ
Глава З. Типы данных, переменные и массивы 81 Обратите внимание на то , что переменной chl присвоено значение 88, обо­ значающее код ASCII (и Юникод) латинской буквы Х. Как отмечалось ранее , на­ бор символов в коде ASCII зани мает первые 127 значений из набора символов в Юникоде. Поэтому все прежние приемы, применяемые для обращения с симво­ лами в других языках программирования , вполне пригодны и для jаvа. Несмотря на то что тип char предназначен для хранения символов в Юникоде , им можно пользоваться и как целочисленным типом для выполнения арифмети­ ческих операций. Например, он допускает сложение символов или приращение значения символьной переменной. Та кое применение типа char демонстрирует­ ся в следующем примере программы: // Симв оль ные переменные ведут себя как целочисле нные значения class Cha rDemo 2 { puЫ ic static void ma iп (St riпg arg s[] ) { char chl; chl"' 'Х'; System.out . priпtlп ("ch l содержит "+chl) ; ch l++; // увеличи ть на единицу значение переменной chl System.out . priпtlп ("ch l теперь содержит " + ch l) ; Эта программа выводит следующий результат: chl содержит Х chl теперь содержит У Сначала в данном примере программы переменной ch 1 присваивается значе­ ние символа Х, а затем происходит приращение значения этой переменной , т. е. его увеличение на единицу. В итоге в переменной chl остается значение символа У - следующего по порядку в наборе символов в коде ASCII (и Юн и коде) . На заметку! В формальной спецификации Java ти п cha r упоминается как целочисленный, а это означает, что он относится к той же общей категории, что и типы int, short, long и byte. Но поскол ьку основное назначение типа cha r - предста влять символ ы в Юн икоде, то он относится к собственной отдел ьной категории. Логические значения В языке jаvа имеется примитивный тип boolean, предназначенный для хране­ ния логических значений. Переменные этого типа могут принимать только одно из двух возможных значений: true (истинное) или false (ложное). Значения это­ го логического типа возвращаются из всех операций сравнения вроде а < Ь. Тип boolean обязателен для употребления и в условных выражениях, которые управля­ ют такими операторами , как if и for. В следующем примере программы демон­ стрируется применение логического типа boolean: // Продемон стрировать приме нение значе ний типа Ьoolean class BoolTest { puЫic static void ma in (String args[] ) (
82 Часть 1. Язы к Java boolean Ь; Ь = false; System. out .println ("b равно "+Ь) ; Ь = true; System.out . println ("b равно "+Ь) ; 11 значение типа boolean может управлять оператором if if (Ь) System.out . println ("Э тoт код выполняется ."); Ь = false; if (b) System.out . println ( "Этoт код не выполн яется ."); 11 резуль тат сравнения - значение типа boolean System.out.println("lO > 9 равно " + (10 > 9)) ; Эта программа выводит следующий результат: Ь равно f alse Ь равно true Этот код выполняе тся . 10 > 9 равно true В приведенном выше примере программы особое внимание обращают на себя три момента. Во-первых, при выводе значения типа boolean с помощью мето­ да println () на экране, как видите , появляется слово "true " или "false". Во­ вторых , одного лишь значения переменной типа boolean оказывается достаточ­ но для управления условным оператором i f. Записывать условный оператор i f так, как показано ниже , совсем не обязател ьно. if(Ь == true) ... И в-третьих , результатом выполнения операции сравнения вроде < является логическое значение типа boolean. Именно поэтому выражение l О > 9 приводит к выводу слова "true ". Более того, выражение 10 > 9 должно быть заключено в дополнительный ряд круглых скобок, поскольку операция + имеет более высо­ кую степень предшествован ия , чем операция >. Подробнее о литералах Литералы были лишь упомянуты в гл аве 2. А теперь, когда формально описаны встроенные примитивные типы дан ных, настало время рассмотреть лите ралы бо­ лее подробно. Цел о численные литералы Целочисленный тип едва ли не чаще всего используется в обычной програм­ ме. Любое целочисленное значение является числовым литералом. Примерами тому могут служить значения 1, 2, 3 и 42. Все они являются десятичными значе­ ниями , описывающими числа по основанию 10. В целочисленных литералах мо­ гут использоваться числа по еще двум основаниям: восьмери'Чнъtе (по основан ию 8) и шестнадцатери'Чнъtе (по основанию 16). В Java восьмеричные значения обозна-
Гл ава З . Ти пы данных, переме нные и массивы 83 чаются начальным нулем, тогда как обычные десятичные числа не моrут содер­ жать начальный нуль. Та ким образом, вполне допусти мое, казалось бы, значение 09 приведет к ошибке компиляции, поскольку значение 9 выходит за пределы допусти мых восьмеричных значений от О до 7. Программисты часто пользуют­ ся шестнадцатеричным представлением чисел, которое соответствует словам по длине 8, 16, 32 и 64 бит, состоящим из 8-разрядных блоков. Значения шест­ надцатеричных констант обозначают начальным нул ем и символом х (Ох или ОХ). Шестнадцатеричные цифры должны указываться в пределах от О до 15, поэтому цифры от 10 до 15 заменяют буквами от А до F (или от а до :f). Целочисленные литералы создают значение типа int, которое в Java является 32-битовым целочисленным значением. ЯзыкJava - сгрого типизированный, и в свя­ зи с этим может возникнуть вопрос: каким образом присвоить целочисленный лите­ рал одному из других целочисленных типов данных вJava, например Ьуtе или long, не приводя к ошибке несоответствия типов? К счастью, ответить на этот вопрос нетруд­ но. Когда значение литерала присваивается переменной типа byte или short, оши&­ ки не происходит, если значение литерала находится в диапазоне допустимых значе­ ний данного типа. Кроме того, целочисленный литерал всегда можно присвоить пе­ ременной типа long. Но чтобы обозначить литерал типа long, придется явно указать компилятору, что значение литерала имеет этот тип. Для этого литерал дополняется строчной или прописной буквой L. Например, значение Ox7:f:f:f:f:f:f:f:f:f:f:ffffL или 922337203685477 5807L является наибольшим литералом типа long. Целочисленное значение типа long можно также присвоить переменной типа char, если оно нахо­ дится в пределах допустимых значений данного типа. Начиная с версии JDK 7 целочисленные литералы можно определить и в дво­ ичной форме. Для этого перед присваиваемым значением указывается префикс ОЬ или ОВ. Например, в следующей строке кода десятичное значение 10 определя­ ется с помощью двоичного литерала: int х = ОЫОlО; Наличие двоичных литералов облегчает также ввод значений, используемых в качестве битовых масок. В таком случае десятичное (или шестнадцатеричное) представление значения внешне не передает его назначение, тогда как двоичный литерал передает его. Начиная с версии JDK 7 в обозначен ии целочисленных литералов можно так­ же указывать один знак подчеркивания или более. Это облегчает чтение крупных целочисленных литералов. При компиляции символы подчеркивания в литерале игнорируются. Например , в строке кода int х = 123_ 456_789; переменной х присваивается значение 123456789, а знаки подчеркивания игнориру­ ются. Эти знаки моrут использоваться только для разделения цифр. Их нельзя указы­ вать в начале или в конце литерала, но вполне допустимо между двумя цифрами, при­ чем не один, а несколько. Например, следующая строка кода считается правильной: int х = 123 4 56_789; Пользоваться знаками подчеркивания в целоч исленных литералах особенно уд обно при указан ии в прикладном коде таких элементов, как номера телефонов,
8' Часть1.ЯэыкJava иде нтификационные номера клиентов, номера частей и т. д . Они также полезны для визуальных группировок при определении двоичных литералов. Например, двоичные значения зачастую визуал ьно группируются в бл оки по четыре цифры: int х = оы 101_ 0101_0 00 1_1 010; Л итералы с плавающей точкой Числа с пл авающей точкой представляют десятичные значения с дробной ча­ стью. Они могут быть выражены в стандартной или экспоненциальной (или на­ учной) форме записи. Число в стаидартиай форме записи состоит из целого числа с последующей десятичной точкой и дробной частью. Например, значения 2.О, 3 .14159 и О.6667 представляют допусти мые числа с пл авающей точкой в стан­ дартной записи. В жсп01tенциа.лънай форме записи ис пол ьзуется стандартная форма за писи чисел с плавающей точкой, дополненная суффиксом, обозначающим сте­ пень числа 10, на которую следует ум ножить данное число. Для указан ия экспо­ ненциальной части в данной форме записи используется символ Е или е, за кото­ рым следует десятичное число (положительное или отрицательное). Примерами такой формы записи могут служить значения б.022Е23, 314159Е-05 и 2е+100. По умолчанию в Java литералам с плавающей точкой присваивается тип dou­ Ыe. Чтобы указать литерал типа float, его следует дополнить символом F или f. Литерал типа douЫ e можно также указать явно, дополнив символом D или d. Но это , конечно, из.лишне. Значения используемого по умолчанию типа douЫe зани­ мают в оперативной памяти 64 бита, тогда как для хранения значений более корот­ кого типа float требуется только 32 бита. Шестнадцатеричные литералы с плавающей точкой также поддерживают­ ся, но они применяются редко. Они должны быть записаны в форме, подобной экспоненциальному представлению, но с обозначением Р или р вместо Е или е. Например, Ох12 . 2Р2 вполне допустимый шестнадцатеричный литерал с плаваю­ щей точ кой. Значение после буквы Р называется двои'Чнъ�м порядхом и обознача­ ет степень числа два, на которое умножается заданное число . Поэтому литерал Ох12 . 2Р2 представляет число 72 , 5. Начиная с верс ии JDK 7 в литералы с плавающей то чкой можно вводить один знак подчеркивания или больше, подобно описанному выше в отношении цело­ численных лите ралов. Знаки подчеркивания облегчают чтение больших литера­ лов с пл авающей точкой. При компиляции символы подчеркивания игнорируют­ ся. Напр имер, в следующей строке кода: douЫe num = 9 _423_4 9 7_8 62 .0; переменной num присваивается значение 94234 97862 , О. Знаки подчеркива­ ния будут проигнорированы. Как и в целочисленных литералах, знаки подчерки­ вания могут служить только для разделения цифр. Их нельзя указывать в начале или в конце литералов, но вполне допустимо между двумя цифрами, причем не один, а несколько . Знаки подчеркивания допустимо также указывать в дробной ча­ сти числа. Например , следующая строка кода считается правильной: douЫe num = 9_423_497 . 1_0_9; В дан ном случае дробная часть составляет . 109.
Глава 3. Типы данных, переменные и массивы 85 Л огичес кие литерал ы Эти литералы очень просты. Тип Ьооl ean может иметь только два логических зна­ чения: true и false. Эти значения не преобразуются ни в одно из числовых представ­ лений. BJava логический литерал true не равен 1, а литерал false - О . Логические литералы в Java могут присваиваться только тем переменным, которые объявлены как boolean, или употребляться в выражениях с логическими операциями. Си м вольны е литерал ы В Java символы представляют собой индексы из набора символов в Юникоде. Это 16-разрядные значения, которые могут быть преобразованы в целые значения и над которым можно выполнять такие целочисленные операции, как сложение и вычитание. Символ ьные литералы заключаются в оди нарные кавычки. Все ото­ бражаемые символы в коде ASCII можно вводить непосредственно, заключая их в оди нарные кавычки , например ' а ' , ' z ' и '@'.Если символы нельзя ввести непо­ средственно, то для их ввода можно воспользоваться рядом управляющих после­ довательностей, кото рые позволяют вводить нужные символы (например, символ оди нарной кавычки как '\" или символ новой строки как '\n'). Существует так­ же механизм для непосредственного ввода значения символа в восьмеричной или шестнадцатеричной форме. Для ввода значений в восьмеричной форме служит символ обратной косой черты , за которым следует трехзначное число. Например , последовател ьность символов ' \ 141 ' равнозначна букве ' а ' . Для ввода значе­ ний в шестнадцатеричной форме применяются символы обратной косой черты и u (\u), а вслед за ними следуют четыре шестнадцатеричные цифры. Например , литерал '\u0061 ' представляет букву ' а ' из набора символов ISO-Lati n- 1, по­ скольку стар ший байт является нулевым, а литерал 'ua432 ' - символ японской катака ны. Управля ющие последовательности символ ов перечислен ы в табл. 3.3. Таблица 3.3. Управл я ющие последовательн ости символов Управляющая последовательность Описание \dd d Восьмеричный символ ( dd d ) \ux xx x Шестнадцатеричный символ в Юникоде ( хх хх ) \' Одинарная кавычка \" Двойная кавычка \\ Обратная косая ч ерта \r Возврат каретки \n Новая строка (или пере вод строки ) \f Подача ст раницы \t Табуляция \Ь Возврат на одну позицию ( "забой")
86 Часть 1. Яэык Java Строковые лите рал ы Строковые литералы обозначаются вJava таким же образом, как и в других язы­ ках программирования. С этой целью последовательность символов заключается в двойные кавычки. Ниже приведены некоторые примеры строковых литералов. "Hello World" "two \nlines" "\"This is in quot es\"" Уп равляющие символы и восьмеричная или шестнадцатеричная форма записи, определе нные для символьн ых литералов, действуют точно так же и в строковых литералах. Однако в Java символ ьные строки должны начинаться и оканчиваться в одной строке. В отличие от других языков программирования, вJ ava отсугствует специальный управляющий символ для продолжения строки. На заметку! Как вам, должно быть, известно, в некоторых языках, включая С/С++, символь­ ные строки реал изованы в виде массивов символов. Но совсем иначе дело состоит в Java. На самом деле символьные строки предста вляют собой объектн ые ти пы. Как поясняется далее, в Java символьные строки реал изованы в виде объектов, поэтому в этом языке пре­ доста вляется цел ый ряд эффективных и простых в использова нии средств для их обработки. П е р еменные Переменная служит основной единицей хранения данных в программе нajava. Переменная определяется в виде сочетания идентификатора, типа и необязатель­ ного начального значения. Кроме того , у всех переменных имеется своя область действия, которая определяет их доступность для других объектов и продолжи­ тельность существован ия. Все эти составляющие переменных будуг рассмотрены в пос_ледующих разделах. Объя вление переменной ВJava все переменные должны быть объявлены до их использования . Основная форма объявления переменных выглядит следую щим образом: !1'JID •ден'l!•Ф•жа.'l!ор [=.эна. чеюrе] [, .-ден'l!Ж"Жа 'l!ор [=.эна чеи.е] ...] ; где параметр тип обозначает один из примитивных ти пов данных вJ ava, имя класса или интерфейса. (Типы классов и интерфейсов рассматриваются в последующих гл авах этой части .) А ид ентифика тор - это имя переменной. Любой переменной можно присвоить начальное значение (инициализировать ее) через знак равен­ ства. Следует, однако, иметь в виду, что инициализирующее выражение должно возвращать значение того же самого (или совместимого) типа, что и у перемен­ ной . Для объявления нескольких переменных указанного типа можно воспользо­ ваться списком, разделяемым запятыми. Ниже приведен ряд примеров объявления переменных разли чных типов. Обратите внимание на то , что в некоторых примерах переменные не только объ­ являютс я, но и инициализируются.
intа,Ь,с; intd=3,е,f bytez=22; 11 5; // douЫ e pi = 3.14159; 11 11 11 11 char х = 'х'; 11 Глава З. Типы данных, переменные и массивы 87 объявление трех переменных а, Ь и с типа int объя вление еще трех переменных типа int с инициали зацией переме нных d и f инициализация переменной z объя вле ние переменной pi и ее инициализ ация приблизительным значе нием числа пи присваивание симв ола •х• переменной х Выбранные имена идентификаторов очень просты и указывают их тип. В язы­ ке Jаvа допускается наличие любого объявленного типа в каком угодно правильно оформленном идентификаторе. Динамичес ка я инициализация В приведенных ранее примерах в качестве начальных значений были исполь­ зованы только константы , но в Java допускается и динамическая инициализация переменных с помощью любого выражения , действительного в момент объявления переменной. В качестве примера ниже приведена краткая программа, в которой длина гипотенузы прямоугольного треугольника вычисляется по д.лине его катето в. // В этом примере демонстрируе тся дин амическая инициализация class Dyninit { puЫ ic static voi d ma in (String args [] ) { douЫeа=3.0,Ь=4.0; // динамическая инициализация переменной с douЫe с=Math.sqrt (a *а+Ь * Ь); System.out . println ( "Гипoтeнyзa равна "+с) ; В данном примере программы объявляются три локальные переменные - а, Ь и с. Две первые из них ( а и Ь) инициализируются константами, тогда как третья (с) инициализируется динамически , принимая значение д.лины гипотенузы , вы­ числяемое по тео реме Пифагора. Для вычисления квадратного корня аргумента в этой программе вызывается встроенный вjava метод sqrt () , который является членом класса Ма th. Самое гл авное в данном примере, что в инициализирующем выражении можно использовать любые эл ементы , действительные во время ини­ циализации , в том числе вызовы методов, другие переменные или константы . Область и срок действия переменных В представленных до сих пор примерах программ все переменные объявлялись в начале метода ma in (). Н о вjava допускается объявление переменных в любом блоке кода. Как пояснялось в гл аве 2, блок кода заключается в фигурные скобки , задавая тем самым областъ действия. Та ким образом, при открытии каждого нового блока кода создается новая область де йствия. Область действия определяет, какие именно объекты доступны д.ля других частей программы. Она опреде_л яет также продолжител ьность существования этих объектов. Во многих других языках программирования различаются две основные катего­ рии области действия: гл обальная и локальная . Но эти традиционные области дей-
88 Ч асть 1. Яэь1к Java ствия не вполне вписываются в строгую объектно-ориентированную модель Jаvа. Несмотря на возможность задать гл обальную область действия, в настоящее время такой подход является скорее исключением, чем правилом. Две основные области действия в Java определяются классом и методом, хотя такое их разделение не­ сколько искусственно. Но такое разделение имеет определен ный смысл , посколь­ ку область действия класса обладает рядом характерных особенностей и свойств, не распространяющихся на область действия метода. Вследствие этих отличий рассмотрение области действия класса (и объявляемых в нем переменных) будет отложено до гл авы 6, в которой описываются классы. А пока рассмотрим только те области действия, которые определяются самим методом ил и в его теле. Область действия, определяемая методом, начинается с его открывающей фи­ гур ной скобки . Но если у метода имеются параметры, то они также включаются в область действия метода. Подробнее параметры методов рассматриваются в гл а­ ве 6, а до тех пор достаточно сказать, что они действуют точно так же, как и любая другая переменная в методе. Как правило, переменные, объявленные в области действия , не доступны из кода за пределами этой области. Та ким образом, объявление переменной в обла­ сти действия обеспечивает ее локальность и защиту от несанкционированного до­ ступа и/или внешних изменений. В действительности правила фун кционирова­ ния обработки области действия составляют основу инкапсуляции. Области действия могут быть вложенными . Та к, вместе с каждым блоком кода, по существу, создается новая , вложенная область действия. В таком случае внеш­ няя область действия включает в себя внутреннюю область. Это означает, что объекты, объявленные во внешней области действия, будут доступны для кода из внутренней области действия , но не наоборот. Объекты, объявленные во внутрен­ ней области действия , будут недоступны за ее пределами. Чтобы стало понятнее, каким образо м фун кционируют вложенные области действия, рассмотрим следу­ ющий пример программы: 11 Продемонстрировать область действия блока кода class Scope { puЫ ic static void main (String args[] ) { int х ; // эта переменная доступна всему коду из ме тода ll l&i n() х=10; if(x == 10) // начало новой области действия , } int у= 20; 11 доступной только этому блоку кода 11 обе переменные х и у доступны в этой области действия System. out . println ("x и у: "+х +" "+у ); х=у*2; 11 у= 100; // ОШИБКА ! переменная у недоступна 11 в этой области действия , тогда как 11 переменная х доступна и эдесь System.out . println ("x равно " + х) ; Как следует из комментариев к данной программе, переменная х объявлена в начале области действия метода ma i n ( ) и доступна всему последующему коду из
Глава 3 . Типы да нных, переменные и м ассивы 89 этого метода. Объявление переменной у делается в блоке кода усл овного операт<r ра i f. А поскольку этот блок кода задает область действия, переменная у доступна только коду из этого блока. Именно поэтому закомментирована строка кода у = 1 О О;, находящаяся за пределами этого блока. Если удалить символы комментария, это приведет к ошибке во время компиляции, поскольку переменная у недоступна за пределами своего блока кода. А к переменной х можно обращаться из блока условного оператора i f, поскольку коду в этом блоке ( т. е . во вложенной области действия) доступны переменные, объявленные в объемлющей области действия. Переменные можно объявлять в любом месте блока кода, но они действитель­ ны только после объявления. Та к, если переменная объявлена в начале метода, она доступна всему коду в теле этого метода. И наоборот, если переменная объяв­ лена в ко нце блока кода, она, по существу, бесполезна, так как вообще недоступна для кода. Например, следующий фрагмент кода ошибочен , поскольку переменной count нельзя пользоваться до ее объявления: // Этот фрагмент кода написан неверно ! couпt = 100; // Переменную count нель зя исполь зовать до ее объявления ! int count ; Следует иметь в виду еще одну важную особенность: переменные создаются при входе в их область действия и уничтожаются при выходе из нее. Это означает, что переменная утратит свое значение сразу же после выхода из ее области действия. Следовательно, переменные, объявленные в теле метода, не будут хранить свои значения в промежутках между последовательными обращениями к этому методу. Кроме того , переменная, объявленная в блоке кода, утратит свое значение после выхода из него . Та ки м образо м, срок действия переменной ограничивается ее об­ ластью действия. Если объявление переменной включает в себя ее инициализацию, то иници­ ализация переменной будет повторяться при каждом вхождении в блок кода, где она объявлена. Рассмотрим в качестве примера приведенную ниже программу. // Продемонстрировать срок действия переме нной cla ss LifeTime { puЫ ic static void ma in (String args[]) { int х; for(x=О;х<3;х++){ int у= -1; 11 переменная у инициализируется при // каждом вхождении в блок кода System.out . println ("y равно : "+у); // здесь все гда // выв оди тся значение -1 у=100; System. out . println ("y теперь равно : "+у ); Эта программа выводит следующий результат: у равно: -1 у теперь равно : 100 у равно: -1 у теперь равно : 100
90 Часть 1. Я:1ык Java у равно: -1 у теперь равно : 100 Как видите, переме11ЮU1· у повrорно инициализируется значением -1 при каж­ дом вхождении ·во виуrре11 11 ий цикл for. И хотя перемен ной у впоследствии при­ сваивается значение 1()09 теине менее оно теряется. ,, '· И последнее: несмотря на 'l'O, что.блоки могут быть вложенными, во внутрен­ нем блоке кода Не.JIЬ3Я· объsвлать переменные с тем же именем, что и во внешней о6J1асти действия. Наоример1 следующая программа ошибочна: з�у 'ripoPpaмwy непь sя 1 v.o id rnc;1in (St riцg. args [] } 1; . . . #'· С!ФWПипировать с.Wз з ScopeErr { puЫ ic static int Ьаr = { 11 создается новая область действия int . Ьar �. 21 11 QluиOкa во время компил�ции - )/ переменная bar уже определена ! Преобраэо вание ,и1nриведение ти п о в Те м, у �ого �М(етОI щ�кOТQpi.UI опыт прог раммирования , должно 1$ыть ИЗВfjСТ­ JЩ " ЧТО nepeмeJJН.Qjt P.дl!QfQ типа, нередко приходится присваивать 3НfiЧение ДР}"' щrо ..-�па. ЕСди оЩ, jl,lfffiJ,AflнtlЫX со.вмес"I:имы, их преобразование будет вi,moлнe­ � J!ljava авт,о�ат,н;че�1'1J, .J;l�ример, значен ие типа int всегда можно присвоить 11 1 ременной .тиnа J,. охщ. ., Цо.11Q �. типы данных совместимы, а, следовательно, не ,вс;е преобразован ия типов разрешены неявно. Н апример, не существует каюго­ �1 рпредеде!JJJЩ'Q; сщ�;rиJ1ес1tого преобразования типа douЫe в тип byte. t;Jp�. прецбр�щ1.а�щ.� �t;совм�тимыми ти пами вы полнять все-ТСЦGI мож­ но. Дд.я. ЭТQЙ цели! �т .��е�ще т.иnов, при котором вьmолняется яцное пре­ образование несовместимых типов. Рассмотрим автоматическое преобразование и приведение типов. Авто мати ческое преобразован и е ти пов в Java Когда данные одКОго тиnа nрисваиваются переменной другого ти па, выполня­ ется автомати-ческое nреобразоеан:и.е типов, если уд овлетворяются два условия: • оба типа совместимы; • длина целевого типа больше длин ы исходного типа. При соблюдении этих услови й выполняется расширяющее преобразование. Например, тип данных int всегда достаточно велик, чтобы хранить все допусти­ мые значения типа byte, поэто му никакие операторы явного приведения ти пов в данном случае не требуются. С точки зрения расширяющего преобразования числовые типы данных, в том числе целочисленные и с плавающей точкой, совместимы друг с другом. В то же
Глава 3 . Типы да нных , переменные и массивы 91 время не существует автоматических преобразований числовых типов в тип char или boolean. Типы char и boolean также не совмести мы друг с другом. Как упо ми­ налось ранее, автоматическое преобразование типов выполняется в Java при сохра­ нении целочисленной константы в переменных типа byte, short, long или cha r. П рив еде ние нес овместимых типов Несмотря на все уд обство автоматического преобразования типов , оно не в со­ стоя нии уд овлетворить все насущные потребности. Например , что делать, если значение типа int нужно присвоить переменной типа byte? Это преобразование не будет выполняться автоматически , поскольку дл ина типа byte меньше, чем у типа int. Иногда этот вид преобразования называется су жающим пр еобразовани­ ем, поскольку значение явно сужается, чтобы уместиться в целевом типе данных. Чтобы выполнить преобразование двух несовместимых типов дан ных, нужно воспользоваться приведен ием типов. Пр иведение - это всего лишь явное преобра­ зование ти пов. Общая форма приведения ти пов имеет следующий вид: (це.певоJi_ !DШ) .sнaveюre где параметр целевой_ тип обозначает тип, в который нужно преобразовать ука­ занное значение. Например, в следующем фрагменте кода тип int приводится к типу byte. Если значение целочисленного типа больше допустимого диапазона значений типа byte, оно будет сведено к результату деления по модулю (остатку от целочисленного деления) на диапазон типа byte. int а; byte Ь; 11 ... Ь = (byte) а; При присваи ван ии значения с пл авающей точкой переменной целочисленно­ го ти па выполняется другой вид преобразования типов - усечение. Как известно, целые числа не содержат дробной части . Та ким образом, когда значение с плаваю­ щей точкой присваивается переменной целочисленного типа, его дробная часть отбрасывается. Та к, если значение 1,23 присваивается целочисленной перемен­ ной, то в конечном итоге в ней остается только значение 1, а дробная часть О,23 усекается . Ко нечно, если длина целочисленной части числового значения слиш­ ком велика, чтобы уместиться в целевом целочисле нном типе, это значение будет сокращено до результата деления по модулю на диапазон целевого типа. В следующем примере программы демонстрируется ряд преобразован ий ти­ пов, которые требуют их приведения. 11 Продемонстрировать приведение типов class Coпversion { puЫ ic static void ma in (Striпg args[] ) byte Ь; iпti=257; douЫe d = 323 .142; System. out . println ("\nПpeoбpaзoвaниe типа int в тип byte.") ; Ь = (byte) i; System.out . println{"i и Ь"+ i +""+ Ь);
92 Часть 1. Язы к Java System.out . println ("\nПpeoбpaзoвaниe типа douЫ e в тип int.") ; i=(int)d; System.out . println ("d и i"+ d +""+ i) ; System.out . println ("\nПpeoбpaзoвaниe типа douЫe в тип byte.") ; Ь = (byte) d; System.out . println ("d и Ь"+ d +" "+Ь) ; Эта программа выводит следующий результат: Преобразование типа int в тип byte . iиь2571 Преобразо вание типа douЫ e в тип int . d и i 323.142 323 Преобразование типа douЫ e в тип byte . dиь323.14267 Рассмотрим каждое из этих преобразований. Когда значение 257 приводится к типу byte, его результатом будет остаток от деления 257 на 256 (диапазон допу­ стимых значений типа byte) , который в данном случае равен 1 . А когда значение переменной d преобразуется в тип i nt, его дробная часть отбрасывается. И когда значение переменной d преобразуется в тип byte, его дробная часть отбрасыва­ ется и значение уменьшается до результата деления по модулю на 256, который в данном случае равен 67. Автоматич е с ко е прод виже ние типов в выражени ях Помимо операций присваивания, определенное преобразование типов может выполняться и в выражениях. Это может происходить в тех случаях, когда тре­ бующаяся точность промежуточного значения выходит за пределы допустимого диапазона значений любого из операндов в выражении. В качестве примера рас­ смотр им следующий фрагмент кода: byte а 40; byteЬ=50; byte с = 100; intd=а*Ь/с; Результат вычисления промежуточного чл ена а * Ь вполне может выйти за пределы диапазона допусти мых значений его операндов типа byte. Для разреше­ ния подобных затруднений при вычислении выражений в Java тип каждого опе­ ранда byte, short или char автомати чески продвигается к типу int. Это означа­ ет, что вычисление промежуточного выражения а * Ь выполняется с помощью цело численных, а не байтовых значений. Поэтому результат 2000 вычисления промежуточного выражения 50 * 40 оказывается вполне допусти мым, несмотря на то, что оба операнда - а и Ь - объявлены как относящиеся к типу byte.
Глава 3. Типы данных, переменные и массивы 93 Несмотря на все уд обства автоматического продвижения типов, оно может приводить к досадным ошибкам во время компиляции. Например, следующий правильный на первый взгляд код приводит к ошибке: byteЬ=50; Ь = Ь * 2; 11 ОШИБКА! Значение типа int не может быть присвоено 11 переменной типа byte ! В этом фрагменте кода предпринимается попытка сохранить произведение 5 0 * 2 (вполне допустимое значение типа byte) в переменной типа byte. Но поскольку во время вычисления этого выражения тип операндов автоматиче­ ски продвигается к типу int, то и тип результата также продвигается к типу int. Так им образом, результат вычисления данного выражения относится к типу int и не может быть присвоен переменной типа byte без приведения ти пов. Это спра­ ведливо даже в том случае, когда присваиваемое значение умещается в перемен­ ной целевого типа, как в данном кон кретн ом примере. В тех случаях, когда последствия переполнения очевидны, следует использо­ вать явное приведение ти пов. Та к, в следующем примере кода переменной Ь при­ сваивается правильное значение 100 благодаря приведению типов: byteЬ=50; Ь= (byte)(Ь*2); Правила продвижения ти п ов В языке Java определен ряд правил продвижения типов, применяемых к выра­ жениям. Сначала все значения типа byte, short и char продвигаются к типу int, как пояснялось выше. Затем тип всего выражения продвигается к типу long, если один из его операндов отн осится к типу long. Если же один из операндов относит­ ся к типу float, то тип всего выражения продвигается к типу float. А если любой из операндов относ ится к типу douЫ e, то и результат вычисления всего выраже­ ния ОТНОСИТСЯ к типу douЫe. В следующем примере программы демонстрируется продвижение типа одного из операндов для соответствия типу второго операнда в каждом операторе с двумя операндами: class Promo te ( puЫ ic static void maiп (Striпg args[] ) ( byteЬ=42; char с= 'а'; short s = 1024; iпt i = 50000; float f = 5 .67f; douЫe d = .1234; douЫeresult=(f*Ь)+(i/с)-(d*s); System.out.println((f * Ь) + "+"+ (i/ с) + " - " + (d* s)); System. out .println ("r esult = " + result ); Проанализируем порядок продвижения типов в следующей строке коде из дан­ ного примера: douЫeresult=(f*Ь)+(i/с)-(d*s);
94 Часть 1. Язы к Java В нсрво:v1 нромежуточном выражении f * Ь тин 11еремешюй Ь продвигаетс я к тину float, а резул 1,тат вычисления этого выражения также опюппся к тину f 1 оа t. В следующем нромежуточном выражении i /с тин переменной с продвига­ ется к ти пу int, а реаул ьтат вычислен ия эт01·0 выражения относ ится к тину int. :Затем в промежуточном выражении d * s ти н 11еремс11ноii s продви га t:тс я к ти пу dо11Ы е, а ре:�ул нгат его в ы числения относится к ти пу douЫe. И 11аконе1\, выпол­ няются операции с эти ми тремя 11ромежуто чпы:vш резулы'Ирующими аначениями ти па float, int и douЫe. Резу;шгат сложения значений типа float и int. отно­ сится к ти ну f loa t. :Затем тип разности (' уммарного :шачения тина f loat. и 11ослед­ не1·0 аначения тина douЫ e продвигается к тину double, который и становится око11чатслы1ым типом реаул ьтата вычисл ения выражения в 11елом. Массивы А1rипю - это груш �а од1ют11 1шых неременных, для обращения к которым испою,ау­ ется общее имя. ВJava допускается соаданис масс ивов любого тина и pa:шoii размерно­ сти. Jl.осту н к конкретному элеме1пу масси ва оср 1 1ествляется по его индексу. Маспшы предоставлякп уд обный способ группирования свя:1а111юй вместе информации. На заметку! Те, кто знаком с языками С/С++, должн ы быть особенно внимательны. Ведь в Java массивы действуют иначе, чем в этих языках. Одномерные массивы По существу, од-номерн:ые ;и,асrивы нредставляют собой список од 1юти 1111ых пере­ менных. Чтобы со:щап, массив, нужно снач;u1а объявит�. 11ереме11 11ую массива тре­ буемого типа. Общая форма объя вления 0;11юмеrшого 1\·�ассива выглядит следую­ щим образом: тип нмя_п еремен н ой[] ; где параметр тип обозначает тип эл емента масоша , на:3ываемы й также ба:ювым тю юм. Ти н :-:1 лемента массива определ яет ти н 11а1111ых ю1 ж;101·0 и:1 эл ементов, со­ ставляющих маспш. Та ким обра:юм, тип эл емента масс ива 011 ре;1еляет тин да н­ ных, которые будет содержать массив. Например, в сле;1укяцей строке кода объ­ явл н ето1 массив mon th days, состоящий из эл ементов типа in t: int month days []; Нес мотря на то что в :-:1 той строке кода утверждается , что mo п th cla ys обън в­ ляется как масс ив переменных, 11а самом дел е никако1·0 массива пока е ще н е суще­ ствует. Чтобы свя:�ать имя mo nth_days с фи:шчески существующим массивом це­ лочисленных :шачений, необходимо :1 аре:к·рвирова1ъ область памяти с 110мощ1,ю оператор а new и назначит�, ее дл я массив;� mo nth__ da ys. Более нодробно :-па онера­ ция будет рас смотрена в следующей гл аве, но те перt> она требуется ;1ш1 ВЫ/\елен ин памяти под масс ивы. Общая форма оператора new применительно к од номерным массивам выг:шщ ит следующим обра:юм: перемен н ая_ма сснва = new тип [размер] ;
Глава З. Типы данных, переменные и массивы 95 где параметр тип обозначает тип данных, для которых резервируется память; параметр ра змер - количество элементов в массиве, а параметр переменна я_ ма ссив а означает переменную , непосредственно связанную с массивом. Иными словами, чтобы воспользоваться оператором new для резервирования памяти , придется указать тип и количество элементов, для которых требуется зарезерви­ ровать память. Элементы массива, для которых память была выделена операто­ ром new, будут автоматически инициализированы нулевыми значениями (для чис­ ловых типов) , логическими значениями false (для логического типа) или пусты­ ми значениями nul l (для ссылочных типов, рассматриваемых в следующей главе) . В приведе нном ниже примере резервируется память для 12-элементного массива целых значений, которые связываются с массивом mo nth_days. После выполне­ ния этого оператора переменная массива mon th_da у s будет ссьтаться на масс ив, состоящий из 12 целочисленных значений. При этом все элементы данного масси­ ва будут инициализированы нулевыми значениями: mon th_days = new int[l2] ; Подведем краткий итог: процесс создания массива происходит в два этапа. Во­ первых, следует объявить переменную нужного типа массива. Во-вторых, с помо­ щью оператора new необходимо зарезервировать память для хранения массива и присвоить ее переменной массива. Та ким образом, все массивы в Java распре­ деляются динамически. Если вы еще не знакомы с понятием и процессом дина­ мического рас пределения памяти , не отчаивайтес ь. Этот процесс будет подробно описан в последующих гл авах. Как только будет создан массив и зарезервирована память для него, к конкрет­ ному эл ементу массива можно обращаться , указывая его индекс в квадратных скоб­ ках. Индексы массива начинаются с нуля. Например, в приведенной ниже строке кода значение 28 присваивается второму элементу массива mon th_da у s. mon th_days [l] = 28; А в следующей строке кода выводится значение, хранящееся в элементе масси­ ва по индексу З: System.out . println (month_days[3] ); Для демонстрации процесса в целом ниже приведен пример программы, в ко­ торой создается массив, содержащий количество дней в каждом месяце. 11 Продемонстрировать приме нение одномерного ма ссива class Array { puЫ ic static void ma in (Striпg args [] ) ( int month days[] ; mon th days = new int[12] ; mon th - days [O] 31; mon th - days [l] 28; mon th - days [2] 31; mon th - days [3] 30; mon th - days [4] 31; month - days [5] 30; mon th - days [ 6] 31; month - days [7] 31; month - days [8] 30; month=days [9] 31;
96 Часть 1. Язы к Java month days [lO] = 30; mo nth - days [ll] = 31; System .out . print ln ("B апреле "+month_days [3] + " дней.") ; Если выполнить эту программу, она выведет на экран количество дней в апреле. Как упоминалось ранее, вJava индексация эл ементов массивов начинается с нуля, поэтому количество дней в апреле хранится в элементе массива mon th_days [3] и равно 30. Объявление переменной массива можно объединять с выделением для него па­ мяти: int month_days [] = new int[l 2] ; Именно так обычно и поступают в программах, профессионально написанных на Java. Массивы можно инициализировать при их объявлении. Этот процесс во многом аналогичен инициализации простых типов. Инициализатор массива - это список выражений, разделяемый запятыми и заключаемый в фигурные скобки. Запятые разделяют значения элементов массива. Массив автоматически создается настолько большим, чтобы вмещать все элементы , указанные в инициализаторе массива. В этом случае потребность в операторе new отпадает. Например, чтобы сохранить кол ичество дней в каждом меся це, можно воспользоваться приведен­ ным ниже кодо м, где создается и инициализируется массив целых значений. 11 Усовершенс твованная версия предыдущей программы class Aut oAr ray { puЫic static void ma in (St ring args[] ) { int month days [J = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, - 30, 31}; System. out . println ("B апреле "+month_days [3] + " дней. ") ; При выполнении этого кода на экран выводится такой же результат, как и в пре­ дыдущей его версии. Исполняющая система Jаvа производит тщательную проверку, чтобы убедиться , не была ли случайно предпринята попытка присвоения или об­ ращения к значениям, которые выходят за пределы допустимого диапазона индек­ сов массива. Например, исполняющая система Jаvа будет проверять соответствие значения каждого индекса массива mo n th_da ys допустимому диапазону от О до 11 включительно. Любая попытка обратиться к элементам массива mo nth_days за пределами этого диапазона (т.е . указать отрицательные индексы или же индексы, превышающие длину массива) приведет к ошибке при выпол нении данного кода. Приведем еще один пример программы, в которой используется одномерный массив. В этой программе вычисляется среднее значение ряда чисел. 11 Вычисление средне го из ма ссива значе ний class Ave rage { puЫic static vo id rna in (String args[] ) { douЫe nurns [] {10.1, 11.2, 12.3, 13.4, 14.5} ; douЫ e result = О; int i;
for (i=O; i<5; i++ ) Глав а 3 . Типы да нны х, пер еменные и массивы 97 result = result + nums [i] ; System.out . println ( "Cpeднee значение равно " + result / 5) ; Многомерные массивы В языке Java многомерные массивы представляют собой массивы масс ивов. Нетрудно догадаться , что они внешне выглядят и действуют подобно обычным многоме рным массивам. Но, как будет показано ниже , у них имеется ряд незначи­ тельных отличий. При объявлении переменной многомерного массива для указа­ ния каждого дополн ительного индекса используется отдел ьн ый ряд квадратных скобок. Например, в следующей строке кода объявляется переменная двухмерно­ го массива two D: int twoD[][] = new int[4][5]; В этой строке кода резервируется память для массива размерностью 4х5, ко­ торый присваивается переменной twoD. Эта матрица реализована внутренним образом как массив массивов значений ти па int. Концептуально этот масс ив будет выглядеть так, как показано на рис. 3 . 1. Левый индекс определяет стр оку Правый индекс определяет столбец 1!!!\ [о] [о] [0] [1] [0] [2] [о][з] [о] [4] [1][0] [1][1] [1][2] [1][з] [1][4] [з] [о] [з] [1] [з] [2] [з] [з] [з] [4] Дано: int twoD[] [] • new int[4][5]; Рис. 3.1 . Ко нцептуал ьное предста вл ение массива размерностью 4ic5 В следующем примере программы элементы массива нумеруются слева напра­ во и сверху вниз, а затем выводятся их значения :
98 Часть 1. Язы к Java 11 Продемонстрировать приме нение двухмерного ма ссива class TwoDAr ray { puЬlic static void ma in (String args[]) int twoD [] []= new int [4] [5] ; inti,j,k=О; for (i=O ; i<4 ; i++) for (j=O; j<S; j++) twoD [i] [j] = k; k++; for(i=O; i<4; i++) { for (j=O; j<S; j++) System. out .print (twoD [i] [j] + • " ); System.out . println (); Эта программа выводит следующий результат: о1234 56789 1011121314 1516171819 При резервиро вании памяти под многомерный массив необходимо указать па­ мять только для первого (левого ) измерения массива. А для каждого из остальных измерений массива память можно резервировать отдел ьно. Например, в приве­ де нном ниже фрагменте кода память резервируется только для первого измере­ ния массива twoD при его объявлении. А резервирование памяти для второго из­ мерения массива осуществляется вручную. int twoD[][] = new int[4][]; twoD [O] new int[5] ; twoD [l] new int[5] ; twoD [2] = new int[5] ; twoD [З] = new int[5] ; Если в дан ном примере отдельное резервирование памяти для второго измере­ ния массива не дает никаких преимуществ , то в других случаях это может быть по­ лезно. Например, при резервировании памяти для отдел ьных измерений массива вручную совсем не обязательно резервировать одинаковое количество элементов для каждого измерения. Как отмечалось ранее, программисту предоставляется полная свобода действий управлять длиной каждого массива, поскольку много­ мерные массивы на самом деле представляют собой массивы массивов. Например, в следующем примере программы создается двухмерный массив с разной размер­ ностью второго измерения: 11 Резервирование памяти вручную для ма ссива с разной 11 размерностью второго измерения class TwoDAgain { puЫic static vo id ma in (String args[] ) int twoD[][] = new int[4][]; twoD [O] new int[l] ; twoD [l] new int[2] ; twoD [2] new int[З] ; twoD [З] new int[4] ;
int_i,j,k =О; Глава 3. Типы данных, переменные и массивы 99 for (i=O; i<4; i++) for (j=O; j<i+l; j++) twolJ[i] [j] = k; k++ ; for(i�O; i<4; i++) { for(j=O; j<i +l; j++) Systeш.out . pr·i nt (two lJ [iJ[jJ +"") ; Systeш. out . println (); Эта программа вы водит следующий резул ьтат: о 12 345 6789 Со:щ; нш ый в итоге массив 1ююван на рис. �.2. [о] [о] [1][0] [1][1] [2] [0] [2] [1] [2] [2] [з][о] [з][1] [з] [2] [з] [з] Рис. 3 .2 . Двухмерный массив с разной размерностью второго измерения Применение неоднородн ых (или нерегулярных) масс ивов может был, непри­ емлемо во многих приложt·ниях, rюскоЛI.ку их новедение отличается от обычного поведения многомерных масс и во в. 1 lo в некоторых случаях нерегулярные масси­ вы могут о ка:штьс я веп.ма эффективными. Например, нерегуля рный масп-�в мо­ жет быть иде<U1ы1 ым ре шением, еош требуется очень боЛ I.ш ой двухмерный раз­ реженный масс ив ( т. е . масси в, в котором будут использоваться не вес :-тсменты). Многомерные массивы можно инициализи ровать. Дл я это 1·0 до(·таточно за­ КJ1 юч и·1ъ ИНИI(иализатор каждого и:Jмерения в о·�дел ьн ый ряд фигурных ско бок. В привс;(сшюм ниже нримсрс нрограммы создаетс я матри ца, в кото рой каж;�ый :� лемент содержит щюизве;(ение инде ксов строки и стол бца. Обратите такжt� вн и­ ман ие на то , что в иницшU1 и:iаторах массивон можно нрименяп, к а к литеральное значение, так и вы ражен ия: 1 1 Инициали зиро вать двухмер ный ма ссив class Mat rix {
100 Часть 1. Язы к Java о.о о.о О.О о.о puЫic static void main (St ring args[J) douЫem[1[] ={ }; { О*О, 1*0, 2*0, 3*0), {O*l, 1*1,2*1,3*1}, { 0*2, 1*2, 2*2, 3*2 ), {0*3, 1*3, 2*3, 3*3) int i, j; for (i=O; i<4 ; i++) { for (jc00; j<4; j++) System.out .print. (m[i][j] + " " ); System.out . println (); Эта 11р01·рамма вы1юдит следующий рсзул ьт<�т: о.о о.о о.о l.о 2.0 3.0 2.0 4.0 6.0 3.0 6.0 9.0 Как ви;1ите, каждая строка массива шш1 1и ал и:ш руетсн в соответствии со :шачс­ ниями, уюван ными в списках и11и11иализа11 ш1. Рас смотрим еще од ин нример 11р и­ мене11ия м1ю1·омсрного массив<t. В 11риве;1ешюм ниже нримерс программы п1а­ чала со:щастс я трехмерный массив размср1юстыо Зх4х5, а :ытем каждый :� лемент массива за11ол11ястся 11роизведе11исм его шще ксов, и, нако1 1е11, эти нроизведенин выводятся на экран. 11 Продемонс триров ать применение трехмерного массива class ThreeDM at rix { о о о о о о о puЫ ic static void ma in (St ring arg s[J) 1 int threeD[][][J = new int[3}[4][5} ; inti,j,k; for (i=O; i<3 ; i++) for (j=O; j<4; j++) for (k=O; k<5; k++) threeD [i} [j] [k} for (i=O; i<3 ; i++) { for (j=O; j<4; j++) { for (k=O; k<5; k++) i*j*k; System.out.print(threeD[i}[j][k} + " "); System. out . println () ; System.out . println (); Эта нрогра мма выводит следую щий резуштп: оооо оооо оооо оооо оооо 1234 246в
Глава 3. Типы данных, переменные и массивы 101 о36912 ооооо о2468 о481216 о6121824 Альтернативный с интакс и с объя вления массивов Для объявления массивов можно использовать и вторую , приведенную нижу фор му. !l!Иn [ ] ин.а_перемен н ой; В этой форме квадратные скобки следуют за спецификатором типа, а не за име­ нем переменной массива. Например, следующие два объявления равнозначны: int al[] = new int[З]; int[] а2 = new int[З] ; И оба приведенных ниже объявления также равнозначны. char twodl [] [) = new char [ЗJ [4] ; char[][] twod2 = new char[3][4]; Вторая форма объя вления массивов удо бна для одновременного объя вл ения нескольких массивов. Например, в объявлении int [] nums , nums2 , nums З; // создать три массива создаются три переменные массивов типа i nt. Оно равнозначно приведенному ниже объя влению. int nums [] , nums2 [], nums З [J; // создать три ма ссива Альтернативная форма объявления массивов уд обна также для указания мас­ сива в качестве типа данных, возвращаемого методом. В примерах, приведенных в этой книге, используются обе формы объявления массивов. Введен ие в символьные строки Как вы, вероятно, заметили, при рассмотрении типов данных и массивов не уп оминались символьные строки ил и строковый тип данных. Это объясняется не те м, что строковый тип данных не поддержи вается в Java, а просто те м, что он не относится к примитивным типам данным. Он не является также массивом символов. Символ ьная строка скорее является объектом класса String, и для яс­ ного представления о ней требуется пол ное описание целого ряда характеристик объекто в. Поэтому строковый тип данных будет рассматриваться в последующих гл авах книги после описания объектов. Но для того чтобы использовать простые символ ьные строки в рассматриваемых до этого примерах программ, ниже приво­ дится краткое введение в строковый тип данных. Тип данных Stri ng служит для объя вления строковых переменных, а также ма ссивов символьных строк. Переменной типа String можно присвоить заклю-
102 Часть 1. Язы к Java ченную в кавычки строковую константу. Переменная типа String может быть присвоена другой переменной типа String. Объект класса String можно указы­ вать в качестве аргумента метода println ().Рассмотрим, например, следующий фрагмент кода: String str = "это тестовая строка "; System.out . println (st r) ; В этом примере str обозначает объект класса String. Ему присваивается символьная строка " это тестовая строка " , которую метод println () выводит на экран. Как будет показано далее, объекты класса String обладают многими ха­ рактерными особенностями и свойствами, которые делают их довольно эффек­ ти вными и простыми в употреблении. Но в примерах программ, представленных в ряде последующих гл ав, они будуг применяться только в своей простейшей форме. Замечани е по поводу указателей для программирующих на С/С++ Те м, у кого имеется опыт программирования на С/С++, известно, что в этих языках поддерживаются указател и. Но в этой гл аве они не упоминались просто потому, что в Java указатели не только не применяются , но и не поддерживаются. (Точнее говоря , в Java не поддерживаются указатели, которые доступны и/или могут быть видоизменены программистом.) В языке Java не разрешается исполь­ зовать указател и, поскольку это позволило бы программам на Java преодолевать защитн ый барьер между исполняющей средой Java и тем компьютером, где она действует. (Напомним, что указателю может быть присвоен любой адрес опера­ ти вной памяти - даже те адреса, которые могут находиться за пределами испол­ няющей системыjаvа.) В программах на С/С++ указатели используются довольно интенсивно, поэтому их отсутствие может показаться существенным недостатком Java, но на самом деле это не так. Исполняющая среда Java построена таким об­ разом, чтобы для выполнения всех действий в ней указатели не требовались, а их применение в ее пределах не давало никаких преимуществ.
В языке Java поддерживается обширный ряд операций. Большинство из них может быть отнесено к одной из следующих ч�ре� гр)'IШ: арифметические, по­ разрядные, логические и отношения. ВJava таюкеопределен ряд дополнительных операций для особых случаев. В этой главе описаны все дОС'I)'ПИЫе в Java опера· Ци и, за исключ ением операции сравнения типов instanc�o f, рассматриваемой в гл аве 13, а также новой операции "стрелки" ( ->) , описываемой в главе 15. Арифм етиче с ки е операции Арифметические операции применяются в математНqеских выражениях та­ ким же образом, как и в алгебре. Все арифметические операции, дос'I)'Пные вJava, перечислены в табл. 4 . 1 Табл ица ,, 1. Арифметические операции в Java Операция + • / ' ++ += -= ·= /= \= Описа ние Сложение (а также унарный плюс) Вычитани е (атакже унарный минус) Ум ножение Деление Деление по модулю Инкремент (приращение на 1) Сложение с присваиванием Вычитание с присваиванием Умножение с присваиванием Деление с присваиванием Деление по модулю с присваиванием Де кремент (отрицательное приращение на 1) Операнды арифметических операций должны иметь числовой тип. Арифме­ тические операции н ельзя вьmолнять над логическими типами данных, но допу� надтипамидан ных cha r, поскольку вJava этот тип, по существу, является разновидцО­ стью типа int.
10' Часть 1. Язы к Java Осн овные арифметические операции Все осно вные арифметич еские операции ( сложения , выч итания, умножения и деления) во здействуют на числовые типы данн ых та к, как этого и следовало ожи­ дать. Оп ерация ун ар ного выч итания изменяет знак своего единственного операн­ да. О пе рация унарного сложения просто возвращает значение своего операнда. Следует, однако, иметь в виду, ч то когда о перация дел ения выполняется над цело­ численным типом данных, ее результат не будет содержать дробный компонент. В следующе м примере просто й программы демонстрируется применение арифметических операций . В нем иллюстрируется также отлич ие в операциях де­ ления с плавающе й точкой и целочисленного деления: 11 Продемонстрировать основные арифме тические операции class BasicMath { puЫ ic static void rnaiп (Striпg args[] ) { 11 арифме тические о п ерации над целочисле нными значе ниями Systern.out . println ("Ц eлoчиcлe ннaя арифме тика ") ; intа 1+1; intЬ а*3; intс=Ь/4; intd с-а; int е -d; Systern.out . println ("a Systern.ou t.println ("b Systern.out . println ("c Systern.out . println ("d Systern.out . println ("e "+а); "+Ь); "+с); "+d); "+е); 11 арифме тиче ские операции над значени ями типа douЫe Systern.out . println ("\nApифмe тикa с плавающей точкой") ; douЫeda 1+1; douЫedb da*З; douЫedc db/4; douЫedd dc-а; douЫe de -dd; Systern.out . println ("da Systern. out . println ("db Systern.out . println ("dc Systern.out . println ("dd Systern.out . println ("de " " " " " +da); + db); +dc); + dd); +de); При выпол нен ии это й программы на экране появляется следующий результат: Целочисленная арифме тика а 2 ь6 с 1 d-1 е=1 Арифме тика с плавающе й точкой da 2.0 db 6.0 dc 1.5 dd -0.5 de 0.5
Операция деления по модулю Глава 4. Операции 105 Опера1щя делени я но модулю % возвращает остаток от деления. Эту операцию можно вьшол нять как над •1исловыми типами данных с плавающей то чкой, та к и над целочислешiым ти11ами данных. В следующем примере программы демон­ <·трируется применение операции %: 11 Прод емонстриро вать применение опер ации % class Modulus { puЫ ic static void main (String args ( ]) { iпtх=42; douЫ e у= 42 .25; System.out . println ("x mod 10 System. o ut . println ("y mod 10 "+х' 610) ; "+у%10); Эта про1рам ма вы1юдит следующий ре3ультат: хmod10 2 уmod10=2.25 Составные арифметические операции с присваиванием В .Java и м е ются спсцшu1ьные опе ра ц ии, объеди няющие арифмети ческие опе­ рации с 011ер<щисii присваивания. Как вы, вероятно, знаете, опера1щи В(>О/\е при­ ве;\е шюй ниже встречаются в 11р о гр а ммах достаточно часто . а=а+4; B.Java :ну <шер а�\ и ю мож но ;3а �шсат�, сле;\ующим образом: а+=4; В этой вер с и и исполь:ю вана соопавна.я оперm&ия с прuсваuвание.м. +=. Обе онера­ ции вы110лняют одно и то же действие: увеличивают значение переменной а на 4. Ниже приведен ещеодин пример совместного нрименения арифметической опе­ рании и операции присваишшия. а=а 2; Эту строку к<ща можно нереп исать следующим образом: аi;=2; В данном случае 1 1ри вы1101шении онерации %= вычисляется о статок от деления а/2, а рсэул I>rат ра:1мещается обратно в н е ре мен ной а. Составные операции с при­ сваи ванием существуют для всех арифмети ческих операци й с двумя 011срандами. Таки�� обра:юм, любую онсрацию следующей формы: перемен н ая = перемен н ая операция выражение; м ожно за11исать та к: перемен н ая операция выражение ;
106 Часть 1. Язы к Java Составные операции с присваиванием дают два преимущества. Во-первых , по­ зволяют уменьшить объем вводимого кода, поскольку являются "сокраще нным" вариантом соответствующих дл инных форм. Во-вторых, их реализация в испол­ няющей системе J ava оказывается эффективнее реализации эквивалентных длин­ ных форм. Поэтому в программах, профессионально написанных на Java, состав­ ные операции с присваиван ием встречаются довольно часто . Ниже приведен еще один пример программы, демонстрирующий практическое применение несколь­ ких составных операций с присваиванием. 11 Продемон стриро вать приме нени несколь ких операций с присваиванием class Op Equals { puЫic static void ma in (String args[] ) intа 1; intЬ=2; intс=3; а+=5; ь*=4; с+=а*Ь; с%=6; System.out . println ("a System.out . println ("b System.out . println ("c "+а); " +Ь); "+с); Эта программа выводит следующий результат: а 6 ь8 с=3 О пера ции инкрем ента и декрем ента Операции ++ и -- выполняют инкремент и декремент. Эти операции были пред­ ставлены в гл аве 2, а в этой гл аве они будут рассмотрены более подробно. Как станет ясно в дальнейшем, эти операции обладают рядом особенностей, благодаря которым они становятся довольно привлекательными для программирования. Рассмотрим подробнее, что именно выполняют операции инкремента и декремента. Операция инкремента увеличивает значение операнда на единицу, а операция декремента уменьшает значение операнда на единицу. Например, выражение х=х+1; с операцией инкремента можно переписать так: х++ ; Аналогично следующее выражение: х=х-1; равнозначно приведенному ниже выражению. х --; Эти операции отличаются тем, что могут быть записаны в постфиксной форме, когда операция следует за операндом, а также в префиксной форме, когда операция
Глава 4. Операции 107 предшествует операнду. В приведенных выше примерах применение любой из этих форм не имеет никакого значения , но когда операции инкремента и декре­ мента являются частью более сложного выражения, проявляется хотя и незначи­ тельное, но очень важное отличие этих двух форм . Та к, в префиксной форме зна­ чение операнда увел ичивается или уменьшается до извлечения значения для при­ менения в выражении. А в постфиксной форме предыдущее значение извлекается для применения в выражении, и только после этого изменяется значение операн­ да. Обратимся к конкретному примеру: х=42; у=++х; В данном примере переменной у присваивается значение 43, как и следовало ожидать, поскольку увеличение значения переменной х выполняется перед его присваиванием переменной у. Та ким образом, строка кода у=++х равнозначна следующим двум строкам кода: х=х+1; у=х; Но если эти операторы переписать следующим образом: х=42; у=х++; то значение переменной х извлекается до выполнения операции инкремента, и поэтому переменной у присваивается значение 42. Но в обоих случаях значение переменной х устанавливается равным 43. Следовательно, строка кода у=х++; равнозначна следующим двум операторам: У=х; х=х+1; В приведенной ниже программе де монстрируется применение операции ин­ кремента. 11 Продемонс трировать приме нение операции инкремента ++ class IncDec { puЫ ic static void ma in (String arg s[] ) { intа=1; intЬ=2; int с; int d; с=++Ь; d=а++; с++; System. out . println ("a System. o ut . println ("b System. o ut . println ("c System. out . println ("d " " " " + а); + Ь); + с); + d); Эта программа выводит следующий результат: а 2 ь3 с 4 d1
108 Часть 1. Язык Java П о разрядные операции В языке Java определяется несколько поразряднъtх операций (табл . 4 .2), которые можно выполнять над целочисленными типами данных: long, int, short, char и byte. Эти операции воздействуют на отдельные двоичные разряды операндов . Табл ица ,.2. Поразрядные операции в Java Операция Описание & " >> >>> << &= 1= " = >>­ >>>= <<= П оразряд ная унарная о перация Н Е П оразряд ная ло гическая о перация И Поразряд ная логическая операция ИЛ И П оразряд ная лог ическая операция искл ючающее ИЛИ Сдвиг вп раво Сдвиг вправо с запол нением нулями Сдвиг вл е во П оразрядная лог и ческая операция И с п рисваиванием П оразрядная логическая операция ИЛ И с присваиванием П оразряд ная логическая операция искл ючающее ИЛИ с присваивание м Сдвиг вправо с присваиванием Сдвиг вправо с зап олне нием нулями и присваиванием С,двиг влево с присваиванием Поразрядные операции манипулируют двоичными разрядами (битами) в цело­ численном значении, поэтому очень важно понимать, какое вл ияние подобные манипул яции моrут оказывать на целочисленное значение. В частности , важно иметь в виду, каки м образом целочисленные значения хранятся в исполняющей cpeдe Java и как в ней представляются отрицательные числа. Следовательно , пре­ жде чем продолжить расс мотрение поразрядных операций, следует вкратце обсу­ дить два важных вопроса. Все целочисленные типы данных представлены двоичными числами разной дл ины. Напри мер, десятичное значение 42 типа byte в двоичном представле­ нии имеет вид 00101010, где позиция каждого двоичного разрядного представ­ ляет степень числа два, начиная с 2° в крайнем справа разряде . Двоичный разряд на следую щей позиции представляет степень числа 21, т. е. 2, следующий - 22, или 4, затем 8, 16, 32 и т.д. Та ким образом, двоичное представление числа 42 содержит единичные двоичные разряды на позициях 1, 3 и 5, начиная с О на крайней справа позиции. Следовательно, 42 = 21 + 23 + 25= 2 + 8 + 32. Все целочисленные типы данных (за исключением char) представлены со зна­ ком. Это означает, что они моrут представлять как положительные, так и отри­ цател ьные целочисленные значения. Отрицательные числа в Java представлены в дополнительном коде путем инвертирования (изменения 1 на О, и наоборот) всех двоичных разрядов исходного значения и последующего добавления 1 к ре-
Глава 4. Операции 109 зультату. Например , число -42 получается путем инвертирования всех двоичных разрядов числа 42, что дает двоичное значение 11010101, к которому затем до­ бавляется 1, а в итоге это дает двоичное значение 11010110, или -42 в десятич­ ной форме. Чтобы получить положительное число из отрицател ьного , нужно сна­ чала инвертировать все его двоичные разряды, а затем добавить 1 к резул ьтату. Например, инвертирование числа -42, или 11010110 в двоичной форме, дает дво­ ичное значение 00101001, или 41 в десятичной форме , а после добавления к нему 1 получается число 42. Причина, по которой вjava (и большинстве других языков программирования) применяется дополнительный код, становится понятной при рассмотрении про­ цесса перехода 'ЧеjJез нулъ. Если речь идет о значении типа byte, то нуль представлен значением 00000000. Для получения его обратного кода достаточно инвертиро­ вать все его двоичные разряды и получить двоичное значение 11111111, которое представляет отрицател ьный нуль. Но дело в том , что отрицательный нуль недо­ пустим в целочисленной математи ке . Выходом из этого затруднения служит до­ полнительный код для представления отрицател ьных чисел. В этом случае к об­ ратному коду нулевого значения добавляется 1 и получается дво ичное значение 100000000. Старший ед иничный разряд оказывается сдвинутым влево слишком далеко, чтобы уместиться в значении типа byte. Те м самым достигается требуемое поведение, когда значения -0 и О равнозначны, а 11111111 - двоичный код зна­ чения -1 . В дан ном примере испол ьзовано значение типа byte, но тот же самый принцип можно применить ко всем целочисленным типам данных вjava. Для хранения отрицательных значений в Java используется дополнительный код, а все целочисленные значения представлены со знаком , поэтому выполне­ ние поразрядных операций может легко привести к неожиданным результатам. Например, установка 1 в самом старшем двоичном разряде может привести к тому, что получ ающееся в итоге значение будет интерпретироваться как отрицательное число , независимо от того , какого именно результата предполагалось добиться. Во избежание неприятных сюрпризов не следует забывать, что старший двоичный разряд определяет знак целого числа независ имо от то го , как он бьт установлен. Поразрядные логические операции Поразрядными логическими являются операции &, I , "и�. Результаты выпол­ нения каждой из этих операций приведены в табл. 4.3. Изучая последующий мате­ риал книги , не следует забывать, что поразрядные логические операции выполня­ ются отдел ьно над каждым двоичным разрядом каждого операнда. Таблица ,.3 . Результаты выполне ния поразрядных логичес ки х операций А в AIB А�В ААВ � о о о о 1 о 1 1 1 о о о о о 1 о о
110 Часть 1. ЯзыкJava П о разрядная ун арна я опера ция НЕ Эта операция обозначается знаком � и называется также поразряди'ЫМ отри ца­ иием, инвертируя все двоичные разряды своего операнда. Например, число 42, представленное в следующей двоичной форме: 00101010 преобразуется в результате выполнения поразрядной унарной операция НЕ в сле­ дующую форму: 11010101 Поразрядная логическая операция И При выполнении поразрядной логической операции И, обозначаемой знаком &, в двоичном разряде результата устанавливается 1 лишь в то м случае, если сооl'­ ветствующие двоичные разряды в операндах также равны 1. Во всех остальных случаях в двоичном разряде результата устанавливается О, как показано ниже. 00101010 42 & 0000 1111 15 00001010 10 Поразрядная логическая операция ИЛИ При выполнении поразрядной логической операции ИЛ И, обозначаемой зна­ ком I , в двоичном разряде результата ус танавливается 1, если соответствующий двоичный разряд в любом из операндов равен 1, как показано ниже. 00101010 42 1 00001111 15 00101111 47 Поразрядная логическая операция исключающее ИЛИ При выполнении поразрядной логической операции исключающее ИЛИ, обозна­ чаемой знаком ", в двоичном разряде результата устанавливается 1, если двоичный разряд только в одном из операндов равен 1, а иначе в двоичном разряде результата устанавливается О, как показано ниже. Приведенный ниже пример демонстрирует также полезную особенность поразрядной логической исключающее операции ИЛИ. Обратите внимание на инвертирование последовательности двоичных разрядов чис­ ла 42 во всех случаях, когда в двоичном разряде второго операнда установлена 1. А во всех случаях, когда в двоичном разряде второго операнда установлен О, двоичный раз­ ряд первого операнда остается без изменения. Этим свойством у.добно пользоваться при манипулировании отдельными битами числовых значений. 00101010 42 л 00001111 15 00100101 37 Применение поразрядных логических операций В следую щем примере программы демонстрируется применение поразрядных логических операций:
Гл ава 4 . Операции 111 // Продемон стрировать применение поразрядных логических операций class BitLogic { puЫ ic static void main (String args [] ) { St riпg binary [] = { "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" ); int а З; // О+2 + 1, или 0011 в двоичном представлении int Ь б; // 4 + 2 +О, или 0110 в двоичном представлении int с а1Ь; intd а&Ь; int е алЬ; intf (-а&Ь)1(а&-Ь); intg -а&OxOf; Systern.out . println (" а= "+binary[a] ); Systern. out . println (" Ь ="+ binary[b] ); Systern.out . println (" alb "+binary[c] ); Systern. out . println (" а&Ь "+binary[d] ); Sys tem. out . println (" аль "+binary[e] ); System. o ut . println ("-a&b l a&-b = "+binar y[f] ); System.out . println (" -а ="+binary[g] ); В данном примере последовательности двоичных разрядов в переменных а и Ь представляют все четыре возможные комбинации двух двоичных цифр: 0-0, 0-1, 1-0 и 1-1. О воздействии операций 1 и & на каждый двоичный разряд мож­ но судить по результирую щим значениям переменных с и d. Значения, присвоен­ ные переменным е и f, иллюстрируют действие операции ". Массив символ ьных строк Ьinary содержит уд обочитаемые двоичные представления чисел от О до 15. Этот массив индексирован , что позволяет увидеть двоичное представление каж­ дого результирующего значения. Он построен таким образом, чтобы соответству­ ющее строковое представление двоичного значения n хранилось в элементе мас­ сива Ьinary [n] . Значение �а уменьшается до величины меньше 16 в результате поразрядной логической операции И со значением OxOf (0000 1111 в двоичном представлении), чтобы вывести его в двоичном представление из массива Ьinary. Эта программа выводит следующий результат: а 0011 ь 0110 alb 0111 а&Ь 0010 аль 0101 - а&Ы а&-Ь 0101 а=1100 Сдвиг вл ево Операция сдвига влево, обозначаемая знаками << , смещает все двоичные раз­ ряды значения влево на указанное количество позиций. Эта операция имеет сле­ дующую общую форму: энаvеюrе << xOJD1vec�вo где количе ство обозначает число позиций, на которое следует сдвинугь влево двоичные разряды в заданном зна чении. Это означает, что операция << смещает
112 Часть 1. Язы к Java влево двоичные разряды заданного значения на кол ичество позиций, указан ных в операнде коли че ств о. При каждом сдвиге влево самый старший двоичный раз­ ряд смещается за пределы допусти мого диапазона значений (и при этом теряется), а справа добавляется нуль. Это означает, что при выполнении операции сдвига влево двоичные разряды в операнде типа int теряются , как только они сдвигают­ ся за пределы 31-й позиции. Если же операнд относится к типу long, то двоичные разряды теряются после сдвига за пределы 63-й позиции. Автоматическое продвижение типа в Java приводит к непредвиденным резуль­ татам при сдвиге влево двоичных разрядов в значениях типа byte и short. Как из­ вестно, типы byte и short продвигаются к типу int при вычислении выражения. Более того , результат вычисления такого выражения также имеет тип int. Это означает, что в результате сдвига влево двоичных разрядов значения типа byte или short получится значение типа int, и сдвинутые влево двоичные разряды не будут отброшены до тех пор, пока они не сдвинутся за пределы 31-й позиции. Более того , при продвижении к типу int отрицательное значение типа byte или short приобретает дополнительный знаковый разряд. Следовательно, старшие двоичные разряды заполняются единицами. Поэтому выполнение операции сдви­ га влево по отношению к значению типа byte или short подразумевает отбрасы­ ва ние старших байтов результата типа int. Например, при сдвиге влево двоичных разрядов значения типа byte сначала происходит продвижение к типу int и толь­ ко затем сдвиг. Это означает, что для получения требуемого сдвинутого влево зна­ чения типа byte придется отбросить три старших байта результата. Простейший способ добиться этого - привести результат обратно к типу byte. Та кой способ демонстрируется в следующем примере программы : 11 Сдвиг влево значения типа Ьуtе class ByteShi ft { puЫ ic static void main (St ring args[] ) { byte а 64, Ь; int i; i а<<2; Ь (byte) (а « 2); System. out . println ( "Пepвoнaчaль нoe значение а: "+а); System. out . println ("i and Ь: "+i +""+Ь) ; Эта программа выводит следующий результат: Первоначальное значение а: 64 iandЬ:256О Для целей вычисления тип переменной а продвигается к типу int, поэтому сдвиг значения 64 (0100 0000) влево на две позиции приводит к значению 256 (1 0000 0000), присваиваемому переменной i. Но переменная Ь содержит нулевое значение, поскольку после сдвига в младшем двоичном разряде устанавливается О. Единственный единичный двоичный разряд оказывается сдвинутым за пределы допустимого диапазона значений. Каждый сдвиг влево на одну позицию, по существу, уд ваивает исходное значе­ ние, поэтому программисты нередко пользуются такой возможностью в качестве
Глава 4. Операции 11З эффективной альтернативы умножению на 2. Но при этом следует соблюдать осторожность. Ведь при сдвиге единичного двоичного разряда на старшую (31-ю или 63-ю) позицию значение становится отрицательным. Та кое применение опе­ рации сдвига влево демонстрируется в следующем примере программы: // Приме не ние сдвига вле во в качестве быстрого способа умноже ния на 2 class MultByTwo { puЫic static void ma in (String args [] ) { int i; int num = OxFFFFFFE ; for (i=O; i<4; i++) { num=num<<1; System. out . println (num ) ; Эта программа выводит следующий результат: 536870908 1073741816 2147483632 -32 Начальное значение было специально выбрано таким, чтобы после сдвига вле­ во на четыре позиции оно стало равным -32. Как видите , после сдвига единично­ го двоичного разряда на 31-ю позицию числовое значение интерпретируется как отрицательное. Сдвиг впра во Операция сдвига вправо , обозначаемая знаками >>, смещает все двоичные раз­ ряды заданного значения вправо на указанное количество позиций. В общем виде эта операция обозначается следующим образом: sначение >> жоличес�во где количе ство обозначает число позиций, на которое следует сдвинуть вправо двоичные разряды в заданном зна чении. То есть операция >> смещает все двоич­ ные разряды в задан ном значении вправо на число позиций, указанное в операнде количе ств о. В следующем фрагменте кода значение 32 сдвигается вправо на две позиции. В итоге переменной а присваивается значение 8 : intа=32; а =а >> 2; // теперь переменная а содержит значение 8 Когда какие-ни будь двоичные разряды значения сдвигаются за его пределы, они теряются . Например, в приведенном ниже фрагменте кода значение 35 сдви­ гается на две позиции вправо. В итоге теряются два младших двоичных разряда и переменной а снова присваивается значение 8. intа=35; а= а>> 2; // переменная а содержит значение 8 Чтобы лучше понять, как выполняется операция сдвига вправо , ниже показа­ но, каким образом сдвиг вправо происходит в двоичном представлении.
11.4 Часть 1.Язык Java 00100011 35 >>2 00001000 8 При каждом сдвиге вправо выполняется деление заданного значения на два с отбрасыванием любого остатка. Этой особенностью данной операции можно воспользоваться для эффективного целочисленного деления на 2. Но при этом следует проявлять осторожность, чтобы не потерять двоичные разряды, безвоз­ вратно сдвинугые за пределы правой границы числового значения. При выполнении операции сдвига вправо старшие двоичные разряды на край­ них слева позициях освобождаются и заполняются предыдущим содержимым старшего двоичного разряда. В итоге происходит так называемое расширепие :така, которое служит для сохранения знака отрицательных чисел при их сдвиге вправо. Например, результат выполнения операции -8 >> 1 равен - 4 , что в двоичном представлении выглядит следую щим образом: 11111000 -8 >>1 11111100 -4 Любопытно, что результат сдвига значения -1 вправо всегда равен -1 , посколь­ ку расширение знака приводит к переносу дополнительных единиц в старшие двоичные разряды . Иногда при выполнении сдвига вправо расширение знака чис­ ловых значений нежелательно. Та к, в приведенном ниже примере программы зна­ чение типа byte преобразуется в соответствующее шестнадцатеричное строковое представление. Обратите внимание на то , что для индексации массива символов, обозначающих шестнадцатеричные цифры, сдвинугое вправо исходное значение маскируется значением OxOf' в поразрядной логической операции И, что приво­ дит к отбрасыванию любых двоичных разрядов расширения знака. 1 1 Маскирование двоичных разрядов расширения знака class HexByte { static puЫic void ma in (String arg s[] ) { char hex[] = { 1 01, 11', '2', '3', 1 4 ', '5', 16', '7' , '8', '9','а', 'Ь', 'с', 'd', 'е', 'f' }; byte Ь = (byte ) Oxfl; System. out .println ("b = Ох" + hex[ ( b >> 4) & OxOf] + hex[b & OxOf]); Эта программа выводит следующий результат: Ь=Oxfl Беээнаковый сдвиг вправо Как пояснялось выше, при каждом выполнении операции >> старший двоич­ ный разряд автоматически заполняется своим предыдущим содержимым. В итоге
Глава 4. Операции 115 сохраняется знак сдвигаемого значения. Но иногда это нежелательно. Например, при сдвиге вправо двоичных разрядов какого-нибудь нечислового значения рас­ ширение знака может быть нежелательным. Подобная ситуация нередко возни­ кает при обработке значений отдел ьных пикселей в графических изображениях. Как правило, в подобных случаях требуется сдвиг нуля на позицию старшего дво­ ичного разряда независимо от его первоначального значения. Это так называе­ мый без зн ах06'Ый сдвиг. Для этой цели вJava имеется операция беззнакового сдвига вправо. Она обозначается тремя знаками >>> и всегда вставляет нуль на позиции старшего двоичного разряда. В приведенном ниже примере кода демонстрируется применение операции >>>. Сначала в данном примере переменной а присваивается значение -1, где во всех 32 битах двоичного представления устанавливается 1. Затем в этом значении выполняется сдвиг вправо на 24 бита, в ходе которого 24 старших двоичных раз­ ряда заполняются нулями и игнорируется обычное рас ширение знака. Та ким об­ разом , в переменной а устанавливается значение 255. intа= -1; а=а>>>24; Чтобы операция беззнакового сдвига вправо стала понятнее, она представлена ниже в двоичной форме. 11111111 11111111 11111111 11111111 значение -1 типа int в двоичном виде »> 24 00000000 00000000 00000000 11111111 значение 255 типа int в двоичном виде Нередко операция >>> оказывается не такой полезной , как хотелось бы, по­ скольку ее выполнение имеет смысл только для 32- и 64-разрядных значений. Не следует забывать, что в выражениях тип меньших значений автоматически про­ двигается к типу int. Это означает, что расширение знака и сдвиг происходят в 32-разрядных, а не 8- или 16-разрядных значениях. Та к, в результате беззнаково­ го сдвига вправо двоичных разрядов значения типа byte можно было бы ожидать заполнения нуля ми, начиная с 7-й позиции, но на самом деле этого не происхо­ дит, поскольку сдвиг вправо фактически выполняется в 32-разрядном значении. Именно это и демонстрируется в приведенном ниже примере программы. 11 Беззнаковый сдвиг двоичных разрядов значения типа Ьуtе class ByteUShift { static puЬlic void ma in (String args[] ) { char hex[] = ( 101, '1', '2', '3', '4', '5', '6', '7', '8', '9', 'а', 'Ь', 'с', 'd', 'е', 'f' ); byte Ь byte с byte d byte е (byte ) Oxfl; (byte) (Ь » 4); (byte) (Ь >» 4); (byte) ( (Ь & Oxff) » 4); System.out . println (" Ь = Ох " + hex[(b >> 4) & OxOf] + hex[b & OxOf]}; System.out . println (" Ь >> 4 = Ох" + hex[(c >> 4) & OxOf] + hex[c & OxOf]}; System.out . println (" Ь >>> 4 = Ох "
116 Часть 1. Язы к Java + hex[(d >> 4) & OxOf] + hex[d & OxOf]); System.out .println("(b & Oxff) >> 4 = Ох" + hex[(e >> 4) & OxOf] + hex[e & OxOf]); Как следует из приведенного ниже результата выполнения дан ной програм­ мы, операция >>> не оказывает никакого воздействия на значения типа byte. Сначала в данном примере программы переменной Ь присваивается произволь­ ное отрицательное значение типа byte. Затем переменной с присваивается зна­ чение переменной Ь типа byte, сдвинутое на четыре позиции вправо и равное Oxff вследствие расширения знака. Далее переменной d присваивается значение переменной Ь типа byte, сдвинутое на четыре позиции вправо без знака. Это зна­ чение должно бьшо бы быть равно OxOf, но в действительности она оказывается равн ым Oxff из-за расширения знака, которое произошло при продвижении типа переменной Ь к типу in t перед сдвигом. И в последнем выражении переменной е присваи вается значение переменной Ь ти па byte, сначала замас кирован ное до 8 двоичных разрядов с помощью поразрядной логической операции И, а затем сдви нутое вправо на четыре позиции. В итоге получается предполагаемое значе­ ние OxOf. Обратите внимание на то , что операция беззнакового сдвига вправо не применяется к значению переменной d, поскольку состояние знакового двоично­ го разряда известно после выполнения поразрядной логической операции И. Ь Oxfl Ь>>4 Oxff Ь»>4 Oxff (Ь&Oxff)>>4 OxOf П оразрядны е соста вны е операции с прис ваивание м Подобно арифметическим операция м, все двоичные поразрядные операции имеют составную форму, в которой поразрядная операция объединяется с опера­ цией присваиван ия. Например, две приведенные ниже операции, выполняющие сдвиг на четыре позиции вправо двоичных разрядов значения переменной а , рав­ нозначны. а=а>>4; а>>=4; Равнозначны и следующие две операции, присваивающие переменной а ре­ зультат выполнения поразрядной логической операции а ИЛИ Ь: а=а1Ь; а 1=Ь; Следующая программа создает несколько целочисленных переменных, а затем использует составные побитовые операторы с присваиванием для манипулирова­ ния этими переменными: class Op BitEqua ls puЫ ic static void ma in (String args[] ) { int а 1 intЬ 2 intс=З
а 1=4; ь>>=1; с<<=1; ал=с; System. out . println ("a System. out . println ("b System.out . println ("c "+а) •• +Ь) "+с) Эта программа выводит следующий результат: а 3 ь1 с=6 Операции отношения Глава 4. Операции 117 Операции отношения, называемые иначе оwфациями ср штенuя, определяют отно­ шение одного операнда к другому. В частности, они определяют равенство и упо­ рядочение. Все доступные в Java операции отношения перечислены в табл. 4 .4 . Табл ица '·'· Операции отношения в Java Оnерацмя Оnмсанме =- Равно != Не равно > Больше < Меньше >= Больше или равно <= Меньше или равно Результатом выполнения этих операций оказывается логическое значение. Чаще всего операции от ношения применя ются в выражениях , управляющих ус­ ло вным оператором i f и различными операторами цикла. В Java допускается сравнивать значения любых ти пов, в то м числе целочислен­ ные значения , числовые значения с плавающей то чкой, символы и логические значения, выполняя проверку на равенство == и неравенство ! =. (Напомним, что в Java равенство обозначается двумя знаками =, тогда как операция прис ваива­ ния - одн им знаком =. ) Сравнение с помощью операций упорядочения допускает­ ся только для числовых типов данных. Та ким образом, сравнение, позволяющее оп редел ить, какой из двух операндов больше или меньше, можно выполнить толь­ ко над целочисленными, символьными операндами ил и числовыми операндами с плавающей точкой. Как отмечалось выше, результатом операции отношения является логическое значение. Например, приведенный ниже фрагмент кода вполне допустим. В дан­ ном примере логическое значение false, получающееся в результате выполнения операции а < Ь, сохраняется в переменной с.
118 Часть 1. Яэы к Java intа 4; intЬ 1; booleanс=а<Ь; Те м , у кого имеется опыт программирования на С/С++, следует обратить вни­ ман ие на то , что в программах на С/С++ очень часто встреч аются приведенные ниже типы операторов. int done ; 11 ... if(!done) . . . 11 Допус тимо в С/С++, if(done) ... //нонев Java. А в программе нa Java эти операторы должны быть написаны следующим об­ разом: if (done == 0) ... // Это стиль Java if(done != О) ... Дело в том , что в Java определение "истинного" и "ложного" значений отл и­ чается от их определения в С/С++. В языках С/С++ истинным с читается любое ненулевое значение, а ложным - нулевое. Но вJava логические значения true ( ис­ ти нно) и false (ложно) являются нечисловыми и никак не свя заны с нулевым или ненулевым значен ием. Поэтому в выражениях приходится явно указывать одну или несколько операций опюшения, чтобы проверить значение на равенство или неравенство нулю. Логические операции Описываемые в это м разделе ло ги ческие операции выполняются только с опе­ рандами типа boolean. Все логические операции с двумя операндами соединяют два логических значения, образуя результи рующее логич еское зн ачение. Все до­ ступные в Java логические операции перечислены в табл . 4 .5. Та блица ,.5. Логические операци и в Java Операцм• ' 11 " &= 1= "= = != ?: Описание Логическая операция И Логическая операция ИЛИ Логическая операция исключающее ИЛ И Уко роченная логическая операция ИЛ И Уко роченная логическая операция И Логическая унарная операция НЕ Логическая операция И с присваиванием Логическая операция ИЛИ с присваиванием Логическая операция исключающее ИЛ И с присваиванием Равенство Неравенство Те рнарная условная операция типа если ..., 11W •••, иначе ...
Глава 4. Операции 119 Логические операции &, 1 и " воздействуют на значения типа boolean точно так же , как и на отдел ьные двоичные разряды целоч исленных значений. Та к, логи­ ческая операция ! инвертирует логическое состояние: ! true == false и ! fal se true . В табл . 4.6 приведены результаты выполнения каждой из логических операций. Табл ица ,.6. Результаты выполнения логических операций А fal s• true fal se true в fal se false true true А/В false true true true А&В false false fal se true А"В fal se true true false !А true fal se true false Ниже приведен пример программы, в которой выполняются практически те же самые действия, что и в представленном выше примере программы B i tLogic. Но эта программа оперирует логическими значениями типа boolean, а не двоич­ ными разрядами. 11 Продемонстрировать приме нение логически х операций class BoolLogic { puЫ ic static boolean а bool ean Ь boolean с boolean d bool ean е boolean f boolean g vo id ma in (String args[] ) { true ; fal se; а1Ь; а&Ь; алЬ; (!а&Ь)1(а&!Ь); !а; System. out . println (" а System. out . println (" ь System. out . println (" alb System. out . println (" а&Ь System.out . println (" аль System. out . println ("!a&Ы a& !b System. out . println (" !а "+ "+ "+ "+ " + " + "+ а); Ь); с); d); е); f); g); Выполняя эту программу, легко убедиться, что к значениям типа boolean при­ меняются те же самые логические правила, что и к двоичным разрядам. Как следу­ ет из приведенного ниже результата выполнения данной программы, вJava стро­ ковым представлением значения типа boolean служит значение одного из литера­ лов true или false. а true ь false alb true а&Ь false аль true !а& Ь/а&!Ь true !а false
120 Часть 1. Язы к Java Уко роченные логи чес кие операции В языке Java предоставляются. две любопытные логические операции, отсут­ ствующие во многих других языках программирования. Эти вторые верс ии логи­ ческих операций И и ИЛИ обычно называются укорачен:н:ы м и логическими опера­ циями. Как следует из табл . 4.6, результат выполнения укороченной логической операции ИЛИ равен true, когда значение операнда А равно true независимо от значения операнда В. Ан алогично результат выполнения укороченной логиче­ ской операции И равен false, когда значение операнда А равно false независимо от значения операнда В. При использовании форм 1 1 и && этих операторов вместо форм 1 и & в программе нa java не будет вычисляться значение правого операнда, если результат выражения можно определить по значению только левого операн­ да. Эти м обстоятельством очень удо бно пользоваться в тех случаях, когда значе­ ние правого операнда зависит от значения левого . Например , в следующей строке кода демонстрируется преимущество применения укороченных логических опе­ раций, чтобы выяснить достоверность операции деления перед вычислением ее результата. if(denom !=О&&num/denom> 10) Благодаря применению укороченной формы логической операции И (&&) от­ сутствует риск возникновения исключений во время выполнения в связи с равен­ ством нулю знаменателя (denom) . Если бы в данной строке кода был указан оди­ нарный знак & логической операции И, то вычислялись бы обе части выражения, что привело бы к исключению во время выполнения в связи с тем, что значение переменной denom равно нулю. Уко роченные формы логических операций И и ИЛИ принято применять в тех случаях, когда требуются логические операции, а их полные формы - исключитель­ но для выполнения поразрядных операций. Но из этого правила есть исключения. Рассмотрим в качестве примера приведенную ниже строку кода. В данном примере оди ночный знак & гарантирует выполнение операции инкремента над значением переменной е, независимо от того, равно ли 1 значение переменной с. if(c==l & е++ < 100) d = 100; На заметку! В формал ьной спецификации языка Java укороченные логические операции назы­ ва ются условными, например, условная логическая операция И или условная логическая операция ИЛИ. Операция присваивания Операция присваивания применялась в примерах программ с гл авы 2. Те перь настало время рассмотреть ее подробно. Операция при сваивания обозначается оди­ ночным знаком равенства =. В Java операция присваивания действует таким же об­ разом, как и во многих других языках программирования . Она имеет следующую общую форму: перенен н ая=В1 1рЗЖ еняе;
Глава 4. Операции 121 В этой форме переме нн ая и выражение должны иметь совместимый тип. Операция присваивания имеет одну интересную особенность, с которой вы, возможно , еще не знакомы: она позволяет объединять присваивания в цепочки. Рассмотрим в качестве примера следую щий фрагмент кода: intх,у,z; х = у=z = 100; // установить значение 100 в переменных х, у и z В этом фрагменте кода единственная операция присваи вания позволяет уста­ новить значение 100 сразу в трех переменных: х , у и z. Это происходит благодаря тому, что в операции = используется значение правого выражения. Та ким обра­ зом , в результате вычисления выражения z = 100 получается значение 100, кото­ рое присваивается сначала переменной у, а затем - переменной х . Применение " цепочки присваивания" - уд обный способ установки общего значения в группе переменных. Тернарная операция ? В синтаксисе Java имеется специальная тернарная операция, которая обознача­ ется знаком ? и которой можно заменить определенные типы условных операто­ ров вроде если.. . , то. . ., инд:че . .. (if- then-e lse). На первый взгляд эта операция может показаться вам не совсем непонятной, но со временем вы убедитесь в ее особой эффектщшости . Эта операция имеет следующую общую форму: JWPazeюrвl ? sip.ureюre2 : вuражеюrеЗ где выражение ] обозначает любое выражение, вычисление которого дает логи­ ческое значение типа boo lean. Если это логическое значение true , то вычисля­ ется выражение2, в противном случае - выражение]. Результат выполнения тер­ нарной операции ? равен значению вычисленного выражения . Из обеих ветвей выражение 2 и выражение ] тернарной операции ? должно возвращаться значение одинакового (или совместимого) типа, которым не может быть тип void. Ниже приведен пример применения тернарной операции ? . ratio=denom==О?О:num/denom; Когда вычисляется данное выражение присваивания , сначала проверяется выражение слева от знака вопроса. Есл и значение переменной denom равно О, то вычисляется выражение, указанное между знаком вопроса и двоеточием, а вычис­ ленное значение используется в качестве значения всего тернарного выражения ? . Если же значение переменной denom не равно О, то вычисляется выражение, указанное после двоеточия, и оно используется в качестве значения всего тернар­ ного выражения ? . И наконец, значение, полученное в результате выполнения те рнарной операции ?, присваи вается переменной ratio. В приведенном ниже примере программы демонстрируется применение тер­ нарной операции ? . Эта программа служит для получения абсолютного значения переменной. // Продемонс триро вать применение тернарной операции ? class Ternary { puЬlic static vo id main (String args[] )
122 Часть 1. Язы к Java int i, k; i=10; k = i < О ? -i i; // nолучить абсолют ное значения п ереме нной i System.out . print ( "Aбcoлютнoe значение ") ; System.out . println (i +"равно " + k) ; i= -10; k = i < О ? -i i; // п олучить абсолют ное значения п еременной i System.out . print ( "Aбcoлют нoe значение ") ; System. out .pr int ( "Абсолютное значение ") ; System.out . println (i +"равно "+k) ; Эта программа выводит следую щий результат: Абсолютное значение 10 равно 10 Абсолют ное значение -10 равно 10 Предшествование операций Предшествование (от высшего к низшему) операций вJava приведено в табл. 4.7 . Операции, находящиеся в одном ряду таблицы, имеют од инаковое предше­ ствование. Операции с двумя операндами имеют порядок вычисления слева на­ право (за исключением операции присваивания, которая выполняется справа налево). Формально скобки [] и () ,атакже точка (.) считаются разделителями, но они могут служить и в качестве операций. Именно в это м качестве они имеют наивысшее предшествование. Обратите также внимание на операцию "стрелка" ( ->) , которая была внедрена в версииJDК 8 и применяется в лямбда-выражениях. Таблица '·7. Предш ествование операций в Java Наивысшее предш ество вание ++ ( постфиксная операция) ++ ( префиксная операция ) * + >> > ' А -- ( постфи ксная операция) -- (префиксная операция) / >>> != << < +(унарная операция) <= instanceof - (унарная операция) ( приведение типов)
Наив1о1 сшее предшествование " 11 ?: -> Наинизшее предш еств ование операцюr= Применение круглых скобок Глава 4. Операции 123 Окон•шние табл. 4, 7 Кру глые скобки повышают предшествование заключенных в них операций. Нередко это требуется для получения нужного результата. Рассмотрим в качестве примера следующее выражение: а>>Ь+З Сначала в этом выражении к значению переменной Ь добавляется значение 3, а затем двоичные разряды значения переменной а сдвигаются вправо на получен­ ное в итоге количество позиций. Используя избыточные круглые скобки , это вы­ ражение можно бьшо бы за писать следующим образом: а»(Ь+3) Но если требуется сначала сдвинуть двоичные разряды значения переменной а вправо на Ь позиций, а затем добавить 3 к полученному резул ьтату, то круглые скобки следует использовать та к, как показано ниже. (а»Ь}+3 Кроме изменения обычного предшествования операций, круглые скобки можно иногда использовать с целью упростить понимание смысла выражения. Сложные выражения могут оказаться трудными для понимания. Добавление избы­ то ч ных, но облегчающих понимание круглых скобок может способствовать устра­ нению недоразумений впоследствии. Например, какое из приведенных ниже вы­ ражений легче прочесть? а\4+с>>Ь&7 (а\({(4+с)»Ь)&7)} И наконец, следует иметь в виду, что применение круглых скобок (избыточ­ н ых или не избыточных) не ведет к снижению производительности программы. Следовател ьно , добавление круглых скобок для п овышения удобочитаемости ис­ ходного текста программы не оказы вает никакого вл ияния на эффекти вность ее работы .
5 Управляющие оператор ы В языках программирования управляющие операторы применяются для реа­ лизации переходов и ветвлений в потоке исполнения команд программы, исходя из ее состояния . Уп равляющие операторы в программе нa java можно разделить на следующие катего рии: операторы выбора, операторы цикла и операторы пере­ хода. Операторы выбора позволяют выбирать разные ветви выполнения команд в соответствии с результатом вычисления заданного выражения или состоянием переменной. Операторы цикла позволяют повторять выполнение одного или не­ скольких операторов (т.е . они образуют циклы). Операторы перехода обеспечива­ ют возможность нелинейного выполнения программы. В этой гл аве будут рассмо­ трены все управляющие операторы , доступные в Java. Оператор ы выбора В языке jаvа поддерживаются два операто ра выбора: if и switch. Эти опера­ торы позволяют управлять порядком выполнения команд программы в соответ­ ствии с условиями, кото рые известны только во время выполнения. Читатели бу­ дут приятно уд ивлены возможностя ми и гибкостью этих двух операторов. Условны й оператор if В этой гл аве подробно рассматривается условный оператор i f, вкратце пред­ ставленный в гл аве 2. Это оператор условного ветвления программы нa Java. Его можно использовать с целью направить выполнение программы по двум разным ветвя м. Общая форма этого условного оператора выглядит следующим образом: if (условяе) опера торl ; else оператор2 ; где каждый опера тор обозначает оди ночный или составной оператор, заключен­ ный в фигурные скобки ( т. е . блок кода) ; условие - любое выражение, возвращающее логическое значение типа boolean. А оператор else указывать необязател ьно. Ус ловный оператор i f действует следую щим образом: если условие истин­ но, то выполняет опера торl , а иначе - опера тор2, если таковой имеется . Но ни в коем случае не будут выполняться оба оператора. Рассмотрим в качестве приме­ ра следующий фрагмент кода:
126 Часть 1. Язы к Java int а, Ь; 11 ". if(а<Ь)а=О; elseЬ=О; Если в данном примере значение переменной а меньше значения переменной Ь, то нулевое значение устанавливается в переменной а, в противном случае - в переменной Ь. Но ни в коем случае нулевое значение не может быть установлено сразу в обеих переменных, а и Ь. Чаще всего в управляющих выражениях оператора i f применяются операции отношения, хотя это и не обязательно. Для управления условным оператором if можно применять и одиночную переменную типа boolean, как показано в следую­ щем фрагменте кода: boolean dataAvailaЬ le; 11 if (dataAv ailaЬ le) ProcessData () ; else wait ForMoreData () ; Следует иметь в виду, что только один оператор может следовать непосред­ ственно за ключевым словом if или else. Если же нужно ввести больше оператсr ров, то придется написать код " аналогичный приведенному ниже. В данном при­ мере оба оператора, размещенные в блоке кода, будут выполняться в том случае, если значение переменной bytesAvailaЬle окажется больше нуля. int byte sAvailaЬle ; 11 if (byte sAvai laЫe > 0) { ProcessData (); byte sAvailaЫe -= n; else wait Fo rMo reData () ; Некоторые программисты предпочитают использовать в условном операторе i f фигурные скобки даже при наличии только одного оператора в каждом выра­ же нии. Это упрощает добавление операторов в дальнейшем и избавляет от необ­ ходимости проверять наличие фигурных скобок. На самом деле пропуск опреде­ ления блока в тех случаях, когда он действительно требуется, относится к числу довольно распространенных ошибок. Рассмотрим в качес1'Jtе примера следующий фрагмент кода: int bytesAvailaЫe ; 11 if (bytesAvailaЫe > 0) 1 Proces sData (); byte sAvailaЫe - = n; else waitForMoreData () ; byte sAvailaЫe = n; Если судить повеличине отступа, то в дан ном примере кода изначально предпсr лагалось, чтo oпepaтop byte sAvailaЫe=n ; должен выполняться в ветви оператсr ра else. Но не следует забывать, что вjava отступы не имеют никакого значения,
Глава 5. Управляющие операторы 127 а компилятору никоим образом не известны намерения программиста. Данный код будет скомпилирован без вывода каких-нибудь предупреждающих сообщений, но во время выполнения он будет вести себя не так , как предполагалось. В при· веденном ниже фрагменте кода исправлена ошибка, допущенная в предыдущем примере. int byte sAv ailaЫe; 11 if (byte sAva ilaЫe > 0) { ProcessData () ; bytesAvailaЫe -= n ; else { wait ForMoreData () ; byte sAv ai laЬle = n; Вяоже нны е условные о ператоры if Вл ожеии'ЫМ называется такой условный оператор i f, который является целью другого условного оператора i f или е l s е. В программах вложенные условные опе­ раторы i f встречаются очень часто. Пользуясь вложенными условными операто­ рами i f, не следует забывать, что оператор е l s е всегда связан с ближайшим услов­ ным оператором i f, находящимся в том же самом блоке кода и еще не связанным с другим оператором else. if(i == 10) { if(j<20)а=Ь; if(k > 100) с = d; // этот условный оператор if else а=с; // связан с данным оператором else , elseа=d; 11 а этот оператор else - с оператором if (i .. .. 10) Как следует из комментариев к данному фрагменту кода , внешний оператор else не связан с оператором if (j < 20) , поскольку тот не находится в том же самом блоке кода, несмотря на то, что он является ближайшим условным операто­ ром if, который еще не связан с оператором else. Следовательно, внешний опе­ ратор еlsе связан с оператором if (i==1О). А внугренний оператор еlsе связан с оператором i f ( k > 1 О О ) , поскольку тот является ближайшим к нему в том же самом блоке кода. Конструкция if-else-if Конструкция if-else-if, состоящая из последовательности вложенных ус· ловных операторов i f, весьма распространена в программировании. В общем виде она выглядит следующим образом: if (ycлo-e) onepa !l.'op; else if (усло.&J(е) опера!l.'ор; else if (ycлoar.re) onepa!l.'op;
128 Часть 1. Язы к Java else OD&pa'.l'Op; Ус ловные операто ры i f выполняются последовательно , сверху вниз. Как толь­ ко одно из условий, управля ющих оператором if, оказывается равным true, вы­ полняется оператор, связанный с данным условным оператором i f, а остальная часть конструкции if-else-if пропускается. Есл и ни одно из условий не выпол­ няется (т.е . не равно true) , то выполняется заключительный оператор else. Этот последний оператор служит условием по умолчанию. Иными словами , если про­ верка всех остальных условий дает отрицател ьный результат, выполняется послед­ ний оператор е 1 se. Если же заключительный оператор е 1 se не указан , а результат проверки всех остальных условий равен false, то не выполняется никаких дей­ ствий. Ниже приведен пример программы, в которой конструкция if-else-if служит для определения времени года, к которому относится ко нкретный месяц. 11 Продемонстрировать приме нение конс трукции if-else-if class IfElse { puЫ ic static void ma in ( String args [] ) { int month = 4; 11 Апрель String season; if(month==12 11month == 1 11 month==2) season = "зиме "; else if (month == З 11 mo nth season = "весне "; else if (month == 6 11 mo nth season = "лету"; 4 11 month 711month 5) 8) else if (month == 9 11 month season = "осени" ; 1011month==11) else season = "вЬIМЫШЛенным ме сяцам" ; System. out .println ( "Aпpeль относится к"+season + ".") ; Эта программа выводит следующий результат: Апрель относится к ве сне Прежде чем продолжить чтение, поэкспериментируйте с этой программой. Убед итесь сами, что, независимо от значения, присвоенного переменной mon th, в конструкции if-else-if будет выполняться только одна операция присваивания. О ператор swi tch В языке Java оператор switch является оператором ветвлен ия. Он предостав­ ляет простой способ направить поток исполнения команд по разным ветвям кода в зависимости от значения управляющего выражения. Зачастую оператор switch оказывается эффективнее дл инных последовательностей операто ров в конструк­ ции if-else-if . Общая форма операто ра swi tch имеет следующий вид: swi tch (JiWРЗЖенхе) ( case зна vеняе1 : 11 последовательность операторов break ; case знаvенже2 :
Глава 5. Управляющие операторь� 129 11 последовательность операторов break ; case sяаvениеN: 11 последовательность операторов break ; defaul t: 11 последовательность операторов по умолч анию Во всех версиях Java до JDK 7 указанное выражение должно иметь тип byte, short, int, char или перечислимый тип. (Перечисления рассматриваются в гл аве 12.) Начиная сJDК 7, выражение может также иметь тип String. Каждое значение, определенное в операторах ветвей case, должно быть однозначным константным выражением (например, литеральным значением) . Дублирование значений в опе­ раторах ветвей case не допускается. Каждое зна чение должно быть совместимо по типу с указанным выражением. Оператор switch действует следующим образом. Значение выражения сравни­ вается с каждым значением в операторах ветвей case. При обнаруже нии совпа­ дения выполняется последовательность кода, следующая после оператора данной ветви ca se. Если значения ни одной из констант в операторах ветвей case не со­ впадают со значением выражения, то выполняется оператор в ветви de faul t. Но указывать этот оператор не обязательно. В отсутствие совпадений со значениями констант в операторах ветвей case, а также оператора de faul t никаких дальней­ ших действий не выполняется. Оператор break служит для прерывания последовательности операторов в вет­ вях оператора switch. Как только очередь доходит до оператора break, выпол­ нение продолжается с первой же строки кода, следующей после всего оператора swi tch. Оператор break служит для немедленного выхода из оператора switch. Ниже представлен простой пример применения оператора switch. 11 Простой пример приме нения оператора switch class SampleSwitch ( puЫic static void ma in (String argз[] ) ( for (int i=O ; i<б; i++) switch (i) ( case О: Syзtem.out . println ("i равно нулю. ") ; break; case 1: System. o ut . println ("i равно единице ."); break; case 2: Syзtem. out . println ("i равно двум. ") ; break; case 3: System. out . println ("i равно трем . ") ; break; default : Syзtem. out . println ("i больше трех. ") ;
130 Часть 1. Яэы к Java Эта программа выводит следующий результат: i равно нулю . i равно единице . i равно двум . i равно трем . i больше трех . i больше трех . Как видите , на каждом шаге цикла в данной программе выполняются операто­ ры, связанные с константой в той ветви case, которая соответствует значению переменной i, а все остальные операторы пропускаются. После того как значение переменной i становится больше З, оно перестает соответствовать значению кон­ станты в любой ветви case, и поэтому выполняется оператор de fault. Ук азывать оператор break необязател ьно . Если его опустить, то выполнение продолжится с оператора следующей ветви case. В некоторых случаях желатель­ но использовать несколько операторов ветвей case без разделяющих их операто­ ров break. Рассмотрим в качестве примера следующую программу: 11 В операторе switch необязательно указывать операторы break class Mi ssingBreak ( puЫic static void ma in ( String args [] ) ( for (int i=O; i<l2; i++) swi tch (i) ( case О: case 1: case 2: case З: case 4: System.out . println ("i ме ньше 5") ; break; case 5: case 6: case 7: case 8: case 9: System.out . println ("i ме ньше 10") ; break; de fault : System.out . println ("i равно или больше 10") ; Эта программа выводит следующий результат: i меньше 5 i меньше 5 i меньше 5 i меньше 5 i меньше 5 i меньше 10 i меньше 10 i меньше 10 i меньше 10 i меньше 10 i равно или больше 10 i равно или больше 10
Глава 5. Управляющие операторы 131 Как видите, операторы выполняются в каждой ветви case до тех пор, пока не буд� достигнут оператор break (или конец оператора switch). Приведенный выше пример специально создан в качестве иллюстрации, тем не менее пропуск операторов break находит немало применений в реальных программах. В каче­ стве более практического примера ниже приведена переделанная версия рассмо­ тренной ранее программы, в которой определяется принадлежность месяца вре­ мени года. В этой версии использован оператор switch, что позволило добиться более эффективной реализации данной программы. 11 Усовершенствованная версия программы, в которой 11 определяется принадлежность ме сяца времени года class Swi tch { puЫic static void ma in (String args[) ) { int month = 4; String season; switch (month) case 12 : case 1: case 2: season break; case 3: case 4: case 5: season break; case 6: case 7: case 8: season break; case 9: case 10 : case 11 : season break; de fault : season "эиме"; "весне "; "лету "; "осени"; "вЬ1МЫ1ПЛенным месяцам" ; System.out . println ( "Aпpeль относится к" + season + ".") ; Как упоминалось ранее, начиная с JDK 7, для управления оператором s wi tch можно использовать символьные строки . 11 Использовать символь ные строки дл я управления оператором svitch class StringSwi tch { puЫic static void ma in (String args[]) { String str = "два"; switch (str) { case "один" : System.out . println ( "oдин " ); break; case "два" : System.out . println ("двa" ); break; case "три" : System.out . println ("т pи" );
132 Часть 1. Язы к Java break; de fault : System. out .println ("нe совпало) ") ; break; Как и следовало ожидать, результат выполнения этой программы будет следу­ ющим: два Символьная строка, содержащаяся в переменной str (в данном случае - "два " ) , сравни вается с константами в операторах ветвей case. Как только обнару­ жится совпадение (в операторе второй ветви саs е), выполняется связанная с ним последовательность кода. Возможность использования символ ьных строк в операторе switch зачастую упрощает дело. В частности , применение оператора switch с символьными стро­ ками является значительным усовершенствованием по сравнению с эквивалент­ ной последовательностью операторов if/else. Но с точки зрения эффективно­ сти кода выбор среди символьных строк обходится дороже выбора среди целых чисел. Поэтому символьные строки лучше применять только в тех случ аях, когда управляющие данные уже находятся в строковой форме. Иными словами , пользо­ ваться символ ьными строками в операторе switch без особой необходимости не следует. Вложенны е операто р ы swi tch Оператор switch можно использовать в последовательности операторов внеш­ него оператора switch. Та кой оператор switch называется вложенным. В каждом операторе swi tch определяется свой блок кода, и поэтому никаких конфликтов между константами в ветвях case внутреннего и внешнего операторов switch не возникает. Например, следующий фрагмент кода вполне допустим: switch (count ) { case 1: switch (target) {// вложенный оператор switch case О: System. out . println ("t arget равно О") ; break; case 1: // конфликты с внешним оператором switch отсутствуют System.out . println ("target равно 1") ; break; case 2: // break; В данном случае оператор ветви case 1: внутреннего оператора swi tch не кон­ фликтует с оператором ветви case1: внешнего оператора switch. Значение пере­ менной count сравнивается только с рядом внешних ветвей case. Если значение переменной count равно 1, то значение переменной target сравнивается с ря­ дом внутренних ветвей case.
Глава 5. Управляющие операторы 133 Та ким образом, можно выделить следующие важные особенности оператора switch. • Оператор switch отличается от условного оператора if те м, что в нем до­ пускается выполнять проверку только на равенство , тогда как в условном операторе i f можно вычислять результат логического выражения любого типа. Следовател ьно , в операторе swi tch обнаруживается совпаден ие вы­ ражения с константой только в одной из ветвей case. • Константы ни в одной из двух ветвей case того же самого оператора switch не могут иметь оди наковые значения. Безусловно, внутренний оператор swi tch и содержащий его внешний оператор switch могут иметь одинако­ вые константы в ветвях ca se. • Как правило, оператор switch действует эффективнее ряда вложенных ус­ ловных операторов i f. Последняя особенность представляет особый интерес , поскольку она позво­ ляет лучше понять принцип действия компилятора Java. Компилируя оператор swi tch, комп илятор Java будет проверять константу в каждой ветви case и соз­ давать "таблицу переходов", чтобы использовать ее для выбора ветви программы в за висимости от получаемого значения выражения. Поэтому в тех случаях, ког­ да требуется делать выбор среди большой группы значений, оператор swi tch бу­ дет выполняться значительно быстрее последовательности операто ров if-else. Ведь компилятору известно, что константы всех ветвей case имеют оди н и тот же тип, и их достаточно проверить на равенство значению выражения switch. В то же время компилятор не располагает подобными сведениями о длинном перечне выражений условного оператора if. Операторы цикла Для управления конструкциями, которые обычно называются циклами, в Java предоставляются операторы for, whi le и do -while. Вам, должно быть, известно, что циклы многократно выполняют один и тот же набор инструкций до тех пор, пока не будет уд овлетворено условие завершения цикла. Как станет ясно в даль­ ней шем, операторы цикла в Java способны удо влетворить любые потребности в программировании. Цикл while Оператор цикла while является самым основополагающим для организации циклов в Java. Он повторяет оператор или блок операторов до тех пор, пока зна­ чение его управля ющего выражения истинно. Этот оператор цикла имеет следую­ щую общую форму: while (услоаяе) { 11 тело цикла
13' Часть 1. Яэык Java где условие обозначает любое логическое выражение. Те ло цикла будет выпол­ няться до тех пор, пока условное выражение истинно . Когда условие становит­ ся ложным, управление передается строке кода, непосредственно следующей за циклом. Фигурные скобки могут быть опущены только в том случае, если в цикле повторяется лишь один оператор. В качестве примера рассмотрим цикл while, в котором выполняется обратный отсчет, начиная с 10, и выводится ровно 10 строк "тактов": 11 Продемонстрировать приме нение оператора цикла while class Wh ile { puЬlic static void main (String args []) intn=10; while(n > 0) { System. out . println ("т aкт " + n) ; n-- ; После запуска эта программа выводит десять "тактов" следующим образом: такт 10 такт 9 такт 8 такт 7 такт 6 такт 5 такт 4 такт З такт 2 такт 1 В начале цикла wh ile вычисляется условное выражение, поэтому тело цик­ ла не будет выполнено ни разу, если в самом начале условие оказывается лож­ ным. Например, в следующем фрагменте кода метод println () вообще не бу­ дет вызван: intа=10,Ь=20; while(a > Ь) System.o ut . println ("Этa строка выводиться не буде т ") ; Те ло цикла while (или любого другого цикла вJava) может быть пустым. Это обусловлено тем, что синтаксис Java допускает применение пус того оператора, со­ держащего только знак точки с запятой. Рассмотрим в качестве примера следую­ щую программу: 11 Целевая часть цикла може т быть пустой class NoBody { puЬl ic static void main (String args [] ) int i, j; i 100; j 200; 11 рассчитать среднее значе ние переменных i и j while (++i < --j) ; 11 у этого цикла отсутствует тело
Глава 5. Управляющие операторы 135 System.out . println ( "Cpeднe e значение равно " + i) ; В этой программе вычисляется среднее значение переменных i и j и выводит­ ся следующий результат: Среднее значение равно 150 Приведенный выше цикл while действует следую щим образом. Значение пере­ менной i увеличивается, а значение переменной j уменьшается на единицу. Затем эти два значения сравниваются. Если новое значение переменной i по-прежнему меньше нового значения переменной j, цикл повторяется . Если же значение пере­ менной i равно значению переменной j или больше него, то цикл завершается. После выхода из цикла переменная i будет содержать среднее исходных значений переменных i и j. (Безусловно, такая процедура оказывается работоспособной только в том случае, если в самом начале цикла значение переменной i меньше значения переменной j .) Как видите , никакой потребности в наличии тела цикла не существует. Все действия выполняются в самом условном выражении. В про­ фессионально написанной программе наJava короткие циклы зачастую не содер­ жат тела, если само по себе управля ющее выражение может выполнять все необ­ ходи мые действия. Ци кл do -while Как было показано выше, если в начальный момент условное выражение , управля ющее циклом while, ложно, то тело цикла вообще не будет выполняться . Но иногда тело цикла желательно выполнить хотя бы один раз , даже если в на­ чальн ый момент условное выражение ложно. Иначе говоря , возможны случаи , когда проверку условия прерывания цикла желательно выполнять в конце цикла, а не в начале. Для это й цели в Java предоставляется цикл, который называется do -while. Те ло этого цикла всегда выполняется хотя бы один раз, поскольку его условное выражение проверяется в конце цикла. Общая форма цикла do -while следую щая: do{ 11 тело цикл а while (ycлo.EIJIJ'e) ; При каждом повторении цикла do -while сначала выполняется тело цикла, а затем вычисляется условное выражение. Если это выражение истинно, цикл по­ втор яется. В противном случае выполнение цикла прерывается . Как и во всех ци­ клах в Java, заданное условие должно быть логическим выражением. Ниже приведена переделанная программа вывода тактов, в которой демон­ стрируется применение оператора цикла do-while. Эта версия программы выво­ дит такой же результат, как и ее предыдущая версия . 11 Продемонс трировать применение оператора ци кл а do-whi le cl ass DoWhile { puЫic static void main (String args[] ) intn=10;
136 Часть 1. Яэы к Java do{ System. out . println ("т aкт " + n) ; n- -; } while(n > 0); Формально цикл в приведенной выше программе организован правильно. Те м не менее его можно переписать и в более эффективной форме , как показано ниже . do{ System. out .println ("т aкт " + n) ; while(--n > О); В данном случае декремент переменной n и сравнение результирующего значе­ ния с нулем объединены в одном выражении ( - -n > О). Это выражение действует следую щим образом. Сначала выполняется операция декремента --n, уменьшая значение переменной n на еди ницу и возвращая новое значение переменной n, а затем это значение сравн ивается с нулем. Если оно больше нуля , то выполнение цикла продолжается, а иначе цикл завершается. Цикл do -while особенно уд обен при выборе пункта меню, поскольку в этом случае обычно требуется, чтобы тело цикла меню выполнялось, по меньшей мере , оди н раз. Рассмотрим в качестве примера следующую программу, в которой реали· зуется очень простая система справки по операторам выбора и цикла вJava : // Использовать оператор цикла do-while для выбора пункта ме ню class Menu { puЫ ic static void ma in ( String args[J ) throws java.io.IOException { char choice ; do{ Systern. o ut . println ("C пpaвкa по оператору: ") ; System. o ut . println (" 1. if") ; System. out .println (" 2. switch" ); System. out .printlп (" З. whil e") ; System.out . println (" 4. do-while"); System. out . println (" 5. for\n" ); Systern. o ut . println ("Bыбepитe нужный пун к т:") ; choice = (char ) System.in.read() ; while ( choice < '1' 1 1 choice > '5'); System.out . println ("\n") ; swi tch (choice ) { сазе '1' : System. o ut . println ("i f:\n ") ; System.out . println ("if (ycлoвиe) оператор;") ; System.out . println ("else оператор; ") ; break; сазе'2': Systern.out . println ("s witch:\n ") ; System.out . println ("s witch (выpaжeниe ) {") ; System.out . println (" сазе константа :") ; System.out . println (" последовательность операторов ") ; System.out . println (" break; ") ; System.out . println (" // ...") ; System.out . println ("}"); break; case'З': System.out . println ( "while :\n ") ;
Глава 5. Управляющие операторы 137 System. o ut . println ("w hile (ycлoвиe ) оператор ; " ); brea k; case '4' : System.out . println ( "do-while :\n ") ; System.out . println ("do {") ; System.out . println (" оператор; ") ; System.out . println ("} while (условие) ;") ; break; case '5' : System.out . println ("for :\n ") ; System.out . print ("for (инициaлиэaция; условие ; итерация} ") ; System.out . println (" оператор; ") ; break; Ниже приведен пример выполнения этой программы. Справка по оператору : 1. if 2. switch З. wh ile 4. do-while 5. for Выберите нужный пункт : 4 do-while : do{ оператор ; wh ile (условие ); В данной программе проверка допустимости введенного пользователем значе­ ния осуществляется в цикле do-while. Если это значение недопустимо, то поль­ зователю предлагается повторить ввод. А поскольку меню должно отобразиться хотя бы один раз, то цикл do-while оказывается идеальным средством для реше­ ния этой задач и. У данной программы имеется ряд других особенностей. В частности, для ввода символов с клавиатуры вызывается метод System . in . read (),выполняющий одну из фун кций консольного ввода в Java. Более подробно методы консольного ввода­ вывода рассматриваются в гл аве 13, а до тех пор следует заметить, что в данном слу­ ч ае метод Sys tem . in . read ( ) служит для получения результата выбора, сделанного пользователем. Этот метод вводит символы из стандартного потока ввода, откуда они возвращаются в виде целочисленных значений. Именно поэтому тип возвра­ щаемого значения приводится к типу char. По умолчанию данные из стандартно­ го потока ввода размещаются в буфере построчно, поэтому нужно нажать клавишу <Enter>, чтобы любые введенные символы бьши переданы программе. Ко нсольный ввод в Java может вызвать некото рые затруднения. Более то го, большинство реальных программ нaJava разрабатываются графическими и ориен­ тированными на работу в оконном режиме. Поэтому в данной книге консольному вводу удел яется не очень много внимания. Но в данном случае он уд обен. Следует также иметь в виду, что данная программа должна содержать выражение throws j ava . io . IOException, поскольку в ней используется метод System. in . read ( ) . Это выражение необходимо для обработки ошибок ввода и является составной ча­ стью системы обработки исключений вJava, которая рассматривается в гл аве 1 О.
138 Часть 1. Язык Java Цикп for Простая форма оператора цикла for была представлена в гл аве 2 , но, как будет показано ниже, это довольно эффективная и универсальная языковая конструк­ ция . Начиная с версии JDK 5, в Java имеются две формы оператора цикла for. Первая форма считается традиционной и появилась еще в исходной версии Java, а вторая - более новая форма цикла в стиле for each. В этом разделе рассматри­ ваются обе формы оператора цикла for, начиная с традиционной. Общая форма тради ционной разновидности оператора цикла for выглядит следующим образом: for (ЯNJЩJr&1М".Sa.ц1 1 •; ycл01Dte ; •!l'&pa.цl l •){ 11 тело Если в цикле повторяется выполнение только одного оператора, то фигурные скобки можно опустить. Цикл for действует следующим образом. Когда цикл начинается , выполняется его инициализ а ция. В общем случае это выражение, устанавливающее значение пере.мен :н ой управления ЦU'КЛО.М, которая действует в качестве счетчика, управляюще­ го циклом. Важно понимать, что первая часть цикла for, содержащая инициали­ зирующее выражение, выполняется только один раз. Затем вычисляется заданное условие, которое должно быть логическим выражением. Как правило, в этом вы­ ражении значение управляющей переменной сравнивается с целевым значением. Если результат этого сравнения истинный , то выполняется тело цикла. А если он ложный, то цикл завершается. И наконец, выполняется третья часть цикла for - итерация. Обычно эта часть цикла содержит выражение, в котором увеличива­ ется или уменьшается значение переменной управления циклом. Затем цикл по­ вторяется , и на каждом его шаге сначала вычисляется условное выражение, затем выполняется тело цикла, а после этого вычисляется итерационное выражение. Этот процесс повторяется до тех пор, пока результат вычисления итерационного выражения не станет ложным. Ниже приведена версия программы подсчета "тактов" , в которой применяется оператор цикла for. 11 Продемонстрировать приме не ние оператора цикла for class Fo rTick { puЫ ic static void rna in (String args [] ) { int n; for (n=lO; n>O ; n--) Systern.out . println ( "тaкт " + n) ; Объявление переменных, управляющих циклом for Зачастую переменная, управляющая циклом for, требуется только для него и нигде больше не используетс я. В таком случае переменную управления циклом можно объявить в инициализирующей части оператора for. Например, предыду­ щую программу можно переписать, объявив управляющую переменную n типа in t в самом цикле for.
Глава 5. Управляющие операторы 139 11 Объя вить переменную управления циклом в самом цикле for class ForT ick { puЫic static vo id ma iп (String args[]) 11 здесь переменная n объявляется в самом цикле for for(int n=lO; n>O; n--) System.out . println ("тaкт " + n) ; Объявляя переменную управления циклом for в самом цикле, не следует забы­ вать, что область и срок действия этой переменной пол ностью совпадают с обла­ стью и сроком действия оператора цикла for. Это означает, что область действия переменной управления циклом for ограничивается пределами самого цикла. А за пределами цикла for эта переменная прекращает свое существование. Если же переменную управления циклом for требуется использовать в других частях программы, ее нельзя объявлять в самом цикле. В тех случаях, когда переменная управления циклом f о r нигде больше не требу­ ется, большинство программирую щих нajava предпочитают объявлять ее в самом операторе цикла for. В качестве примера ниже приведена простая программа, в которой проверяется , является ли число простым. Обратите внимание на то , что переменная i управления циклом объявлена в самом цикле for, поскольку она нигде больше не требуется. 11 Проверить на простые числа class FindPrime { puЫ ic static void ma in (St ring args[] ) { int num; Ьoolean isPrime ; num:14; if (num < 2) isPrime = fal se; else isPrime true ; for(int i=2; i <= num/i; i++) { if((num%i)==0){ isPrime = false ; Ьreak; if (isPrime ) Sys tem.out . println ( "Пpocтoe число" ); else System. out .println ("He простое число") ; Испоnьэование запятой В ряде случаев требуется указать несколько операторов в инициализирующей и итерационной частях оператора цикла for. Рассмотрим в качестве примера цикл в следующей программе: class Sample { puЫic static void ma in (String args[] ) { int а, Ь; ь=4;
140 Часть 1. Язы к Java for (a=l; а<Ь ; а++ ) System.out . println ("a System . out .println ("b ь-- ; "+а); " +Ь); Как видите , управление этим циклом осуществляется одновременно двумя пе­ ременными. А поскольку цикл управляется двумя переменными, то их желатель­ но включить в сам оператор цикла for, а не выполнять обработку переменной Ь вручную . Правда, для решения этой задач и в Java предоставляется специальная возможность. Для того чтобы две переменные или больше могли управлять ци­ клом for, в Java допускается указывать несколько операторов как в инициализи­ рующей , так и в итерационной части оператора цикла for, разделяя их запятыми. Используя запятую, предыдущий ци кл for можно организовать более рацио­ нально: 11 Исполь зование запятой в операторе цикла for class Comma { puЫ ic static void ma in (String args [] ) int а, Ь; for (a=l, Ь=4; а<Ь; а++, Ь--) { System. out . println ("a "+а ); System.out . println ("b ="+ Ь) ; В да нном примере программы начальные значения обеих переменных, а и Ь, управления циклом for устанавливаются в инициализирующей части цикла. Оба разделя емых запятой оператора в итерационной части цикла выполня ются при каждом повторении цикла. Эта программа выводит следующий результат: а 1 ь4 а 2 ь3 На заметку! Те м, у кого имеется опыт програ ммирования на С/С++, должно быть известно, что в этих языках запятая обозначает операцию, которую можно использовать в любом допу­ сти мом выражении. А в Java запятая служит лишь в качестве раздел ителя. Разновидности ци кл а for У оператора цикла for имеется несколько разновидностей, расширяющих воз­ можности его применения. Ги бкость этого цикла объясняется тем , что три его части (инициализацию, проверку условий и итерацию) совсем не обязательно ис­ пользовать только по прямому назначению. По существу, каждую часть оператора цикла for можно применять в любых требующихся целях. Рассмотрим несколько примеров такого применения. В одной из наиболее часто встречающихся разновидностей цикла for пред­ полагается употребление условного выражен ия. В частности , в этом выражении
Глава 5. Управляющие операторы 1'1 совсем не обязательно сравнивать переменную управления циклом с некоторым целевым значением. По существу, условием, управляющим циклом for, может быть любое логическое выражение. Рассмотрим в качестве примера следующий фрагмент кода: bo olean done = false; for (int i=l ; !done ; i++) 11. " if ( interrupted ()) done = true ; В этом примере выполнение цикла for продолжается до тех пор, пока в пере­ менной done не установится логичес кое значение true. В этой разновидности цикла for не выполняется проверка значения в переменной i управления циклом. Рассмотримеще однуинтереснуюразновидностьцикла f о r.Инициализирующее или итерационное выражения или оба вместе могут отсутствовать в операторе цикла for, как показано ниже . 11 Отдель ные части оператора цикла for могут отсутствовать class ForVar { puЫic static void ma in ( String args [] ) { int i; boolean done = false; i=О; for( ; !done; ) { System. o ut . println ("i равно " + i) ; if(i == 10) done = true; i++ ; В дан ном примере инициализирующее и итерационное выражения вынесены за пределы цикла for. В итоге соответствующие части оператора цикла for ока­ зываются пустыми. В данном очень простом примере , де монстрирующем далеко не самый изящный стиль программирования, это не так важно, но иногда такой подход имеет смысл . Та к, если начальное условие определяется сложным выра­ жением где-то в другом месте программы или значение переменной управления циклом изменяется случай ным образом в зависимости от действий, выполняемых в теле ц икла, то эти части оператора цикла for имеет смысл оставить пусты ми. Приведем еще одну разновидность цикла for. Оставляя все три части операто­ ра пустыми , можно умышленно создать бесконечный цикл, т. е. такой цикл, кото­ рый никогда не завершается: for(;;) 1/ ... Этот цикл может выполняться бесконечно, поскольку отсутствует условие, по которому он мог бы завершиться. Если в некоторых программах вроде команд­ ного процессора операционной системы требуется наличие бесконечного цикла, то в больш инстве случаев "бесконечные" циклы на самом деле являются лишь ци­ клами с особыми условиями прерывания. Как будет показано ниже , существует
1'2 Часть1.ЯэыкJava способ прервать цикл (даже бесконечный, как в приведенном выше примере), не требующий указывать обычное условное выражение в цикле. Разновидность цикла for в стиле for each Начиная с версииJDК 5, вjava можно использовать вторую форму цикла for, ре­ ализующую цикл в стиле for each. Вам, вероятно, известно, что в современной те­ ории языков программирования все большее применение находит понятие циклов в стиле for each, которые постепенно становятся стандартными средствами во многих языках программирования. Цикл в стиле for each предназначен для стро­ го последовательного выполнения повторяющихся действий над коллекцией объ­ ектов вроде массива. В отличие от некоторых языков, подобных С#, где для реализа­ ции циклов в стиле for each используется ключевое слово foreach, вjava возмож­ ность организации такого цикла реализована путем усовершенствования цикла f or. Преимущество такого подхода состоит в том, что для его реализации не требуется дополнительное ключевое слово, а уже существующий код не нарушается. Цикл for в стиле for each называется также усовершенствованным циклом for. Общая форма разновидности цикла for в стиле for each имеет следующий вид: for(т11 1 пJ1 1 '1'ера ЦJ1 1 ан н ая_пвремен н &.1 1 :жол л вжц�wя) блож_ опвра'1'оро• где тип обозначает конкретный тип данных; итера ционна я_ переме нна я - имя итерациоиной переменной, которая последовательно принимает значения из коллек­ ции: от первого и до последнего: а коллекция - перебираемую в цикле коллекцию. В цикле for можно перебирать разные ти пы коллекций, но здесь для этой цели будут использоваться только массивы. (Другие типы коллекций, которые можно перебирать в цикле for, в том числе и те, что определены в каркасе коллекций Collection Fгamewoгk, рассматриваются в последующих гл авах.) На каждом шаге цикла из коллекции извлекается очередной элемент, который сохраняется в ука­ занной итера ционной_ переменной. Цикл выполняется до тех пор, пока из коллек­ ции не будут извлечены все эл ементы . Поскольку итерационная переменная получает значения из коллекции, тип должен совпадать (или быть совместимым) с типом элементов, хранящихся в кол­ лекции. Та ким образо м, при переборе массива тип должен быть совместим с ти­ пом элемента массива. Чтобы стали понятнее побудител ьные причины для применения циклов в сти­ ле for each, рассмотрим разновидность цикла for, для замены которого этот стиль предназначен. В следующем фрагменте кода для вычисления суммы значе­ ний элементов массива применяется традиционный цикл for: intnums[]={1,2,3,4,5,6,7,8,9,10}; intsum=О; for(int i=O; i < 10; i++) sum += nums [i]; Чтобы вычислить сумму, значения каждого элемента последовательно извлека­ ются из массива nшns . Та ким образом , чтение всего массива выполняется в стро­ гой последовательности. Это достигается благодаря индексации массива nums вручную по переменной i управления циклом.
Гл ава 5 . Управляю щ ие операторы 1'3 Цикл for в стиле for each позволяет автоматизировать этот процесс. В част­ ности , применяя такой цикл, можно не устанавливать значение счетчика цикла, указывать его начальное и ко нечное значения, а также индексировать массив вручную. Вместо этого цикл выполняется автоматически по всему массиву, после· довательно получая значения каждого его элементов: от первого до последнего. Например, предыдущий фрагмент кода можно переписать , используя вариант цикла for в стиле for each следую щим образом: intnums[]={1,2,3,4,5,6,7,8,9,10); intsum=О; for(int х: nums) sum += х; На каждом шаге цикла переменной х автоматически присваивается значение следующего элемента массива nums . Та ким образом, на первом шаге цикла пере­ ме нная х содержит значение 1, на втором шаге - значение 2 и т.д . Та кая разновид· ность цикла for не только упрощается синтаксис, но и исключает возможность ошибок, связанных с выходом за пределы массива. Ниже приведен полноценный пример программы, демонстрирующий приме­ нение описанной выше разновидности цикла for в стиле for each. 11 Приме нение цикла for в стиле for each class ForEach { puЫ ic static void ma in {String args [] ) { intnums[]={1,2,3,4,5,6,7,8,9,10}; intsum=О; 11 исполь зовать цил в стиле for each для вывода и 11 суммирования значений for(int х : nums) { System.out . println ("З нaчeниe равно : "+х) ; sum += х; System. out . println {"Cyм м a равна: " + sum); Эта программа выводит следую щий результат: Значение равно : 1 Значение равно : 2 Значение равно : 3 Значение равно : 4 Значение равно : 5 Значение равно : 6 Значение равно : 7 Значение равно : 8 Значение равно : 9 Значение равно : 10 Сумма равна : 55 Как следует из этого результата, оператор цикла for в стиле for each автома­ тически перебирает элементы массива, от наименьшего индекса к наибольшему. Повторение цикла for в стиле for each выполняется до тех пор, пока не будут пе­ ре браны все элементы массива, но этот цикл можно прервать и раньше, используя оператор break. В следующем примере программы суммируются значения пяти первых элементов массива nums:
1'4 Часть 1. Язы к Java 11 Приме нение оператора break в ци кле for в стиле for each class Fo rEach2 { puЫ ic static vo id rna iп (String args[] ) { intsurn=О; intnurns[]={1,2,3,4,5,6,7,В,9,10); 11 использовать цикл for в стиле for each для 11 вывода и суммирования значений из части ма ссива for(int х : nurns) { Systern.out . println ("З нaчeниe равно : "+х) ; surn+=х; if (x == 5) break; // прер вать цикл после 11 получения 5 значений Systern. out . println ("Cyм м a пяти первых элеме нтов равна : "+surn) ; Эта программа выводит следую щий результат: Значение равно : 1 Значение равно : 2 Значение равно : 3 Значение равно : 4 Значение равно : 5 Сумма пяти первых элементов равна : 15 Как видите , выполнение цикла прерывается после то го , как будет получено зна· чение пятого элемента массива. О ператор break можно использовать и в других операторах цикла, доступных вjava. Подробнее оператор break рассматривается далее в этой главе. Применяя цикл for в стиле for each, не следует забывать о то м, что его ите· рационная переменная доступна "только для чтения", поскольку она связана толь· ко с исходн ым массивом. Присваивание значения итерационной переменной не оказывает никакого влияния на исходный массив. Иначе говоря, содержимое мас­ сива нельзя изменить, присваивая новое значение итерационной переменной. Рассмотрим в качестве примера следующую программу: 11 Переменная цикла в стиле for each доступна толь ко для чтения class NoChange { puЫ ic static vo id rna in (String args[]) { intnurns[]={1,2,3,4,5,6,7,В,9,10}; for(int х : nurns) { Systern. out .print (x + " ") ; х = х * 10; 11 этот о ператор не оказывает никакого 11 вли яния на ма ссив nums Systern. out . println () ; for(int х : nurns) Systern.out .print (x +" ") ; Systern.out . println (} ; В первом цикле for значение итерационной переменной увеличивается на 10. Но это присваивание не оказывает никакого вл ияния на исходный массив nums ,
Глава 5. Управля ющие операторы 1д5 ка к видно из результата выполнения второго оператора for. Следующий резуль­ тат, выводимый данной программой , подтверждает сказанное: 1234567в910 1234567в910 Итерация в многомерных массивах Ус овершенствованная разновидность цикла for рас пространяется и на много­ мерные массивы. Но не следует забывать, что в Java многомерные массивы пред­ ставляют собой массивы массивов. Например, двухмерный массив - это массив од­ номерных массивов. Это обстоятельство важно иметь в виду при переборе много­ мерного массива, поскольку результатом каждой итерации оказывается следующий массив, а не отдел ьный элемент. Более того, тип итерационной переменной цик­ ла for должен быть совместим с типом получаемого массива. Например , в двух­ мерном массиве итерационная переменная должна быть ссылкой на одномерный массив. В общем случае при использовании цикла в стиле for each для перебора массива размерностью N получаемые в итоге объекты будут массивами размерно­ стью N-1. Чтобы стало понятнее, что из этого следует, рассмотр им следую щий пример програм мы, где вложенные циклы for служат для получения упорядочен­ ных по строкам элементов двух мерного массива: от первого до последнего: 11 Применение цикла for в стиле for each дл я 11 обраще ния к двухмерному ма ссиву class Fo rEachЗ { puЫic static void ma in (String args[]) intsurn=О; int nurns[][] = new int[З][5]; 11 присвоить значение элеме нтам ма ссива DUl ll S for(inti=О;i<3;i++) for(intj=О;j<5;j++) nurns [i] [j] (i+l) * (j+l) ; 11 исполь зовать цикл for в стиле for each для 11 вывода и суммирования значе ний for(int х[] : nurns) { for(intу:х){ Systern. o ut . println ( "Знaчeниe равно : " + у) ; surn += у; Systern.out . println ("C yм мa :"+surn); Эта программа выводит следующий результат: Значение равно : 1 Значение равно : 2 Значение равно : З Значение равно : 4 Значение равно : 5 значение равно : 2 З начение равно : 4 З начение равно : 6 Значение равно : 8 Значение равно : 10
146 Часть 1 . Язык Java Значение равно : З Значение равно : 6 Значение равно : 9 Значение равно : 12 Значение равно : 15 Сумма : 90 Следующая строка кода из данной программы заслуживает особого внимания: for(int х[] : nums) { Обратите внимание на порядок объявления переменной х. Эта переменная яв­ ляется ссылкой на одномерный массив целочисленных значений. Это необходи­ мо, потому что результатом выполнения каждого шага цикла for является следую­ щий массив в массиве nums , начиная с массива, обозначаемого элементом n ums [О] . Затем каждый из этих массивов перебирается во внутреннем цикле for, где выво­ дится значение каждого элемента. П рименени е усовершенст вованного ци кла for Каждый оператор цикла for в стиле for each позволяет перебирать элементы массива только по очереди, начиная с первого и оканчивая последним, и поэтому может показаться, что его применение огран ичено. Но на самом деле это не так. Ведь именно такой механизм требуется во многих алгоритмах. Одн им из наибо­ лее часто применяемых является алгоритм поиска. В приведенном ниже примере программы цикл for использует для поиска значения в неупорядоченном массиве. Поиск прекращается после обнаружения искомого значения. 11 Поиск в ма ссиве с приме нением цикла for в стиле for each class Sea rch { puЫ ic static void ma in (String args[] ) { intnums[]={6,8,З,7,5,6,1,4); intval=5; boolean found = false; 11 исполь зовать цикл for в стиле for each для 11 поиска значения переменной val в ма ссиве nums for(int х : nums) { if(x == val) { found = true ; break; if( found) System.out . println ( "Знaчeниe найдено !"); В данном случае выбор стиля for each для организации цикла for полностью оправдан , поскольку поиск в неупорядоченном массиве п редполагает последо­ вател ьный просмотр каждого эл емента. (Безусловно, есл и бы массив был упоря­ доченным, можно было бы организовать двоичный поиск, реализация которого потребовала бы применения цикла другого стиля .) К другим примерам, где при­ менение циклов в стиле for each дает заметные преимущества, относятся вычис­ ление среднего значения, отыскание минимального или максимального значения в множестве , поиск дубликатов и т. п.
Глава 5. Управляющие операторы 1'7 Несмотря на то что в примерах, приведенных ранее в этой гл аве, были ис­ пользованы массивы, цикл for в стиле for each особенно удобен при обращении с коллекциями, определенными в каркасе Collections Framework, описываемом в части 11 данной книги . В более общем случае оператор for позволяет перебирать элементы любой коллекции объектов, если эта коллекция удовлетворяет опреде­ ленному ряду ограничений, описываемых в гл аве 18. Вложенны е ци клы Как и в других языках программирования , в Java допускается применение вло­ женных циклов. Это означает, что один цикл может выполняться в другом. В сл е­ дующем примере программы используются вложенные циклы for: 11 Циклы могут быть вл оже нными class Ne sted { puЫ ic static void ma in (String args [] ) { int i, j; for (i=O; i<lO; i++) { for (j=i; j<lO; j++) System.out . print (".") ; System.out . println (); Эта программа выводит следующий результат: Операторы перехода В языке Java определены три оператора перехода: break, cont inue и return. Они передают непосредственное управление другой части программы . Рассмотрим каждый из них в отдел ьности. на заметку! Кроме операторов перехода, рассматриваемых в этом разделе, в Java поддерживает­ ся еще оди н способ изменения порядка выполнения инструкций программы, который состо­ ит в обработке искл ючений. Обработка исключений предоставляет структурированный ме­ ха низм, с помощью которого можно обнаруживать и обрабаты вать ошибки во время выпол­ нения программы. Для поддержки этого механизма служат ключевые слова try, catch, throws и finally. По существу, механизм обработки исключен ий позволяет выполнять нелокальные ветви программы. Впрочем , тема обработки исключений настолько обширна, что она рассматривается отдел ьно в специально посвященной ей гл аве 10.
1'8 Часть 1. Язы к Java П рим енение о ператор а break В языке Java оператор break находит три применения. Во-первых, как было показано ранее , он завершает последовательность операторов в операторе switch. Во-вторых, его можно использовать для выхода из цикла. И в-третьих, этот оператор можно применять в качестве "цивилизованной" формы операто­ ра безусловного перехода goto. Рассмотрим два последних применения данного оператора. Испол ьзование оператора break для выхода из цикла Используя оператор break , можно вызвать немедленное завершение цикла, пропуская условное выражение и любой остальной код в теле цикла. Когда в теле цикла встречается оператор break , выпол нение цикла прекращается и управле­ ние передается операто ру, следующему за циклом. Ниже приведен простой тому пример. 11 Приме нение оператора break для выхода из цикла class Brea kLoop { puЬlic static void maiп (String args[] ) for ( int i=O; i<lOO; i++) { if (i == 10) break; // выход из цикла, если значение 11 переменной i равно 10 System.out . println ("i: "+i) ; System. out . println ("Цикл заверше н.") ; Эта программа выводит следующий результат: i:о i:1 i:2 i:з i:4 i:5 i:6 i:7 i:8 i:9 Цикл завершен . Как видите , оператор break приводит к более раннему выходу из цикла for, когда значение переменной i становится равным 10, хотя цикл for должен был бы выполняться при значениях переменной управления цикла в пределах от О до 99. Оператор break можно использовать в любых циклах, доступных вjava, вклю­ чая преднамеренно бесконечные циклы. Та к, ниже приведен переделанный вари­ ант предыдущего примера программы где применяется цикл whi le. В этом вари­ анте программа выводит такой же результат, как и в предыдущем. 11 Приме нение оператора break для выхода и з цикла while class BreakLoop2 { puЫ ic static vo id ma in (String args [J ) { inti=О;
Гл ава 5. Управляющие операторы 149 while(i < 100) { } if (i == 10) break; // выход из цикла, если значение // переме нной i равно 10 System.out . println ("i: "+i) ; i++; System. out .println ( "Цикл завершен ."); Если в программе применяется ряд вложенных циклов, то оператор break осу­ ществляет выход только из самого внутреннего цикла. Ниже приведен характер­ ный тому пример. // Приме не ние оператора break во вложенных ци клах class BreakLoopЗ { puЬlic static void main (String args[] ) for (int i=O; i<З; i++) { System.out.print("Пpoxoд " + i + ": "); for (int j=O; j<lOO; j++) { if (j == 10) break; // выход из цикла , значение 11 переменной j равно 10 System. out .print (j +"" ) ; } System.out . println (); System.out . println ("Циклы завершены. ") ; Эта программа выводит следующий результат: ПроходО:О12З456789 Проход1:О12З456789 Проход2:О12З456789 Циклы завершены . Как видите, оператор break во внутреннем цикле может приводить к выходу то лько из этого цикла. А на внешний цикл он не оказывает никакого вл ияния. Используя оператор break, необходимо помнить следующее. Во-первых, в ци­ кле можно использовать больше одного оператора break. Но при этом следует со­ блюдать осторожность. Как правило , применение слишком большого количества операторов break приводит к нарушению структуры кода. Во-вторых, оператор break, завершающий последовательность операторов, выполняемых в операторе switch, оказывает вл ияние только на данный оператор swi tch, но не на любые содержащие его циклы. Помните! Оператор brea k не предназначен в качестве обычного средства выхода из цикла. Для этого служит условное выражение в ци кле. Этот оператор следует испол ьзовать для вы· хода из цикла только в особых случаях. Применение о ператора break в ка ч естве форм ы оператора qoto Оператор break можно применять не только в операторах swi tch и циклах, но и самостоятельно в качестве "цивилизованной" формы оператора безусловного
150 Часть 1. Язы к Java перехода goto. В языке jаvа оператор goto отсугствует, поскольку он позволяет выполнять ветвление программ произвольным и неструктурированным образом. Как правило, код, который управляется оператором goto, труден для понимания и сопровождения. Кроме того , этот оператор исключает возможность оптимизи­ ровать код для определенного компилятора. Но в некоторых случаях оператор goto оказывается удобным и вполне допустимым средством для управления по­ током исполнения команд. Например, оператор goto может оказаться полезным при выходе из ряда гл убоко вложенных циклов. Для подобных случаев вJava опре­ делена расширенная форма оператора break. Используя эrу форму, можно , напри­ мер, организовать выход из одного или нескольких блоков кода. Они совсем не обязательно должны быть частью цикла или оператора swi tch, но могут быть лю­ быми блоками кода. Более того , можно точ но указать оператор, с которого будет продолжено выполнение программы, поскольку данная форма оператора break наделена метками. Как будет показано ниже, оператор break с меткой предостав­ ляет все преимущества оператора goto, не порождая присущие ему недостатки. Общая форма оператора break с меткой имеет следующий вид: break ме!l'жа ; Чаще всего ме тка - это имя метки , обозначающее блок кода. Им может быть как самостоятельный блок кода, так и целевой блок другого оператора. При вы­ полнении этой формы оператора breakуправление передается блоку кода, поме­ ченному меткой. Та кой блок кода должен содержать оператор break, но он не обя­ зател ьно должен быть непосредственно объемлющим его блоком. В частности , это означает, что оператор break с меткой можно применять для выхода из ряда вложенных блоков. Но его нельзя использовать для передачи управления внешне­ му блоку кода, который не содержит данный оператор break. Чтобы пометить блок, достаточно поместить в его начале метку. Метка - это любой допусти мый вJava идентификатор с двоеточием. Как только блок помечен, его метку можно использовать в качестве адресата для оператора break. В итоге выполнение программы будет продолжено с конца помеченного блока. Та к, в при­ веденном ниже примере программа содержит три вложенных блока, каждый из которых помечен отдел ьной меткой. Оператор break осуществляет переход в ко­ нец блока с меткой second, пропуская два вызова метода pri ntln (). 11 Приме нение оператора break в качестве цивилизованной 11 формы оператора qoto class Break { puЬlic static void ma in (String args[] ) { boolean t = true; first : { second : { third : { System.out . println ("Пpeдшecтвyeт оператору break. ") ; if(t) break second; // выход из блока second System. out .println ( "Этoт оператор не будет выполняться") ; System.out . println ( "Этoт оператор не будет выполнят ься" ); System.out . println ( "Этoт оператор следуе т за блоком second. ") ;
Глава 5. Управляющие операторы 151 Эта программа выводит следующий результат: Предшествуе т оператору break . Этот оператор следует за блоком second . Одним из наиболее распространенных применений оператора break с меткой служит выход из вложенных циклов. Та к, в следующем примере программы внеш­ ний цикл выполняется только один раз: // Применение оператора break для выхода из вложенных циклов class BreakLoop 4 { puЬ lic static void ma in ( String args [] ) outer: for(int i=O; i<З; i++) { System.out . print ("Пpoxoд " + i + ": ") ; for (int j=O; j<lOO; j++) { ) if (j == 10) break outer; // выход из обоих ци кл ов System. out .print (j +" ") ; System.out . println ("Этa строка не буде т выводиться") ; System. out . println ("Циклы завершены. ") ; Эта программа выводит следующий результат: ПроходО:О123456789Циклызавершены. Как видите , когда выполняется выход из внутреннего цикла во внешний цикл, это приводит к завершению обоих циклов. Следует иметь в виду, что нельзя вьшол­ нить переход к метке , если она не определена для объемлющего блока кода. Та к, в следующем примере программа содержит ошибку, и поэтому скомпилировать ее нельзя: // Эта программа содержит ошибку class BreakErr { puЬlic static void ma in (St ring args (J ) one: for(int i=O; i<З; i++) { System.out.print("Пpoxoд " + i + "· "); for (int j=O; j<lOO; j++) { if(j == 10) break one; // ОШИБКА! System. out .print (j +" ") ; Блок кода, помеченный меткой one , не содержит оператор break, и поэтому передача управления этому внешнему блоку невозможна. П рименение опе рато ра continue Иногда требуется , чтобы повторение цикла осуществлялось с более раннего оператора в его теле. Это означает, что на данном конкретном шаге может воз-
152 Часть 1. Яэык Jаvа никнуть потребность продолжить выполнение цикла без выполнения остального кода в его теле. По существу, это означает переход в теле цикла к его окончанию. Для выполнения этого действия служит оператор continue. В циклах wh ile и do­ wh ile оператор continue вызывает передачу управления непосредственно услов­ ному выражению, управляющему циклом. В цикле for управление передается вна­ чале итерационной части цикла for, а затем условному выражению. Во всех трех видах циклов любой промежуточный код пропускается. Ниже приведен пример программы, в которой оператор continue использует­ ся для вывода двух чисел в каждой строке. 11 Продемонстрировать применение оператора continue class Continue { puЫ ic static void ma in (String args []) for (int i=O; i<lO; i++) { System. out .print (i +"") ; if (i%2 == 0) continue ; System.out . println ("") ; В данном примере кода оператор % служит для проверки четности значения переменной i. Если это четное значение, то выполнение цикла продолжается без перехода к новой строке. Эта программа выводит следующий результат: о1 23 45 67 89 Как и оператор break, оператор cont inue может содержать метку объемлюще­ го цикла, который нужно продолжить. Ниже приведен пример программы , в ко­ торой оператор cont inue применяется для вывода треугольной таблицы умноже­ ниячиселотОдо9. 11 Приме нение оператора continue с ме ткой class Cont inueLabel { puЫ ic static void main (String args[J ) outer: for (int i=O; i<lO; i++) { for (int j=O; j<lO; j++) { if(j>i){ System. out . println () ; continue outer; Systern.out . print (" "+ (i * j)); } Systern.out . println (); В данном примере оператор cont inue прерывает цикл подсчета значений пе­ ременной j и продолжает его со следующего шага цикла, в котором подсчитыва­ ются значения переменной i. Эта программа выводит следующий результат:
о о1 о2 о3 о4 о5 о6 о7 о8 о9 4 69 81216 101520 121824 142128 162432 182736 25 30 36 354249 404856 455463 64 72 81 Гл ава 5 . Управляющ ие операторы 153 Уд ачные примеры применения оператора cont inue встречаются редко . Это объясняется , в частности , те м, что в Java предлагается широкий выбор операторов цикла, удовлетворяющих требованиям большинства приложений. Но в тех случаях, когда требуется более раннее начало нового шага цикла, опе­ ратор con tinue предоставляет структурированный способ решения данной задач и. Оператор re tu rn Последним из рассматриваемых здесь управляющих операторов является return. Он служит для выполнения явного выхода из метода, т. е . снова передает управление объекту, который вызвал данный метод. Как таковой, этот оператор относится к операторам перехода. Полное описание оператора return придется отложить до рассмотрения методов в гл аве 6, тем не менее, здесь уместно предста­ вить хотя бы вкратце его особенности. Оператор return можно использовать в любом месте метода для возврата управления тому объекту, который вызвал данный метод. Следовател ьно , опера­ тор return немедленно прекращает выполнение метода, в теле которого он нахо­ дится, что и демонстрирует приведенный ниже пример. В данном примере выпол­ нение оператора return приводит к возврату управления исполняющей системе Java, поскольку именно она вызывает метод ma in (). // Продемонс трировать приме нение оператора return class Re turn { puЬlic static vo id rna in (String args[] ) boolean t = true; Systern . out .println ( "Дo возврата ."); if (t) return ; // возврат в вызывающий код Systern. o ut . println ( "Этoт оператор выполняться не будет ."); Эта программа выводит следующий результат: До возврата . Как видите , заключительный вызов метода println () не выполняется . Сразу после выполнения оператора return управление возвращается вызывающему объекту.
15' Часть 1. Язы к Java И наконец, в приведенном выше примере программы наличие оператора i f ( t) обязател ьно. Без него компилятор Java сигнализировал бы об ошибке типа "unreachaЫe code " (недостижимый код) , поскольку он выяснил бы, что послед­ ний вызов метода println () вообще не будет выполняться. Во избежание подоб­ ной ошибки в данном примере пришлось прибегнуть к помощи оператора i f, что­ бы ввести компилятор в заблуждение.
6 Введение в классы Класс - это элемент, составляющий основу Java. А поскольку класс определяет форму и сущность объекта, то он является той логической конструкцией, на осно­ ве кото рой построен весь языкjаvа. Как таковой, класс образует основу объектно­ ориентированного программирования нajava. Любое понятие, которое требуется реализовать в программе нajava, должно быть инкапсулировано в пределах класса. В связи с тем, что класс играет такую основополагающую роль вjava, эта и не­ сколько последующих гл ав посвящены исключительно классам. В этой гл аве пред­ ставлены основные элементы класса и поясняется, как пользоваться классом для создания объектов. Здесь также представлены методы , конструкторы и клю­ чевое слово this. Основы классов Классы употреблял ись в примерах программ едва ли не с самого начала книги. Но в приведенных до сих пор примерах демонстрировалась только самая прими­ тивная форма класса. Классы в этих примерах служили только в качестве контей­ неров для метода main ( ) , который предназначался гл авн ым образом для ознаком­ ления с основами синтаксиса Java. Как станет ясно в дальнейшем, классы предо­ ставляют значительно бол ьше возможностей , чем те, кото рые использовались в рассматривавшихся до сих пор примерах. Вероятно, наиболее важная особенность класса состоит в том, что он опре­ деляет новый ти п данных. Как только этот новый тип данных будет определен , им можно воспользоваться для создания объектов данного типа. Та ким образом, кл асс - это шаблО'Н для создания объекта, а объект - это экземпляр класса. А по­ скольку объект является экземпляром класса, то понятия обоек т и :жзе.мпляр упо­ требляются как синонимы. Общая форма класса При определении класса объявляется его кон кретная форма и сущность. Для этого указываются данные, которые он содержит, а также код, воздействую­ щий на эти данные. И хотя очень простые кл ассы могут содержать только код или только данные, большинство классов, применяемых в реальных программах , со-
156 Часть1.ЯзыкJava держит оба ко мпонента. Как будет показано далее , код класса определяет интер­ фейс с его данными. Для объявления класса служит ключевое слово class. Уп оминавшиеся до сих пор классы на самом деле представляли собой очень ограниченные примеры пол­ ной формы их объявления. Классы могут быть (и обычно являются) значительно более сложн ыми. Упрощенная общая форма определения класса имеет следую­ щий вид: class мня ж.пасса { 'VSП перенен н ая ЭlСЭенпляра l; 'l'Иn перенен н а1 1 - эжэенпляр а2 ; 11... - 'l'ИП перенен н а1 1 эжэенпляраN; 'l'lltП 1IOUl HS'l'Oдal (CПJrCOJC napaнe!Z'JIOB) 11 тело ме тода - 'l'11tn 110U1 нетода2 (сп11 1 сож паране!Z'JЮВ) 11 тело ме тода - } // ... 'l'11tn юц не'l'одаN (сп11 1 сож паране!Z'J>Ов) 11 тело ме тода - Данные, или переменные, определенные в классе, называются перемен н:ыми !Ж­ зем пляра. Код содержится в теле методов. Вместе с переменными экземпляра ме­ тоды , определенные в классе, называются •1.1 1еН амu класса. В большинстве классов действия над переменными экземпляра и доступ к ним осуществляют методы, определенные в этом классе. Та ким образом, именно методы, как правило, опре­ деляют порядок использования данных класса. Как упо миналось выше, переменные, определенные в классе, называются пе­ ременными экземпляра, поскольку каждый экземпляр класса ( т. е . каждый объект класса) содержит собственные копии этих переменных. Та ким образом, данные одного объекта отделены и отличаются от данных другого объекта. Мы еще вер­ немся к рассмотрению этого понятия, но оно настолько важное, что его следовало ввести как можно раньше и пояснить хотя бы кратко. Все методы имеют ту же общую форму, что и метод ma in (), который употре­ блялся в рассматривавшихся до сих пор примерах. Но большинство методов ред­ ко объявляются как static или puЫic. Обратите внимание на то , что в общей форме класса отсугствует определение метода ma in ().Классы jаvа могут и не со­ держать этот метод. Его обязательно указывать только в тех случаях, когда данный класс служит отп равной точкой для выполнения программы. Более того , в неко­ торые видах приложенийjаvа вроде аплетов метод ma in ( ) вообще не требуется. Простой класс Итак , начнем рассмотрение классов с простого примера. Ниже приведен код класса Вох (Параллелепипед) , который определяет три переменные экземпляра: width (ширина), height (высота) и depth (глубина) . В настоящий момент класс Вох не содержит никаких методов (но в дальнейшем они будут введены в него).
class Вох { douЫ e width; douЫ e height ; douЫ e depth; Гл ава 6. Введение в кл ассы 157 Как пояснялось выше, класс определяет новый тип данных. В данном случае новый тип дан ных называется Вох. Это имя будет использоваться для объявления объектов типа Вох. Не следует забывать, что объя вление с 1 а s s создает только ша­ блон, но не конкретный объект. Та ким образом, приведенный выше код не приво­ дит к появлению каких-н ибудь объектов типа Вох. Чтобы действительно создать объект класса Вох, нужно воспользоваться опе­ ратором наподобие следующего : Вох myb ox = new Вох() ; // создать объект mybox класса Вох После выполнения этого оператора объект myb ox станет экземпляром класса Вох. Та ким образом, он обретет "физическое" существование. Оставим пока что без внимания особенности выполнения этого оператора. Напомним, что всякий раз , когда получается экземпляр кл асса, создается объ­ ект, кото рый содержит собственную копию каждой переменной экземпляра, определенной в данном классе. Та ким образом, каждый объект класса Вох будет содержать собственные копии переменных экземпляра width, height и depth. Для доступа к этим переменным служит операция-тачка (.). Эта операция связы­ вает имя объекта с именем переменной экземпляра. Например, чтобы присвоить пере менной width экземпляра myb ox значение 100, нужно выполнить следующий оператор: mybox .width = 100; Этот оператор предписывает компилятору, что копии переменной width, хранящейся в объекте mybox, требуется присвоить значение 100. В общем, опе­ ра ция-точка служит для доступа как к переменным экземпляра, так и к методам в пределах объекта. Следует также иметь в виду, что в формальной спецификации языкаJаvа точка (.) относится к категории разделителей , несмотря на то , что она обозначает операцию-точ ку. Но пос кольку термин операция-тачка широко распро­ стр анен , то он употребляется и в этой книге. Ниже приведен полноценный пример программы, в которой используется кл асс Вох. /* Программа , исnоль зующая кл асс Вох Прис воить исходному файлу имя BoxDeшo .java */ class Вох { douЫ e width; douЫ e height ; douЫ e depth; // В этом кла ссе объя вляется объе кт тиnа Вох cl ass BoxDemo { puЫ ic static void main (String args[] ) { Вох mybox = new Вох();
158 Часть 1. Язы к Java douЫe vol; // присвоить значение переменным экземпляра шуЬох mybox .width = 10; mybox .height = 20; mybox .depth = 15; // рассчитать объем параллелепипеда vol = mybox .width * mybox .height * mybox .depth; System. out . println ("Oбъeм равен " + vol) ; Файлу этой программы следует присвоить имя BoxDemo . j ava, поскольку метод ma i n () определен в классе BoxDemo , а не Вох. После компиляции этой програм­ мы вы обнаружите , что в конечном итоге были созданы два файла с расширением . class: один - для класса Вох, другой - для класса BoxDemo . Компилятор Java ав­ томатически размещает каждый класс в отдел ьном файле с расширением . class. На самом деле классы Вох и BoxDemo совсем не обязательно должны быть объяв­ лены в одном и том же исходном файле. Каждый из этих классов можно было бы разместить в отдел ьном файле под именем Box. java и BoxDemo .ja va соответ­ ственно. Чтобы запустить эту программу на выполнение, следует выполнить файл BoxDemo . class. В итоге будет получен следующий результат: Объем равен 3000 .О Как пояснялось ранее, каждый объект содержит собствен ные копии перемен­ ных экземпляра. Это означает, что при наличии двух объектов класса Вох каждый из них будет содержать собствен ные копии переменных depth, width и height. Следует, однако , иметь в виду, что изменения в переменных экземпляра одного объекта не влияют на переменные экземпляра другого. Например, в следующей программе объявлены два объекта класса Вох: 11 В этой программе объявляются два объе кта класса Вох class Вох { douЫe width ; douЫe height; douЫ e depth ; class BoxDemo2 { puЫ ic static void main ( String args [] ) { Вох mybox l = new Вох() ; Вох mybox2 = new Вох(); douЫe vol; 11 присвоить значения переменным экземпл яра myЬoxl mybox l.w idth = 10; myboxl . height = 20; myboxl .depth = 15; //* присвоить другие значения переменным экземпляра шуЬох2 */ mybox2 .width = З; mybox2 . height = б; mybox2 .depth = 9;
Гл ава 6. Введение в кл ассы 159 11 рассчитать объем первого параллелепипеда vol = myboxl .width * mybo xl . height * mybo xl .depth; System. out .println ("Oбъeм равен "+vol) ; // рассчитать объем второго параллелепипеда vol = mybox2 . width * myb ox2 .height * myb ox2 .depth ; System. out . println ( "Oбъeм равен "+vo l) ; Эта программа выводит следующий результат: Объем равен 3000 .0 Объем равен 162.О Как видите , данные из объекта myb ox 1 полностью изолированы от данных, со­ держащихся в объекте mybox2. Объявление объектов Как отмечалось ранее , при создан ии класса создается новый тип данных, ко­ торый можно использовать для объявления объектов данного типа. Но созда ние объектов класса представляет собой двухэтапный процесс. Сначала следует объ­ явить переменную типа класса. Эта переменная не определяет объект. Она явля­ ется лишь переменной, которая может ссылатъся на объект. Затем нужно получить конкретную, физическую копию объекта и присвоить ее этой переменной. Это можно сделать с помощью оператора new. Этот оператор динамически (т.е . во вре мя выполнения ) резервирует память для объекта и возвращает ссылку на него. В общих чертах эта ссылка представляет собой адрес объекта в памяти , зарезер­ вированной оператором new. Затем эта ссылка сохраняется в переменной. Та ким образом, оперативная память должна динамически выделяться для объектов всех классов вJava. Рассмотр им эту процедуру более подробно. В приведенном ранее примере программы строка кода, аналогичная приведен­ ной ниже , служит для объявления объекта типа Вох. Вох mybox = new Вох(); В этой строке кода объединяются оба этапа только что описанного процесса. Чтобы каждый этап данного процесса стал более очевидным, приведенную выше строку кода можно переписать следующим образом: Вох mybox ; // объявить ссылку на объект mybox = new Вох() ; // выделить памя ть дл я объе кта Вох В первой строке приведенного выше фрагмента кода переменная mybox объ­ является как ссылка на объект типа Вох. В данный момент переменная mybox пока еще не ссылается на конкретный объект. В следующей строке кода вьщеляется па­ мя ть для конкретного объекта, а переменной mybox присваивается ссьшка на этот объе кт. После выполнения второй строки кода переменную mybox можно исполь­ зовать так , как если бы она бьша объектом типа Вох. Но в действительности пе­ ременная mybox просто содержит адрес памяти конкретного объекта типа Вох. Результат выполнения этих двух строк кода показан на рис. 6. 1 .
160 Часть 1. Язы к Java Оператор Объект типа Вох; mybox newBox () Результат D mybox " Width mybox He ight Depth Объекттипа Вох Рис 6.1 . Объя вление объекта ти па Вох На заметку! Те м, у кого имеется опыт программирования на С/С++, вероятно, известно, что ссыл­ ки на объекты подобны указателям. В цел ом это верно. Ссылка на объект похожа на ука­ затель в памяти. Основное их отличие и в то же время гл авное средство, обеспечивающее безопасность програ мм на Java , состоит в то м, что ссылками нел ьзя манипулировать, ка к настоя щи ми указателями. В частности, ссылка на объект не может ука зывать на произволь­ ную ячейку памяти , и ею нельзя манипулировать ка к целочисленным значением. Подробное рассмотрение оператора new Как отмечалось ранее, оператор new динамически выделяет оперативную па­ мять для объекта. Общая форма этого оператора имеет следующий вид: перенен н аR_жла сса = new •Нl l _ жласса () ; где переменна я_ кл асса обозначает переменную создаваемого класса, а имя_ кл асса - конкретное имя класса, экземпляр которого получается. Имя класса, за которым следуют круглые скобки , обозначает конструктор данного класса. Конструктор определяет действия, выполняемые при создании объекта класса. Конструкторы являются важной частью всех классов и обладают множеством важных свойств. В большинстве классов, используемых в реальных программах, явно объявляются свои конструкторы в пределах определения класса. Но если ни один из явных конструкторов не указан, то в Java будет автоматически предостав­ лен конструктор по умолчанию. Именно это и происходит при создании объекта класса Вох. В рассматриваемых здесь примерах будет пока еще употребляться кон· структор по умолчанию, а в дальнейшем будет показано , как определять собствен­ ные конструкторы. В связи с изложенным выше может возникнуть вопрос: почему оператор new не требуется для таких типов данных, как целочисленные или символ ьные? Это объясняется тем, что примитивные типы данных вJava реализованы не в виде объ· ектов, а как "обычные" переменные. И сделано это ради повышения эффектив­ ности . Как будет показано далее , объекты обладают многими свойствами и сред-
Глава 6. Введение в классы 161 ствам и, которые требуют, чтобы они трактовались вJava иначе, чем примитивные ти пы. Отсугствие таких же издержек, как и на обращение с объектам и, позволяет эффективнее реализовать примитивные ти пы дан ных. В дальнейшем будут пред­ ставлены объектные версии примитивных типов , которые моrут пригодиться в тех случаях, когда требуются полноценные объекты этих типов. Однако оператор new выделяет оперативную память для объекта во время вы­ полнения. Преимущество такого подхода состоит в то м, что в программе можно создать ровно столько объектов, сколько требуется во время ее выполнения. Но п оскольку объем оперативной памяти ограничен , возможны случаи, когда опера­ тор new не в состоянии выделить память для объекта из-за ее нехватки. В таком случае возникает исключение времени выполнения. (Более подробно обработка исключений рассматривается в гл аве 10.) В примерах программ, приведенных в этой книге, недостатка в объеме памяти не возникает, но в реал ьных программах такую возможность придется учитывать. Еще раз рассмотрим отличие класса от объекта. Класс создает новый тип дан­ ных, который можно использовать для создания объектов. Это означает, что класс создает логический каркас , определяющий взаимосвязь между его членами. При объявлении объекта класса создается экземпляр этого класса. Таким образом, класс - это логическая конструкция , а объект имеет физическую сущность, т. е . он занимает конкретную область операти вной памяти . Об этом отличии важно пом­ нить. Присваи вание переменным ссылок на объекты При присваивании переменные ссылок на объекты действуют иначе, чем мож­ но было бы предположить. Например, какие действия выпол няет приведен ный ниже фрагмент кода? Вох Ы = new Вох(); ВохЬ2=Ы; На первый взгляд, переменной Ь2 присваи вается ссылка на копию объекта, на которую ссылается переменная Ы. Та ким образом, может показаться , что пере­ менные Ы и Ь2 ссылаются на совершенно разные объекты , но это совсем не так . После выполнения дан ного фрагмента кода обе переменные, Ы и Ь2, будут ссы­ латься на один и тот же объект. Присваивание переменной Ы значения перемен­ ной Ь2 не привело к выделению области памяти или копированию какой-нибудь части исходного объекта. Та кое присваивание приводит лишь к тому, что перемен­ ная Ь2 ссылается на тот же объект, что и переменная Ь 1. Та ким образом, любые из­ менения, внесенные в объекте по ссылке в переменной Ь2, окажуг влияние на объ­ ект, на который ссылается переменная Ь 1, поскольку это оди н и тот же объект. Это положение наглядно иллюс трирует рис. 6.2. Помимо того , что переменные Ь 1 и Ь2 ссьшаются на один и тот же объект, они не связаны никак иначе. Та к, в приведенном ниже примере кода присваивание пу­ стого значения переменной Ы просто разръ�вшrт связь переменной Ы с исходным объектом, не оказывая никакого вл ияния на сам объект или переменную Ь2 . В дан-
162 Часть 1. Язык Java ном примере в переменной Ы устанавливается пустое значение nu ll, но перемен­ ная Ь2 по-прежнему указывает на исходный объект. Вох Ы new Вох() ; ВохЬ2=Ы; 11 Ы = null; г-: :J. .. ._ _ Width �- He ight Depth Ь2 Объект кпасса вох Рис. 6.2 . Присваивание переменным ссылок на од ин и тот же объект Помните! Присваивание одной переменной ссылки на объект другой приводит к созда нию только коп ии ссыл ки на объект, а не копии самого объекта. Введение в методы Как упоминалось в начале этой гл авы , кл ассы обычно состоят из двух компо­ нентов: переменных экземпля ра и методов. Те ма методов довольно обширна, по­ скольку вJava они получают немалые возможности и уд обства. По существу, мно­ гие из последующих гл ав данной книги посвящены методам. Но для того чтобы вводить методы в классы, нужно знать хотя бы самые основные их особенности. Общая форма объя вления метода выглядит следую щим образом: 'l'IШ1U Ul (cn11coж паране!!'рО:в) 11 тело ме ТОда где тип обозначает конкретный тип данных, возвращаемых методом. Он может быть любым допустимым типом данных, в том числе и типом созданного класса. Если метод не возвращает значение, то его возвращаемым ти пом должен быть void. Для указания имени метода служит идентификатор имя . Это может быть лю­ бой допустимый идентификатор, кроме тех, которые уже используются другими элементами кода в текущей области действия . А список_ параме тров обозначает последовательность пар "тип-идентификатор", разделенных запятыми. По суще­ ству, параметры - это переменные, которые принимают значения аргументов, передаваемых методу во время его вызова. Если у метода отсутствуют параметры, то список_ параме тров оказывается пустым. Методы, возвращаемый тип которых отличается от void, возвращают значе­ ние вызывающей части программы в соответствии со следующей формой опера­ тора return: return .sнаvенже ;
Глава 6. Введение в кл ассы 163 где параметр зна чение обозначает возвращаемое методом значение. В последую­ щих разделах будет показано, каким образом создаются различные типы методов, в том числе принимающие параметры и возвращающие значения. Ввод метода в класс Вох Было бы очень удо бно создать класс, содержащий только данные, но в реаль­ н ых программах подобное встречается крайне редко. В большинстве случаев для доступа к переменным экземпляра, определенным в классе, приходится поль­ зо ваться методами. По существу, методы определяют интерфейс для большинства кл ассов. Это позволяет тому, кто реализует класс, скрывать конкретное располо­ жение внутре нних структур данных за более понятными абстракциями методов. Кроме методов, обес печивающих доступ к данным, можно определить и методы, применяемые в самом классе. Итак, приступим к вводу метода в класс Вох. Просматривая предыдущие приме­ ры программ, нетрудно прийти к выводу, что класс Вох мог бы лучше справиться с расчетом объема параллелепипеда, чем класс BoxDemo . В конце концов , было бы логично, если бы такой расчет выполнялся в классе Вох, поскольку объем паралле­ лепипеда зависит от его размеров. Для этого в класс Вох следует ввести метод, как показано ниже. // В этой программе применяется ме тод, введенный в класс Вох class Вох { douЫ e width ; douЫ e height ; douЫ e depth; // вывести объем параллелепипеда void volume () { System.out . print ( "Oбъeм равен ") ; System.out . println (width * height * depth) ; class BoxDemo З { puЫic static void ma in (String args[] ) { Вох mybox l new Вох() ; Вох mybox2 = new Вох() ; 11 присвоить значение переменным экземпляра JIYЬoxl myboxl.w idth = 10; myboxl . height = 20; myboxl .depth = 15 ; /* присвоить другие значения переменным экземпляра .iytюx2 экземпл яра mybox2 */ myb ox2 .width = 3; myb ox2 .height = 6; myb ox2 .depth = 9; 11 вывести объем первого параллелепипеда myboxl . volume (); 11 вывести объем второго параллелепипеда
16' Часть 1. Язы к Java mybox2 . volume () ; Эта программа выводит следующий резул ьтат, совпадающий с результатом в предыдущей версии: Объем равен 3000 .0 Объем равен 162.0 Внимательно рассмотрите две следующие строки кода: myboxl . volume () ; mybox2 . vo lume (); В первой строке кода вызывается метод vo lume () для объекта myboxl. Следовательно, метод volume () вызывается по отношению к объекту mybo xl, для чего было указано имя объекта, а вслед за ним - операция-точка. Та ким обра­ зо м, в результате вызова метода mуЬохl . vo lume () выводится объем параллелепи­ педа, определяемого объектом mybox1, а в результате вызова метода mybox2 . v o 1- ume () - объем параллелепипеда, определяемого объектом mybox2 . При каждом вызове метод vo lume () выводит объем указанного параллелепипеда. Пояснения, приведенные в следующих абзацах, облегчат понимание принципа вызова методов. При вызове метода mуЬох l . vo lume () исполняющая системаJаvа передает управление коду, определенному в теле метода vo 1 ume ( ) . По окончании выполнения всех операторов в теле метода управление возвращается вызываю­ щей части программы и далее ее выполнение продолжается со строки кода, следу­ ющей за вызовом метода. В самом общем смысле метод - это способ реализации подпрограмм вjava. В методе volume () следует обратить внимание на еще одну очень важную осо­ бенность: ссылка на переменные экземпляра width, height и depth делается непосредственно без указания перед ними имени объекта или операции-точки. Когда в методе испол ьзуется переменная экземпляра, определенная в его же клас­ се, это делается непосредственно, без указания явной ссылки на объект и приме­ нения операции-точ ки . Это становится понятным, если немного подумать. Метод всегда вызывается по отношению к какому-то объекту его класса. Как только этот вызов сделан, объект известен. Та ким образом, в теле метода вторичное указание объекта совершенно излишне. Это означает, что переменные экземпляра width, height и depth неявно ссылаются на копии этих переменных, хранящиеся в объ­ екте, кото рый вызывает метод volume (). Подведем краткие итоги . Когда доступ к переменной экземпляра осуществля­ ется из кода, не входящего в класс , где определена переменная экземпляра, следу­ ет непременно указать объект с помощью операции-точки . Но когда такой доступ осуществляется из кода, входящего в класс , где определена переменная экземпля­ ра, ссылка на переменную может делаться непосредственно. Эти же правила от­ носятся и к методам. В озврат зна чений Несмотря на то что реализация метода volume () переносит расчет объема па­ раллелепипеда в пределы класса Вох, которому принадлежит этот метод, такой
Гл ава 6. Введение в классы 165 способ расчета все же не является наилучшим. Например, что делать, если в дру­ гой части программы требуется знать объем параллелепипеда без его вывода? Более рациональный способ реализации метода volшne (} состоит в том, чтобы рассчитать объем параллелепипеда и возвратить результат вызывающему коду. Именно эта задача решается в приведенном ниже примере усовершенствованной версии предыдущей программы. 11 Теперь ме тод volume () возвращает объем параллелепипеда class Вох { douЫ e width; douЫ e height; douЫ e depth; 11 рассчитать и возвратить объем douЫe volume () { return width * height * depth; clas s BoxDemo 4 { puЫ ic static void main ( String args[]) { Вох mybox l = new Вох() ; Вох mybox2 = new Вох() ; douЫe vol; 11 присвоить значения переменным экземп ляра шуЬохl mybox l.width = 10; mybox l.height = 20; mybox l.dep th = 15; /* прис воить другие значения переменным экземпл яра шуЬох2 */ mybox2 .width = З; mybox2 . height = 6; mybox2 . depth = 9; // получить объем первого параллелепипеда vol = mybox l.volume () ; System. out .println ( "Oбъeм равен " + vol) ; 11 получить объем второго параллелепипеда vol = mybox2 . volume () ; System. out .println ( "Oбъeм равен " + vol) ; Как видите, вызов метода vo l шnе ( } выполняется в правой части операции при­ сваивания. В левой части этой операции находится переменная , в данном случае vo l, которая будет принимать значение, возвращаемое методом volume ().Та ким образом, после выполнения операции vol = mybox l.volume () ; м етод mybo xl . volшne (} возвращает значение 3000, и это значение рассчитанно­ го объема сохраняется в переменной vo l. При обращении с возвращаемыми зна­ чениями следует принимать во внимание два важных обстоятельства. • Тип данных, возвращаемых методом, должен быть со вместим с возвращае­ мым типом , указан ным в методе. Так, если какой-нибудь метод должен воз-
166 Часть 1. Я зык Java вращать логический тип boolean, то возвратить из него целочисленное зна­ чение нельзя. • Переменная , принимающая возвращаемое методом значение (например, vo l ) , также должна быть совместима с возвращаемым типом , указанным для метода. И еще одна особенность: приведенную выше программу можно было бы напи­ сать и в более эффективной форме, поскольку переменная vol в действительно­ сти совсем не нужна. Метод volume () можно было бы вызвать непосредственно в операторе с вызовом метода println (),как в следующей строке кода: System. out . println ( "Oбъeм равен " + mybo xl . vol ume () ); В этом случае метод myboxl.volume () будет вызываться автоматически при выполнении оператора с вызовом метода println (), а возвращаемое им значе­ ние будет передаваться методу println (). Ввод метода, принимающего параметры Хотя некоторые методы не нуждаются в параметрах, большинство из них все же требует их передачи. Параметры позволяют обобщить метод. Это означает, что метод с параметрами может обрабатывать различные дан ные и/ или применяться в самых разных случаях. В качестве иллюстрации рассмотрим очень простой при­ мер. Ниже приведен метод, возвращающий квадрат числа 10. int squa re () { return 10 * 10; Хотя этот метод действительно возвращает значение 102, его применение очень ограничено. Но если метод square () изменить таким образом, чтобы он принимал параметр , как показано ниже , то пользы от него будет много больше. int square (int i) { return i * i; Те перь метод squa re () будет возвращать квадрат любого значения, с которым он вызывается. Та ким образом, метод square ( ) становится теперь методом обще­ го назначения , способным вычислять квадрат любого целочисленного значения , а не только числа 10. Ниже приведены некоторые примеры применения метода square (). int х, у; х = square(5); 11 х равно 25 х = square(9); 11 х равно 81 у2; х = square(y); 11 х равно 4 При первом вызове метода square () значение 5 передается параметру i. При втором вызове этого метода параметр i принимает значение 9, а при третьем вы­ зове метода square ( ) ему передается значение переменной у, которое в данном
Глава 6. Введение в классы 167 примере равно 2. Как следует из этих примеров, метод square () способен возвра­ щать квадрат любых передаваемых ему числовых значений. В отношении методов важно различать два термина: параметр и аргумент. Пар аметр - это определенная в методе переменная , которая принимает задан ное значение при вызове метода. Например, в методе square ( ) параметром является i. Аргумент - это значение, передаваемое методу при его вызове. Например, при вызове square ( 100) в качестве аргумента этому методу передается значение 100. Это значение получает параметр i в теле метода square ( ) . Методом с параметрами можно воспол ьзоваться для ус овершенствования клас­ са Вох. В предшествующих примерах размеры каждого парал л елепипеда нужно было устанавливать отдел ьно, используя последовательность операто ров вроде следующей: mybox l.w idth = 10; myboxl . hei ght = 20; myboxl .depth = 15; Несмотря на то что приведенный выше код вполне работоспособен, он не очень уд обен по двум причинам. Во-первых, он громоздок и чреват ошибками. Та к, можно вполне забыть определить один из размеров параллелепипеда. И во­ вторых , в правильно написанных программах нajava доступ к переменным экзем­ пляра должен осуществляться только через методы, определенные в их классе. В дальнейшем поведение метода можно изменить, но нельзя изменить поведение предоставляемой переменной экземпляра. Следовательно, более рациональный способ устан овки размеров параллелепи­ педа состоит в создании метода, который принимает размеры параллелепипеда в виде своих параметров и соответствующим образом устанавливает значение каждой переменной экземпляра. Именно этот принцип и бьш реализован в при­ веденн ом ниже примере программы. 11 В этой программе применяется ме тод с параметрами class Вох { douЫ e width; douЫ e height ; douЫe depth ; 11 рассчитать и возвратить объем douЫ e volume () { return width * height * depth ; } 1 1 установить размеры параллел епипеда void setDim(douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; class BoxDemo S { puЫ ic static vo id ma in (String args[]) { Вох myboxl = new Вох(); Вох myb ox2 = new Вох(); douЫe vol;
168 Часть1.ЯзыкJava 11 инициализировать каждый экземпл яр кл асса ВОх myboxl . setDim(lO, 20, 1 5 ); myb ox2 .setDim(3, 6, 9) ; 11 получить объем первого параллеле пипеда vol = myboxl .volume () ; System.out . println ( 110C\ъeм равен 11 + vo l) ; 11 получить объем второго параллелепипеда vol = mybox2 . volume (); System.out . println ( 11 0C\ъeм равен 11 + vol) ; Как видите , метод setDi m() использован для установки размеров каждого па­ раллелепипеда . Например, при выполнении следующей строки кода: myboxl . setDim(lO, 20, 15) ; значение 10 копируется в параметр w, значение 20 - в параметр h, а значение 15 - в параметр d. Затем в теле метода setDim () значения параметров w, h и d присва­ иваются переменным width, height и depth соответственно. Понятия, представленные в этих разделах, вероятно, знакомы многим читате­ ля м. Но если вы еще не знакомы с такими понятиями, как вызовы методов, аргу­ менты и параметры, немного поэкспериментируйте с ними, прежде чем продол­ жить изучение материала, изложенного в последующих разделах. Понятия вызо­ ва метода, параметров и возвращаемых значений являются основополагающими в программировании на языке J ava. Ко н структо ры Инициализация всех переменных класса при каждом создании его экземпля­ ра может оказаться утомительным процессом. Даже при добавлении служебных функций вроде метода setDim () было бы проще и удо бнее, если бы все действия по установке значений переменных выполнялись при первом создании объекта. В с вязи с тем что потребность в инициализации возникает очень часто , объек­ там вjava разрешается выполнять собственную инициал изацию при их создании. И эта автоматическая инициализация осуществляется с помощью конструктора. Канструктар инициализирует объект непосредственно во время его создания. Его имя совпадает с именем класса, в котором он находится, а синтаксис анало­ гичен синтаксису метода. Как только конструктор определен, он автоматически вызывается при создании объекта перед окончанием выполнения оператора new. Конструкторы выглядят не совсем привычно, поскольку они не имеют возвра­ щаемого ти па - даже типа vo id. Это объясняется тем, что неявно заданным воз­ вращаемым ти пом конструктора класса является тип самого класса. Именно кон­ структор инициализирует внутреннее состояние объекта таким образом, чтобы код, создаю щий экземпляр, с самого начала содержал полностью инициализиро­ ванный, пригодный к употреблению объект. Пример класса Вох можно переделать, чтобы значения размеров параллелепи­ педа присваивались при конструировании объекта. Для этого придется заменить метод setDim () конструкто ро м. Сначала в приведенной ниже новой версии про-
Глава 6. Введение в классы 169 граммы определяется простой конструктор, устанавливающий одинаковые значе­ ния размеров всех параллелепипедов. /* В данном примере программы для инициализации размеров параллелепипеда в кл ассе Вох применяется конструктор */ class Вох { douЫ e width; douЫ e height ; douЫ e depth ; 11 Это конструктор кл асса Вох Вох(){ System.out . println {"Koнc тpyиpoвaниe объе кта Вох") ; width = 10; height = 10; depth = 10; 11 рассчитать и возвратить объем douЫ e volume () { return width * height * depth; class BoxDemo б { puЫ ic static vo id ma in (String args[] ) { 11 объя в ить , выделить пам ять и инициализировать объе кты типа Вох Вох mybox l = new Бох{) ; Вох mybox2 = new Бох{) ; douЫe vol; // получи ть объем первого параллелепипеда vol = myboxl . volume {); System. o ut . printlп {"Oбъeм равен " + vol) ; // получ ить объем второго параллелепипеда vol = mybox2 . volume {); System.out . println {"Oбъeм равен "+vol) ; Эта программа выводит следующий результат: Конс труирование объе кта Бох Ко нструирование объе кта Вох Объем равен 1000 .0 Объем равен 1000 .О Как видите , оба объекта, myboxl и myb ox2, инициализированы конструктором В ох ( ) при их создан ии. Конструктор присваивает всем параллелепипедам одина­ ковые размеры lOxlOxlO, поэтому объекты mybox l и mybox2 будут иметь одина­ ковый объем. Вызов метода println () в конструкторе Вох () делается исключи­ тельно ради иллюстрации. Но большинство конструкторов не выводят никаких дан ных , а лишь выполняют инициализацию объекта. Прежде чем продолжить дальше, вернемся к рассмотрению оператора new. Как вам, должно быть уже известно, при выделении памяти для объекта испол ьзуется следующая общая форма: перенен н ая_ жласса = new иня_ жласса() ;
170 Часть 1.Язык Java Те перь вам должно быть ясно, почему после имени класса требуется указывать круглые скобки. В действительности оператор new вызывает конструктор класса. Та ким образом, в следующей строке кода: Вох mybox l = new Вох() ; оператор new Вох ( ) вызывает конструктор Вох ( ) . Если конструктор класса не опре­ делен явно , то в Java для класса создается конструктор по умолчанию. Именно по­ этому приведенная выше строка кода бьта вполне работоспособной в предыдущих версиях класса Вох, где конструктор не бьт определен. Конструктор по умолчанию ин ициализирует все переменные экземпляра устанавливаемыми по умолчанию зна­ чениями, которые могут быть нул евыми , пустыми (null) и логическими (false) для числовых, ссьточных и логических (boo lean) типов соответственно. Зачастую конструктора по умолчанию оказывается достаточно для простых классов, чего, как правило, нельзя сказать о более сложных классах. Как только в классе будет опреде­ лен собственный конструктор, конструктор по умолчанию больше не используется . П ара м етр и э ированные ко н структо ры В предыдущем примере конструктор Вох ( ) инициализирует объект класса Вох, но пол ьзы от такого конструктора немного, так как все парал л елепипеды получа­ ют одинаковые размеры. Следовательно, требуется найти способ создавать объ­ екты класса Вох с разными размерами. В качестве простейшего решения достаточ­ но ввести в конструктор параметры. Нетрудно догадаться, такое решение делает конструктор более полезным. Например, в приведенной ниже версии класса Вох определяется параметризированный конструктор, устанавливающий размеры па­ раллелепипеда по значениям параметров конструктора. Обратите особое внима­ ние на порядок создания объектов класса Вох. /* В данном примере программы для инициализации размеров параллелепипеда в классе Вох применяется параме тризированный конструктор */ class Вох { douЫ e width; douЫ e height ; douЫ e depth ; 11 Это конструктор кл асса Вох Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; 11 рассчитать и возвратить объем douЫ e volume () { return width * height * depth ; class BoxDemo7 { puЬlic static void ma in (String arg s[] ) { 11 объявить , выдели ть памят ь и инициализировать объе кты типа Вох
Вох myboxl = new Box (lO, 20, 15); Вох mybox2 = new Вох(З, 6, 9); douЫe vol; Гл ава 6. Введение в кл асса.1 171 11 nолучить объем первого параллелепипеда vol = mybox l.volume (); System.out . println ("Oбъeм равен " + vol) ; 11 получи ть объем второго параллелепипеда vol = mybox2 . vol ume (); System. o ut . println ("Oбъeм равен " + vol) ; Вывод этой программы имеет следующий вид: Объем равен 3000 .О Объем равен 162.О Как видите, инициализация каждого объекта выполняется в соответствии с за­ данными значениями параметров его конструкто ра. Например, в следующей стро­ ке кода: Вох myboxl = new Box(lO, 20, 15) ; значения 10, 20 и 15 передаются конструктору Вох ( ) при создании объекта с по­ мощью оператора new. Та ким образом, копии переменных width, height и dep th будут содержать значения 10, 20 и 15 соответственно. Ключевое слово this Иногда требуется, чтобы метод ссылался на вызвавший его объект. Для этой цели вjava определено ключевое слово this. Им можно пользоваться в теле любо­ го метода для ссылки на текущий объект. Это означает, что ключевое слово this всегда служит ссылкой на объект, для которого был вызван метод. Ключевое слово this можно использовать везде , где допускается ссылка на объект типа текущего класса. Для того что бы стало понятнее назначение кл ючевого слова this, рассмо­ трим следующую версию конструктора Вох ( ) : 11 Избыточное применение ключевого слова this Box (douЫ e w, douЫe h, douЫe d) { this .width = w; this . height = h; this . depth = d; Эта версия конструктора Вох ( ) действует то чно так же , как и предыдущая . Ключевое слово this применяется в данном случае избыточно, хотя и верно. В теле конструктора Вох () ключевое слово this будет всегда ссылаться на вызы­ вающий объект. И хотя здесь это совершенно излишне, в других случаях, один из которых рассмотрен в следующем разделе, пользоваться ключевым словом this очень уд обно.
172 Часть 1. Яэы к Java Сокр ы тие переменной э кз емп ляра Как известно, в Java не допускается объявление двух локал ьных переменных с одним и тем же именем в то й же самой или в объемлющей областях действия. Те м не менее допускается существование локальных переменных, включая и фор­ мальные параметры методов, имена которых совпадают с именами переменных экземпляра класса. Но когда имя локальной переменной совпадает с именем пе­ ременной экземпляра, локальная переменная скръюалп переменную экземпляра. Именно поэтому именами width, height и depth в классе Вох не были названы па­ раметры конструктора Вох (). В противном случае имя width, например, обозна­ чало бы формальный параметр , скрывая переменную экземпляра width. И хотя было бы проще выбрать разные имена, существует и другой способ выхода из данного положе ния. Ключевое слово this позволяет ссьиаться непосредственно на объект, и поэтому его можно применять для разрешения любых конфликто в, которые могут возни кать между переменными экземпляра и локальными перемен­ ными в пространстве имен. В качестве примера ниже приведена еще одна версия конструктора Вох (), в которой имена width, height и depth служат для обозна­ чения параметров, а ключевое слово this - для обращения к переменным экзем­ пляра по этим же именам . 11 В этом коде разрешаются конфли кты в пространстве имен Box ( douЫe width, douЫe height, douЫe depth) { this . width = width; this . height = height ; this . depth = depth ; Те м не менее иногда подобное применение ключевого слова this может приво­ дить к недоразумениям, и некото рые программисты стараются не применять име­ на локальных переменных и параметров, скрывающие переменные экземпляра. Безусловно, есть программисты , которые придерживаются иного мнения , считая целесообразным использовать одни и те же имена для большей ясности программ, а ключевое слово thi s - для предотвращения сокрытия переменных экземпляра. Впрочем , выбор любого из этих двух подходов зависит от личных предпочтений. Сборка " мусора" В связи с тем что выделение операти вной памяти для объектов осуществляет­ ся динамически с помощью оператора new, невольно может возникнуть вопрос: каким образом объекты уничтожаются и как занимаемая ими память освобожда­ ется для повторного выделения под другие объекты. В некоторых языках, подоб­ ных С++, объекты, динамически размещаемые в оперативной памяти , приходится освобождать из нее вручную с помощью оператора de lete. А вJava применяется другой подход: освобождение операти вной памяти осуществляется автоматиче­ ски. Этот процесс называется сборкай ''мусора " и происходит следующим образом: в отсутствие любых ссылок на объект считается , что этот объект больше не нужен и за нимаемую им память можно освободить. В Java не нужно уничтожать объекты
Гл ава 6. Введение в классы 173 явным образом, как это требуется в С++. Во время выполнения программы сборка "мусора" выполняется только изредка, есл и вообще требуется. Она не будет вы­ полняться лишь пото му, что один или несколько объектов существуют и больше не используются. Более того , в различных реализациях исполняющей системы jаvа могут применяться разные подходы к сборке "мусора", но, как правило, об этом можно не беспокоиться при написании программ. М етод finalize () Иногда уничтожаемый объект должен выпол нять некоторое действие. Та к, ес л и объект содержит какой-нибудь ресурс , отличающийся от pecypcajava (вроде файлового дескриптора или шрифта) , такой ресурс, скорее всего , придется осво­ бодить перед уничтожением объекта. Для разрешения подобных ситуаций вjava предоставляется механизм, называе мый полным завершением. Применяя механизм полно го завершения , можно определить конкретные действия, которые будут вы­ полняться непосредственно перед удал ением объекта сборщиком "мусора". Чтобы ввести в класс средство полного завершения, достаточно определить метод finalize (),который вызывается в исполняющей cpeдe java непосредствен­ но перед уд алением объекта данного класса. В теле метода finalize () нужно ука­ зать те действия , которые должны быть выполнены перед уничтожением объекта. Сборщик "мусора" запускается периодически, проверяя наличие объектов, на кото­ рые отсутствуют ссылки как непосредственно из любого рабочего состояния, так и косвенно через другие доступные по ссьшке объекты. Непосредственно перед ос­ вобождением ресурсов метод finalize ( ) вызывается из исполняющей среды jаvа для удал яемого объекта. Общая форма метода finalize () имеет следующий вид: protected void finali ze ( ){ 11 здесь долже н следовать код полного завершения В этой синтаксической конструкции ключевое мово protected служит моди­ фикатором, предотвращающим доступ к методу finalize ().Этот и другие моди­ фикаторы доступа подробно описываются в гл аве 7. Следует иметь в виду, что метод finalize () вызывается лишь непосредственно перед сборкой "мусора". Например , он не вызывается при выходе объекта из обла­ сти действия. Это означает, что заранее неизвестно, когда будет (и будет ли вообще) выполняться метод finalize ().Поэтому в программе должны быть предоставлены другие средства для освобождения используемых объектом системных ресурсов и т. п . Нормальная работа программы не должна зависеть от метода finalize (). На заметку! Те м, у кого имеется опыт программирования на С++, должно быть известно, что в зтом языке допускается определять деструктор класса , который вызывается при выходе объекта из области де йств ия. А в Java та кая возможность не поддержи вается, поэтому применение деструкто ров не до пускается. По своему действ ию метод fina laize () лишь отдаленно напоминает деструктор. Постепенно приобретая опыт программирования на Java, вы са ми убедитесь, что благода ря наличию подсистемы сборки "мусора " потребность в функциях де­ структора возникает крайне редко.
174 Часть 1. Язы к Java Класс Stack Несмотря на то что класс Вох уд обен для демонстрации основных элементов класса, его практическая ценность невелика. Поэтому в завершение этой гл авы рассмотрим более сложный пример , демонстрирующий истинный потенциал классов. Как пояснялось при изложении основ объектно-ориентированного про­ граммирован ия (ООП) в гл аве 2, одно из гл авных преимуществ ООП дает инкап­ суляция данных и кода, который манипулирует эти ми данными. А в этой гл аве пояснялось, что в Java класс реализует механизм инкапсуля ции. Вместе с классом создается новый тип данных, который определяет характер обрабатываемых дан­ ных, а также используемые для этой цели процедуры. Далее методы задают согла­ сованный и управляемый интерфейс с данными класса. Та ким образом, классом можно пользоваться через его методы, не обращая внимания на особенности его реализации или порядок управления данными в классе. В каком-то смысле класс действует подобно механизму данных. Для того чтобы привести такой механизм в действие с помощью его элементов управления, совсем не обязательно знать его внутреннее устройство. А поскольку подробности реализации этого механизма скрыты , то его внутреннее устройство можно измен ить по мере необходимости . Изменения можно вносить во внутреннее устройство класса, не вызывая побоч­ ные эффекты за его пределами , при условии, что класс испол ьзуется в приклад­ ном коде через его методы. Для практического применения приведенных выше теоретических положений рассмотрим организацию стека как один из типичных примеров инкапсуля ции. Дан ные хранятся в стеке по принципу "первым пришел, последним обслужен". В этом отношении стек подобен стопке тарелок на столе: тарелка, поставленная на стол первой , будет использована последней. Для управления стеком служат две операции, тради ционно называемые размещением (в стеке) и извлечением (из стека). Для того чтобы рас положить элемент на вершине стека, выполняется операция размещения. А для того чтобы изъять элемент из стека, выполняется операция из­ влечения. Как будет показано ниже , инкапсуляция всего механизма действия стека не представляет никакой сложности . Ниже приведен класс Stack, реализующий стек емкостью до десяти целочис­ ленных значений. // В этом кл ассе определяется целочисленный стек, в котором 11 можно хранить до 10 целочисленных значений class Stack { int stck [J new int[lO] ; int tos ; 11 инициализировать вершину стека Stack () { tos = -1; 11 разместить элемент в стеке void push (int item) { if ( tos==9 ) System. out . println ( "Cтeк заполнен ."); else
stck [ ++tos ] item; 11 извлечь элемент из стека int рор() { if(tos < 0) { Гл ава 6. Введение в классы 175 System.out . println ( "Cтeк не загружен .") ; re turn О; else return stck [ tos-- J; Как видите, в классе Stack определены два элемента дан ных и три метода. Стек целочисленных значений хранится в массиве stck. Этот массив индексируется по переменной tos, которая всегда содержит индекс вершины стека. В конструк­ торе Stack ( ) переменная tos инициализируется значением -1, обозначающим пустой стек. Метод push () размещает элемент в стеке. Чтобы извлечь элемент из стека, следует вызвать метод рор ( ) . А поскольку доступ к стеку осуществляется с шг мощью методов push ( ) и рор ( ) , то для обращения со стеком на самом деле не имеет никакого значения, что стек хранится в массиве. Стек можно было бы с тем же успе­ хом хранить и в более сложной струк-rуре данных вроде связного списка, но интер­ фейс, определяемый методами push ( ) и рор ( ) , оставался бы без изменения. Применение класса Stack демонстрируется в приведенном ниже примере класса TestStack. В этом классе организуются два целочисленных стека, в каж­ дом из которых сначала размещаются , а затем извлекаются некоторые значения. class TestStack { puЫic static void Stack mys tackl Stack my stack2 main (String args[] ) new Stack (); = new Stack() ; 11 разместить числа в стеке for (int i=O; i<lO; i++) mys tackl .push (i) ; for ( int i=lO; i<20; i++) my stack2 .push (i) ; 11 извлечь эти числа из сте ка System. out . println ("C oдe pжимoe стека mys tackl :") ; for ( int i=O; i<lO; i++) System. out . println (mystackl .pop()); System. out .pr intln ( "Coдe pжимoe стека mystack2 :") ; for (int i=O ; i<lO; i++) System.out . println (mystack2 .pop() ); Эта программа выводит следующий результат: Содержимое стека my stackl : 9 8 7 6 5 4 3
176 Часть 1. Язы к Java 2 1 о Содержим ое стека mys tack2 : 19 18 17 16 15 14 13 12 11 10 Как видите , содержимое обоих стеков отличается. И последнее замечание по поводу класса Stack. В том виде, в каком он реализо­ ван, массив stck, содержащий стек, может быть изменен из кода, определенного за пределами класса Stack. Это делает класс Stack уязвимым к злоупотреблениям и повреждениям. В следующей главе будет показано, как исправить этот недостаток.
7 Подробное рассмотрен ие классов и методов В этой гл аве будет продолжено более подробное рассмотрение методов и клас­ сов, начатое в предыдущей гл аве. Вначале мы обсудим ряд вопросов, связанных с ме­ тодам и, в том числе перегрузку, передачу параметров и рекурсию. Затем еще раз обратимся к классам и рассмотрим управление доступом, использование ключевого слова static и String - одного из самых важных классов, встроенных вJava. П ерегруз ка методо в ВJava разрешается в одном и том же классе определять два или более метода с од­ ним именем, если только объявления их параметров отличаются . В этом случае мето­ ды называют перегружШJМыми, а сам процесс - перегрузкой методов. Перегрузка методов является одним из способов поддержки пОJrnморфизма в Java. Те м, кто никогда не программировал на языке, допускающем перегрузку методов, это понятие может по­ началу показаться странным. Но, как станет ясно в дальнейшем, перегрузка методов - одн а из наиболее замечательных и полезных функциональных возможностейjаvа. При вызове перегружаемого метода для определения нужного его варианта в Java ис пол ьзуется тип и/или количество аргументов метода. Следовательно, перегружаемые методы должны отличаться по ти пу и/или количеству их параме­ тров. Возвращаемые типы перегружаемых методов могут отличаться , но самого возвращаемого типа явно недостаточно для того, чтобы отличать два разных ва­ рианта метода. Когда в исполняющей cpeдe java встречается вызов перегружаемо­ го метода, в ней просто выполняется тот его вариант, параметры которого соот­ ветствуют аргументам, указанным в вызове . В следующем простом примере демонстрируется перегрузка метода: 11 Продемон стрировать перегрузку ме тодов class Ove rloadDemo { void test () { System.out . println ( "Пapaмe тpы отсутст вуют") ; 11 Пере гружа емый ме тод , проверяющий наличие 11 одного целочисленного параме тра void test (int а) { System.out . println ("a: "+а); 11 Перегружа емый ме тод, проверяющий наличие
178 Часть 1. Язы к Java ) 11 двух целочисле нных параме тров void test (int а, int Ь) { System. out . println ("a и Ь: "+а+""+Ь) ; 11 Пере гружаемый ме тод , провер яющий наличие 11 параме тра типа douЬle douЫ e test (douЫe а) { System. o ut . println ("d ouЫ e а: "+а ); return а*а; clas s Ove rload { puЫ ic static void ma in (String args[J ) { Ove rloadDemo оЬ = new Ove rloadDemo () ; douЫe result; 11 вызвать все варианты ме тода teвt () ob .test () ; ob .test (lO} ; ob .test (lO, 20) ; result = ob .test (12 3.25) ; System. out . println ("P eзyл ь тaт вызова ob .test (123 .25} : "+result ); Эта программа выводит следующий результат: Параме тры отсутствуют а: 10 аиЬ:1020 douЫe а: 123 .25 Результат вызова ob .test (123 .25) : 15190 . 5625 Как видите , метод test () перегружается четыре раза . Первый его вариант вообще не принимает параметров, второй принимает один целочисленный пара­ метр, третий вариант - два целочисленных параметра, а четвертый - один пара­ метр типа douЫe. То т факт, что четвертый вариант метода test () еще и возвра­ щает значение, не имеет никакого значения для перегрузки , поскольку возвраща­ емый тип ни коим образом не влияет на поиск перегружаемого варианта метода. При вызове перегружаемого метода в Java обнаруживается соответствие аргу­ ментов, использованных для вызова метода, и параметров метода. Но это соот­ ветствие совсем не обязательно должно быть полным. Иногда важную роль в раз­ решении перегрузки может играть автоматическое преобразование типов в Java. Рассмотрим в качестве примера следующую программу: 11 Приме ни ть ав тома тическое пре образование типов к пере грузке class Ove rloadDemo { void test () { System. o ut . println ("П apaмe тpы отсутствуют" ) ; 11 Пере гружаемый ме тод , проверяющий наличие наличие // двух целочисленных параме тров void test (int а, int Ь) { System. out . println ("a и Ь: "+а+" "+Ь} ; 11 Пере гружаемый ме тод, проверяющий наличие // параме тра типа douЬle vo id test (douЫe а) {
} Глава 7. Подробное рассмотрение классов и методов 179 System.out .println ( "Внутреннее преобразование при вызове test (douЬle ) а: "+а ); class Ove rload { puЫic static void ma in (String args[] ) { Ove rloadDemo оЬ = new Ove rloadDemo (); inti=88; ob .test () ; ob .test (lO, 20) ; ob .test (i) ; // здесь вызывается вариант ме тода test (douЬle) ob .test (l23.2) ; // а здесь вызывается вариант ме тода test (douЬle) Эта программа выводит следующий результат: Параме тры отсутствуют аиЬ:1020 Внутре ннее преобразование при вызове test (douЬle) а: 88 Внутреннее преобразование при вызове test (douЬle ) а: 123 .2 Как видите, в данной версии класса Ove r loadDemo перегружаемый вариант метода test ( i nt ) не определяется. Поэтому при вызове метода test () с цело­ численным аргументом в классе Ove rload отсутствует соответствующий метод. Но в Java может быть автоматически выполнено преобразование типа integer в тип douЫ е, чтобы разрешить вызов нужного варианта данного метода. Так , если вар иант метода test (int ) не обнаружен, тип переменной i автоматически про­ двигается вJava ктипу dо uЫ е , а затем выЗЬ1вается вариант метода test (douЬle ) . Безусловно, если бы вариант метода test ( i nt ) был определен, то был бы вызван именно он. Автоматическое преобразование типов в Java выполняется только в том случае, если не обнаружено полное соответствие. Перегрузка методов поддерживает полиморфизм, поскольку это один из спосо­ бов реализации вJava принципа "один интерфейс, несколько методов". Разъяс ним это положение подробнее, чтобы оно стало понятнее. В тех языках программиро­ вания , где перегрузка методов не поддерживается, каждому методу должно быть прис воено одн означное имя. Но зачастую требуется реализовать, по существу, один и тот же метод для разных типов данных. Рассмотрим в качестве примера фун кцию, вычисляющую абсолютное значение. Обычно в тех языках программи­ рования , где перегрузка методов не поддерживается, существуют три или больше вар ианта этой функции с несколько отличающимися именами. Например, в язы­ ке С функция abs ( ) возвращает абсолютное значение типа integer, функция l ab s () -абсолютное значение ти па long i ntege r, а функция fabs () -абсолют­ ное значение с плавающей то чкой. В языке С перегрузка не поддерживается , и по­ этому у каждой из этих фун кций должно быть свое имя, несмотря на то, что все три фун кции выполняют, по существу, одно и то же действие. В итоге ситуация ста­ новится принципиально более сложной, чем она есть на самом деле. И хотя каж­ дая функция построена по одному и тому же принципу, программирующему на С приходится помнить три разных имени одной и той же функции. В Java подобная ситуация не возникает, поскольку все методы вычисления абсолютного значения могут называться одинаково. И действительно, в состав стандартной библиотеки классовJаvа входит метод вычисления абсолютного значения, называемый abs ().
180 Часть 1. Язы к Java Перегружаемые варианты этого метода для обработки всех числовых типов дан­ ных определены во встроенном вjava классе Ma th. И в зависимости от типа аргу­ мента в Java вызывается нужный вариант метода abs ( ) . Перегрузка методов ценна те м, что позволяет обращаться к похожим методам по общему имени. Следовательно, имя ab s представляет общее действие, которое должно выполняться. Выбор подходящего варианта метода для конкретной ситуа­ ции входит в обязан ности компилятора, а программисту нужно лишь запомнить общее выполняемое действие. Полиморфизм позволяет свести несколько имен к одно му. Приведен ный выше пример довольно прост, но если расширить проде­ монстрированный в нем принцип, то нетрудно убедиться, что перегрузка позволя­ ет упростить решение более сложных задач . При перегрузке метода каждый его вариант может выполнять любые требующи­ еся действия. Не существует правила, согласно которому перегружаемые методы должны быть связаны друг с другом. Но со стилистической точки зрения перегруз­ ка методов предполагает определенную их связь. И хотя одно и то же имя можно было бы употребить для перегрузки несвязанных методов, делать этого все же не следует. Например, имя sqr можно было бы употребить для создания методов, воз­ вращающих квадрат целочисленного значения и квадратный кортъ числового зна­ чения с плавающей точкой. Но эти две операции принципиально различны. Та кое применение перегрузки методов противоречит ее первоначальному назначению. Поэтому на практике перегружать следует только тесно связанные операции. П ерегрузка ко н структо ров Наряду с пере грузкой обычных методов можно также выполнять перегрузку методов-конструкто ров. По существу, перегружаемые конструкторы станут скорее нормой, а не исключением для большинстве классов, которые вам придется соз­ давать на практике. Чтобы стали понятнее причины этого, вернемся к классу Вох, разработанному в предыдущей гл аве. Ниже приведена самая последняя его ве рсия. class Вох { douЫ e width ; douЫ e height ; douЫ e depth; 11 Это конструктор кла сса Вох Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; 11 рассчитать и возвратить объем douЫ e volume () { return width * height * depth; Как видите , конструктору Вох ( ) требуется передать три параметра. Это озна­ чает, что во всех объявлениях объектов класса Вох конструктору Вох ( ) должны передаваться три аргумента. Например, следующая строка кода недопустима: Вох оЬ = new Вох();
Глава 7. Подробное рассмотрение классов и методов 181 Вызов конструктора Вох ( } без аргументов приводит к ошибке, поскольку ему следует непременно передать три аргумента. В связи с этим возникают следующие ва жные вопросы: что, если требуется просто определить параллелепипед, а его на­ чальные размеры не имеют значения или вообще неизвесmы, и можно ли иници­ ализировать куб, указав только одно значение для всех трех измерений? Те кущее определение класса Вох не дает ответы на эти во просы, поскольку запрашиваемые дополнительные возможности в нем отсутствуют. Правда, разрешить подобные вопросы совсем не трудно: достаточно перегру­ зить конструктор Вох ( ) , чтобы принять во внимание упомянутые выше требова­ ния. Ниже приведен пример программы с усовершенствованной версией класса Вох, где решается подобная задача. /* В данном примере конс трукторы определяются в классе Вох дл я инициализации разме ров параллелепипеда тремя разными способами */ class Вох { douЫ e wi dth; douЫ e height ; douЫ e depth; 11 конструктор , используемый при указании всех размеров Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; 11 конструктор , Вох(){ width = -1; height = - 1; depth = -1; исполь зуемый , когда ни один из размеров не ука зан 11 исполь зовать значение - 1 для обозначения 11 неинициализированного 11 параллелепипеда // конс труктор , исполь зуемый при создании куба Box (douЫe len) { width = height = depth = len; /! рассчитать и возвратить объем douЫe volume () { return width • height * depth; class Ove rloadCons { puЬlic static void ma in (String args[] ) { // создать параллелепипеды, исполь зуя разные 11 конс трукторы Вох myboxl new Вох(10, 20, 15) ; Вох myb ox2 = new Вох() ; Вох mycube = new Вох(7) ; douЫe vol ; // получить объем первого параллелепипеда vol = myboxl . volume (); System. o ut . println ( "Oбъeм myboxl равен "+vo l) ; 11 получи ть объем второго параллелепипеда
182 Ч асть 1. Язык Java vol = mybox2 . volume (); System. out . println ( "Oбъeм mybox2 равен " + vol) ; 11 получить объем куба vo l = mycube .volwne () ; System. out .println ( "Oбъeм mycube равен " + vol) ; Эта программа выводит следующий результат: Объем mybox l равен 3000 .О Объем mybox2 равен - 1.О Объем mycuЬe равен 343 .0 Как видите, соответствующий перегружаемый конструктор вызывается в зави­ симости от параметров, указываемых при выполнении оператора new. Применение объектов в качестве параметров В представленных до сих пор примерах программ в качестве параметров мето­ дов употреблялись только простые ти пы данных. Но передача методам объектов не только вполне допустима, но и довольно распространена. Рассмотрим в каче­ стве примера следующую короткую программу: 11 Объе кты допускается передавать методам в качестве параме тров class Test { int а, Ь; Test (int i, int j) { а i; ь=j; 11 возвратить логическое значение true, если в качестве 11 параме тра о указан вызывающий объект boolean equals (Test о) if(o.a == а && о.Ь == Ь) return true; else return false; class PassOb { puЫ ic static void ma in (String args[]) Test оЫ new Test (lOO, 22 ); Test оЬ2 = new Test (lOO, 22) ; Test оЬ3 = new Test (-1, -1) ; System.out . println ("oЫ оЬ2 : "+oЫ . equals (ob 2) ); System. out . println ("oЫ == оЬ3 : "+oЫ . equals (ob 3) ); Эта программа выводит следующий результат: оЫ == оЬ2: true оЫ == оЬ3 : false Как видите, метод equals () проверяет в классе Test на равенство два объекта и возвращает получаемый результат. Та ким образом, он сравнивает вызывающий объект с тем, который бьm ему передан. Если оба объекта содержат одинаковые зна-
Глава 7 . П одробное рассмотрение классов и методов 183 чения, метод equa ls ( ) возвращает логическое значение true, а иначе - логическое значение false. Обратите внимание на то, что в качестве типа параметра о в методе e qua ls ( ) указывается класс Test. И хотя Test обозначает тип класса, создаваемого в программе, он употребляется точно так же, как и типы данных, встроенные вjava. В качестве параметров объекты чаще всего употребляются в конструкторах. Нередко новый объект приходится создавать таким образом, чтобы он первона­ чально ничем не отличался от уже существующего объекта. Для этого придется определить конструктор, принимающий в качестве параметра объект своего клас­ са. Например, приведенная ниже очередная версия класса Вох позволяет инициа­ лизировать один объект други м. // В этой версии кл асса Вох один объе кт допускается // инициализировать другим объе ктом class Вох { douЫe width; douЫ e height ; douЫ e depth; // Обр атите внимание на этот конс труктор . // В качестве параме тра в нем используется объект типа Вох Вох (Вох оЬ ) { // передать объе кт конструктору width = ob . width; height = ob . height; depth = ob . depth; 11 конструктор , исполь зуемый при указании всех размеров Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; // конструктор , исполь зуемый , если ни один из разме ров не указан Вох() { width = -1; height = -1; depth = -1; // исполь зовать значение -1 для обозначения 11 неинициали зированного 11 параллелепипеда 11 конструктор , исполь зуемый при создании куба Box (douЫe len) { width = height = depth = len; 11 рассчи тать и возвратить объем douЫ e volume () { return width * height * depth; class OverloadCons2 { puЫic static void ma in (String args[) ) { 11 создать параллелепипеды, исполь зуя 11 разные конструкторы Вох myboxl new Box(lO, 20, 15) ; Вох mybox2 = new Вох() ; Вох mycube = new Вох(7) ; Вох myclone = new Box (myboxl) ;
18it Часть 1. Язы к Java // создать копию объе кта JDYЬoxl douЫe vol ; 11 получить объем первого параллеле пипеда vol = mybox l.v olume () ; System.out . println ( "Oбъeм myboxl равен " + vol) ; 11 получить объем второго параллелепипеда vol = mybox2 . volume () ; System.out . println ( "Oбъeм mybox2 равен " + vo l) ; 11 получить объем куба vol = mycube . volume (); System.out . println ( "Oбъeм куба равен " + vo l) ; 11 получить объем клона vol = myclone . volume (); System.out . println ( "Oбъeм кл она равен " + vol) ; Приступив к разработке своих классов, вы быстро обнаружите потребность иметь в своем распоряжении многие формы конструкто ров. Они позволят вам удо бно и эффекти вно создавать нужные объекты. Подробное рассмотрение особенностей передачи аргументов В общем, для передачи аргументов подпрограмме в языках программирова­ ния имеются два способа. Первым способом является въtЗов по зна-чению. В этом случае зна-ченш аргумента копируется в формальный параметр подпрограммы. Следовательно, изменен ия, вносимые в параметр подпрограммы, не оказывают никакого влияния на аргумент. Вторым способом передачи аргумента является в'ЫЗов по ссъt.Лке. В этом случае параметру передается ссьшка на аргумент, а не его значение. В теле подпрограммы эта ссьшка служит для обращения к конкретно­ му аргументу, указанному в вызове. Это означает, что изменения , вносимые в па­ раметр подпрограммы, будут оказывать влияние на аргумент, используемый при ее вызове . Как будет показано далее , все аргументы вJava передаются при вызове по значению, но конкретн ый результат зависит от то го , какой именно тип данных передается: примитивный или ссьшочный. Когда методу передается аргумент примитивного типа, его передача происхо­ дит по значению. В итоге создается копия аргумента, и все, что происходит с па­ раметром, принимающим этот аргумент, не оказывает никакого вл ияния за преде­ лами вызываемого метода. Рассмотрим в качестве примера следующую программу: 11 Аргументы примитивных типов передаются по значению class Test ( void meth(int i, int j) { i*=2; j/=2;
class CallByValue { Гл ава 7 . П одробное рассмотрение классов и методов 185 puЫ ic static void ma in ( String args [] ) { Test оЬ new Test () ; intа=15,Ь=20; System.out .println("a и Ь до вызова: " + а+""+Ь); ob .meth (a, Ь) ; System. out . println ("a и Ь после вызова : " + а+""+Ь); Эта программа выводит следующий результат: аиЬдовызова:1520 а и Ь после вызова: 15 20 Как видите, операции, выполняемые в теле метода me th (), не оказывают ника­ кого влияния на значения переменных а и Ь, используемых при вызове этого мето­ да. Их значения не изменились на 30 и 10 соответственно, но остались прежними. При передаче объекта в качестве аргумента методу ситуация меняется корен­ ным образом, поскольку объекты , по существу, передаются при вызове по ссыл ке . Не следует, однако , забывать, что при объявлении переменной типа класса соз­ дается лишь ссылка на объект этого класса. Та ким образом, при передаче этой ссылки методу принимающий ее параметр будет ссылаться на тот же самый объ­ ект, на который ссылается и аргумент. По существу, это означает, что объекты дей­ ствуют так, как будто они передаются методам по ссылке. Но изменения объекта в теле метода оказывают влия:ние на объект, указываемый в качестве аргумента. Рассмотр им в качестве примера следующую программу: // Объе кты передаются по ссылке на них class Test { int а, Ь; Test(int i, int j) { а i; ь=j; // передать объе кт void meth(Test о) { о.а *= 2; о.Ь /= 2; class Pas sObj Re { puЫic static vo id ma in (String arg s[] ) { Test оЬ = new Test (l5, 20) ; System.out . println ("o b.a и оЬ .Ь до вызова : " + оЬ.а+""+оЬ.Ь); ob .meth (оЬ) ; System. out . println ("o b.a и оЬ .Ь после вызова : " +
186 Часть 1. Язык Java оЬ.а +""+оЬ.Ь); Эта программа выводит следующий результат: оЬ.а и оЬ.Ь до вызова: 15 20 оЬ.а и оЬ.Ь после вызова: 30 10 Как видите , в данном случае действия , выполняемые в теле метода me th (), ока­ зывают вл ияние на объект, указываемый в качестве аргумента. Помните! Когда методУ передается ссылка на объект, сама ссыл ка передается способом вызова по значению. Но поскольку переда ваемое значение ссылается на объект, то копия этого зна­ чения все равно будет ссылаться на тот же са мый объект, что и соответствующий аргумент. Возврат объектов Метод может возвращать любой тип данных, в том числе созданные типы клас­ сов. Та к, в приведенном ниже примере программы метод icrByTen () возвращает объект, в кото ром значение переменной а на 10 больше значения этой перемен­ ной в вызывающем объекте. 11 Возврат объе кта class Test { int а; Test ( int i) а=i; Test inc rByTen () Test temp = new Test (a+lO) ; return temp; class Re tOb { puЫ ic static void ma in (String args [] ) { Test оЫ = new Test(2); Test оЬ2 ; оЬ2 = oЫ . incrByTen (); System.out .println ("o Ы .а: " + оЫ .а) ; System.out . println ("o b2 .a: "+оЬ2.а) ; оЬ2 = ob2 . incrByTen (); System.out . println ("o b2 .a после второго увеличения значения : " + оЬ2.а); Эта программа выводит следующий результат: оЫ.а: 2 оЬ2.а: 12 оЬ2 .а после второго увеличения значения : 22
Гл ава 7 . Подробное р ассмотр ение кл ассо в и методов 187 Как видите , при каждом вызове метода incrByTen () в данной программе создается новый объект, а ссылка на него возвращается вызывающей части про­ граммы. В данной программе демонстрируется еще один важный момент: память выделяется для всех объектов динамически с помощью оператора new, а следова­ тельно, программисту не нужно принимать никаких мер, чтобы объект не вышел за пределы области своего действия , поскольку выполнение метода, в котором он был создан, прекращается. Объект будет существовать до тех пор, пока будет су­ ществовать ссылка на него в каком-нибудь другом месте программы. В отсутствие любых ссьmок на объект он будет уничтожен при последующей сборке "мусора" . Рекурси я В языке Java поддерживается рекурсия - процесс определения чего-либо отно­ сительно самого себя. Применительно к программированию на Java рекурсия - это средство, которое позволяет методу вызывать самого себя. Та кой метод назы­ вается рекурсивнъш. Классическим примером рекурсии служит вычисление факториала числа. Факториал числа N - это произведение всех целых чисел от 1 до N. Например, факториал числа 3 равен 1х2х3, т. е. 6. Ниже показано, как вычисл ить факториал, используя рекурсивный метод. 11 Простой пример ре курсии class Factorial { 11 это рекурсивный ме тод int fact (int n) { int result; if (n= =l ) return 1; result = fact (n-1) * n; return result ; class Re cursion { puЫ ic static void ma in (String args[] ) Factorial f = new Fa ctorial (); System.out . println ( 11 Фaктopиaл 3 равен 11 + f.fact(З) ) System. out . println ( 11 Фaктopиaл 4 равен 11 + f.fact(4) ) System. o ut . println ( 11 Фaктopиaл 5 равен 11 + f.fact (5) ) Ниже приведен результат, выводимый этой программой. Факториал 3 равен 6 Факториал 4 равен 24 Факториал 5 равен 120 Те м, кто незнаком с рекурсивными методами , принцип действия метода fact ( ) может быть не совсем понятным. Метод fact ( ) действует следующим образом. Когда этот метод вызывается со значением 1 своего аргумента, возвращается зна­ чение 1. В противном случае возвращается произведение fact (n-1 ) *n. Для вы-
188 Часть 1. Язык Java числения этого выражения метод fact () вызывается со значением n-1 своего аргумента. Этот процесс повторяется до тех по р, пока n не станет равным 1, после чего начнется возврат из последовательных вызовов метода fact (). Для того чтобы стал понятнее принцип действия рекурсивного метода fact (), обратимся к небольшому примеру. Для расчета факториала числа З вслед за пер­ вым вызовом метода fact () происходит второй вызов этого метода со значением 2 его аргумента. Это , в свою очередь, приводит к третьему вызову метода fact () со значением 1 его аргумента. Возвращаемое из этого вызова значение 1 затем ум­ ножается на 2 (т.е . значение n при втором вызове метода). Полученный результат, равный 2, возвращается далее исходному вызову метода f act () и умножается на З (исходн ое значение n). В конечном итоге получается искомый результат, равный 6. В метод fact () можно было бы ввести вызовы метода println () , чтобы отобра­ жать уровень каждого вызова и промежуточные результаты вычисления фактори­ ала заданного числа. Когда рекурсивный метод вызывает самого себя , новым локальным переменным и параметрам выделяется место в стеке и код метода выполняется с этими новы­ ми исходными значениями. При каждом возврате из вызова рекурсивного метода прежние локальные переменные и параметры уд аляются из стека, а выполнение продолжается с точки вызова в самом методе. Рекурсивные методы выполняют дей­ ствия, которые можно сравнить с раскладыванием и складыванием телескопа. Вследствие издержек на дополнительные вызовы рекурс ивные варианты мно­ гих процедур могут выполняться медленнее их итерационных аналогов. Слишком большое количество вызовов рекурсивного метода может привести к переполне­ нию стека, поскольку параметры и локальные переменные сохраняются в стеке , а при каждом новом вызове создаются новые копии этих значен ий. В таком случае в исполняющей системеJаvа возникнет исключение. Но подобная ситуация не воз­ никнет, если не выпустить рекурсивный метод из-под контроля. Гл авное преимущество рекурсивных методов заключается в то м, что их можно применять для реализации более простых и понятных вариантов некоторых алго­ ритмов , чем их итерационные аналоги . Например , алгоритм быстрой сортировки очень трудно реализовать итерационным способом. А некото рые виды алгорит­ мов, связанных с искусственным интеллектом, легче всего реализовать с помо­ щью рекурс ивных решений. При написании рекурсивных методов следует позаботиться о том, чтобы в каком­ нибудь другом месте программы присутствовал условный оператор i f, осуществляю­ щий возврат из метода без его рекурсивного вызова. В противном случае возврата из рекурсивно вызываемого метода так и не произойдет. Подобная ошибка очень часто встречается при организации рекурсии. Поэтому на стадии разработки рекурсивных методов рекомендуется как можно чаще делать вызовы метода pr in t ln ( ) , чтобы сле­ дить за происходящим и прерывать выполнение при обнаружении ошибки. Рассмотрим еще оди н пример организации рекурсии. В данном примере рекур­ сивный метод рr in tArrау ( ) выводит первые i элементов из массива vа 1 ue s. 11 Еще один пример рекурсии class Re cTest { int values [];
Гл ава 7. Подробное рассмотрение классов и методов 189 RecTest(i nt i) values = new int[i] ; 11 вывести рекурсивно элементы ма ссива void printAr ray (int i) { if (i==O ) return; else printArray (i- 1) ; System.out . println ("[" + (i-1 ) + "] " + values [i-1] ); class Re cur sion2 { puЫic static void main (String args[] ) RecTest оЬ = new RecTest (lO); int i; for (i=O; i< lO; i++) ob . values [i] i; ob .pr intArray (lO) ; Эта программа выводит следующий результат: [О] О [1] 1 [2] 2 [3] з [4] 4 [5] 5 [6] 6 [7] 7 [8] 8 [9] 9 Введение в управл ение доступом Как вам , должно быть уже известно, инкапсуляция связывает данные с манипу­ лирующи м ими кодо м. Но инкапсуляция предоставляет еще одно важное средство: упрсюлсние досту пом. Инкапсуляция позволяет управлять доступом к членам класса из отдел ьных частей программы, а следовательно, предотвращать злоупотребле­ н ия. Например , предоставляя доступ к данным только с помощью вполне опре­ деленного ряда методо в, можно предотвратить злоупотребление этими данными. Та к, если класс реализован правильно, он создает своего рода "черный ящик" , кото рым можно пользоваться, но внутренний механизм которого защищен от по­ врежден ий. Но представленные ранее классы не полностью удо влетворяют этому требованию. Рассмотрим , например, класс Stack, представленный в конце гл а­ вы 6. Несмотря на то что методы push ( } и рор ( } де йствительно предоставляют управляемый интерфейс со стеком , придерживаться этого интерфейса совсем не обязател ьно. Это означает, что другая часть программы может обойти эти мето­ ды и обратиться к стеку непосредственно. Ясно, что злоумышл енник не преминет воспользоваться такой возможностью. В этом разделе представлен механизм, с по­ мо щью которого можно то чно управлять доступом к разным членам класса.
190 Часть /. Язы к Java Способ доступа к члену класса определяется модифика:тором доступа, присут­ ствующим в его объявлении. В Java определен обширный ряд модификаторов до­ ступа. Некоторые аспекты управления доступом связаны гл авным образом с насле­ дованием и пакетами. (Пакет - это , по существу, группировка классов.) Эти состав­ ляющие механизма управления доступом вjava будут рассмотрены в последующих раздел ах, а сейчас рассмотрим управление доступом применительно к отдел ьному классу. Когда основы управления доступом станут вам понятны, освоение других особенностей данного процесса не составит особого труда. В Java определяются следующие моди фикаторы доступа: puЫic (открытый) , pr i vate (закрытый) и protected (защищенный) , а также уровень доступа, предо­ ставляемый по умолчанию. Модификатор protected применяется только при на­ следовании. Остальные модификаторы доступа описаны далее в этой гл аве. Начнем с определения модификаторов puЫ ic и private. Когда член объявля­ ется с модификатором доступа puЫ ic, он становится доступным из любого дру­ гого кода. А когда член класса объявляется с модификатором доступа pri va te, он доступен только другим членам этого же класса. Те перь становится понятно, по­ чему в объявлении метода mа in () всегда присутствует модификатор puЫ ic. Этот метод вызывается из кода, находящегося за пределами данной программы, т. е . из исполняющей системы Java. В отсутствие модификатора доступа по умолчанию член класса считается открытым в своем пакете , но недоступным для кода, находя­ щегося за пределами этого пакета. (Пакеты рассматриваются в следующей гл аве.) В рассмотренных ранее примерах классов все чл ены класса действовали в вы­ бираемом по умолчанию режиме доступа. Но этот режим доступа зачастую не соот­ ветствует практическим требованиям. Как правило, доступ к данным класса при­ ходится огран ичивать, предоставляя доступ к ним только через методы . А в ряде случаев в классе придется определять и закрытые методы. Модификатор доступа предшествует остальной спецификации типа члена. Это означает, что операто р объя вл ения члена должен начинаться с модификатора до­ ступа, как показан о ниже. puЫ ic int i; private douЫ e j; private int rnyMethod (int а, char Ь) { // ... Чтобы стало понятнее вл ияние, которое оказывает организация открытого и закрытого доступа, рассмотрим следующий пример программы: /* В этой программе демонс трируе тся отличие модифи каторов puЫ ic и private . */ class Test int а; 11 доступ , определяемый по умолчанию // открытый доступ puЫic int Ь; private int с; // закрытый доступ // ме т оды дос тупа к члену с данного кл асса void setc (int i) { // установить значе ние члена с данного кл асса с=i; int getc () return с; // получи ть значение члена с данного класса
Глава 7. Подробное рассмотрение классов и методов 191 class Ac cessTest { puЫ ic static void ma in ( String args [] ) { Test оЬ = new Test() ; 11 Эти операторы правильны, поэтому члены а и Ь // данного кл асса доступны непосредственно оЬ .а 10; оЬ.Ь = 20; // Этот оператор не верен и може т вызвать ошибку // оЬ.с = 100; //ОШИБКА! // Доступ к члену с данного класса долже н осуществляться 11 с помощь ю ме тодов ее кл асса ob .setc (lOO) ; // ВЕРНО ! System.out.println(11a, Ь, и с: 11 + оЬ.а + 11 " + оЬ.Ь + 11 11 + оЬ.getc()); Как видите, в классе Test используется режим доступа, выбираемый по умолча­ нию, что в данном примере равносильно указанию модификатора доступа puЫic. Член Ь данного класса явно указан как puЫic, тогда как член с указан как закрытый. Это означает, что он недоступен из кода за пределами его класса. Поэтому в самом классе Acce ssTest член с не может применяться непосредственно. Доступ к нему до лжен осуществляться с помощью открытых методов setc ( ) и ge tc ( ) данного клас­ са. Удал ение символов комментариев в начале следующей строки кода: // оЬ.с = 100; //ОШИБКА! сделало бы компиляцию данной программы невозможной из-за нарушений пра­ вил доступа. В качестве более реального примера организации управления доступом рас­ смотрим следующую ус овершенствованную версию класса Stack, код которого бьm приведен в конце гл авы 6: // В этом кл ассе определяется целочисленный стек, который може т // содержа ть 10 значений class Stack { /* Теперь переме н ные stck и tos являются закрытыми . */ Это означает, что они не могут быть случайно или намеренно изме нены таким образом, чтобы наруши ть стек. private int stck [J = new int[lOJ ; private int tos ; 11 инициализировать вершину стека Stack () { tos = -1; // разместить элемент в стеке void push (int item) ( if (tos==9) System. o ut . println (11Cтeк заполнен .") ; else
192 Часть 1. Язы к Java stck [ ++tos ] = item; 11 извлечь элемент из стека iпt рор() { if(tos < 0) { else System.out . priпtlп ("C тeк не за гружен ."); returп О; returп stck [ tos--] ; Как видите, теперь как private объявлены обе переменные: stck, содержащая стек, а также tos, содержащая индекс вершины стека. Это означает, что обраще­ ние к ним или их изменение может осуществляться только через методы push () и рор (). Например, объявление переменной tos закрытой препятствует случайной установке ее значения за границами массива stck из других частей программы. В следующем примере представлена усовершенствованная версия класса s taс k. Чтобы убедиться в том , что члены класса stck и tos действительно недоступны, попытайтесь уд алить символы комментариев из закомментированных строк кода. class TestStack { puЫic static void ma iп ( Striпg args [] ) Stack my stackl пеw Stack () ; Stack my stack2 = пеw Stack () ; 11 разместить числа в стеке for ( iпt i=O; i<lO; i++ ) mystackl .push (i) ; for (iпt i=lO; i<20; i++) mystack2 .push(i) ; 11 извлечь э ти числа из стека System. out . priпtlп ("Cтeк в mystackl :") ; for ( iпt i=O; i<lO; i++) System.out . priпtlп (mystackl .pop() ); System. out . priпtlп ("Cтeк в my stack2 :") ; for (iпt i=O; i<lO; i++) System.out . priпtlп (mystack2 .pop() ); 11 эти операторы недопус т имы 11 my stackl .tos = -2; 11 my stack2 .stck [ЗJ = 100; } Обычно методы будут обеспечивать доступ к данным, которые определены в классе, хотя это и не обязательно. Переменная экземпляра вполне может быть открытой, если на то имеются веские основания. Та к, ради простоты переменные экземпляра в большинстве несложных классов, представленных в данной книге , определены как открытые. Но в большинстве классов, применяемых в реальных программах, манипулирование данными должно осуществляться только с помо­ щью методов. Мы еще вернемся в следующей гл аве к теме управления доступом, и у вас будет возможность убедиться в то м, что управление доступом особенно важ­ но при наследовании.
Гл ава 7. П одробное рассмотрение классов и методов 193 Ключевое сл ово static Иногда желательно определить член класса, который будет исполь.зоваться не­ зависимо от любого объекта этого класса. Как правило, обращение к члену класса должно осуществляться только в сочетании с объектом его класса. Но можно создать член класса, чтобы пользоваться им отдел ьно, не ссылаясь на конкретный экземпляр. Чтобы создать такой член, в начале его объявления нужно разместить ключевое сло­ во static. Когда член класса объявлен как static (статический) , он доступен до соз­ дания любых объектов его класса и без ссылки на какой-нибудь объект. Статическими могут быть объявлены как методы, так и переменные. Наиболее распространенным примером статического члена служит метод ma in ( ) , который объявляется как s ta t ic, поскольку он должен быть объявлен до создания любых объектов. Переменные экземпляра, объявленные как static, по существу, являются гл о­ бальными. При объявлении объектов класса этих переменных их ко пии не соз­ даются. Вместо этого все экземпляры класса совместно используют одну и ту же статическую переменную. На методы, объявленные как static, накладывается следующие ограничения: • Они могут непосредственно вызывать только другие статические методы. • Им непосредственно доступны только статические переменные. • Они никоим образом не могут делать ссылки типа th i s или s upe r. (Ключевое слово super связано с наследованием и описывается в следующей гл аве .) Если для инициализации статических переменных требуется произвести вы­ числения , то для этой цели достаточно объявить статический блок, который будет выполняться только оди н раз при первой загрузке класса. В приведенном ниже примере демонстрируется класс, который содержит статический метод, несколь­ ко статических переменных и статический блок инициализации. 11 Продемонстрировать статические переменные , ме тоды и блоки кода class UseStatic ( static iпt а = З; static int Ь; static void meth (int х) ( System.out . println ("x System.out . println ("a System. out . println ("b static "+Х); "+а); "+Ь); System.out . println ( "Cтaтичe cкий блок инициализирован .") ; Ь=а*4; puЫic static void ma in (Striпg args [] ) ( meth(42); Как только загружается класс UseStatic, выполняются все статические опе­ раторы. Сначала в переменной а устанавливается значение З, затем выполняется статический блок кода, в котором выводится сообщение, а переменная Ь иници-
19' Часть 1. Язык Java ализируется значением а*4, т. е . 12. После этого вызывается метод ma in (), кото­ рый, в свою очередь, вызывает метод me th (), передавая параметру х значение 42. В трех вызовах метода println () делаются ссылки на две статические перемен­ ные, а и Ь, а также на локальную переменную х . Результат, выводимый этой программой, выглядит следующим образом: Статический блок инициализирован . х=42 а=З ь=12 За пределами класса, в котором определены статические методы и перемен­ ные, ими можно пользоваться независимо от любого объекта. Для этого достаточ­ но указать имя их класса через операцию-точку непосредственно перед их имена­ ми. Та к, если требуется вызвать статический метод за пределами его класса, это можно сделать, используя следующую общую форму: мюr_.хласса . не 'l'од ( ) где имя_ кла сса обозначает имя того класса, в котором объявлен статический ме­ тод. Как видите , эта форма аналогична той , что применяется для вызова неста­ тических методов через переменные ссылки на объекты. Аналогично для досту­ па к статической переменной ее имя следует предварить именем ее класса через операцию-точку. Именно так вJava реализованы управляемые версии гл обальных методов и переменных. Обратимся к конкретному примеру. В теле метода ma in () обращение к стати­ ческому методу callme () и статической переменной Ь осуществляется по имени их класса StaticDemo , как показано ниже . class StaticDemo static int а 42; static int Ь 99; static void callme () { System. out . println ("a class StaticByName { "+а); puЬlic static vo id ma in (St ring args [] ) { StaticDemo .callme (); System. out . println ("b ="+ StaticDemo.b) ; Эта программа выводит следующий результат: а 42 ь=99 Кл ючевое слово final Поле может быть объявлено как final (завершенное). Это позволяет предот­ вратить изменение содержимого переменной, сделав ее, по существу, константой. Следовательно, завершенное поле должно быть инициализировано во время его
Гл ава 7. П одробное рассмотрение классов и методов 195 объявления. Значение такому полю можно присвоить и в пределах конструктора, но первый подход более распространен. Ниже приведен ряд примеров объявления за­ вершенных полей. final int FILE_NEW = 1; final int FILE OPEN = 2; final int FILE - SAVE = З; final int FILE - SAVEAS = 4; final int FILE=QU IT = 5; Те перь во всех последующих частях программы можно пользоваться полем F ILE_OPEN и прочими полями таким образом, как если бы они были константами, без риска изменить их значения. В практике программирования нajava идентифи­ каторы всех завершенных полей принято обозначать прописными буквами, как в приведенном выше примере. Кроме полей , объявленными как final моrут быть параметры метода и локаль­ ные переменные. Объявление параметра как f inal препятствует его изменению в пределах метода, тогда как аналогичное объявление локальной переменной - присвоению ей значения больше одного раза. Ключевое слово f inal можно указывать и в объявлении методов, но в этом слу­ чае оно имеет совсем иное назначение, чем в переменных. Это дополнительное применение ключевого сл ова final более подробно описано в следующей гл аве, посвященной наследованию. Еще раз о массивах Массивы были представлены ранее в этой книге еще до рассмотрения классов. Те перь, имея представление о классах, можно сделать следующий важный вывод от­ носительно массивов: все они реализованы как объекть1. В связи с этим для масси­ во в существует специальное средство, которым выгодно воспользоваться при про­ грам мировании на Java. В частности , размер массива, т.е . количество элементов, которые может содержать массив, хранится в его переменной экземпляра length. Все массивы обладают этой переменной, которая всегда будет содержать размер массива. Ниже приведен пример программы, которая демонстрирует это средство. 11 В этой программе демон стрируе т ся применение член а длины ма ссива class Length { puЫic static void ma in (String args[]) int al [] new int[!O] ; intа2[]={З,5,7,1,8,99,44, -10}; intаЗ[]={4,З,2,1}; System.out . println ( "длинa al равна " + al . length ) System.out . print ln ( "длинa а2 равна " + a2 . length) System.out . println ( "длинa аЗ равна " + aЗ . length ) Эта программа выводит следующий результат: длина al равна 10 длина а2 равна 8 длина аз равна 4
196 Часть 1. Язы к Java Как видите , в данной программе выводится размер каждого массива. Имейте в виду, что значение переменной length никак не связано с количеством действи­ тельно используемых элементов массива. Оно отражает лишь то количество эле­ ментов , которое может содержать массив. Члену length можно найти применение во многих случаях. В качестве при­ мера ниже приведена усовершенствован ная версия класса Stack. Напомним, что в предшествующих версиях этого класса всегда создавался 1 О-элементный стек. А новая версия класса Stack позволяет создавать стеки любого размера. Значение stck . length служит для того, чтобы предотвратить переполнение стека. 11 Усовершенствованный класс Stack , в котором // исполь зован член длины ма ссива class Stack { priva te int stck[]; private int tos ; 11 выделить памя ть под стек и инициализировать его Stack (int size) { stck = new int [size] ; tos = -1; // разме тить элемент в стеке vo id push (int item) { if ( tos==stck . length- 1) // исполь зовать член длины ма ссива System. out . println ("Cтeк заполнен .") ; else stck [ ++tos ] = item; // извлечь элеме нт из стека int рор() { if(tos < 0) { System.out . println ("Cтeк не загружен . ") ; return О; } else return stck [tos-- ]; class TestStack2 { puЫic static vo id Stack my stackl Stack mys tack2 main (String args [] ) new Stack (5) ; = new Stack (8) ; 11 разместить числа в стеке for ( int i=O; i<5; i++) mystackl .push(i); for ( int i=O; i<8; i++) mys tack2 .push (i) ; // извлечь эти числа из стека System.out . println ("Cтeк в mys tackl :") ; for ( int i=O; i<5; i++) System.out . println (mystackl .pop() ); System. out .println ("Cтeк в mys tack2 :") ; for (int i=O; i<B; i++) System.out . priпtln (mys tack2 .pop() );
Глава 7 . Подробн ое рассмотрение классов и методов 197 Обратите внимание на то , что в данной программе создаются два стека: один глуб иной в пять элементов, другой - в шесть. Как видите , тот факт, что в мас­ сивах подде рживается информация об их длине, упрощает организацию стеков л юбой величины. Вложенные и внутренние классы В языке jаvа допускается определять один класс в другом классе. Та кие классы называются вложен нъ�ми. Область действия вложенного класса ограничена обла­ стью действия внешнего класса. Та к, если класс В определен в классе А, то класс в не может существовать независимо от класса А. Вложенный класс имеет доступ к членам (в том числе закрытым) того класса, в кото рый он вложен. Но внешний класс не имеет доступа к членам вложенного класса. Вложенный класс, объявлен­ ный непосредстве нно в области действия своего внешнего класса, считается его членом . Можно также объявлять вложенные классы, являющиеся локальными для блока кода. Существуют два типа вложенных классов: статu'Чl!ские и нестати'Чl!ские. Статическим называется такой вложенный класс, который объявляется с моди­ фикатором s ta t i с. А поскольку он является статическим, то должен обращаться к нестатическим членам своего внешнего класса посредством объекта. Это означа­ ет, что вложенный статический класс не может непосредственно ссылаться на не­ статические члены своего внешнего класса. В силу этого ограничения статические вложенные классы применяются редко. Наиболее важным типом вложенного класса является внутре нний класс. Внутре нний класс - это нестатический вложенный класс. Он имеет доступ ко всем переменным и методам своего внешнего класса и может непосредственно ссы­ латься на них таким же образом, как это делают остальные нестатические члены внешнего класса. В приведенном ниже примере программы демонстрируется определение и ис­ пользование внутреннего класса. В классе Outer содержится одна переменная эк­ земпляра outer_x , один метод экземпляра test () и определяется один внутрен­ ний класс Inne r. 11 Продемонс трировать приме нение внутреннего кл асса class Ou ter { int ou ter х 100; void test () Inner inne r = new Inner () ; inner . display () ; 11 это внутренний класс class Inne r { void display( ) { System.out . println ("в ывoд : ou ter х "+outer_x) ;
198 Часть 1. Язык Java class InnerClassDemo { puЬlic static void main (String args[] ) { Outer outer = new Outer (); outer. test (); Эта программа выводит следующий результат: вывод : oute r_x = 100 В данной про грамме внутренний класс Inner определен в области действия класса Ou ter. Поэтому любой код из класса Inne r может непосредственно обра­ щаться к переменной outer_х . Метод экземпляра d i splay () определен в классе Inne r. Этот метод выводит значение переменной oute r_x в стандартный поток вывода. В методе ma i n () из класса InnerClass Demo создается экземпляр класса Ou ter и вызывается его метод test ().А в этом методе создается экземпляр класса Inne r и вызывается метод d i splay (). Следует иметь в виду, что экземпляр класса Inner может быть создан только в контексте класса Outer. В противном случае компилятор Java выдаст сообще­ ние об ошибке. В общем, экземпляр внутреннего класса нередко создается в коде, находящемся в объемлющей области действия, как демонстрирует рассматривае­ мый здесь пример. Как пояснялось выше, внутренний класс имеет доступ ко всем элементам свое­ го внешнего класса, но не наоборот. Члены внутреннего класса доступны только в области действия внутреннего класса и не могут быть использованы внешним классом. Как показано в приведенном ниже примере программы, переменная у объявлена как переменная экземпляра класса Inner. Поэтому она недоступна за пределами этого класса и не может использоваться в методе showy ( ) . 11 Эта программа не подлежит компил яции class Ou ter { int outer х 100; void test () Inner inner = new Inner (); inner . display () ; 11 это внутренний кл асс class Inner { int у= 10; // у - локальная переме нная класса Inner voi d display () { System.out . println ( "вывoд : outer х void showy () { "+outer_x) ; System.out . println (y) ; // ошибка , здесь переме нная 11 у недоступна ! class InnerClassDemo { puЫ ic static void ma in (St ring args[J) {
Глава 7. П одробное рассмотрение классов и методов 199 Ou ter outer new Out er (); outer. test (); Выше основное внимание было уделено внутренним классам, определенным в качестве членов в области действия внешнего класса. Но внутренние класс ы можно определять и в области действия любого блока кода. Например, вложен­ ный класс можно определить в блоке кода, относящегося к методу, или даже в теле цикла for: 11 Определить внутренний кл асс в цикле for class Ou ter { int outer х = 100; void test () for (int i=O; i<lO; i++) { class Inner { void display () { System. o ut . println ("в ывoд : outer х Inner inner = new Inne r () ; inner . display () ; class InnerClassDemo { puЫ ic static void ma in (String args []) { Outer outer = new Outer (); outer . test () ; "+outer_x) ; Ниже приведен результат, выводимый этой версией программы. вывод : outer х 100 вывод : outer х 100 вывод : outer х 100 вывод : outer х 100 вывод : outer х 100 вывод : outer х 100 вывод : outer - x 100 вывод : outer х 100 вывод : out er - x 100 вывод : outer х 100 Вложенные классы можно применять не во всех случаях. Те м не менее они осо­ бенно удо бны для обработки событий. Мы еще вернемся к теме вложенных клас­ сов в гл аве 24, где представлены внутренние классы, которые можно использовать для уп рощения кода , предназначенного для обработки определенных типов собы­ ти й. Там же будут представлены и анонимнъtе ( т. е . безымянные) внутренние классы. И последнее замечание: в первоначальной спецификации языка Java, относя­ щейся к версии 1.0. вложенные классы не допускались. Они появились лишь в вер­ сии Jаvа 1.1.
200 Часть 1. Язы к Java Кратки й обзор кл а сса String Более подробно класс String будет рассмотрен в части 11 этой книги , а до тех пор уместно рассмотреть его хотя бы вкратце , поскольку символьные строки будут ис пользоваться в ряде последующих примеров из части I. Класс String относит­ сякчислу наиболее часто употребляемых в библиотеке классов jаvа. И это , оче­ видно , происходит потому, что символьные строки являются очень важным сред· ством программирования. Во-первых, следует уя снить, что любая создаваемая символ ьная строка на са­ мом деле является объектом класса String. И даже строковые константы в дей­ ствительности являются объектами класса String. Например, в следующем опе­ раторе: System.out . println ("Этo также объе кт String" ) ; символ ьная строка " Это также объе кт String" является объекто м кл асса String. Во-вторых, объекты класса String являются неизменяемыми. Как только та· кой объект будет создан , его содержимое не подлежит изменению. На первый взгляд это может показаться серьезным ограничен ием , но на самом деле это не так по следующим причинам. • Если требуется изменить символьную строку, то всегда можно создать новую символьную строку, содержащую все требующиеся изменения. • В Java определены классы StringBuffer и StringBuffer, равноправные классу String и допускающие изменение символьных строк, что позволяет выполнять вjava все обычные операции с символ ьными строками. (Классы StringBuffer и StringBuffer будут описаны в части П.) Символ ьные строки можно создавать разными способам и. Самый простой из них - воспользоваться оператором вроде следующего: String myS tring = "Это тестовая строка "; Как только объект класса String будет создан , им можно пользоваться везде, где допускаются символьные строки. Например, в следующей строке кода выво­ дится содержимое объекта myS tring: System.out . println (myString) ; Для объектов класса String вjava определена одна операция +, предназначен­ ная для сцепления двух символьных строк. Например, в результате приведенной ниже операции переменной myS tring присваивается символьная строка "Мн е нра вится Java ". String myString = "Мне " + " нравится " + "Java."; Все упомянугые выше понятия демонстрируются в следующем примере про­ грам мы: 11 Продемонстрировать приме нение симв оль ных строк class St ringDemo {
Гл ава 7 . Подроб ное рассмотрени е кл ассов и методов 201 puЫic static void ma in (String args [] ) { String st rOЫ "Первая строка" ; String strOb2 = "Вторая строка" ; String strObЗ = strOЫ + " и " + str0b2; System. out . println (strOЫ) ; System. out .println (str0b2 ); System.out . println (strOb З ); Эта программа выводит следую щий результат: Перв ая строка Вторая строка Первая строка и вторая строка В классе String содержится ряд методов, которыми можно пользоваться, программируя на Java. Обсудим вкратце некоторые из них. Так , с помощью ме­ тода equal s () можно проверить две символьные строки на равенство, а ме­ тод length () позволяет выяснить дл ину символьной строки. Вызывая метод charAt (), можно получить символ по заданному инде ксу. Ниже приведены общие формы этих трех методов. boolean equals (в!rора• C!rpOXa) int lenqth () - char charAt (Ю1Дехс) Применение этих методов демонстрируется в следующем примере программы: 11 Продемонстрировать некоторые методы из класса Strinq clas s StringDemo2 { puЬlic static void ma in (String args [] ) String st rOЫ "Первая строка" ; String str0b2 = "Вторая строка" ; String strObЗ = strOЫ ; System. o ut . println ( "Длинa строки strOЫ : " + strOЫ .le ngth () ); System. out . println ("Cимвoл по индексу 3 в строке strOЫ : " + strOЫ . cha rAt (З) ); if( strOЫ .equals ( strOb2)) else System. out . println ("strOЫ str0b 2") ; System. out . println ("strOЫ != str0b2") ; if(strOЫ . equals (strOb З )) else System. out . println ("s trOЫ strObЗ") ; System.out . println ("strOЫ != strOb З"); Эта программа выводит следующий результат: Длина строки strOЫ : 12 Симво л по индексу 3 в строке strOЫ : s strOЫ != strOb2 st rOЫ == strObЗ
202 Часть 1.ЯэыкJava Безусл овно, подобно существованию массивов объектов любого другого типа, могут существовать и массивы символ ьных строк. Ниже приведен характерный тому пример. 11 Продемонс трироват ь применение ма ссивов объе ктов типа Strinq class StringDemoЗ ( puЫic static void ma in (String args[J ) { String str[] = { "один", "два", "три" } ; for ( int i=O; i<str . length ; i++) System.out.println("str[" + i + "] : " + str [i] ); Результат, вы води мый этой программой , выглядит так: str [O] : ОДИН str[l] : два str [2] : три Как будет показано в следующем разделе, строковые массивы играют важную роль во многих программах наJava. При менен ие аргументов командной строки Иногда определенную информацию требуется передать программе во время ее запуска. Для этой цели служат аргументы командной строки для метода ma in ( ) . Арrумент командной стрики - это информация , которая во время запуска програм­ мы указывается в командной строке непосредственно после ее имени. Доступ к ар­ гу ментам командной строки в программе нa java не представляет особого труда , поскольку они хранятся в виде символьных строк в массиве типа String, переда­ ваемого методу main ( ) . Первый аргумент командной строки хранится в элементе массива args [О] , второй - в элементе args [ 1] и т.д. В следующем примере про­ граммы выводятся все аргументы командной строки , с которыми она вызывается: 11 Вывести все ар гуме нты командной строки class CommandLine ( puЫ ic static void ma iп (String args(] ) for (int i=O ; i<args .length; i++) System.out . println ("args [" + i + "] : " + args[i ]); Попробуйте выполнить эту программу, введя следующую команду в командной строке: java CommandLine this is а test 100 -1 В итоге будет выведен следующий результат: args [O]: this args[l] : is args(2] : а args(З] : test
arg s[4] : 100 args [5] : -1 Гл ава 7 . Подроб ное р ассмотрение кл ассо в и методо в 203 Помните! Все аргументы кома ндной строки переда ются в виде символ ьных строк. Числовые значения следует вручную преобразовать в их внутренние представления, ка к поясняется в главе 17. Аргументы переменной длины В версииJDК 5 было введено новое языковое средство, упрощающее создание методов, принимающих переменное количество аргументов. Оно получило назва­ ние varargs (variabk-kngth arguments - аргументы переменной длины). Метод , кото­ рый принимает переменное количество аргументов, называется методом с ар гумен ­ тами перемсн:ной дл инъt. Ситуации , когда методу требуется передать переменное количество аргумен­ тов, встречаются не так уж редко. Например , метод, устанавливающий соедине­ ние с Интернетом, может принимать имя пользователя , пароль , имя файла, сете­ вой протокол и прочие дан ные, но в то же время выбирать значения , задаваемые по умолчанию, если какие-нибудь из этих данных опущены. В подобной ситуации было бы удобнее передать только те аргументы , для которых неприменимы зна­ чения , задаваемые по умолчанию. Еще одним примером служит метод print f () , входя щий в состав библиотеки ввода-вывода вjava. Как будет показано в гл аве 20, этот метод принимает переменное кол ичество аргументов, которые форматиру­ ются, а затем выводятся. До версии J2SE 5 обработка аргументов переменной длины могла выпол нять­ ся двумя способами, ни один из которых не был особенно удо бным. Во-первых , если максимальное количество аргументов бьuю небольшим и известным, можно было создавать перегружаемые варианты метода - по одному для каждого из воз­ можных способов вызова метода. И хотя такой способ вполне работоспособен , он пригоден только в редких случаях. Во-вторых, когда максимальное количество возможных аргументов было боль­ ш им или неизвестным, применялся подход, при котором аргументы сначала раз­ ме щались в массиве, а затем массив передавался методу. Та кой подход демонстри­ руется в следующей программе: 11 Исполь зовать массив для передачи ме тоду переменного 11 количества аргументов . Это старый подход к обработке 11 аргументов переменной длины class Pas sAr ray { static void vaT est (int v[ ]) { System. out .print ("K oличecтвo аргументов : " + v. length + " Содержимое : ") ; for(int х : v) System. out . print (x +" ") ; System. o ut . println () ; puЫic static vo id ma in (String args[] ) { 11 Обратите внимание на порядок создания ма ссива
20' Часть 1. Язы к Java 11 для хранения аргументов . intnl[]={10}; intn2[]={1,2,3}; intnЗ[]={}; vaтe st (nl) ; // 1 аргуме нт vaTest (n2) ; // 3 аргуме нта vaTe st (nЗ) ; // без аргументов Эта программа выводит следующий результат: Количество аргументов : 1 Содержимое : 10 Количество аргументов : 3 Содержимое : 1 2 3 Количество аргументов : О Содержимое : В данной программе аргументы пе редаются методу vaTest () через массив v. Этот старый подход к обработке аргументов переменной дл ины позволяет методу vaTe st () принимать любое кол ичество аргументов. Но он требует, чтобы эти аргументы были вручную размещены в массиве до вызова метода vaTe st (). Создание массива при каждом вызове метода vaTest ( ) - задача не только трудоемкая , но и чреватая ошибка ми. Методы с аргументами перемен· ной длины обеспечивают более простой и эффективный подход к обработке таких аргументов. Для указания аргументов переменной длины служат три точки ( ...) . В приве· денном ниже примере показано, каким образом метод vaTe st ( ) можно объявить с аргументами переменной длины. static void vaTe st (int ... v) { В это й синтакс ической конструкции компилятору предписывается , что ме· тод vaTest ( ) может вызываться без ар гументов или с несколькими аргумента· ми. В итоге массив v неявно объявляется как массив типа int []. Та ким образом, в теле метода vaTest () доступ к массиву v осуществляется с помощью синтаксиса обычного массива. Ниже приведена переделанная версия предыдущего примера программы, где применяется метод с аргументами переменной дл ины. По резуль· тату своего выполнения она ничем не отличается от предыдущей версии. // Продемонстрировать применение аргументов переменной длины class VarArgs { 11 теперь ме тод vaTest () объя вл яется с аргуме нтами переменной длины static void vaTe st (int ... v) { System. out .print ("Koличecтвo аргументов : " + v. length + " Содержимое : ") ; for(int х : v) System. out .print (x +" ") ; System.out . println () ; puЬlic static void ma in (String args[] ) { // Обратите внима ние на возможные спос обы вызова // метода vaTest () с аргуме нтами переменной длины vaTe st (lO) ; // 1 аргумент vaTest (l, 2, 3) ; // 3 аргумента
vaTest (); Глава 7. Подробное рассмотрение классов и методов 205 // без аргуме нтов Отметим две важные особенности этой версии программы. Во-первых , как от­ мечалось ранее , в теле метода vaTest ( ) переменная v действует как массив, по­ скольку она действительно являете.я массивом . Синтаксическая конструкция .. . просто указывает компилятору, что в данном методе предполагается использовать переменное количество аргументов и что эти аргументы будут храниться в масси­ ве, на который ссылается переменная v. Во-вторых, метод vaTe st () вызывается в методе ma in ( ) с разн ым количеством аргументов, в том числе и совсем без них. Аргументы автоматически размещаются в массиве и передаются переменной v. Если же аргументы отсутствуют, дл ина этого массива равна нулю. Наряду с параметром переменной длины у метода могут быть и "обычные" па­ раметр ы. Но параметр переменной длины должен быть последним среди всех па­ раметров, объявляемых в методе. Например , следующее объявление метода впол­ не допустимо: int doit(int а, int Ь, douЫe с, int ... vals) { В дан ном случае первые три аргумента, указанные в объявлении метода do I t ( ) , соответствуют первым трем параметрам. А все остальные аргументы считаются принадлежащими параметру vals. Напомним, что параметр с переменным коли­ чеством аргументов должен быть последним. Например, следующее объя вление сделано неправильно: int doit(int а, int Ь, douЫe с, int ... va ls, boolean stopFlag) { // ОШИБКА ! В данном примере предпринимается попытка объявить обычный параметр после параметра с переменным количеством аргументов, что недопусти мо. Существует еще одно ограничение , о котором следует знать: метод должен содер­ жать только один параметр с переменным количеством аргументов. Например, приведенное ниже объявление также неверно. int do it (int а, int Ь, douЫe с, int ... vals, douЫe ... rnorevals) { // ОШИБКА! Поп ытка объявить второй параметр с переменным количеством аргументов недопустима. Ниже приведена измененная версия метода vaTest ( ), который принимает как обычный аргумент, так и аргументы переменной дл ины. 11 Исполь зовать аргуме нты переменной длины вместе 11 со с тандартными аргументами class Va rA rgs2 { 11 В данном приме ре шsg - обычный параме тр, 11 а v - параме тр переменной длины static void va Test (String rns g, int .. . v) { Systern. out . print (rnsg + v. length + "Содержимое : ") ; for(int х : v) Systern. out .print (x +" ") ; Systern. out . println ();
206 Часть 1. Язы к Java } puЬlic static voi d main ( String args[] ) { vаТеst ("Один параметр переменной длины : ", 10) ; vaтest { "Tpи параме тра переменной длины : ", 1, 2, 3) ; vaTest ( "Бeз параме тров переменной длины : ") ; Результат, выводимый этой программой , выглядит следующим образом: Один параметр переменной длины : 1 Содержимое : 10 Три параме тра переменной длины : 3 Содержимое : 1 2 3 Без параме тров переменной длины : О Содержимое : П ерегрузка методов с аргументами п еременной дл ины Метод, принимающий аргументы переменной длины, можно перегружать. В приведенном ниже примере программы метод vaTe st () перегружается трижды. 11 Аргуме нты переменной длины и перегрузка class VarArgs3 { static void vaTe st (int ... v) { System. out .print ( "vaTest (int ...): "+ for(int х : v) "Количество аргументов : " + v.length + " Содержимое: "); . System.out . print (x +" ") ; System. out . println () ; static void vaтe st (boolean ... v) { System. out .print ( "vaTest (boolean ...) "+ "Количество аргументов : " + v.le ngth + " Содержимое : ") ; for (boolean х : v) System.out . print (x +"") ; System.out . println () ; static void vaTest ( String ms g, int ... v) { System. out .print ( "vaTest ( String, int ...) : "+ msg + v. length + " Содержимое: "); for(int х : v) System.out . print (x +" ") ; System. out . println (); puЫ ic static void main (String args[] ) { vaтest (1, 2, 3); vаТеst ( "Проверка : " , 10, 20) ;
Глава 7. П одробное рассмотрение классов и методов 207 vaT est (true , fal se, false) ; Эта программа выводит следующий результат: va Tes t(i nt ...): Количе ство аргументов : 3 Содержимое : 1 2 3 va Tes t(String , int ...): Проверка : 2 Содержимое : 10 20 vaт es t(b oolean ...) Колич ество аргум ентов : 3 Содержимое : true false false В приведенном выше примере программы демонстрируются два возможных способа перегрузки метода с аргументами переменной дл ины. Первый способ состоит в том, что у параметра данного метода с переменным количеством ар· гументов могут быть разные типы. Именно это имеет место в вариантах метода vaRe st ( int ... ) и vaTe st (boolean . . . ) . Напомним, что языковая конструкция . . . вынуждает компилятор обрабатывать параметр как массив заданного типа. Поэтому, используя разные типы аргументов переменной длины, можно выпол­ нять перегрузку методов с переменным кол ичеством аргументов таким же обра­ зом , как и обычных методов с массивом разнотипных параметров. В этом случае исполняющая система Java испол ьзует отличие в типах аргументов для выбора нужного варианта перегружаемого метода. Второй способ перегрузки метода с аргументами переменной дл ины состоит в том , чтобы добавить один или несколько обычных параметров. Именно это и бьvю сделано при объявлении метода vaTe st (String , int .. .).В данном слу­ чае для выбора нужного варианта метода исполняющая системаJаvа использует не только количество аргументов, но и их тип. На заметку! Метод, поддержи вающий переменное кол ичество аргументов, может быть та кже пе­ регружен методо м, который не поддержи вает та кую возможность. Та к, в приведенном выше примере программы метод vaTest ( ) может быть перегружен методом vaTest ( int х) . Этот специализированный вариант вызывается тол ько при наличии аргумента int. Есл и же переда ются два или более аргумента типа int, то будет выбран вариант метода vaTest ( int . . .v) с аргументами переменной дл ины. Аргументы переменной дл ины и неоднозначность При перегрузке метода, принимающего аргументы переменной дл ины, могут происходить непредвиденные ошибки. Они связаны с неоднозначностью , кото­ рая может возникать при вызове перегружаемого метода с аргументами перемен­ ной дли ны. Рассмотрим в качестве примера следующую программу: 11 Арг ументы переменной длины , пере грузка и неоднозначность 11 11 Эта программа содержит ошиСку , и поэ тому не може т Сыть скомпилирована ! class VarArgs4 ( stat ic void vaT est (int ... v) System. out .print ( "vaTest (int ...) : "+ for (int х v) "Количество аргументов : " + v. length + "Содержимое : ") ;
208 Часть 1. Язы к Java System . out .print (x +" ") ; System. out . println () ; static vo id vaTest (boolean ... v) { System . out .print ( "vaTest (boolean . . . ) "+ "Количество аргументов : " + v. length + "Содержимое : ") ; for (boolean х : v) System.out . print (x +"") ; System.out . println (} ; puЬlic static void main (String args[]) { vaTest(l, 2, 3); // Верно ! vaTest (true , false, false) ; // Верно ! vaTest (); // ОшиОка : неоднозначность ! В этой программе перегрузка метода vaTe st ( ) задается вполне корректно. Те м не менее скомпилировать ее не уд астся из-за следующего вызова: va Test () ; // ОШИ БКА : неоднозначность ! Параметр с переменным кол ичеством аргументов может быть пустым, по­ этому этот вызов может быть преобразован в вызов метода vaTest ( int ...) или vaTe st (boolean . ..) . А поскольку вполне допустимы оба варианта, то данный вызов принципиально неоднозначен. Рассмотри м еще один пример неоднозначности . Приведенные ниже перегру­ жаемые варианты метода vaTe st ( ) изначально неоднозначны, несмотря на то , что один из них принимает обычный параметр . static void vaTest(int ... v) { // . . . static void vaTest(int n, int ... v) { // ... Несмотря на то что оба списка параметров метода vaTe st ( ) отличаются, ком­ пилятор не в состоянии разрешить следующий вызов: vaTe st (l) Должен ли он быть преобразован в вызов метода vaTe st (int .. .) с аргумен- тами переменной дл ины ил и же в вызов метода vaTe st ( int , int ... ) без аргу- ментов переменной дл ины? Компилятор не в состоянии разрешить этот вопрос . Та ким образом, возникает неоднозначная ситуа ция. Из-за ошибок неоднозначности , подобных описанным выше, иногда приходит­ ся отказываться от перегрузки и п росто использовать один и тот же метод под дву­ мя разными именами . Кроме того , ошибки неоднозначности порой служат при­ знаком принципиальных изъянов в программе, которые можно устранить, тща­ тельно проработав решения поставленной задачи.
8 Наследование Одним из основополагающих принципов объектно-ориентированного про­ граммирования является наследование, поскольку оно позволяет создавать иерар­ хические классификации. Используя наследование, можно создать класс, кото рый определяет характеристики , общие для набора связанных элементов. Затем этот общий класс может наследоваться другими, более специализированными класса­ м и, каждый из которых будет добавлять свои особые характеристики. В те рмино­ логии Jаvа наследуемый класс называется суперк.л,ассом, а наследующий класс - под­ 'КЛассом. Следовательно, подкласс - это специализированная версия суперкласса. Он наследует все чл ены, определенные в суперклассе, добавляя к ним собствен­ ные, особые элементы . Основы насл едо вани я Чтобы наследовать класс , достаточно ввести определение одного класса в дру­ гой, используя ключевое слово extends. Для иллюстрации принципа наследова­ ния обратимся к краткому примеру. В приведенной ниже программе создаются супер класс А и подкласс В. Обратите внимание на использование ключевого слова extends для создания подкласса, производного от класса А. 11 Простой пример наследования 11 создать суперкласс class А { int i, j; void showij () { Systern.out . println ("i и j: "+i +""+ j); 11 создать подкласс путем расширения класса А class В extends А { int k; void showk () { Systern.out . println ("k: "+k) ; 1 void surn () {
210 Часть 1. Язы к Java System.out . println ("i+j +k: "+ (i+j+k) ); class Simpleinheritance { puЫic static void ma in ( String args[] ) { А superOb = new А() ; В subOb = new В(); // суперкласс може т исполь зоват ься самостоятельно supe rOb .i = 10; superOb .j = 20; System. o ut . println ("C oдepжимoe объе кта supe rOb : ") ; supe rOb .showij (); System. out . println (); /* Подкласс име ет доступ ко всем от крытым членам своего суперкласса . */ subOb .i = 7; subOb .j = В; subOb .k = 9; System. o ut . println ("C oдepжимoe объе кта subOb : ") ; subOb .showij () ; subOb .зhowk (); System. out . println () ; System. out .println ("Cyм м a i, j и k в объекте subOb:"); subOb . sum( ); Эта программа выводит следующий резул ьтат: Содержимо е объе кта superOb : iиj:1020 Содержимое объе кта subOЬ : iиj:7в k:9 Сумма i, j и k в объекте subOЬ: i+j +k: 24 Как видите , подкласс В включает в себя все члены своего суперкласса А. Именно поэтому объект subOb имеет доступ к переменным i и j и может вызывать метод showij ().Кроме того, в методе sum () возможна непосредственная ссылка на пе­ ременные i и j, как если бы они бьvш частью класса В. Несмотря на то что класс А является суперклассом для класса В, он в то же время остается полностью независимым и самостоятельным классом. То , что один класс является суперклассом для другого класса, с овсем не исключает возможность его самостоятельного использования. Более того , один подкласс может быть супер­ классом другого подкласса. Ниже при ведена о бщая форма объявления класса, который наследуется от су­ перкласса. class •НJ1 noд:ruracca extends •юr _ супер:класса { / / те:Ло класса Для каждого создаваемого подкласса можно указать только один суперкласс. ВJava не поддерживается наследование нескольких суперклассов в одном подклас-
Гл ава 8. Наследо ва ние 211 се. Как отмечалось ранее, можно создать иерархию наследования, в которой один подкласс становится суперклассом другого подкласса. Но ни оди н из классов не может стать суперклассом для самого себя. Доступ к членам кпасса и наследование Несмотря на то что подкласс включает в себя все члены своего суперкласса, он не может иметь доступ к тем членам супер класса, которые объявлены как pr i va te. Рассмотрим в качестве примера следующую прос'I)'Ю иерархию классов: /* В иерархии классов за крытые члены остают ся закрытыми */ в пределах своего кла сса . Эта программа содержит ошибку , и поэ тому скомпилировать ее не удастся. // созда ть суперкласс class А { int i; // этот член открыт по умолчанию, private int j; // а этот член закрыт в классе А void setij (iпt х, iпt у) { iх; j=у; // Член j класса А в этом кла ссе недоступен class В exteпds А { int total ; void sum() { total = i + j; // ОШИ БКА : член j в этом классе недоступен class Access { puЫic static void ma in (Striпg args [] ) { В subOb = пеw В(); subOb. setij (10, 12) ; subOb.sum(); System.out . println ( "Cyммa равна " + subOb.total) ; Скомпилировать эту программу не удастся, потому что использование перемен­ ной j из класса А в методе sum ( ) из класса В приводит к нарушению правил досту­ па. Но поскольку переменная j объявлена в классе А как priva te, то она доступна только другим членам ее собственного класса. А подклассы не имеют к ней доступа. Помните! Член класса , который объя влен за крыты м, оста н ется закрытым в предел ах своего класса. Он недоступен дл я любого кода за пределами его класса , в том числе дл я под­ кл ассов.
212 Часть 1. Язы к Java Более практический пример Рассмотрим более практический пример, который поможет лучше продемон­ стрировать возможности наследования. В этом примере последняя версия клас­ са Вох из в предыдущей гл авы расширена четвертым компонентом we ight ( вес) . Та ки м образом, новая версия класса будет Вох содержать поля ширины, высоты, глубины и веса парал л елепипеда. 11 В этой программе наследование применяется 11 для расширения класса Вох class Вох { } douЫ e width ; douЫe height ; douЫe depth; 11 сконс труировать клон объе кта Вох (Вох оЬ ) { // передать объе кт конструктору width = ob . width; height = ob . height ; depth = ob . depth; 11 конструктор , примен яемый при указании всех разме ров Box(douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; 11 конструктор , Вох(){ width = -1; height = -1; depth = -1; применяемый в отсутствие разме ров // значение -1 служит для обозначения 11 неинициализированного // параллелепипеда // конструктор , применяемый при создании куба Box ( douЫe lеп) { width = height = depth = len; 11 рассчитать и возвратить объем douЫe volume () { return width * height * depth ; 11 расширить кл асс Вох , включив в него поле веса class BoxWeight extends Вох { douЫ e weight ; // вес параллелепипеда 11 конструктор BoxWe ight () BoxWeight(douЫe w, douЫe h, douЫe d, douЫe m) { width = w; height = h; depth = d; weight = m; class DemoBoxWe ight { puЫic static void ma in (String args [] ) {
Глава 8. Наследование 213 BoxWe ight mybox l BoxWe ight myb ox2 douЫe vol; new BoxWe ight (lO, 20, 15, 34 .3) ; new BoxWe ight (2, 3, 4, 0.076) ; vol = mybox l.volume () ; System. out . println ( "Oбъeм mybox l равен "+vol) ; System. out .println ("Bec mybox l равен " + myboxl .weight) ; System. o ut . println (); vol = mybox 2.volume (); System. out . println ( "Oбъeм mybox2 равен "+vol) ; Sys tem . out .println ("Bec mybox2 равен "+mybox2 .weigh t) ; Эта программа выводит следующий результат: Объем mybox l равен 3000 .0 Вес mybox l равен 34 .3 Объем mybox2 равен 24 .О Вес myb ox2 равен 0.076 Класс BoxWe ight наследует все характеристики класса Вох и добавляет к ним ком­ понент we ight. Классу BoxWe ight не нужно воссоздавать все характеристики класса Вох. Для этого достаточно расширить класс Вох, исходя из конкретных целей. Гл авное преимущество наследо вания состоит в том, что, как только будет соз­ дан суперкласс , определяющий общие свойства ряда объектов, его можно исполь­ зовать для разработки любого кол ичества более специализированных классов. Каждый подкласс может точно определять свою собственную классификацию. Например, следующий класс наследует характеристики класса Вох и добавляет к ним свойство цвета параллелепипеда: // Этот кл асс расширяет кл асс Вох , включ ая в него свойство цвета cl ass Colo rвox extends Вох { int color; // цвет параллелепипеда ColorBox (douЫe w, douЫ e h, douЫ e d, int с) { width = w; height = h; depth d; color = с; Напомним, как только суперкласс , который определяет общие свойства объ­ екта, будет создан , он может наследоваться для разработки специализированных классов. Каждый подкласс добавляет собственные особые сво йства. В этом и со­ стоит сущность наследования. Перем е нная суперкласса м ожет ссылаться на объект подкласса Ссылочной переменной из суперкласса может быть присвоена ссылка на лю­ бой подкласс, производный от этого суперкласса. Этот аспект наследования мо­ жет оказаться удоб ным во многих случаях. Рассмотрим следующий пример:
214 Часть /. Яэык Java class Re f Demo { puЫ ic static void main (St ring args[] ) { BoxWeight weightbox = new BoxWeight (З, 5, 7, 8.37) ; Вох plainbox = new Вох() ; douЫe vol; vol = we ightbox . volume (); System.out . println ( "Oбъeм wei ghtbox равен " + vol) ; System.out .println ("Bec we ightbox равен " + wei ghtbox .weight ); System.out . println () ; // nрисвоить ссылке на объе кт ВO:itWeight ссылки на объе кт ВОх plainbox = wei ghtbox ; vol = plainbox . volume (); // Верно , т.к . ме тод volW1 18 () // определен в классе Вох System.out . println ( "Oбъeм plainbox равен " + vol) ; /* Следующий оператор оши бочен , поскол ьку член plainЬox не определяет член veight . */ // System.out . println ("Bec plainbox равен " + plainbox .weight ); В данном примере член we ightbox содержит ссьшку на объекты класса Bo xWe ight, а член ра inЬох - ссылку на объекты класса Вох. А посколъку ВохWе ight ­ это подкласс, производный от класса Вох, то его члену pa inbox можно присвоить ссЬVIку на объект we ightbox. Следует иметь в виду, что доступные члены класса определяются типом ссы­ лочной переменной , а не типом объекта, на который она ссьшается. Это означает, что если ссылочной переменной из суперкласса присваивается ссылка на объект подкласса, то доступ предоставляется только к указанным в ней частям объекта, определяемого в суперклассе. Именно поэтому у объекта plainbox нет доступа к полю we ight даже в том случае, когда он ссылается на объект класса BoxWe ight. Это становится понятным по зрелом размышлении, ведь суперклассу неизвестно, что именно в него добавляет подкласс. Поэтому последняя строка кода в рассма­ триваемом здесь примере кода закомментирована. Ссьшка на объект класса Вох не предоставляет доступ к полю we ight, поскольку оно не определено в классе Вох. Все сказанное выше может показаться не совсем понятным. Те м не менее ему можно найти ряд практических применений, два из которых рассматриваются в последующих разделах. Ключевое слово super В предыдущих примерах классы, производные от класса Вох, были реализова­ ны не столь эффективно и надежно, как следовало бы. Например, конструктор Bo xWe ight () явно инициализирует поля width, he ight и depth из класса Вох. Это не только ведет к дублированию кода суперкласса, что весьма неэффектив­ но, но и п редполагает наличие у подкласса доступа к этим членам суперкласса. Но иногда приходится создавать суперкласс, подробности реализации которого до­ ступны только для него самого (т.е . класс с закрытыми членами). В этом случае
Гл а ва 8. Н а следование 215 подкласс не в состоянии непосредственно обращаться к этим переменным или инициализировать их. Инкапсуляция - один из гл авных принципов ООП, поэто­ му и не уд ивител ьно , что в Java предлагается свое решение этой проблемы. Всякий раз, когда подклассу требуется сослаться на его непосредственный суперкласс, это можно сделать с помощью ключевого слова supe r. У ключевого слова super имеются две общие формы. Первая форма служит для вызова конструктора суперкласса, вторая - для обращения к члену суперклас­ са , скрываемому членом подкласса. Рассмотрим обе эти формы подробнее. Вызов ко н структо ров суперкласса с п омощью кл ючевого слова super Из подкласса можно вызывать конструктор, определенный в его суперклассе, используя следующую форму кл ючевого слова super: super ( списож_аргухенwо.в) ; где спи сок_ аргуме нтов определяет любые аргументы, требующиеся конструкто­ ру в суперклассе. Вызов метода super () всегда должен быть первым оператором, выполняемым в конструкторе подкласса. В качестве примера, демонстрирующего применение метода supe r (), рассмо­ трим следующую ус овершенствованную версию класса BoxWe ight: // Теперь в кл ассе BoXWeiqht ключе вое слово super используется // для инициализации собственных свойств объе кта типа Вох class BoxWeight extends Вох { douЫ e we ight ; // вес параллелепипеда 11 инициализировать поля width , heiqht и depth // с помощью метода super () BoxWeight (douЫe w, douЫ e h, douЫe d, douЫe m) super (w, h, d) ; // выз вать конструктор суперкласса weight = m; В данном примере метод super () вызывается с аргументами w, h и d из метода BoxWe ight (). Это приводит к вызову конструктора Вох (), в котором поля width, height и depth инициализируются передаваемыми ему значениями соответству­ ющих параметров. Те перь эти значения не инициализируются в самом классе BoxWe ight. В нем остается только инициализировать его собственное поле we ight. В итоге эти поля могут, если требуется , оставаться закрытыми в классе Вох. В приведенном выше примере метод supe r () вызывался с тремя аргументами. Конструкторы могут быть перегружа емыми, и поэтому метод super () можно вы­ зывать, испол ьзуя любую форму, определяемую в суперклассе. Выполнен будет тот конструкто р, который соответствует указанным аргументам . В качестве примера ниже приведена полная реализация класса BoxWe ight, предоставляющая кон­ структо ры для разных способов создания парал л елепипедов. В каждом случае ме­ тод super () вызывается с соответствующими аргументами. Обратите внимание на то, что члены width, height и depth объявлены в классе Вох как закр ытые.
216 Часть 1. Язык Java // Полная реализация класса ВO.Jt tfe ight class Вох { private douЫ e width ; private douЫe height; private douЫe depth; 11 сконс труировать клон объе кта Вох ( Вох оЬ ) { // передать объе кт конструктору width = ob . width; height = ob . height ; depth = ob . depth; // конструктор , приме няемый при указании всех размеров Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; 11 конструктор , Вох() { width = -1; height = -1; depth= -1; применяемый в отсутствие размеров // значение -1 служит дл я обозначения // неинициализированного // параллелепипеда // конс труктор , применяемый при создании куба Box ( douЫe len) { width = height = depth = len; 11 рассчитать и возвратить объем douЫe volurne () { return width * height * depth; // Теперь в кл ассе ВOxWeight полностью реализованы все конс трукторы class BoxWe ight extends Вох { douЫ e wei ght; // вес параллелепипеда // сконструировать клон объе кта BoxWeight (Bo xWe ight оЬ ) { // передать объект конструктору super (ob) ; we ight = ob .weight ; 11 конструктор , приме няемый при указании всех параме тров BoxWeight (douЫ e w, douЫ e h, douЫe d, douЫe rn) { super (w, h, d) ; // вызвать конструктор из суперкласса weight = rn; // конс труктор , применяемый по умолчанию BoxWeight () { super (); weight = -1; 11 конс труктор , применяемый при создании куба
BoxWeight ( douЫ e len, douЫ e m) { super (len) ; weight = m; class DemoSuper { puЫ ic static void ma in (String args[] ) { Гл ава 8. Наследо вание 217 BoxWe ight mybox l new BoxWe ight (lO, 2 0, 15, 34 .3) ; BoxWe ight mybox2 new BoxWeight (2, 3, 4, 0.076) ; BoxWe ight mybox3 = new BoxWe ight () ; // по умолчанию BoxWe ight mycube = new BoxWe ight (3, 2) ; BoxWe ight myclone = new BoxWeight (myboxl) ; douЫe vol; vol = myboxl . volume () ; System. out . println ("Oбъeм mybox l равен "+vol) ; System.out . println ("Bec mybox l равен " + myboxl .weight) ; System. out . println () ; vol = mybox2 . volume (); System.out . println ("Oбъeм mybox2 равен "+vol) ; System. out . println ( "Bec mybox2 равен " + mybox2 .weight) ; System.out . println () ; vol = mybox3 . vo lume (); System. o ut . println ("Oбъeм mybox3 равен "+vol) ; System. out . println ( "Bec mybox3 равен " + mybox3 .weigh t) ; System. o ut . println () ; vol = myclone . volume (); System.out . println ("Oбъeм myclone равен " + vol) ; System. out . println ("B ec myc lone равен "+myclone .weigh t) ; System. out . println () ; vol = mycube . volume (); System.out . println ("Oбъeм mycube равен "+vol) ; System. out . println ("B ec mycube равен " + mycube .weight ); System. o ut . println () ; Эта программа выводит следующий результат: Объем mybox l равен 3000 .О Вес myboxl равен 34 .3 Объем myb ox2 равен 24 .О Вес myb ox2 равен 0.076 Объем myb ox3 равен -1 .О Вес mybox3 равен -1 .0 Объем myclone равен 3000 .0 Вес myc lone равен 34 .3 Объем mycube равен 27 .0 Вес mycube равен 2.0 Обратите особое внимание на следующий ко нструктор из класса BoxWe ight: 11 сконструировать клон объе кта BoxWei ght ( BoxWe ight оЬ ) { // передать объе кт конструктору super (оЬ) ; we ight = ob . weight;
218 Часть1.ЯзыкJava Обратите также внимание на то , что методу super ( ) передается объект типа BoxWe ight, а не Вох. Но это все равно приводит к вызову конструктора Вох (Вох . оЬ ) . Как отмечалось ранее, переменную из суперкласса можно использовать для ссылки на любой объект, унаследованный от этого класса. Та ким образом, объ­ ект класса BoxWe ight можно передать конструктору Вох (). Разумеется, классу Вох будут доступны только его собственные члены. Рассмотрим основные принципы, положенные в основу метода supe r (). Когда метод supe r () вызывается из подкласса, вызывается конструктор его непосредствен­ ного суперкласса. Та ким образом, метод super ( ) всегда обращается к суперклассу, на­ ходящемуся в иерархии непосредственно над вызывающим классом. Это справедливо даже для многоуровневой иерархии. Кроме того, вызов метода supe r ( ) должен быть всегда в первом операторе, выполняемом в теле конструктора подкласса. Другое применение ключевого слова super Вторая форма ключевого слова super действует подобно ключевому слову this, за исключением того , что ссылка всегда делается на суперкласс того под­ класса, в кото ром используется это ключевое слово. В общем виде эта форма при­ менения ключевого слова super выглядит следующим образом: suреr . член где член может быть методом или переменной экземпляра. Вторая форма при­ менения ключевого слова supe r наиболее пригодна в тех случаях, когда имена членов подкласса скрывают члены суперкласса с такими же именами . Рассмотрим в качестве примера следующую простую иерархию классов: 11 Исполь зовать ключ евое слово super с целью предотвратить сокрытие имен class А { int i; 11 создать подкласс путем расширения кла сса А class В extends А { int i; // этот член i скрывает член i из класса А B(int а, int Ь) { super.i =а; // член i из класса А i=Ь; // член i из класса В void show () { System.out . println ( "Члeн i в суперклассе : "+super .i) ; System. out . println ( "Члeн i в подклас се : "+i) ; class UseSupe r { puЫic static vo id ma in (String args[] ) { В subOb = new B(l, 2); subOb .show() ;
Эта программа выводит следующий результат: Член i в суперклассе : 1 Член i в подклассе: 2 Гл ава 8. Насл едование 219 Хотя переменная экземпляра i из класса В скрывает переменную экземпляра i из класса А, ключевое слово supe r позволяет получить доступ к переменной i, определяемой в суперклассе. Как будет показано далее , ключевое слово supe r можно использовать также для вызова методов, которые скрываются в подклассе. Соэдан и е многоуровневой и ерархии В приведенных до сих пор примерах использовались простые иерархии клас­ сов, которые состояли только из суперкласса и подкласса. Но ничто не мешает строить иерархии, состоя щие из любого количества уровней наследования. Как отмечалось ранее, подкласс вполне допустимо использовать в качестве суперклас­ са другого подкласса. Например, класс С может быть подклассом, производным от класса В, который , в свою очередь, является производным от класса А. В подоб­ ных случаях каждый подкласс наследует все характеристики всех его суперклас­ сов. В приведенном выше примере класс С наследует все характеристики классов В и А. В качестве примера построения многоуровневой иерархии рассмотрим е ще одну программу, в которой подкласс BoxWe ight служит в качестве суперкласса для создания подкласса Shipment. Класс Shipme nt наследует все характеристики классов BoxWe ight и Вох и добавляет к ним поле co st, которое содержит стои­ мость доставки посылки. // Расширение класса BoxWeiqht включ ением в него // поля стоимости дос тавки // создать сначала класс Вох class Вох { private douЫ e width; private douЫ e height ; private douЫ e depth; // сконс труировать клон объекта Вох (Вох оЬ ) { // передать объе кт конс труктору width = ob . width; height = ob . height; depth = ob . depth; // конструктор , приме няемый при ука зании всех размеров Box(douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; // конструктор , Вох(){ width = -1; height = -1; depth = -1; приме няемый в отсутст вие размеров 11 значение -1 служи т для обозначения 11 неинициализированного 11 параллелепипеда
220 Часть 1. Язык Java 11 конструктор , применяемый при создании куба Box (douЫe len) { width = height = depth = len; 11 рассчитать и возвратить объем douЫ e volume () { returп width * height * depth ; 11 добавить поле веса clas s BoxWe ight extendз Вох { douЫ e we ight ; // вес параллелепипеда 11 сконструировать клон объе кта BoxW eight ( BoxWe ight оЬ ) { // передать объе кт конструктору supe r (оЬ) ; weight = ob . we ight; 11 конструктор, приме няемый при указании всех параме тров BoxWe ight (douЫe w, douЫe. h, douЫe d, douЫ e m) { sup er (w, h, d) ; // вызвать конс труктор суnеркласса weight = т; 11 конструктор , применяемый по умолчанию BoxWe ight () { supe r (); weight = - 1; 11 конструктор , применяемый при создании куба BoxWe ight ( douЫe len, douЫ e m) { super (len) ; weight = m; 11 добавить поле стоимости доставки clas s Shipme nt extends BoxWe ight { douЫ e cost; 11 сконс труир овать клон объекта Shipment (Shipment оЬ ) { // передать объе кт конструктору supe r (оЬ) ; cost = ob .cost ; 11 конструктор , используемый при указании всех параме тров Shipment (douЫe w, douЫe h, douЫ e d, douЫe m, douЫe с) { super (w, h, d, m) ; // вызвать конструктор суперкласса cost = с; 11 конструктор , применяемый по умо лчанию Shipment () { supe r (); cost= -1;
Гл ава 8. Наследо вание 221 11 конструктор , применяемый при создании куб а Shipme пt (douЫe len, douЫ e m, douЫ e с) { super (len, m) ; cost = с; class DemoShipme nt { puЫ ic static void ma in (String args[] ) { Shipme nt shipment l = new Shipment (lO, 20, 15, 10, 3.41) ; Shipme nt shipment2 = new Shipme nt (2, 3, 4, 0.76, 1.28) ; douЫe vol; vol = shipme ntl . volume () ; System.out . println ("Oбъeм shipment l равен " + vol) ; System. out . println ("Bec shipment l равен " + shipme nt l.w eigh t) ; System.out . println ( "Cтoимocть доставки : $" + shipme n tl .cost) ; System. o ut . println (); vol = shipme nt2 . volume () ; System. out . println ( "Oбъeм shipme nt2 равен " + vo l) ; System. o ut . println ("Bec shipme nt2 равен " + shipment2 .weigh t) ; Sys tem. out . println ( "Cтoимocть доставки : $ " + shipment2 .cost) ; Результат, выводимый этой программой, выглядит следую щим образом: Объем shipmentl равен 3000.О Вес shipmen tl равен 10 .0 Стоимость доставки : $3.41 Объем shipment2 равен 24 .0 Вес shipment2 равен 0.76 Ст оимость доставки : $1 .28 Благодаря наследованию в классе Shipme nt могут использоваться ранее опре­ деленные классы Вох и BoxWe i ght, добавляя только те дополнительные данные, ко торые требуются для его собственного специализи рованного применения. В этом и состоит одно из ценных свойств наследования. Оно позволяет использо­ вать код повтор но. Приведенный выше пример демонстрирует следующий важный аспект: метод su­ per ( ) всегда ссьтается на конструктор ближайшего по иерархии суперкласса. В мето­ де suре r () из клacca Shipme nt вызывается конструктор класса ВохWе i ght. Авметоде super () из класса BoxWe i ght вызывается конструктор класса Вох. Если в иерархии классов требуется передать параметры конструктору суперкласса, то все подклассы должны передавать эти параметры вверх по иерархи и. Данное утверждение справед­ ливо независимо от того , нуждается ли подкласс в собственных параметрах. На заметку! В приведенном выше примере программы вся иерархия классов, включая вох, BoxWe ight и Shipme nt, находится в од ном файле. Это сдел ано тол ько ради удо бства. В Java все три класса могли бы быть размещены в отдел ьных файлах и скомпилированы неза висимо друг от друга . На самом деле использование отдел ьных файлов при создании иерархий классов считается скорее правилом, а не исключением.
222 Часть 1. Язы к Java Порядок вызова конструкторов В каком порядке вызываются конструкторы классов, образующих иерархию при ее создании? Например, какой конструктор вызывается раньше: А ( ) или В ( ) , есл и В - это подкласс , а А - суперкласс? В иерархии классов конструкторы вызыва­ ются в порядке наследования, начиная с суперкласса и кончая подклассом. Более то го, этот порядок остается неизменным независимо от того , используется форма supe r () или нет, поскольку вызов метода supe r () должен быть в первом операто­ ре , выполняемом в конструкторе подкласса. Если метод super () не вызывается, то используется конструктор по умолчанию или же конструктор без параметров из каждого суперкласса. В следующем примере программы демонстрируется по­ рядок вызова и выполнения конструкторов: 11 Продемонстрировать порядок вызова конструкторов 11 создать суперкласс class А А() System. out . priпtln ("B конс трукторе А. ") ; 11 созда ть подкласс путем расширения класса А class В extends А { В() { System.out . println ("B конс трукторе В.") ; 11 создать еще один подкласс путем расширения кл асса В class С extends В { с() { System. out . println ("B конструкторе С. ") ; class CallingCons puЫ ic static void ma in (String args[] ) { С с=new С() ; Эта программа выводит следующий результат: Внутри конструктора А Внутри конструктора В Внутри конструктора С Как видите , конструкторы вызываются в порядке наследования. По зрелом раз­ мышлении становится ясно, что выполнение конструкторов в порядке наследова­ ния имеет определенный смысл. Суперклассу ничего неизвестно о своих подклас­ сах, и поэтому любая инициализация должна быть выполнена в нем совершенно независимо от любой инициализации, выполняемой подклассом. Следовательно, она должна выполняться в первую очередь.
Переопредел е н и е методов Гл ава 8. Наследование 223 Если в иерархии классов совпадают имена и сигнатуры типов методов из под­ класса и суперкласса, то говорят, что метод из подкласса переопределяет метод из суперкласса. Когда переопределенный метод вызывается из своего подкласса, он всегда ссылается на свой вариант, определенный в подклассе. А вариант метода, определенный в суперклассе , будет скрыт. Рассмотрим следующий пример: / ! Переопределение ме тода class А { int i, j; A(int а, int Ь) { iа; j=Ь; 11 вывести содержимо е п ереме нных i и j void show () { System. out . println ("i и j: "+i +""+j); class В extends А { int k; В(intа,intЬ,intс){ super (а, Ь) ; k=с; 11 вывести содержимо е переме нной k с помощью ме тода , /! переопределяюще го ме тод show () из класса А void show() { System. out . println ("k: "+k) ; class Ove rride { puЫ ic static void ma in (String args[] ) { В subOb = new B(l, 2, 3); subOb .show( ); // здесь вызывается метод show () из класса В Эта программа выводит следующий результат: k:3 Когда в данной программе вызывается метод show ( ) для объекта типа В, выби­ рается вариант этого метода, определенный в классе В. Это означает, что вариант метода show ( ) , определенный в классе В, переопределяет его вариант, объявл ен­ ный в классе А. Если требуется получить доступ к варианту переопределенного метода из су­ перкласса, это можно сделать с помощью ключевого слова super. Например, в следующей версии класса В вариант метода show (), объявленный в суперклассе, вызывается из подкласса. Благодаря этому выводятся все переменные экземпляра. class В extends А { int k;
224 Часть 1. Язык Java B(intа,intЬ,intс){ super (а, Ь) ; k=с; void show() { super .show() ; // эдесь вызывается ме тод show () из кл асса А System. out . println ("k: " + k) ; Подстановка этой версии класса А в предыдущую программу приведет к выводу следующего результата: iиj:12 k:з В этой версии выполнение оператора super . show () приводит к вызову вари­ анта метода show ( ) , определенного в суперклассе. Переопределение методов выполняется талъко в том случае, есл и и мена и с иг­ натуры типов о боих методов одинаковы. В противном случае оба метода счита­ ются перегружаемыми . Рассмотр им следующую измененную версию предыдущего примера: // Ме тоды с отлич ающимися сигнатурами считаются // перегружаемыми , а не переопределяемыми class А { int i, j; A(int а, int Ь) { iа; j=Ь; } 11 вывести содержимо е переме нных i и j void show() { System. out . println ("i и j: "+i +" "+j); // создать подкласс путем расширения класса А class В extends А { int k; в(intа,intЬ,intс){ super (a, Ь) ; k=с; } // перегрузить ме тод show () void show (St ring ms g) { System.out . println (msg + k) ; class Ove rride { puЫic static void ma in (String args[] ) { В subOb = new B(l, 2, 3); subOb . show ("Это k: "); // вызвать метод show () из класса В subOb .show () ; // вызвать ме тод show () из кл асса А
Эта программа выводит следующий результат: Этоk:3 iиj:12 Глава 8. Наследование 225 Вариант метода show ( ) , определенный в классе В, принимает строковый пара­ метр. В итоге его сигнатура типа отличается от сигнатуры типа метода без пара­ метров из класса А. Поэтому никакого переопределения (или сокрытия имени) не происходит. Вместо этого выполняется перегрузка варианта метода show ( ) , опре­ деленного в классе А, вариантом , определенным в классе В. Динамическая диспетчеризация методов Несмотря на то что приведенные в предыдущем разделе примеры демонстри­ руют механизм переопределения методов, они не раскрывают весь его потенциал. В самом деле, если бы переопределение методов служило лишь для условного обо­ значения пространства имен, оно имело бы скорее теоретическое, а не практиче­ ское значение. Но в действительности это не так. Переопределение методов слу­ жит основой для одного из наиболее эффективных принципов в Java - динамu'Ческой дш:пет1t ери.з ации м.етодов. Динамическая диспетчеризация методов - это механизм, с помощью которого вызов переопределенного метода разрешается во время вы­ полнения , а не компиляции. Динамическая диспетчеризация методов важна пото­ му, что благодаря ей полиморфизм вJava реализуется во время выполнения. Прежде всего сформулируем еще раз следующий важный принцип: ссылоч­ н ая переменная из суперкласса может ссылаться на объект подкласса. Этот прин­ цип используется в Java для разрешения вызовов переопределенных методов во время выполнения следующим образом: когда переопределенный метод вызы­ вается по ссылке на суперкласс, нужный вариант этого метода выбирается вjava в зависимости от типа объекта, на который делается ссылка в момент вызова. Следовательно, этот выбор Делается во время выполнения. По ссылке на разные типы объектов будут вызываться разные варианты переопределенного метода. Иначе говоря, вариант переопределенного метода выбирается для выполнения в зависимости от типа обТ>еКmа, на который дела.ется ссъ1.1 1 ка, а не типа ссылочной переменной. Та к, если суперкласс содержит метод, переопределяемый в подклас­ се, то по ссылке на разные типы объектов через ссылочную переменную из супер­ класса будут выполняться разные варианты этого метода. В следую щем примере кода демонстрируется динамическая диспетчеризация методов: 11 Ди намическая диспе тчеризация методов class А { void callme () { System.out . println ("B ме тоде callme () из кл асса А" ); class в extends А { 11 пере определи ть ме тод callшe () void callme () { System.out . println ("B ме тоде callme () из кл асса В") ;
226 Часть 1. Яэы к Java class С extends А { 11 переопределить ме тод callll le () void callrne () { Systern.out . println ("B ме тоде callrne () из класса С" ) ; class Di spatch { puЫ ic static void ma i n(String args [] ) А а new А() 11 объект класса А в ь new В() 11 объект класса в с с new С() 11 объект класса с Аr; 11 получить ссылку на объе кт типа А r=а; 11 переменная r ссылается на объект r. callme() ; 11 выз вать вариант ме тода callll le (), 11 определе нный в классе А r=Ь; 11 переменная r ссылается на объект r.callrne () ; 11 выз вать вариант ме тода cal lll le (), 11 определе нный в классе В r=с; 11 переменная r ссыла ется на объект r.callrne () ; 11 вызвать вариант ме тода callшe () , // определенный в классе С Эта программа выводит следующий результат: В методе callrne {) из класса А В методе callrne {) из класса В В методе callrne {) из класса С типа А типа В типа с В этой программе создаются один суперкласс А и два его подкласса В и С. В под­ классах В и С переопределяется метод callme () , объявляемый в классе А. В методе ma in ( ) объявляются объекты классов А, В и С, а также переменная r ссылки на объ­ ект типа А. Затем переменной r присваивается по очереди ссылка на объект каждо­ го из классов А, В и С, и по этой ссылке вызывается метод callme {).Как следует из результата, выводимого этой программой, выполняемый вариант метода cal lme { ) определяется исходя из типа объекта, на который делается ссылка в момент вызова. Если бы выбор делался по типу ссылочной переменной r, то выводимый результат отражал бы три вызова одного и того же метода саl lme ( ) из класса А. На заметку! Тем, у кого имеется опыт программирования на С++ или С#, следует иметь в виду, что переопределенные методы в Java подобны виртуальным функциям в этих языках. Назначение переопределенных м етодов Как пояснялось ранее, переопределенные методы позволяют поддерживать в Java полиморфизм во время выполнения. Особое значение полиморфизма для ООП объясняется следующей причиной: он позволяет определить в общем
Глава 8. Наследо вание 227 классе методы, которые стануг общими для всех производных от него классов, а в подклассах - конкретные реализации некоторых или всех этих методов. Переопределенные методы предоставляют еще один способ реализовать в Java принцип полиморфизма "один интерфейс , множество методов". Одним из основных условий успешного применения полиморфизма является яс­ ное понимание, что суперклассы и подклассы образуют иерархию по степени увели­ чения специализации . Если суперкласс применяется правильно, он предоставляет все элементы, которые мoiyr непосредственно использоваться в подклассе. В нем также определяются те методы, которые должны быть реализованы в самом произ­ водном классе. Это дает уд обную возможность определять в подклассе его собствен­ н ые методы, сохраняя единообразие интерфейса. Та ким образом, сочетая насле­ дование с переопределенными методами, в суперклассе можно определить общую форму для методов, которые будут использоваться во всех его подклассах. Динамический, реализуемый во время выполнения полиморфизм - один из са­ мых эффективных механизмов объектно-ориентированной архитектуры, обеспе­ чивающих повторное использование и надежность кода. Возможность вызывать из библиотек уже существующего кода методы для экземпляров новых классов, не прибегая к повторной компиляции и в то же время сохраняя ясность абстрактного интерфейса, является сильно действующим средством. П рим енени е переопредел ения методов Рассмотрим более практический пример, в котором применяется переопре­ деление методов. В приведенной ниже программе создается суперкласс Figure для хранения размеров двумерного объекта, а также определяется метод area ( ) для расчета площади этого объекта. Кроме того, в этой программе создаются два класса, Re ctangle и Triangle, производные от класса Fig ure. Метод area () пе­ реопределяется в каждом из этих подклассов, чтобы возвращать площадь четыре­ хугольника и треугол ьника соответственно. 11 Применение полиморфизма во время выполнения class Fi gure { douЫ e diml ; douЫ e dim2 ; Figure ( douЫ e а, douЫe Ь) { diml а; dim2 � Ь; douЫe area() { System.out . priпtlп ("Плoщaдь фи гуры не определена ."); returп О; class Re ctaпgle extends Figure { Re ctangle (douЫ e а, douЫe Ь) super (a, Ы; 11 переопределить ме тод area () для че тырехугольника douЫe area () {
228 Часть 1. Язы к Java System. out . println ("B области четырехугольника ."); return diml * dirn2; class Triangle ext ends Figure { Triangle ( douЫ e а, douЫ e Ь) super (а, Ь) ; 11 переопредели ть метод area () для прямоуголь ного треугольника douЫe area () { System. out . println ("B области треугольника ."); return diml * dim2 / 2; class FindAreas { puЫ ic static void ma in (String args [] ) Figure f = new Figure (lO, 10) ; Re ctangle r = new Re ctangle (9, 5) ; Triangle t = new Triangle (lO, 8) ; Figure figre f; figref = r; System. out . println ("Плoщaдь равна " + figref .area ()); figref = t; System. out . println ("Плoщaдь равна "+figref . area () ); figref = f; System.out . println ("Плoщaдь равна "+figref .area() ); Эта программа выводит следующий результат: В области че тырехугольника . Площадь равна 45 В области треуголь ника . Площадь равна 40 Обла сть фигуры не определена . Площадь равна О Двойной механизм наследования и полиморфизма во время выполнения по­ зволяет определить еди ный интерфейс , используемый разнотипными, хотя и свя­ зан ными вместе классами объектов. Та к, если объект относится к классу, произ­ водному от класса Figure, его площадь можно рассчитать, вызвав метод area (). Интерфейс для выполнения этой операции остается неизменным независимо от вида фигуры. Применение абстрактных классов Иногда суперкласс требуется определить таким образом, чтобы объявить в нем структуру заданной абстракции , не предоставляя полную реализацию каждого ме­ тода. Это означает создать суперкласс, определяющий только обобщенную форму для совместного использования всеми его подклассами , в каждом из которых мо-
Глава 8. Н аследование 229 гут быть добавлены требующиеся детал и. В таком кл ассе определяется характер методов, которые должны быть реализованы в подклассах. Подобная ситуация мо­ жет, например, возникнуть, когда в суперклассе не уд ается полностью реализовать метод. Именно так и бьшо в классе Fig ure из предыдущего примера. Определение метода area () в этом классе служит лишь в качестве шаблона, не позволяя рассчи­ тать и вывести площадь объекта какого-нибудь типа. В процессе создания собственных библиотек классов вы сами убедитесь, что отсутствие полного определения метода в контексте суперкласса - не такая уж и редкая ситуация . Выйти из этой ситуации можно двумя способами. Один из них, как бьшо показано в предыдущем примере, состо ит в том, чтобы просто вывести предупреждающее сообщение. Та кой способ удо бен в определенных случаях, на­ пример , при отлад ке, но, как правило, он не годится. Ведь могут быть и такие ме­ тоды, которые должны быть переопределены в подклассе, чтобы подкласс имел хотя бы какой-то смысл. Рассмотрим в качестве примера класс Triang le. Он ли­ шен всякого смысла, если метод area ( ) не определен. В таком случае требуется каким-то образом убедиться, что в подклассе действительно переопределяются все необходимые методы. И для этой цели в Java служит абстрактный метод. Для того чтобы некоторые методы переопределял ись в подклассе, достаточно объявить их с модификатором ти па abstract. Иногда они называются методами под ответственностъю поОк.ласса, поскольку в суперклассе для них никакой реализа­ ции не предусмотрено. Следовател ьно , эти методы должны быть переопределены в подклассе , где нельзя просто воспользоваться их вариантом, определенным в су­ перклассе. Для объявления абстрактного метода используется приведенная ниже общая форма. Как видите, в этой форме тело метода отсутствует. aЬstract !l'XJI 11М1 1 (сп11сож _ параив!l'ров ); Любой класс , содержащий один или больше абстрактных методов, должен быть также объявлен как абстрактный. Для этого достаточно указать ключевое слово abstract перед кл ючевым словом class в начале объявления класса. У аб­ страктно го класса не может быть никаких объектов. Это означает, что экземпляр абстрактного класса не может быть получен непосредственно с помощью операто­ ра new. Та кие объекты были бы бесполезны, поскольку абстрактный класс опреде­ лен не пол ностью. Кроме то го , нельзя объявлять абстрактные конструкторы или абстрактные статические методы. Любой подкласс, производный от абстрактного класса, должен реализовать все абстрактные методы из своего суперкласса или же сам быть объявлен абстрактным. Ниже приведен простой пример класса, содержащего абстрактный метод, и класса, реализующего этот метод. 11 Простой пример абстракции abs tract class А { abs tract void callme() ; 11 абстрактные кл ассы все же могут содержа ть конкре тные ме т оды void callme too () { System.out . println ("Этo конкретный ме тод.") ;
230 Часть 1. Яэы к Java class В extends А { void callme () { System.out . println ("P eaлизaция ме тода callme () из класса В. ") ; class AЬ stractDemo { puЫ ic static void ma in (String args[] ) { ВЬ=newВ(); b.callme (); b.callme too (); Обратите внимание на то , что в этой программе объекты класса А не объяв­ ляются. Как отмечалось ранее, получить экземпляр абстрактного класса нельзя. И еще одно: в классе А реализуется конкретный метод cal lmetoo (),что вполне допустимо. В абстрактные классы может быть включена реализация какого угодно количества конкретных методов. Несмотря на то что абстрактные классы не позволяют получать экземпляры объектов, их все же можно применять для создания ссылок на объекты, поскольку вJava полиморфизм во время выполнения реализован с помощью ссылок на супер­ класс. Поэтому должна быть возможность создавать ссылку на абстрактн ый класс для указания на объект подкласса. В приведенном ниже примере показано, как воспользоваться такой возможностью. Используя абстрактный класс, можно ус овершенствовать созданный ранее класс Figure. Понятие площади неприменимо к неопределенной двумерной фи­ гуре , поэтому в приведенной ниже новой версии программы метод area ( ) объяв­ ляется в классе .Figure как abstract. Это , конечно, означает, что метод area () должен быть переопределен во всех классах, производных от класса Figure. 11 Приме нение абстрактных ме тодов и кл ассов abstract class Figure { douЫe diml ; douЫe dim2 ; Figure (douЫe а, douЫe Ы { diml а; dim2 = Ь; 11 теперь ме тод area () объявляется абстрактныы abstract douЫ e area() ; class Re ctangle extends Figure { Re ctan gle (douЫ e а, douЬle Ь) super (а, Ь) ; 11 переопредели ть ме тод area () для четырехугольника douЫe area() { System.out . println ("B области че тырехуголь ника .") ; return diml * dim2 ;
class Triangle ext ends Figure { Triang le ( douЫ e а, douЫe Ь) super (a, Ь); Гл ава 8. Н а следование 231 // переопределить ме тод area () для прямоуг оль ного треуголь ника douЫe area() { System. o ut . println ("B области треугольника .") ; return diml * dim2 / 2; class AЬst ractAre as { puЫic static void ma in ( String args[] ) { // Figure f = new Figure (lO, 10) ; // теперь недопус тимо Re ctangle r = new Rectangle (9, 5) ; Triangle t = new Triang le (lO, 8) ; Figure figref; // верно , но объект не создается figref = r; System. out . println ("П лoщaдь равна "+figref . area () ); figref = t; System. out .println ( "Плoщaдь равна " + figref . area ()); Как следует из комментариев в теле метода rna in ( ) , объявление объектов ти па Figure больше не допускается, пос кольку те перь этот класс является абстрактн ым. И во всех подклассах , производных от класса Figure, должен быть переопределен метод area ( ) . Чтобы убедиться в этом, попытайтесь создать подкласс, в котором метод area ( ) не переопределяется. Это приведет к ошибке во время компиляции. Если создать объект типа Figure нельзя, то можно хотя бы создать ссьuючную переменную типа F igure. Переменная f i gre f объявлена как ссьтка на класс F i gure, т.е. ее можно использовать для ссьтки на объект любого класса, производного от клас­ са Figure. Как пояснялось ранее, вызовы переопределенных методов разрешаются во время выполнения с помощью ссьточных переменных из суперкласса. Ключевое слово final в сочетании с насл едованием Ключевое слово f inal можно использовать тремя способами. Первый способ служит для создания эквивалента именованной константы . Та кое применение ключевого слова final было описано в предыдущей гл аве . А два других способа его применения относятся к наследованию. Рассмотрим их подробнее. П редотвращени е переопредел е ния с помощью кл ючевого слова final Несмотря на то что переопределение методов является одним из самых эффектив­ ных языковых средств Java, иногда его желательно избегать. Чтобы запретить пере-
232 Часть 1. Яэы к Java определение метода, в начале его объявления следует указать ключевое слово f inal. Методы , объявленные как final, переопределяться немшуг. Та кой способ примене­ ния ключевого слова final демонстрируется в приведенном ниже фрагменте кода. class А { final void me th () { System.out . println ("Этo завершенный ме тод. ") ; class В extends А { vo id me th () ( // ОШИБКА ! Этот ме тод не може т быть переопределен . System.out . println ("H eдonyc тимo !"); Метод me th ( ) объявлен как f ina1 и поэтому не может бьпъ переопределен в клас· се В. Любая попытка переопределить его приведет к ошибке во время компиляции. Иногда методы , объявленные как final , могут способствовать увеличению производительности программы. Компилятор вправе встраившпъ вызовы этих методов, поскольку ему извеспю, что они не будут переопределены в подклассе. Нередко при вызове небольшого завершенного метода ко мпилятор Java может встраивать байт-код для подпрограммы непосредственно в скомпилированный код вызывающего метода, тем самым снижая издержки на вызов метода. Та кая возможность встраивания вызовов присуща только завершенным методам. Как правило, вызовы методов разрешаются в Java динамически во время выполнения. Та кой способ называется поздним связыванием. Но поскольку завершенные методы нс могут быть переопределены, их вызовы могут быть разрешены во время компи­ ляции. И такой способ называется ранним связыванием. Предотвращен ия наспедования с помощью кп ючевого слова final Иногда требуется предотвратить наследование класса. Для этого в начале объявления класса следует указать ключевое слово final. Объявление класса за· вершенным неявно делает завершенными и все его методы. Нетрудно догадаться, что одновременное объя вление класса как abstract и final недопустимо, по· скольку абстрактный класс принципиально является незавершенным и только его подклассы предоставляют полную реализацию методов. Ниже приведен пример завершенного класса. final class А { 11 ... // Следующий класс недопустим . class в extends А { // ОШИБКА! Класс А не может иметь подклассы 11 ... Как следует из комментария к приведенному выше коду, класс В не может на­ следовать от класса А, поскольку класс А объявлен завершенным.
Класс Object Гл ава 8. Н аследование 233 В Java определен один специальный класс, называемый Obj ect. Все осталь­ ные классы являются подклассами, производными от этого класса. Это означает, что класс Ob j ect служит суперклассом для всех остальных классов, и ссьшочная переменная из класса Ob j ect может ссьшаться на объект любого другого класса. А поскольку масс ивы реализованы в виде классов, то ссьшочная переменная типа Obj ect может ссылаться и на любой массив. В классе Ob j ect определены методы, перечисленные в табл. 8.1 и доступные для любого объекта. Табл ица 8.1. Методы из класса Object Метод OЬject clone () Ьoolean equals (OЬject oЬject) void finalize О Class<?> qetClass () int hashCode () void notify () void notifyAll () Strinq toStrinq () void wai.tО void wait (lonq ЮUJ.1 1И С81СУНд) void wait(lonq ии.nпиС81СУНд, int наносекунд) Назначение Создает новый объект, не отл ичающийся от клонируемого Определяет, равен ли один объект другому Вызывается перед удалением неиспользуемого объекта Получает класс объекта во время выполнения Возвращает хеш-код, связанный с вызывающим объектом Возобновляет исполнение потока, ожидающего вызывающего объекта Возобновляет исполнение всех потоков, ожидающих вызывающего объекта Возвращает символьную строку, описывающую объект Ожидает другого потока исполнения Методы getClass (), notify (), notifyAl l () и wa i t () объявлены как final. Остальные методы можно переопределять (они будуг описаны в последующих гла­ вах) . Обратите, однако, внимание на два метода: equa ls () и toString (). Метод equa ls () сравнивает два объекта. Если объекть1 равны, он возвращает логическое значение true , в противном случае -логическое значение false. То чное определе­ ние равенства зависит от типа сравниваемых объектов. Метод toString () возвра­ щает символьную строку с описанием объекта, для которого он вызван. Кроме того, метод toString () вызывается автоматически , когда содержимое объекта выводится с помощью метода println ().Этот метод переопределяется во многих классах, что­ бы приспосабли вать описание к создаваемым в них конкретным типам объектов. И последнее замечание: обратите внимание на необычный синтаксис ти па, возвращаемого методом getClass (). Этот синтаксис имеет отношение к обобщ е· ни.ям вjava, которые описываются в гл аве 14.
9 Пакеты и интерфейсы В этой гл аве рассматриваются два наиболее новаторских языковых средсгваjаvа: пакеты и интерфейсы. Пакеты являются контейнерами классов. Они служат для раз­ делен ия пространств имен класса. Например, можно создать класс List, чтобы хра­ нить его в отдел ьном пакете, не беспокоясь о возможных конфликтах с другим клас­ сом List, хранящимся в каком-нибудь другом месте. Пакеть1 хранятся в иерархиче­ ской структуре и явным образом импортируются при определении новых классов. В предыдущих гл авах было оп исано применение методов для определения интерфейса с да нными в классе. С помощью ключевого слова interface в J ava можно полностью абстрагировать интерфейс от его реализации. Ключевое слово interface позволяет указать ряд методов , которые могут быть реализован ы в од­ ном или нескольких классах. В своей тради ционной форме сам интерфейс не определяет никакой реализации. Несмотря на то что интерфейсы подобны аб­ страктн ым классам , они предоставляют дополнительную возможность: в одном классе можно реализовать несколько интерфейсов. Напротив, класс может насле­ доваться только от одного суперкласса (абстрактного или не абстрактного) . Пакеты Ранее во всех примерах классов использовались имена из одного и того же пространства имен. Это означает, что во избежание конфликта имен для каждого класса нужно было указывать однозначное имя. Но со временем в отсутствие како­ го-нибудь с пособа управления пространством имен может возникнуть ситуация , когда выбор уд обных описательных имен отдел ьных классов станет затруднитель­ ным. Кроме то го, требуется каким-то образом обеспечить, чтобы выбранное имя класса было обоснованно од нозначным и не конфликтовало с именами классов, выбранными другими программистами. (Представьте себе небольшую группу пр о­ граммистов, спорящих о том, кто имеет право использовать Foobar в качестве име ни класса . Или вообразите себе все сообщество Интернета , спорящее о то м, кто первым назвал класс Espre sso.) К счастью, вjava предоставляется механизм разделения пространства имен на более удо бные для управления фрагменты. Этим механ измом является пакет, который одновременно используется и как механизм присвоения имен, и как механизм управления доступностью объектов. В п акете можно определить классы, недоступные для кода за переделами этого пакета. В нем можно также определить чл ены класса, доступные только другим чле-
236 Часть 1. Язы к Java нам этого же пакета. Благодаря такому механизму классы моrуг располагать полны­ ми сведениями друг о друге, но не предоставлять эти сведения остальному миру. Определение пакета Создать пакет совсем не трудно - достаточно включить оператор pac kage в первую строку кода исходного файла программы на Java. Любые классы , объ­ явленные в этом файле , будут принадлежать указанному пакету. Оператор pack­ age определяет пространство имен, в котором хранятся классы. Если же опера­ тор pac kage отсутствует, то имена классов размещаются в пакете , используемом по умолчанию и не имеющем имени. (Именно поэтому в приведенных до сих пор примерах не нужно было беспокоиться об определении пакетов. ) Если пакет по умолчанию вполне подходит для коротких примеров программ, то он не годит­ ся для реальных приложений. Зачастую для прикладного кода придется опреде­ лять отдел ьный пакет. Оператор package имеет следующую общую форму: package паже!l' ; где па кет обозначает имя конкретного пакета. Например, в приведенной ниже строке кода создается пакет MyPackage . package MyPackage ; Для хранения пакетов в Java используются каталоги файловой системы. Например, файлы с расширением . class для любых классов, объявленных в каче­ стве составной части пакета MyPackage , должны храниться в каталоге My Package. Напомним, что в именах файлов и каталогов учитывается регистр символов, а кроме того , они должны точ но соответствовать имени пакета. Один и тот же операто р package может присугствовать в нескол ьких исход­ ных файлах. Этот оператор просто обозначает пакет, которому принадлежат клас­ сы, определенные в данном файле. Это никак не мешает классам из других файлов входить в тот же самый пакет. Больш инство пакетов, применяемых в реальных программах, распределено по многим файлам. В Java допускается создавать иерархию пакетов. Для этой цели служит опера­ ция-точка. Объявление многоуровневого пакета имеет следующую общую форму: package паже!l' 1 [ • паже!1'2 [ • паже!1'3] ] ; Иерархия пакетов должна быть отражена в файловой системе той среды , где разрабатываются программы нajava. Например, в среде Windows пакет, объявлен­ ный как package j ava . awt . image ; , должен храниться в каталоге j ava\awt \im­ age. Имена пакетов следует выбирать очень аккуратно и внимательно. Ведь имя пакета нельзя изменить, не переименовав каталог, в котором хранятся классы. Поиск пакетов и переменная окружения CLASSPATH Как пояснялось в предыдущем разделе, пакеты соответствуют каталогам. В свя­ зи с эти м возникает следующий важный вопрос: откуда исполняющей системеJ ava известно, где следует искать создаваемые пакеты ? Ответ на него можно разделить
Глава 9. Пакеты и интерфейсы 237 на три части . Во-первых, в качестве отправной то чки исполняющая система jаvа испол ьзует по умолчанию текущий рабочий каталог. Та к, если пакет находится в подкаталоге текущего каталога, он будет найден. Во-вторых, один или несколько путей к каталогу можно указать, устан овив соответствующее значение в перемен­ ной окружения CLASS PATH. И в-третьих, команды java и javac можно указывать в командной сроке с параметром -classpath, обозначающим путь к классам. Рассмотрим в качестве примера следующую спецификацию пакета: package MyPack; Чтобы программа могла найти пакет MyPack, должно быть выполнено одно из следующих усло вий. Программа должна быть запущена на выполнение из катало­ га, находящегося непосредственно над каталогом MyPack; переменная окружения CLASSPATH должна содержать путь к каталогу MyPa ck, или же параметр -c lasspath должен обозначать путь к каталогу MyPack в команде java , запускаемой из команд­ ной строки. При использован ии двух последних способов путь к классу не должен вклю­ чать сам пакет MyPack, а просто указывать путъ к этому пакету. Та к, если в среде Windows путь к каталогу MyPac k выглядит следующим образом: C:\MyPrograms \Java \MyPack то путь к классу из пакета MyPack будет таким: C: \MyPrograms \Java Самый простой способ опробовать примеры программ, приведенные в этой книге, состоит в то м, чтобы создать каталоги пакетов в текущем каталоге разра­ ботки программ, разместить файлы с расширением . class в соответствующих каталогах и запускать программы из каталога разработки. В примере, рассматри­ ваемом в следующем разделе, применяется именно такой подход. Кратки й п ример п акета Принимая во внимание все сказанное .\iЫШе, рассмотрим следующий пример простого пакета: 11 Прос той пакет pac kage My Pack; class Balance { String name ; douЫe bal; Balance (String n, douЫ e Ы { name = n; bal=Ь; void show () { if (bal<O ) System. out .print ("--> ") ; System.out.println(name + "· $" + bal); ·
238 Ч асть 1. Язык Java class AccountBal ance { puЫic static void ma in {String args []) { Balance current [] = new Balance [3] ; current [O] new Balance ("K. J . Fielding", 123 .23) ; current [l] = new Balance ("Will Tell ", 157 .02) ; current [2] = new Balance {"T om Jackson" , -12 .33) ; for (int i=O ; i<3 ; i++ ) current [i] .show() ; Назовите этот исходный файл Accoun tBalance . j ava и разместите его в ката­ логе MyPack, а затем скомпилируйте его. Непременно расположите получаемый в итоге файл с расширением . class в том же каталоге MyPack. После этого попро­ буйте выполнить класс AccountBalance, введя в командной строке следующую команду: java МyPack . Accountвalance Напомним, что при выполнении этой команды текущим должен быть каталог, рас положенный над каталогом MyPack, или же в переменной окружения CLASSРАТН должен быть указан соответствую щий путь. Как пояснялось ранее, теперь класс Ac countBalance входит в пакет MyPack. Это означает, что его нельзя выполнять самостоятельно. Следовательно , в командной строке нельзя выполнить приведен­ ную ниже команду, поскольку для уточнения имени клacca AccountBalance требу­ ется указать имя его пакета. java Accountвalance Защита доступа В предыдущих гл авах были рассмотрены различные особенности механизма управления доступом в Java и его модификаторы. В частности , доступ к закрыто­ му члену класса предоставляется только другим членам этого класса. Пакеты рас­ ширяют возможности управления доступом. Как станет ясно в дальнейшем, в Java предоставляются многие уровни защиты , обеспечивающие очень то чное управле­ ние доступностью переменных и методов в классах, подклассах и пакетах. Классы и пакеты одновременно служат средствами инкапсуляции и хранили­ щем пространства имен и области видимости переменных и методов. Пакеть1 играют роль контейнеров классов и других подчиненных пакетов. Классы служат коtпейнерами данных и кода. Класс - наименьшая единица абстракции в Java. Вследствие взаимодействия между классами и пакетами в Java определяются четы­ ре категории доступности членов класса. • Подклассы в одном пакете . • Классы в одном пакете , не являющиеся подклассами. • Подклассы в различных пакетах. • Классы, кото рые не находятся в одном пакете и не являются подклассами.
Глава 9. Пакеты и интерфейсы 239 Тр и модификатора доступа (pri va te, puЫ ic и protected) предоставляют раз­ личные способы создания многих уровней доступа, необходи мых для этих катего­ рий. Взаимосвязь между ними описана в табл. 9 . 1 . Табл ица 9 .1 . Доступ к членам класса Private М оди ф и катор Protected PuЬlic отсутствует Один и тот же класс Да Да Да Да Подкласс , п роизвод ный от кл асса Нет Да Да Да из того же самого пакета Класс из того же самого пакета, Нет Да Да Да не являющийся подклассом Подкл асс , п роизводный от класса Нет Нет Да Да другого п акета Класс из другого пакета, не являющий- Нет Нет Нет Да ся подклассом, п роизводный от класса из да нного пакета На первый взгляд , механизм управления доступом в Java может показаться сложн ым, поэтому следующие разъяснения помогут легче понять этот механизм. Любой компонент, объя вленный как puЫ ic, доступен из любого кода. А любой ко мпонент, объявленный как private, недоступен для компонентов, находящих­ ся за пределами его класса. Есл и в объявлении члена класса отсугствует явно ука­ занный модификатор доступа , он доступен для подклассов и других классов из дан ного пакета. Этот уровень доступа испол ьзуется по умолчанию. Если же тре­ буется, чтобы элемент был доступен за пределами его текущего пакета, но только кл ассам , непосредственно производным от данного класса, такой элемент должен быть объявлен как protected. Правила доступа, приведенные в табл . 9 . 1, применимы только к членам класса. Для класса, не являющегося вложенным, может быть указан только один из двух возможных уровней доступа: по умолчанию и открытый (puЫic). Если класс объ­ явлен как puЫ ic, он доступен из любого другого кода. Если у класса имеется уро­ вень доступа по умолчанию, такой класс оказывается доступным только для кода из данного пакета. Если же класс оказывается открытым, он должен быть един­ ственным открытым классом, объявленным в файле, а имя этого файла должно совпадать с именем класса. Пример защиты доступа В приведенном ниже примере демонстрируется применение модификаторов уп равления доступом во всех возможных сочетаниях. В этом примере употребля­ ются два пакета и пять классов. Не следует, однако , забывать, что классы из двух разных пакетов должны храниться в каталогах, имена которых совпадают с имена­ ми соответствующих пакетов (в данном случае - p l и р2).
2'0 Часть 1. Я зык Java В исходном файле первого пакета определяются три класса: Protection, De rived и SarnePackage. В первом классе определяются четыре переменные типа int: по одной в каждом из допустимых режимов защиты доступа. Переменная n объявлена с уровнем защиты по умолчанию, переменная n_pri - как private, переменная n_p ro - как protected, переменная n_pub - как puЫic. В данном примере все другие классы будут предпринимать попытку обращения к переменным экземпляра первого класса. Строки кода, компиляция которых не­ возможна из-за нарушений правил доступа, закомментированы. Перед каждой из этих строк расположен комментарий с указанием мест программы, из которых был бы возможен доступ с заданным уровнем защиты . Вторым в пакете рl является класс De rived, производный от класса Protection из этого же пакета. Это предоставляет классу De rived доступ ко всем перемен­ ным из класса Protection, кроме переменной n_pri, объявленной как private. Тр етий класс, SarnePackage , не является производным от класса Protection, но находится в этом же пакете и обладает доступом ко всем переменным, кроме пере­ менной n_pri. Файл Protection . java содержит следующий код: package pl ; puЫ ic class Protection intn=1; private int n pri = 2; protected int - n_yro 3; puЫ ic int n_pub = 4; puЫic Protection ( ) { System. out . println ("кoнcтpyктop базового кл асса") ; System.out . println ("n ="+ n) ; System.out . println ("n pri "+n pri) ; System.out . println ( "n - pro "+n - pro) ; System.out . println ("n - pub "+n::_:pub) ; } - Файл Der i ved . j а va содержит такой код: package pl ; class De rived extends Protection { Derived () { System. out . println {"к oнcтpyктop подкласса" ); System.out . println ("n = " + n) ; 11 доступно только дл я класса 11 System. out . priпtlп ("n _ pri "4 + n_pri) ; System . out .println ("n pro System. out . println ("n::_:pub "+npro); " + n::_:pub); Файл SarnePackage . java содержит следующий код: package pl ; class SamePackage
SamePackage () { Глава 9. Пакеты и интерфейсы 241 Protection р = new Protection () ; System.out . println ("к oнcтpyктop этого же пакета" ); System.out . println ("n = "+p.n) ; 11 доступно толь ко для кл асса 11 System.out . printlп ("n_pri " + p.n_pri) ; System.out . println ("n pro System.out . println ("n:: :: pub "+p.n pro) ; "+p.n:: :: pub) ; Ниже приведен исходный код второго пакета р2. Два определенных в. нем класса отражают два оставшихся режима упрамения доступом. Первый класс, Рrоtесtiоn2 , является производным от класса pl . Protection. Он имеет доступ ко всем перемен­ ным класса pl . Protect ion, кроме переменной n_pri, поскольку она объявлена как private, а также переменной n, объявленной с уровнем защиты по умолчанию. Напомним , что режим доступа по умолчанию разрешает доступ из данного класса или пакета, но не из подклассов другого пакета. И наконец, класс Othe rPackage имеет до­ ступ только к одной переменной n_pub, объя вленной как puЫic. Файл Protection2 . java содержит следующий код: package р2 ; class Protection2 extends pl . Protection { Protection2 () { System.out . println ( "конструктор , унаследованный иэ другого пакета"} ; 11 доступно толь ко для данного класса или пакета 11 System.out . println ("n ="+ n) ; 11 доступно только для данного кл асса 11 System.out . println ("n_pri "+n_pri) ; System.out . println ("n __ pro System.out . println ("n_pub " + n__pro); "+n__pub) ; Файл Othe rPac kage . j ava содержит следую щий код: pac kage р2 ; class OtherPackage Ot herPackage () pl . Protection р = new pl . Protection () ; System.out . println ("кoнcтpyктop и э другого пакета") ; 11 доступно только для данного класса или пакета 11 System.out . println ("n ="+ p.n) ; 11 доступно только для данно го класса 11 System .out . println ("n_pri ="+ p.п_pri) ; 11 доступно только для данного кл асса, подкласса или пакета 11 System.out . println ("n_pro "+p.n_pro ) ;
242 Часть 1. Язы к Java System.out . priпtlп ("п _ pub "+р.п_рuЬ) ; Для того чтобы проверить работос пособность упомя нутых выше двух пакетов, следует воспол ьзоваться двумя тестовыми файлами. В частности , тестовый файл для пакета р 1 имеет следующее содержимое: // Демонстрационный пакет pl package pl ; 11 получить экземпляры различных кла ссов из пакета pl puЫic class Demo { puЫic static vo id ma iп (St riпg args[]) { Protectioп оЫ = пеw Protectioп () ; De rived оЬ2 = пеw De rived (); SamePackage оЬЗ = пеw SamePackage (); Еще один тестовый файл из пакета р2 содержит следующее: 11 Демонстрационный пакет р2 package р2 ; 11 получи ть экземпляры различных кл ассов из пакета р2 puЬl ic class Demo { puЬlic static vo id ma iп ( Striпg args [] ) { Protectioп2 оЫ = пеw Protectioп2 () ; Othe rPackage оЬ2 = пеw OtherPackage (); И мпорт пакето в Если вспомнить, что пакеты предлагают эффективный механизм изоляции раз­ личных классов друг от друга, то становится понятно, почему все встроенные классы Java хранятся в пакетах. Ни один из основных классов Java не хранится в неимено­ ванном пакете, используемом по умолчан ию. Все стандартные классы непременно хранятся в каком-нибудь именованном пакете. А поскольку в самих пакетах классы должны полностью определяться по именам их пакетов, то длинное, разделяемое точками имя пути к пакету каждого используемого класса может оказаться слишком громоздким. Следовательно, чтобы о'IДельные классы или весь пакет можно было сде­ лать доступн ыми, в Java внедрен оператор import. После того как класс импортиро­ ван, на него можно ссьиаться непосредственно, используя только его имя. Оператор import служит только для удобства программирования и не является обязательным с формальной точки зрения для создания завершенной программы нa java. Но если в прикладном коде приходится ссьиаться на несколько десятков классов, то оператор import значительно сокращает объем вводимого исходного кода. В исходном файле программы нajava операторы import должны следовать не­ посредственно за оператором package (если таковой имеется ) и перед любыми определениями класс ов. Оператор import имеет следующую общую форму: import паже�l [.nаже�2] .(ян�r_жласса 1 *) ;
Гл ава 9 . Пакеты и интерфейсы 2'3 где па ке т 1 обозначает имя пакета верхнего уровня, а па кет2 - имя подчиненного пакета из внешнего пакета, отделяемое знаком точки ( . ) . Гл убина wюженности пакетов практически не огран ичивается ничем , кроме файловой системы. И на­ конец, имя_ кла сса может быть задано явно или с помощью знака "звездочка" ( *), который указывает компилятору Java на необходимость импорта всего пакета. В следующем фрагменте кода демонстрируется применение обоих вариантов об­ щей формы оператора import: import java .util . Date; import java.io.*; Все классы из стандартной библиотеки jаvа хранятся в пакете java. Основные языковые средства хранятся в пакете j а va . lang, входящем в пакет j а va. Обычно каждый пакет ил и класс, который требуется использовать, приходится импорти­ ровать. Но поскольку программировать на Java бесполезно без многих средств, определенных в пакете j ava . lang, компилятор неявно импортирует его для всех программ. Это равнозначно наличию следующей строки кода в каждой из п ро­ грамм нa java: import java .lang.*; Комп илятор никак не отреагирует на наличие классов с одинаковыми именами в двух разных пакетах, импортируемых в форме со звездочкой , если только не бу­ дет предпринята попытка воспользоваться одним из этих классов. В таком случае возникнет ошибка во время компиляции, и тогда имя класса придется указать явно вместе с его пакетом. Следует особо подчеркнуть, что указывать оператор import совсем не обяза­ тельн о. Полностъю утачненное имя класса с указан ием всей иерархии пакетов можно использовать везде, где допускается имя класса. Например , в приведенном ниже фрагменте кода применяется оператор import. import java .util .*; class MyDate extends Date { } Этот же фрагмент кода, но без оператора import, показан ниже. В этой версии класс Da te полностью определен. class MyDate extends jav a.util .Date { } Как следует из табл. 9. 1, при импорте пакета классам , не производным от клас­ сов из данного пакета в импортирующем коде, будут доступны только те элементы пакета, которые объявлены как рuЫ i с. Та к, если требуется , чтобы упоминавший­ ся ранее класс Balance из пакета MyPack был доступен в качестве самостоятель­ ного класса за пределами пакета MyPack, его следует объявить как puЫic и разме­ стить в отдельном файле, как показано в приведенном ниже примере кода. pac kage MyPack; / * теперь класс вalance , его конструктор и ме тод show () */ являются от крытыми . Это означает , что за пределами своего пакета они доступны из кода классов, не производных от кла ссов их пакета .
244 Часть1.ЯзыкJava puЫ ic class Balance String name ; douЫe bal; puЫ ic Balance (String n, douЫ e Ь) { name = n; bal=Ь; puЫic void show () { if (bal<O) System. out .pr int ("-- > ") ; System. out . println (name + "· $" + bal) ; Как видите , класс Balance те перь объя влен как puЫic. Его конструктор и ме­ тод show () также объя вл ены как puЫic. Это означает, что они доступны для лю­ бого кода за пределами пакета MyPack. Например, класс TestBalance импортиру­ ет пакет MyPack, и поэтому в нем может быть использован класс Balance. import MyPack .*; class TestBalance puЫic static void ma in (String args [] ) { /* Кл асс Balance объявлен как puЬlic, поэтому им можно восполь зоваться и вызвать его конструктор . */ Balance test = new Balance ("J. J . Jaspers", 99 .88) ; test . show() ; // можно также вызвать ме тод show () В качестве эксперимента уд алите модификатор puЫ ic из объя вл ения класса Balance, а затем попытайтесь скомпилировать класс TestBalance . Как упомина­ лось выше, это приведет к появлению ошибок. И нтерфейс ы С помощью ключевого слова interface можно полностью абстрагировать ин­ терфейс класса от его реализации. Это означает, что с помощью ключевого слова interface можно указать, 'Что именно должен выполнять класс, но не как это де­ лать. Синтакс ически интерфейсы аналогичны классам , но не содержат перемен­ ные экземпля ра, а объя вления их методов, как правило , не содержат тело метода. На практике это означает, что можно объя влять интерфейсы, которые не делают никаких допущений относительно их реализации. Как только интерфейс опреде­ лен , его может реализовать любое количество классов. Кроме того , один класс мо­ жет реализовать любое количество интерфейсов. Чтобы реализовать интерфейс, в классе должен быть создан полный набор ме­ тодов, определенных в этом интерфейсе. Но в каждом классе могут быть опреде­ лены особенности собственной реализации данного интерфейса. Ключевое слово inte rface позволяет в полной мере использовать принцип полиморфизма "один интерфейс , несколько методов".
Глава 9. Пакеты и интерфейсы 245 Интерфейсы предназначены для подцержки динамического разрешения вы­ зовов методов во время выполнения. Как правило, для нормального выполнения вызова метода из одного класса в другом оба класса должны присутствовать во вре­ мя компиляции, чтобы компилятор Java мог проверить совместимость сигнатур методов . Само по себе это требование создает статическую и нерасширяемую сре­ ду распределения классов. В такой систе ме функциональные возможности неиз­ бежно передаются вверх по иерархии классов, в результате чего механизмы будут становиться доступными все большему количеству подклассов. Инте рфейсы пред­ назначены для предотвращения этой проблемы. Они изолируют определение ме­ тода или набора методов от иерархии наследования. А поскольку иерархия интер­ фейсов не совпадает с иерархией классов, то классы, никак не связанные между собой иерархически , могут реализовать один и тот же интерфейс. Именно в этом возможности инте рфейсов проявляются наиболее полно. Объявление интерфейса Во многом определение интерфейса подобно определению класса. Уп рощенная общая форма интерфейса имеет следующий вид: доступ interface 11 1 :ня{ 11 .воs.врца&НIDi тип 11 1 :ня нвтодаl ( сп11 1 сож паранв!L'рОв) ; воs.врца&Н1Di- т11 1 п ш- нетода2 ( сп11 1 сож-паране!l'рОв) ; тип11 1 НSl sа.ввРшен н о:М nеренен н ойl - sначвнже ; тип 11 1 :ня:s�вр1 1l ен н о:М=пвренен н ой2 = sначвнив; воs.врцавншi тип 11 1НЯ нетодаN (списож паране!l'рОв) ; типJl lНSl _ SавёРшен н о:М_nеренен н оiiN = sначеюtе ; Если определение не содержит никакого модификатора доступа, используется доступ по умолчанию, а интерфейс доступен только другим членам того пакета, в котором он объявлен. Если интерфейс объя вл ен как puЫ ic, он может быть ис­ пользован в любом другом коде. В этом случае интерфейс должен быть единствен­ ным открытым интерфейсом, объявленным в файле, а имя этого файла должно со­ впадать с именем интерфейса. В приведенной выше общей форме указанное имя обозначает конкретное имя интерфейса, которым может быть любой допустимый идентификатор. Обратите внимание на то , что объявляемые методы не содержат тел. Их объявления завершаются списком параметров, за которым следует то чка с запятой. По существу, они явля ются абстрактными методами. Каждый класс, ко­ торый включает в себя интерфейс , должен реализовать все его методы. Прежде чем продолжить дальше, важно отметить, что в версииJDК 8 ключевое слово interface дополнено средством, значительно изменяющим его возможно­ сти . До версии JDК 8 в инте рфейсе вообще нельзя бьшо ничего реализовать. В ин­ терфейсе, упрощенная форма объя вл ения которого приведена выше, наличие тела в объя вл яемых методах не предполагалось. Следовательно, до версии JDK 8 в интерфейсе можно бьшо оп ределить только то , что именно следует сделать, но не как это сделать. Это положение изменилось в версии JDК 8. Те перь метод мож­ но объявлять в интерфейсе с реализацией по умолчанию, т. е . указать его поведение. Но методы по умолчанию, по существу, служат специальной цели, сохраняя в то же
2'6 Часть 1. Язык Java время исходное назначение интерфейса. Следовательно, интерфейсы можно по­ прежнему создавать и использовать и без методов по умолчанию. Именно по этой причине обсуждение интерфейсов будет начато с их традиционной формы. А ме­ тоды по умолчанию описываются в конце этой гл авы. Как следует из приведенной выше общей формы, в объявлениях интерфейсов могут быть объявле ны переменные. Они неявно объявляются как f inal и s ta t ic, т.е. их нельзя изменить в классе, реализующем интерфейс. Кроме того, они долж­ ны быть инициализированы. Все м етоды и переменные неявно объявляются в ин­ терфейсе как рuЫi с. Ниже приведен пример объявления интерфейса. В нем объявляется простой интерфейс, который содержит один метод callback (), принимающий един­ ствен ный целочисленный параметр. inter face Callback { void callback (int param) ; Реаn иэа ция интерфейсов Как только интерфейс определен, он может быть реализован в одном ил и не­ скольких классах. Чтобы реализовать интерфейс, в определение класса требуется включить выражение imp l emen ts, а затем создать методы, определенные в интер­ фейсе. Общая форма класса, который содержит выражение implements, имеет следующий вид: дос'J!уп class .- JtJ J acca [extends cyпepJtJ J acc] [i11PleМi i ts -'J!eptuc [, -'J!epфвi i c...JJ 11 тело класса Если в классе реализуется больше одного интерфейса, имена интерфейсов раз­ деляются запятыми. Та к, если в классе реализуются два интерфейса, в которых объявляется один и тот же метод, то этот же метод будет исполь.зоваться клиен­ тами любого из двух интерфейсов. Методы, реализующие элементы интерфейса, должны быть объявлены как puЫ ic. Кроме того, сигнатура типа реализующего метода должна в точности совпадать с сигнатурой типа, указанной в определении inter face. Рассмотрим небольшой пример класса, где реализуется приведенный ранее интерфейс Callback. Обратите внимание на то, что метод callback () объявлен с модификатором доступа puЫ i c. class Client implement s Callback { 11 реал изовать интерфейс CallЬaclt puЬl ic void callback (int р) { System.out . println ( "Метод callback (), вызываеNЫЙ со значением " + р) ; Помн11 1'8 1 Когда реал изуется метод из интерфейса, он должен быть объявлен как рuЫ i с.
Глава 9. Пакеты и интерфейсы 21+7 Вполне допустима и достаточно распространена ситуация , когда в классах, реализующих интерфейсы, определяются также собственные члены. Например, в следующей версии класса Client реализуется метод callback () и добавляется метод non i faceMe th (): class Client implements Callback { 11 реализовать интерфейс CallЬack puЫic void callback (int р) { System. out . println ( "Метод callback () , вызываемый со значением " + р) ; void noni f aceMe th () { System. out . println ("B кл ассах , реализующих интерфейсы, " + "могут определяться и другие члены.") ; Доступ к реализациям через ссыпки на интерфейсы Переменные можно объявлять как ссылки на объекты , в которых используется тип интерфейса, а не тип класса. С помощью такой переменной можно ссылаться на любой экземпляр какого угодно класса, реализующего объявленный интерфейс . При вызове метода по одной из таких ссылок нужный вариант будет выбираться в зависимости от конкретного экземпляра интерфейса, на который делается сс ыл­ ка. И это одна из гл авных особенностей интерфейсов. Поиск исполняемого метода осуществляется динамически во время выполнения, что позволяет создавать клас­ сы позднее, чем код, из которого вызываются методы этих классов. Вызывающий код может выполнять диспетчеризацию методов с помощью интерфейса, даже не имея никаких сведений о вызываемом коде . Этот процесс аналогичен использова­ нию ссьшки на суперкласс для доступа к объекту подкласса (см. гл аву 8). Внимание! Поскол ьку в Java динамический поиск методов во время выполнения сопряжен со зна­ чительными издержка ми по сравнению с обычным вызовом методов, в прикладном коде, критичном к производител ьности , инте рфейсы следует использовать тол ько тогда, когда это действител ьно необходимо. В следующем примере программы метод callback () вызывается через пере­ менную ссьшки на интерфейс: class Testiface { puЬlic static voi d ma in (String args [] ) { Callbac k с=new Client () ; с.callback ( 42 ) ; Эта программа выводит следующий результат: Ме тод callback (), вызыв а емый со значением 42 Обратите внимание на то , переменной с присвоен экземпля р класса Client , несмотря на то , что она объявлена с типом интерфейса Callback. Переменную с можно использовать для доступа к методу callback (), она не предоставляет до-
2'8 Часть1.ЯэыкJava ступа к каким-нибудь другим членам класса Client. Переменная ссылки на интер­ фейс располагает только сведениями о методах, объявленных в том интерфейсе, на который она ссылается. Та ким образом, переменной с нельзя пользоваться для доступа к методу non i faceMe th (), поскольку этот метод объявлен в классе Client, а не в интерфейсе Callback. Приведенный выше пример формально показывает, каким образом перемен­ ная ссьшки на интерфейс может получать доступ к объекту его реализации, тем не менее , он не демонстрирует полиморфные возможности такой ссылки. Чтобы продемонстрировать такие возможности , создадим сначала вторую реализацию интерфейса Ca l lback, как показано ниже . // Еще одна ре ализация интерфейса CallЬack class AnotherClient implements Callback { 11 реализовать интерфейс CallЬack puЫic void callback (int р) { System.out . println ( "Eщe один вариант ме т ода callback ()"); System.out . println ("p в квадрате равно " + (р*р) ); Те перь попробуем создать и опробовать следующий класс : class Te sti face2 { puЫ ic static void ma in (String args[]) { Callback с=new Client () ; An otherClient оЬ = new AnotherClient () ; c.callback (42) ; с = оЬ ; // теперь переменная с ссылается на 11 объект типа AnotherClient c.callback (42) ; Эта программа выводит следующий результат: Метод callback () , вызываемый со значением 42 Еще один вариант ме тода callback () р в квадрате равно 1764 Как видите, вызываемый вариант метода callback () выбирается в зависимо­ сти от типа объекта, на который переменная с ссьшается во время выполнения. Приведенный выше пример довольно прост, поэтому далее будет рассмотрен еще один , более практический пример. Частичные реализации Если класс включает в себя интерфейс , но не полностью реализует определен­ ные в нем методы , он должен быть объявлен как abstract: ab stract class Incomplete impleme nts Callback { int а, Ь; void show() { System.out.println(a + " " + Ь);
11 ... Глава 9. Пакеты и интерфейсы 2'9 В дан ном примере кода класс Incomplete не реал изует метод callback (), поэтому он должен быть объявлен как абстрактный. Любой класс, наследующий откласса Incomplete, дoлжeн реализовать метод саll Ьасk () или быть также объ­ явленным как ab stract. Вложенные интерфейсы Интерфейс может бьrгь объявлен членом класса или другого интерфейса. Та кой ин терфейс называется иитерфейсом-'Ч.!lеН О м или вложениъ�м иитерфейсом. Вложенный интерфейс может быть объявлен как puЫ ic, pr ivate или protected. Этим он от­ личается от интерфейса верхнего уровня , который должен бьrгь объявлен как pub­ lic или использовать уровень доступа по умолчанию, как отмечалось ранее. Когда вложенный интерфейс используется за пределами объемлющей его области дей­ ствия, его имя должно быть дополнительно уточнено именем класса или интерфей­ са, членом которого он является. Это означает, что за пределами класса или интер­ фейса, в котором объявлен вложенный интерфейс, его имя должно бьrгь уточнено полностью. В следующем примере демонстрируется применение вложенного интерфейса: 11 Пример вложе нного интерфейса 11 Этот класс содержи т интерфейс как свой член class А { 11 это вложенный интерфейс puЫ ic interface NestedI F { boolean isNotNegat ive (int х) ; 11 Кла сс В реализует вложенный интерфейс class В implements A.NestedI F { puЫ ic bool ean isNotNegative (int х) { returnх<О?false:true; class NestedIFDemo { puЫ ic static vo id ma in (String args[] ) { 11 исполь зовать ссыл ку на вложенный интерфейс A.NestedIF nif = new В(); if (nif .is NotNegative (lO) ) System. o ut . println ( "Чиcлo 10 не отрицательное ") ; if (nif.is NotNegative (-1 2) ) System. o ut . println ("Э тo не будет выведено" ); Обратите внимание на то , что в классе А определяется вложенный интерфейс Ne stedI F, объявленный как puЫ ic. Затем вложенный интерфейс реализуется в классе В следующим образом: implements A.NestedI F
250 Часть 1. Язы к Java Обратите также внимание на то , что имя интерфейса полностью уточнено и содержит имя класса. В теле метода ma in ( ) создается переменная nif ссылки на инте рфейс А.Ne stedIF, которой присваивается ссылка на объект класса В. И это вполне допустимо, поскол ьку класс В реализует инте рфейс А. Ne stedI F. Применение интерфейсов Чтобы стали понятнее возможности интерфейсов, рассмотрим более практи­ ческий пример. В предыдущих главах был разработан класс Stack, реализующий простой стек фиксированного размера. Но стек можно реализовать самыми раз­ ными способами. Например, стек может иметь фиксированный или "наращивае­ мый" размер. Стек может также храниться в массиве, связанном списке, двоичном дереве и т. п. Независимо от реализации стека, его интерфейс остается неизмен­ ным. Это означает, что методы push ( ) и рор ( ) определяют интерфейс стека не­ зависимо от особенностей его реализации. А поскольку интерфейс стека отделен от его реализации, то такой интерфейс можно определ ить без особого труда, оста­ вив уточнение конкретных деталей в его реализации. Рассмотрим два примера применения интерфейса стека. Создадим сначал а интерфейс, определяющий целочисленный стек, разместив его в файле IntStack . java . Этот интерфейс будет использоваться в обеих реали­ зациях стека. 11 Определить интерфейс для целочисленного стека interface IntStack { void push (int item) ; // сохранить элемент в стеке int рор() ; // извлечь элемент из стека В приведенной ниже программе создается класс FixedStack, реализующий версию целочисленного стека фиксированной дл ины. 11 Реализация интерфейса IntStack дл я сте ка фиксированного размера class FixedStack implements IntStack { private int stc k(] ; private int tos ; 11 выд елить памя ть и инициализировать стек FixedStack (int size) { stck = new int [size] ; tos = -1; 11 разместить элемент в стеке puЫic void push (int item) { if ( tos==stck . length- 1) // использовать поле длины стека System. out . println ("Cтeк заполнен ."); else stck [ ++tos] = item; 11 извлечь элеме нт и з стека puЫic int рор () { if(tos < 0) { System. out .println ( "Cтeк не загружен.") ; re turn О;
else return stck [ tos--] ; class IFТest { Глава 9. Пакеть1 и интерфейсы 251 puЫic static void main (String args[J ) { FixedStack my stackl new FixedS tack (5) ; FixedStack my stack2 = new FixedStack (B) ; // разместить числа в стеке for (int i=O; i<5; i++) mys tackl .push (i) ; for (int i=O; i<B; i++) mystack2 .push (i); // извлечь эти числа из стека System. out .println ( "Cтeк в my stackl :") ; for (int i=O ; i<5 ; i++) System. out . println (mys tackl .pop() ); System.out . println ("Cтeк в mystack2 :") ; for (int i=O; i<B; i++) System. out . println (mystack2 .pop() ); Ниже приведена еще одна реализация и нтерфейса I n t S tack, в которой с помо­ щью того же самого определения in te r f асе создается динамически й стек . В этой реализации каждый стек создается с первоначальной дл иной. При превышении этой начальной длины размер стека увеличивается . Каждый раз, когда возни кает пот ребность в дополнительном свободном месте , размер стека уд ваивается. // Реализация "наращив аемого" стека clas s DynStack implements IntStack { private int stck[] ; private int tos ; // выдели ть памя ть и инициализировать стек DynStack (int size) { stck = new int [size] ; tos = -1; // разместить элеме нт в стеке puЬlic void push (int item) { 11 если стек заполнен, выдели ть памя ть под стек большего размера if ( tos==stck . length- 1) { int temp[] = new int [stck . length * 2] ; // удвоить размер стека for (int i=O ; i<stck . length; i++) temp[i] = stck[i] ; stck = temp ; stck [ ++tos ] = item; else stck [ ++tos ] item; // извлечь элемент из стека puЬlic int рор () { if(tos < 0) { System. out . println ("Cтeк не загруже н .");
252 Часть 1. ЯзыкJava return О; else return stck [ tos--] ; class IFTest2 { puЫic static void main (St ring args[] ) { DynStack my stackl new DynStack (5) ; DynStack mys tack2 = new DynS tack (8) ; 11 В этих циклах увеличиваются разме ры каждого стека for ( int i=O; i<l2; i++) mystackl .push (i) ; for ( int i=O ; i<20; i++) mystack2 .push (i) ; System.out . println ("Cтeк в mys tackl :"); for (int i=O; i<12; i++) System.out . println (mystackl .pop() ); Systern.out . println ("Cтeк в mys tack2: ") ; for ( int i=O; i<20; i++) Systern. out . println (mystack2 .pop() ); В приведенном ниже примере программы создается класс, в котором исполь­ зуются обе реализации данного интерфейса в классах FixedStack и DynStack. Для этого применяется ссылка на интерфейс. Это означает, что поиск вариантов при вызове методов push ( ) и рор ( ) осуществляется во время выполнения , а не во время компиляции. /* Создать переменную интерфейса и обратиться к сте кам через нее . */ class IFTestЗ { puЫic static void main (St ring args [] ) { IntStack mystack; // создать переменную ссылки на интерфейс DynStack ds = new DynStack (5) ; FixedStack fs new FixedStack (8) ; mystack = ds ; 11 загрузить динамический стек 11 разместить числа в стеке for ( int i=O; i<l2; i++) mystack.push (i); mystack = fs; // загрузить фиксированный стек for (int i=O; i<8; i++) mystack.push (i) ; rnystack = ds ; Systern.out .pr intln ("З нaчeния в динамич еском стеке:") ; for ( int i=O; i<l2; i++) System.out . println (mystack .pop () ); mystack = fs; System.out . println ("З нaчeния в фиксированном стеке:") ; for (int i=O ; i<8 ; i++) System.out .println (mystack .pop ( ));
Гл ава 9. Пакеты и интерфейсы 253 В этой программе переменная mys tack содержит ссылку на интерфейс IntStack. Следовател ьно, когда она ссьшается на переменную ds , выбираются варианты методов push () и рор (), определенные при реализации данного интер­ фейса в классе DynStack. Когда же она ссылается на переменную fs, выбираются варианты методов push () и рор (), определенные при реализации данного интер­ фейса в классе FixedS tack. Как отмечалось ранее, все эти решения принимаются во время выпол нения. Обращение к нескольким реализациям интерфейса через ссылочную переменную инте рфейса является наиболее эффективным средством в Java для поддержки полиморфизма во время выполнения. Переменные в интерфейсах Интерфейсы можно применять для импорта совместно используемых констант в несколько классов пугем простого объявления интерфейса, который содержит переменные, инициализированные нужными значениями. Когда интерфейс включа­ ется в класс (т.е . реализуется в нем), имена всех этих переменных оказываются в об­ ласти действия констант. (Это аналогично использованию в программе на С/С++ заголовочного файла для создания большого количества констант с помощью дирек­ тив #define или объявлений const.) Если интерфейс не содержит никаких методов, любой класс, включающий в себя такой интерфейс , на самом деле ничего не реализу­ ет. Это все равно, как если бы класс импортировал постоя нные поля в пространство имен класса в качестве завершенных переменных. В следующем примере программы эта методика применяется для реализации автоматизированной системы "принятия решений". import java.util .Random; interface SharedConstants intNO=О; intYES=1; int МАУВЕ = 2; int LATER = 3; int SOON = 4; int NEVER = 5; class Que stion implements SharedConstants { Random rand = new Random() ; int ask() { int prob = (int ) (100 * rand . nextDouЬle () ); if (prob < 30) return NO ; // 30% else if (prob < 60) return YES ; // 30% else if (prob < 75) return LATER; // 15% else if (prob < 98) else return SOON ; // 13% return NEVER; 11 2%
25' Часть 1. Язык Java class As kМe implement s SharedConstants static vo id answe r (int result) { switch ( result ) { case NO : System. out . println ("Heт" ); break; case YES : System. out . println ("Дa ") ; break; case МАУВЕ : System. out . println ("B oзмoжн o" ) ; break; case LATER : System. out . println ( "Пoзднee ") ; break; case SOON : System. out . println ( "Bcкope") ; break; case NEVER : System . out .println ( "Hикoгдa ") ; break; puЫ ic static void ma in (String args[] ) Que stion q = new Que stion (); answe r (q.as k() ); answe r (q.as k() ); answe r (q.as k() ); answe r (q.ask ( )); Обратите внимание на то , что в этой программе используется класс Random из стандартной библиотеки Jаvа. В этом классе создаются псевдослучайные чис­ ла. Он содержит несколько методов, которые позволяют получать случайные числа в требуе мой для программы форме. В данном примере применяется метод ne xtDouЫe (),возвращающий случайные числа в пределах от О,О до 1,О. В расс матриваемом здесь примере программы два класса, Que stion и As kMe , реализуют инте рфейс SharedConstant s, в котором определены константы NO (Нет) , Y ES (Да) , МАУВЕ (Возможно ), SOON (Вскоре) , LAT ER (Позднее) и NEVER (Никогда) . Код из каждого класса ссылается на эти константы так, как если бы они определялись и наследовались непосредственно в каждом классе. Ниже приведен резул ьтат, выводимый при выполнении дан ной программы. Обратите внимание на то , что при каждом запуске программы результаты ее выполнения оказываются разн ыми. Позднее Вс коре Нет Да Не заметку! Уп омянутая выше методика применения инте рфейса для определения общих констант весьма проти воречива и предста влена ради полноты изложения материала.
Расширение интерфей сов Глава 9. Пакеты и интерфейсы 255 Ключевое слово extends позволяет одному интерфейсу наследовать другой. Синтаксис определения такого наследования аналогичен синтаксису наследования классов. Когда класс реализует интерфейс, наследующий другой интерфейс, он дол­ жен п редоставлять реализации всех методов, определенных по цепочке наследова­ ния интерфейсов. Ниже приведен характерный пример расширения интерфейсов . 11 Один интерфейс може т расширять друг ой interface А { void rnethl(); void rneth2 (); 11 Теперь интерфейс В включает в себя ме тоды methl () и meth2 () 11 и добавляет метод шеthЭ () interface В extends А { void rneth3 (); 11 Этот кл асс долже н реализовать все ме т оды из интерфейсов А и В class MyClass irnplernents В { puЫic void rnethl() { Systern.out . println ("Peaлизaция метода rne thl () .") ; puЫic void rneth2( ) { Systern. out . println ("Реализация ме тода rne th2 () . " ); puЬlic void rne th3 () { Systern. out . println ("P eaлизaция метода rne th3 () .") ; clas s IFExtend { puЫ ic static void rna in (String arg[] ) MyClass оЬ = new MyClass () ; ob .rnethl () ; ob .rneth2 () ; ob .rneth3 (); В порядке эксперимента можете попытаться уд алить реализацию метода methl () из класса MyClass. Это приведет к ошибке во время компиляции. Как от­ мечалось ранее, любой класс, реализующий интерфейс, должен реализовать и все определенные в этом интерфейсе методы, в том числе и любые методы, унаследо­ ванные от других интерфейсов. Методы поумолчанию Как пояснялось ранее, до версии JDK 8 в интерфейсе нельзя было вообще реализовывать методы. Это означало, что во всех предыдущих версиях Java ме-
256 Часть 1. Язы к Java тоды, указанные в интерфейсе , были абстрактными и не имели своего тела. Это традиционная форма интерфейса, обсуждавшаяся в предыдущих разделах гл авы. Но с выпуском версии JDK 8 положение изменилось, поскольку появилась новая возможность вводить в интерфейс так называемый метод по умол'Чанию. Иными словами, метод по умолчанию позволяет теперь объявлять в интерфейсе метод не абстрактным, а с конкретным телом. На стадии разработки версииJDК 8 метод по умолчанию назывался методом расширения, поэтому в литературе можно ветре· тить оба обозначения этого нового языкового средстваjаvа. Гл авной побудительной причиной для внедрения методов по умолчанию было стремление предоставить средства, позволявшиеся расширять интерфейсы, не нарушая уже существующий код. Напомним, что если ввести новый метод в широ­ ко применяемый интерфейс , такое дополнение нарушит уже существующий код из-за того , что не уд астся обнаружить реализацию нового метода. Данное затрудне· ние позволяет устранить метод по ум олчанию, поскольку он предоставляет реали· зацию, которая будет использоваться в то м случае, если не будет явно предоставле· на другая реализация . Та ким образом, ввод метода по умолчанию в интерфейс не нарушит уже существующий код. Еще одной побудительной причиной для внедрения метода по умолчанию бьvю стремление указывать в интерфейсе, по существу, необязательные методы в зависимости от того , каким образом используется интерфейс. Например, в ин· те рфейсе можно определить группу методов, воздействующих на последователь· ность элементов. Оди н из этих методов можно назвать remo ve () и предназначить его для удал ения элемента из последовател ьности . Но если интерфейс предназна· чен для поддержки как изменяемых, так и неизменяемых последовательностей, то метод remove ( ) оказывается, по существу, необязательным, поскольку его нельзя применять к неизменяемым последовательностям. В прошлом в классе, предназначавшемся для обработки неизменяемых последовател ьностей, прихо· дилось реализовывать факти чески пустой метод remove ( ) , даже если он и не был нуже н. А теперь реализацию по умолчанию метода remove (), не выполняющего никаких действий или генерирующего исключение, можно указать в интерфей· се. Благодаря этому исключается потребность реализовывать свой замещающий вариант этого метода в классе, предназначаемом для обработки неизменяемых последовательностей. Следовательно, если предоставить в интерфейсе метод re ­ mo ve ( ) по умолчанию, его реализация в классе окажется необязательной. Важно отметить , что внедрение методов по умолчанию не изменяет гла в· ную особенность интерфейсов: неспособность сохранять данные состояния. В частности , в интерфейсе по-прежнему недопустимы переменные экземпляра. Следовательно , интерфейс отличается от класса те м, что он не допускает сохра· пение состояния. Более того , создавать экземпля р самого интерфейса нельзя. Поэтому интерфейс должен быть по-прежнему реализован в классе, если требу· ется получить его экземпляр, несмотря на возможность определять в интерфейсе методы по умолчанию, начиная с версииJDК 8. И последнее замечание: как правило, методы по умолчанию служат специальным целям. А создаваемые интерфейсы по-прежнему определяют, гл авным образом, 'Что именно следует сделать, но не как это сделать. Те м не менее внедрение методов по умолчанию доставляет дополнительные уд обства для программирования нajava.
Глава 9. Пакеты и интерфейсы 257 Основы прим енения м етодов по умолчанию Метод по умолчанию определяется в интерфейсе таким же образом, как и ме­ тод в классе. Гл авное отличие состоит в том, что в начале объявления метода по умолчанию указывается ключевое слово de fault. Рассмотрим в качестве при­ мера следующий простой интерфейс: puЫic interface MyI F { 11 Это объявление обычного ме тода в интерфейсе . 11 Он НЕ предоставляет реализацию по умолчанию int getNumЬe r () ; 11 А это объявление ме тода по умолчанию . Обратите 11 внимание на его реализ ацию по ум олчанию default String getString () { return "Объе кт типа String по умолчанию" ; В интерфейсе My IF объявляются два метода. Первый из них, g etNumЬer (), является стандартно объя вляемым в интерфейсе и вообще не предоставляет ни­ какой реализации. А второй метод, ge tString ( ), включает в себя реализацию по умолчанию. В данном случае он просто возвращает символьную строку "Объе кт типа Str ing по ум олчанию " . Обратите особое внимание на порядок объявления метода ge tString (), где в самом начале указан модификатор досrупа default. Этот синтаксис можно обобщить. В частности , для того чтобы определить метод по умолчанию, его объя вление достаточно предварить ключевым словом de f а и 1 t. В связи с тем что в объявление метода ge tString () включена его реал изация по ум олчанию, его совсем не обязательно переопределять в классе , реализующем интерфейс My IF. Например, объявление приведенного ниже класса MyIFimp вполне допусти мо. 11 Реализовать интерфейс МyIF class MyI Fimp implements MyIF 11 В этом кл ассе должен быть реализован только ме тод qe tNuJl lЬe r() , 11 определенный в интерфейсе МyIF . 11 А вызов метода qetStrinq () разрешается по умолчанию puЬlic int getNumЬer () { return 100 ; В следую щем примере кода получается экземпляр класса MyI F imp, который ис­ пользуется для вызова обоих методо в, ge tNumЬer () и get String (): 11 Исполь зовать ме тод по умолч анию class De faultMethodDemo { p uЫ ic static vo id main (String args[] ) My IFirnp obj � new MyIFirnp() ; 11 Ме тод qetNWl lЬe r() можно вызвать , т.к. он явно реализован 11 в классе МyIFimp: Systern.out . println (obj . ge tNumЬ er () ); 11 Метод getStrinq () также можн о вызва ть, т.к. в интерфейсе 11 имеется его реализация по умолч анию :
258 Часть1.ЯэыкJava System.out . println (obj . getString () ); Ниже приведен результат, выводимый при выполнении данного кода. 100 Объе кт типа String по умолчанию Как видите , в данном примере кода автоматически используется реализация метода getString () по умолчанию. Определять и тем более реализовывать этот метод в классе My !Flmp совсем не обязательно. (Разумеется , реализация метода getString () в классе nompef fy emcя в том случае, если его предполагается использо­ вать в этом классе для каких-нибудь других целей, а не только по умолчанию.) В классе, реализующем интерфейс, вполне возможно определять собственную реализацию метода по умолчанию. Например, метод getString () переопреде­ ляется в классе My !Flmp2, как показано ниже. Те перь в результате вызова метода getString () возвращается другая символьная строка. class MyI Fimp2 implements MyIF { 11 В этом кл ассе предоставляются реализац ии обоих ме тодов // qetNuJDЬer () и qetStrinq() puЫ ic int getNumЬer () { return 100; puЫ ic String getString ( ) { return "Это друг ая символьная строка ."; Более практический пример Несмотря на то что в приведенном выше примере демонстрируется механизм применения методов по умолчанию, в нем все же не показана практическая польза от такого нововведения. С этой целью вернемся к интерфейсу IntS tack, рассмо­ тренному ранее в этой гл аве. Ради уд обства обсуждения допустим, что интерфейс IntStack широко применяется во многих программах и что в нем требуется вве­ сти метод, очищающий стек, чтобы подготовить его к повторному использованию. Следовател ьно, интерфейс IntStack требуется дополнить новыми функциональ­ ными возможностями, но не нарушая уже существующий код. Прежде это было просто невозможно, но благодаря внедрению методов по умолчанию это совсем не трудно сделать теперь. Например, интерфейс IntStack можно усовершенствовать следующим образом: interface IntStack { void push (int item) ; // сохранить элемент в стеке int рор() ; // извлечь элеме нт из стека // У ме тода clear () теперь им еется вариант по умолч анию, поэтому // его приде тся реализоваться в том суще ствующем классе , где уже // применяется инт ерфейс IntStack de fault void clear () { System.out . println ( "Meтoд clear () не реали зован .") ;
Глава 9. Пакеты и интерфейсы 259 В данном примере метод clear ( ) по умолчанию выводит сообщение о том, что он не реализован. И это вполне допустимо, поскольку метод clear ( ) нельзя вы· звать из любого уже существующего класса, реализующего интерфейс IntStack, если он не был определен в предыдущей версии интерфейса IntStack. Но метод clear ( ) может быть реализован в новом классе вместе с интерфейсом IntStack. Более того, новую реализацию метода clear ( ) потребуется определить лишь в том случае, если он используется. Следовательно, метод по умолчанию предо­ ставля ет возможность сделать следующее: • изящно рас ширить интерфейс со временем; • предоставить дополнительные функциональные возможности , исключая за­ мещающую реализацию в классе, если эти функциональные возможности не требуются. Следует также иметь в виду, что в реальном коде метод clear () должен гене­ рировать исключение, а не выводить сообщение. Об исключениях речь пойдет в следую щей гл аве. Проработав материал этой гл авы , попробуйте видоизменить реализацию метода clear ( ) по умолчанию таким образом , чтобы он генерировал ис ключение ти па Un sup portedOpe rationException. Воп рос ы множе ственного насл едо вания Как пояснялось ранее в этой книге, множественное наследование классов вJ ava не поддерживается . Но теперь, когда в интерфейсы внедрены методы по умолча­ нию, может возникнуть во прос: позволяет ли интерфейс обойти это ограничение? По существу, позволяет. Напомним о следующем гл авном отличии класса от интер­ фейса: в классе могут сохраняться данные состояния , особенно с помощью пере­ менных экземпляра, тогда как в интерфейсе этого сделать нельзя. Несмотря на все сказанное выше, методы по умолчанию предоставляют отчасти возможности , которые обычно связываются с понятием множественного наследо­ вания. Например, в одном классе можно реализовать два интерфейса. Если в каж­ дом из этих интерфейсов предоставляются методы по умолчанию, то некоторое поведение наследуется от обоих интерфейсов. Поэтому в какой-то, хотя и ограни­ ченной , степени эти методы все же поддерживают множественное наследование. Нетрудно догадаться , что в подобных случаях может возникнуть конфликт имен. Допустим, два интерфейса, Alpha и Be ta, реализуются в классе MyClass. Что, если в обоих этих интерфейсах предоставляется метод reset ( ) , объявляемый с ре­ ализацией по умолчанию? Какой из вариантов этого метода будет выбран в классе MyClass: из интерфейса Alpha или Beta? С другой стороны, рассмотрим ситуацию, когда интерфейс Be ta расширяет интерфейс Alpha. Какой вариант метода по умол­ чанию используется в этом случае? А что, если в классе MyClass предоставляется собственная реализация этого метода? Для ответа на эти и другие аналогичные во­ просы вjava определен ряд правил разрешения подобных конфликтов. Во-первых, во всех подобных случаях приоритет отдается реализации метода в классе над его реализацией в интерфейсе. Та к, если в классе MyClass переопре­ деляется метод по умолчанию reset () ,то выбирается его вариант, реализуемый
260 Часть 1. Язы к Java в классе MyClass. Это происходит даже в том случае, если в классе MyClass реали­ зуются оба интерфейса, Alpha и Be ta. И это означает, что методы по умолчанию переопределяются их реализацией в классе MyClass. Во-вторых, если в классе реализуются два интерфейса с одинаковым методом по умолчанию, но этот метод не переопределяется в данном классе, то возникае т ошибка. Если же в классе MyClass реализуются оба интерфейса, Alpha и Beta, но метод re set () в нем не переопределя ется , то и в этом случае возн икает ошибка. В тех случаях, когда один интерфейс наследует другой и в обоих интерфейсах определяется общий метод по умолчанию, предпочтение отдается варианту мето­ да из наследующего интерфейса. Та к, если интерфейс Beta расширяет интерфейс Alpha , то используется вариант метода reset () из интерфейса Beta. Впрочем, ис­ пользуя новую форму ключевого слова super, вполне возможно ссылаться на ре­ ализацию по умолчанию в наследуемом интерфейсе. Эта общая форма ключевого слова super выглядит следующим образом: .1 11' .NR_ЯН!l'ерфейса . super . ННR_не!l'ода () Та к, если из интерфейса Beta требуется обратиться поссылке к методу поумол­ чанию reset () в интерфейсе Alpha, то для этого достаточно воспользоваться сле­ дующим оператором: Alpha .super. reset () ; Применение статических методов в интерфейсе Начиная с версии JDK 8, у интерфейсов появилась еще одна возможность: определять в нем один или несколько статических методов. Аналогично статиче­ ским методам в классе, метод , объя вляемый как static в интерфейсе, можно вы­ зывать независимо от любого объекта. И для этого не требуется ни реализация такого метода в интерфейсе, ни экземШiяр самого инте рфейса. Напротив, для вы­ зова статического метода достаточно указать имя интерфейса и через точку имя самого метода. Ниже приведена общая форма вызова статического метода из ин­ терфейса. Обратите внимание на ее сходство с формой вызова статического ме­ тода из класса. иНR_ЯН!l'ерфеЖса .иНR � С!l'а!l'Яvесrого_нетода В приведенном ниже примере кода демонстрируется ввод статического метода ge tDefaultNшnЬe r () в упоминавшийся ранее интерфейс My IF. Этот метод воз­ вращает нулевое значение. puЬl ic interface MyI F { 11 Это объявление обычного ме тода в интерфейсе . 11 Он НЕ предоставляет реализ ацию по умо лчанию int getNumЬ er () ; 11 А это объявление ме тода по умолчанию . Обратите 11 внима ние на его реализацию по умолчанию default String getString () { return "Объе кт типа String по умолч анию" ;
Глава 9. Пакеты и интерфейсы 261 11 Это объявление статического метода в интерфейсе static int getDe faultNumЬer () { return О; Метод get De f а u l tNumЬe r ( ) может быть вызван следующим образом: int de fNum = My IF.getDe faultNumЬ er (); Как уп оминалось выше, для вызова метода getDefaultNшnЬe r ( ) реализация или экземпляр интерфейса My IF не требуется, поскольку это статический метод. И последнее замечание: статические методы из интерфейсов не наследуются ни реализующими их классами, ни подчиненными интерфейсами. Заключ ительные соображения по поводу пакетов и и нтерфейсов В примерах, представленных в этой книге, пакеты и или интерфейсы исполь­ зуются нечасто. Те м не менее оба эти языковые средства являются очень важной составляющей среды программирования нajava. Буквально все реальные програм­ мы, которые придется писать нa java, будут входить в состав пакетов и скорее все­ го будут реализовывать интерфейсы. Поэтому очень важно ос воить пакет и интер­ фейсы настолько, чтобы свободно пользоваться эти ми языковыми средствами , програм мируя на J ava.
Обработка .. .. . 10 исключении В этой гл аве рассматривается механизм обработки исключений в Java. Искл ючение - это ненормальная ситуация , возникающая во время выполнения по­ следовател ьности кода. Иными словами, исключение - это ошибка, возникающая во время выполнения. В тех языках программирования, где не поддерживается обработка исключений , ошибки должны проверяться и обрабатываться вручную и, как правило , благодаря использованию кодов ошибок и т. п . Та кой подход об­ ременительный и хлопотный. Обработка исключений в Java позволяет избежать подобных трудностей, а кроме того , переносит управление ошибками, возникаю­ щими во время выполнения , в область ООП. Основы обработки исключений Исключение в J ava представляет собой объе кт, описывающий исключительную (т.е . ошибочную) ситуацию, возникающую .в оп ределенной части программного кода. Когда возникает такая ситуация, в вызвавшем ошибку методе генерируется объект, который представляет исключение. Этот метод может обработать исклю­ чение самостоятельно или же пропустить его. Так или иначе , в определенный момент исключение перехва:тЪtва,еrпся и обрабатъtвшт�ся. Исключения могут ге нери­ роваться автоматически исполняющей системой Jаvа или вручную в прикладном коде . Исключения, генерируе мые исполняющей системойjаvа, имеют отношение к фундаментальным ошибкам , нарушающим правила языка jаvа или ограничения , накладываемые исполняющей системой Java. А исключения , генерируемые вруч­ ную, обычно служат для уведомления вызывающего кода о некото рых ошибках в вызываемом методе. Управление обработкой исключений в Java осуществляется с помощью пяти кл ючевых слов: try, catch, throw, throws и finally. Если говорить вкратце, то эти ключевые слова действуют следующим образом. Операторы программы, ко­ торые требуется отслеживать на предмет исключений, размещаются в блоке try. Если исключение возникает в блоке try, оно генерируется. Прикладной код мо­ же т перехватить исключение, используя блок catch, а затем обработать его не­ кото рым рациональным способом. Системные исключения автомати чески гене­ рируются исполняющей системой Java. Для генерирования исключения вручную служит ключевое слово throw. Любое исключение, ге нерируе мое в теле метода,
264 Часть 1. Язы к Java должно быть обозначено в его объявлении ключевым словом throws . А любой код, который должен быть непременно выполнен по завершении блока try, раз­ мещается в бл оке finally. Ниже приведена общая форма блока обработки исклю­ чений. try{ 11 блок кода , в котором отслеживаю тся ошибки } catch (!1'.IUJ ЖCЖJDOVeIOIJl 1 ехОЬ) { 11 обработчик исклЮчений !1'Жn _ .1 1 сжлюч8НJl'J1_ 1 } catch (!l.'.IUJ ЖCЖJDOVeIOIJl 2 ехОЬ ) { 11 обработчик исклЮче ний !1'.1 1 n_•сжлюч8НJl'Jl_2 } 11 ... finally 11 блок кода , который должен быт ь не преме нно 11 выполнен по заверше нии блока try Здесь тип_ исключения обозначает тип происходящего исключения. В осталь­ ной части этой гл авы описывается, каким образом приведенная выше языковая струкrура применяется для обработки исключений. На заметку! В версии JDK 7 внедрена новая форма оператора try, обеспечивающая автомати­ ческое уп равление ресурса ми. Эта форма называется операто ром try с ресурсами и под­ робнее рассматривается в гл аве 13 в контексте упра вления файлами, поскольку файлы от­ носятся к наиболее часто используемым ресурсам. Типы исключений Все типы исключений являются подклассами, производными от встроенного класса ThrowaЬ le. Это означает, что класс ThrowaЫ e находится на вершине ие­ рархии классов исключений. Сразу же за классом ThrowaЫ e ниже по иерархии следуют два подкласса, разделяющие все исключения на две отдельные ветви . Одну ветвь возглавляет класс Exception. Он служит для исключительных усл овий, которые должна перехватывать прикладная программа. Именно от этого класса вам и предстоит наследовать свои подклассы при создании собственных типов ис· ключений. У класса Exception имеется важн ый подкласс - Ru ntimeException. Исключения этого ти па автоматически определяются для создаваемых вами при­ кладных программ и охватывают такие ошибки, как деление на нуль и ошибочная индексация массивов. Другая ветвь возглавляется классом Error, определяющим исключения , по­ явление которых не предполагается при нормальном выполнении программы. Исключения типа Error используются в исполняющей системе Java для обозначе· ния ошибок, происходящих в самой исполняющей среде . Примером такой ошиб· ки может служить переполнение стека . В этой гл аве не рассматриваются исклю· чения типа Error, поскольку они, как правило, возникают в связи с аварийными сбоя ми, которые не могут быть обработаны в прикладной программе. Верхний уровень иерархии исключений представлен на рис. 10.1.
Гл ава 1 О. Обработка исключений 265 ThrowaЫ e Exception , Error Run timeException _ Рис. 10.1 . Верхний уровень иерархии исключений Необрабатываемые исключения Прежде чем перейти непосредственно к обработке исключений, имеет смысл продемонстрировать , что происходит, когда исключения не обрабатываются. В приведенный ниже пример небольшой программы намеренно введен оператор, вызывающий ошибку деления на нуль. clas s ЕхсО { puЬlic static void main (String args [] ) intd О; intа=42/d; Когда исполняющая система Jаvа обнаруживает попытку деления на нуль, она создает новый объект исключения, а затем геиерирует исключение. Это прерывает выполн ение класса ЕхсО, ведь как только исключение сгенерировано, оно должно быть перехвачено обработчиком исключений и немедленно обработано. В данном примере обработчик исключений отсутствует, и поэтому исключение перехваты­ вается стандартн ым обработчиком, предоставляемым испол няющей системой Java. Любое исключение, не перехваченное прикладной программой , в конеч­ ном итоге будет перехвачено и обработано этим стандартным обработчиком. Станда ртный обработч ик выводит символьную строку с описанием исключения и результат трассировки стека, начиная с момента возникновения исключения, а затем прерывает выполнение программы. Ниже приведен пример исключения , сгенерированного при выполнении приведенного выше кода. java.lang .ArithmeticException : / Ьу zero at Exc0 .main ( Exc0 .jav a:4) Обратите внимание на то , что в результат трассировки стека включены име­ на класса ЕхсО, метода ma in (), файла ЕхсО . java и номер 4 строки кода. Следует также иметь в виду, что сгенерированное исключение относится к подклассу ArithmeticException, производному от класса Exception и точнее описываю­ щему тип возникшей ошибки. Как будет показано далее в этой гл аве , вjava предо-
266 Часть 1. Язы к Java ставляется несколько встроенных типов исключений, соответствующих разным типам ошибок, которые могут возникнугь во время выполнения. Трассировка стека позволяет проследить последовательность вызовов мето­ до в, кото рые привели к ошибке. В качестве примера ниже приведена другая вер­ сия предыдущей программы, где вносится та же самая ошибка, но уже не в методе main( ) , а в другом методе. class Exc l { static void subroutine () intd О; intа=10/d; puЫic static vo id ma in (String args[] ) { Excl .su brout ine () ; Результат трассировки стека стандартного обработчика исключений отобража­ ет весь стек вызовов следующим образом: jav a.lang .ArithmeticException : / Ьу zero at Excl . subroutine (Excl .ja va:4) at Ex cl .main (Excl .java:7) Как видите , на дне стека находится седьмая строка кода из метода ma in (), в которой делается вызов метода subrout ine (),вызвавший исключение при вы­ полнении четвертой строки кода. Тр ассировкой стека удо бно пользоваться для от­ ладки, поскольку ее результат показывает всю последовательность вызовов, при­ ведших к ошибке. Использование блоков операторов try и catch Стандартн ый обработчик исключений, предоставляемый исполняющей систе­ мойJаvа, безусловно, уд обен для отладки , но, как правило, обрабатывать исключе­ ния приходится вручную. Это дает два существенных преимущества. Во-первых, поя вляется возможность исправить ошибку. И во-вторых, предотвращается авто­ матическое прерывание выполнения программы. Большинство пользователей бу­ дуг, по меньшей мере, недовольны, если программа будет прерываться и выводить результат трассировки стека всякий раз, когда возникает ошибка. Правда, предот­ вратить это совсем нетрудно. Чтобы застраховаться от подо бных сбойных ситуаций и органи зовать обработ­ ку ошибок, возникающих во время выполнения, достаточно разместить контроли­ руемый код в блоке оператора try. Сразу же за бл оком оператора try должен сле­ довать блок оператора са tch, где указывается тип перехватываемого исключения. Для того чтобы показать, насколько просто это делается, в следующий пример программы включены блоки операторов try и catch для обработки исключения типа Ar i thmeticException, ге нерируемого при попытке деления на нуль: class Ехс2 { puЫic static void ma in (St ring args [] ) { int d, а;
Гл ава 1 О. Обработка исключений 267 try { // проконтролировать блок кода d=О; } а=42/d; System . out .println ("Этo не буде т выведено .") ; catch (ArithmeticException е) { // перехватить ошибку 11 деления на нул ь System.out . println ( "Дeлeниe на нул ь.") ; System. o ut . println ( "Пocлe оператора catch.") ; Эта программа выводит следующий результат: Деление на нуль . После оператора catch . Обратите внимание на то , что вызов метода println () в блоке оператора try вообще не будет выполняться . Как только возникнет исключение, управление сра­ зу же передается из блока оператора try в блок оператора catch. Это означает, что символьная строка " Это не буде т выведено " не выводится. По завершении блока оператора са tch управление передается в строку кода, следующую после всего блока операторов try/ catch. Операторы try и catch составляют единое целое . Область действия блока оператора catch не распространяется на операторы, предшествующие блоку опе­ ратора try. Оператор catch не в состоянии перехватить исключение, передан­ ное другим оператором try, кроме описываемых далее конструкций вложенных операторов try. Операторы, защищаемые блоком оператора try; должны быть за ключены в фигурные скобки (т. е. должны находиться в самом блоке). Оператор try нельзя применять к отдел ьному оператору в исходном коде программы. Целью большинства правильно построенных операторов catch является раз­ решение исключ ительных ситуаций и продолжение нормальной работы програм­ мы, как если бы ошибки вообще не бьvю . В приведенном ниже примере програм­ мы на каждом шаге цикла for получаются два случайных числа. Эти два числа де­ лятся одно на другое, а результат используется для деления числового значения 12345. Окончательный результат размещается в переменной а. Если какая-ни будь из этих операций деления приводит к ошибке деления на нуль, эта ошибка пере­ хватывается, в переменной а устанавливается нулевое значение и программа про­ должает выполняться дальше. 11 Обработат ь ис ключение и продолжить работу import java.util . Random; class HandleError { puЫic static vo id ma in (String args[] ) { int а=О, Ь=О, с=О; Random r = new Random() ; for ( int i=O ; 1<32 000; i++) try { Ь r.ne xtlnt () ; с= r.nextint () ; а 12345 / (Ь/с) ; catch (ArithmeticException е) {
268 Часть 1. Язык Java System.out . println ( "Дeлeниe на нул ь.") ; а = О; 11 прис воить нуль и продолжить работу Systern. out . println ("a: "+а ); Вывод о п и сания и сключения В классе ThrowaЫ e переопределяется метод toString (), определенный в клас­ се Ob ject таким образом, чтобы возвращать символьную строку, содержащую опи­ сание исключения . Это описание можно вывести с помощью метода println (), просто передав ему исключение в виде аргумента. Например, блок оператора са tch из предыдущего примера может быть переписан следующим образом: catch (ArithmeticExc eption е) { Systern.out . println ( "Иcключeниe : "+е) ; а= О; 11 присвоить нул ь и продолжить работу Когда эта версия блока оператора catch подставляется в программу и програм­ ма запускается, то при любой попытке деления на нуль выводится следующее со­ общение: Исключение : jav a.lang . ArithmeticException : / Ьу zero И хотя в данном контексте это не имеет особого значения, тем не менее воз­ можность вывести описание исключения в некоторых случаях оказывается весьма кстати, особенно на стадии экспериментирования с исключениями ил и отладки программы. П рименение нескольких о п е р ато р ов catch Иногда в одном фрагменте кода может возникнуть не одно исключение. Чтобы справиться с такой ситуацией, можно указать два или больше оператора са tch, каждый из которых предназначается для перехвата отдел ьного типа исключения. Когда ге нерируется исключение, каждый. оператор catch проверяется по порядку и выполняется тот из них, который совпадает по типу с возникшим исключением. По завершении одного из операторов са tch все остальные пропускаются и вы­ полн ение программы продолжается с оператора, следующего сразу после блока операторов try/catch. В следующем примере программы перехватываются два разных типа исключений: 11 Продемонс трировать применение несколь ких операторов catch class Mul tipleCatche s { puЫic static void rna in (String args [] ) { try { int а = args . length; System. out . println ("a ="+а ) ; intЬ=42/а; intс[]={1}; с[42] = 99;
Глава 1 О. Обработка исключений 269 catch ( ArithmeticExcept ion е) System. o ut . println ( "Дeлeниe на нуль : "+е) ; catch (ArrayindexOutOfBoundsException е) { System. out . println ( "Ошибка индексации за пределами массива : "+е) ; System. out .println ( "Пocлe блока операторов try/catch. ") ; В этой программе произойдет исключение в связи с делением на нуль, если она будет запущена без аргументов командной строки . Ведь в этом случае значе­ ние переменной а будет равно нулю. Деление будет выполнено нормально, если п рограмме будет передан аргумент командной строки, устанавливающий в пере­ менной а значение больше нуля . Но в этом случае возникнет исключение типа Arra y! ndexOutOfBoundsException, поскольку длина массива целых чисел с рав­ на 1, тогда как программа пытается присвоить значение элементу массива с [ 42] . Ниже приведены результаты выполнения данной программы обоими способами. C : \>j ava Mu ltipleCatches а=О Деление на нуль : java . lang . ArithmeticException : / Ьу zero П осле блока операторов try/catch . C: \>j ava Mul tipleCatches TestArg а=1 ошибка инде ксации за пределами ма ссива: java .lang . Ar rayindexOutOfBoundsException:42 Пос ле блока операторов try/catch . Применяя несколько операторов catch, важно помнить, что перехват исклю­ че ний из подклассов должен следовать до перехвата исключений из суперклассов. Дело в том, что оператор са tch, в котором перехватывается исключение из супер­ класса, будет перехватывать все исключения из этого суперкласса, а также все ис­ ключения из его подклассов. Это означает, что исключения из подкласса вообще не будут обработаны, если попытаться перехватить их после исключений из его су­ перкласса. Кроме того , недостижимый код считается в Java ошибкой. Рассмотрим в качестве примера следующую программу: /* Эта программа содержит ошибку . */ В последовательности операторов catch подкласс исключений долже н быт ь указан перед его суперклассом, ин аче это приведе т к недостижимому коду и ошибке во время компиляции . class Supe rSubCatch { puЫ ic static void ma in (String args[J ) { try { } intа=О; intЬ=42/а; catch (Exception е) { System. out . println ( "Перехват исключений общего кл асса Exception. ") ; /* Этот оператор catch вообще не буде т достигнут , т.к. подкласс ArithJaeticException является производным от класса Exception . */
270 Часть 1. Я зык Java catch (ArithrneticException е) { // ОШИБКА : недостижимый код ! System.out . println ("Э тoт код вообще недостижим. ") ; Если попытаться скомпилировать эту программу, то появится сообщение об ошибке, уведомляющее, что второй оператор catch недостижим, потому что исключение уже перехвачено. Класс исключения типа ArithrneticException является производным от класса Exception, и поэтому первый оператор catch обработает все ошибки, относящиеся к классу Exception, включая и класс Ar i thrneticException. Это означает, что второй оператор catch так и не будет выполнен. Чтобы исправить это положение, придется изменить порядок следова­ ния операторов са tch. Вл оженные операторы try Операторы try могут быть вложенными. Это означает, что один оператор try может находиться в блоке друтого оператора try. Всякий раз, когда управление передается блоку оператора try, контекст соответствующего исключения раз­ мещается в стеке . Если во вложенном операторе try отсутствует оператор catch для перехвата и обработки конкретного исключения, стек развертывается и про­ веряется на соответствие оператор са t ch из внешнего блока оператора t ry, и так до тех пор, пока не будет найден подходящий оператор са tch или не будут исчер­ паны все уровни вложенности операторов try. Если подходя щий оператор catch не будет найден, то возникшее исключение обработает исполняющая система Java. Ниже приведен пример программы, демонстрирующий применение вложен­ ных операторов try. 11 Пример применения вложе нных операторов try class Ne stTry { puЫic static vo id main (String args[] ) { try { int а = args . length; /* Если не указаны аргуме нты кома ндной строки , в следующем операторе будет сгенерировано исключение в связи с деле нием на нуль . */ intЬ=42/а; System.out . println ("a "+а); try { // вложе нный блок try /* Если ука зан один аргуме нт кома ндной строки , то исключение в связи с делением на нул ь буде т сгенерировано в следующем коде . */ if (a==l ) а=а/ (а-а) ; // деление на нуль /* Если указаны два ар гумента кома ндной строки , то генерируется ис ключение в связи с выходом за пределы ма ссива . */ if(а==2) { intс[]={1}; с[42] = 99; // здесь генерируе тся ис ключение в связи
} Глава 1О. Обработка исключений 271 // с выходом за пределы ма ссива catch (ArrayindexOutOfBoundsException е) { System. out . println ( "Индeкc за пределами ма ссива : "+е) ; catch (ArithmeticException е) { System. o ut . println ( "Дeлeниe на нуль : "+е) ; Как видите , в этой программе один блок try вложен в другой. Программа ра­ ботает следующим образом. Когда она запускается на выполнение без аргументов командной строки, во внешнем блоке оператора try генерируется исключение в связи с делением на нуль. А если программа запускается на выполнение с одн им аргументом, то исключение в связи с делением на нуль ге нерируется во вложен­ ном блоке оператора try. Но поскольку это исключение не обрабатывается во вложе нном блоке , то оно передается внешнему бло ку оператора try, где и обра­ батывается. Есл и же программе передаются два аргумента командной строки , то во внутреннем блоке оператора try генерируется исключение в связи с выходом индекса за пределы массива. Ниже приведены результаты выполнения этой про­ граммы в каждом из трех случаев ее запуска на выполнение. C: \>j ava Ne stTry Деление на нуль : java .lang .ArithrneticException : / Ьу zero C:\>j ava Ne stTry One а=1 Деление на нуль : java .lang . Ar ithmeticExcept ion : / Ьу zero C:\>java NestTry One Two а=2 Инд е кс за пределами массива : java .lang . Array!nde xOutOfBoundsException:42 Вложение операторов try может быть не столь очевидным при вызовах мето­ до в. Например, вызов метода можно закл ючить в блок оператора t ry, а в теле это­ го метода организовать еще один блок оператора try. В этом случае блок опера­ тора try в теле метода оказывается вложенным во внешний бл ок оператора try, от куда вызывается этот метод. Ниже приведена версия предыдущей программы, где блок вложенного оператора try перемещен в тело метода ne sttry (). /* Оп ераторы t:r:y мо гут быть неявно вложены в вызовы методов . */ class Me thNe stTry { static void ne sttry ( int а) { try { // вложенный блок оператора t:r:y /* Если указан один аргумент кома ндной строки , то ис ключение в связи с делением на нуль буде т сгенерировано в сле дующем коде . */ if (a== l) а=а/ (а-а) ; 11 деление на нул ь /* Если ука заны два аргумента командной строки , то генерируется исключение в связи с выходом за пределы ма ссива . */ if(a==2 ) { intс[]=(11;
272 Часть 1. Я зык Java с[42] = 99; // эдесь генерируется исключение в связи 11 с выходом за пределы массива catch (ArrayindexOutOfBoundsException е) { System. out . println ( "Индe кc за пределами массива : "+е) ; puЫic static void ma in (String args[] ) { try int а args . length ; /* Если не указаны аргуме нты командной строки , в следующем операторе будет сгенерировано исключение в связи с делением на нуль . */ intЬ=42/а; System.out . println ("a ="+а); nesttry (a) ; catch (ArithmeticException е) { System.out . println ( "Дeлeниe на нуль : "+е) ; Эта версия программы выводит такой же результат, как и предыдущая. Операто р throw В приведенных до сих пор примерах перехватывались только те исключения , которые генерировала исполняющая системаjаvа. Но исключения можно генери· ровать и непосредственно в прикладной программе, испол ьзуя оператор throw. Его общая форма выглядит следую щим образом: throw генвр.мруемпi_эж.эенпляр ; где ге нерируемый _ экз емпляр должен быть объектом класса ThrowaЫe или про­ изводного от него подкласса. Примитивные типы вроде int или char, а также классы, кроме ThrowaЫe, например String или Ob j ect, нельзя использовать для ге нерирования исключений. Получить объект класса ThrowaЫe можно двумя способами, указав соответствующий параметр в операторе catch или создав этот объект с помощью оператора new. Поток исполнения программы останавливается сразу же после оператора th row, и все последующие операторы не выполняются . В это м случае ближайший объемлющий бл ок оператора try проверяется на наличие оператора catch с со­ впадающим типом исключения. Если совпадение обнаружено, управление пере­ дается этому оператору. В противном случае проверяется следующий внешний блок оператора try и т.д. Есл и же не уд астся найти оператор catch, совпадающий с типом исключения, то стандартный обработчик исключений прерывает выпол· пение программы и выводит результат трассировки стека. Ниже приведен пример программы, в которой генерируется исключение. Обработчик, перехваты вающий это исключение, повторно ге нерирует его для внешнего обработч ика.
Гл ава 1 О. Обработка исключ ени й 273 11 Продемонстрировать применение оператора throw class ThrowDemo { static vo id demop roc () { try { throw new Nul lPointerException ( "дeмoнcтpaция ") ; catch ( Nul lPointerException е) { System. o ut . println ( "Исключение перехвачено в теле ме тода demoproc () ."); throw е; 11 повторно сгенерировать ис ключение puЫic static void ma in (String args[] ) try { demop roc () ; catch (NullPointerException е) { System. out . println ("П oвтopный перехват : "+е) ; Эта программа получает две возможности для обработки одной и той же ошиб­ ки. Сначала в методе ma in () устанавливается контекст исключения, затем вызыва­ ется метод demoproc ( ) , где задается другой контекст обработки исключения и сра­ зу же генерируется новый экземпляр исключения типа NullPointerException, который перехватывается в следующей строке кода. Затем исключение ге нериру­ ется повторно. Ниже приведен результат, выводимый этой программой. Исключение перехвачено в теле ме тода demop roc () . Пов торный перехват : java . lang . Nul lPointerException : демонс трация В этом примере программы демонстрируется также, каким образом создаются объекты стандартных исключений вJava. Обратите внимание на следующую стро­ ку кода: throw new Nul lPointerExcept ion ("дeмoнcтpaция") ; Здесь оператор new служит для создания экземпляра исключения типа Nul lPointe rException. Многие классы встроенных вJava исключений, возника­ ющих во время выполнения, имеют по меньшей мере две формы конструктора: без параметров и со строковым параметром. Если применяется вторая форма кон­ структо ра, его аргумент обозначает символьную строку, описывающую исключе­ ние. Эта символьная строка выводится , когда объект исключения передается в ка­ ч естве аргумента методу рrint () ил и println ().Она может быть также получена в результате вызова метода getMessage (),определенного в классе ThrowaЫe. Оператор throws Если метод способен вызвать исключение, которое он сам не обрабатывает, то он должен задать свое поведение таким образом, чтобы вызывающий его код мог обезопасить себя от такого исключения. С этой целью в объявление метода вводится оператор throws , где перечисляются типы исключений, которые ме­ тод может ге нерировать. Это обязательно для всех исключений, кроме тех , кото-
271. Часть 1. Язык Java рые относятся к классам Error и Ru nt imeExcep tion или любым их подклассам. Все остал ьные исключения , которые может сгенерировать метод, должны быть объявлены в операторе throws. Если этого не сделать, то во время компиля ции возникнет ошибка. Ниже приведена общая форма объявления метода, которая включает оператор throws. �яп жю� _ не�ода (сп•сож_паране !l'ров) throws спжсож жсжлюч81U1• { 11 тело ме тода Здесь список_ исключений обозначает разделяемый запятыми список исключе­ ний, которые метод может сгенерировать. Ниже приведен пример неверно написанной программы, пытающейся сгене­ рировать исключение, которое в самой программе не перехватывается. Эта про­ грамма не подлежит компиляции, поскольку в ней отсугствует оператор throws, в котором объявляется неперехватываемое исключение. 11 Эта программа содержит ошибку , и поэ тому она не подлежит компил яции class Throws Demo { static void throwOn e () { ) System. out . println ("B теле ме тода throwOne () .") ; throw new IllegalAccessException ("дeмoнcтpaция ") ; puЫic static void ma in (String args[] ) { throwOne () ; Чтобы эту программу можно было скомпилировать, в ее исходный следует внести два изменения. Во-первых, объявить в методе throwOne ( ) генерирование исключения типа IllegalAcce ssException. И во-вторых, определить в методе ma in ( ) блок оператора try/catch для перехвата этого исключения. Ниже приве­ ден исправленный код данной программы. 11 Эта программа написана верно class ThrowsDemo { static void throwOn e () throws IllegalAccessException { System. out . println ("B теле ме тода throwOne () .") ; throw new IllegalAccessException ("дeмoнcтpaция ") ; puЫ ic static void ma in ( String args[] ) { try { throwOпe (); catch (IllegalAccessException е) { System.out . println ( "Пepexвaчeнo исключение : "+е) ; Результат, выводимый этой программой, выглядит следующим образом: В теле ме тода throwOne () . Перехвачено исключение : java . laпg.Il legalAcce ssException : демонстрация
О перато р finally Глава 1 О. Обработка исключений 275 Когда генерируется исключение, выполнение метода направляется по нели­ нейному пуги, резко изменяющему нормальную последовательность выполнения операторов в теле метода. В зависимости от того , как написан метод, исключение м ожет даже стать причиной преждевременного возврата из метода. В некоторых м етодах это может вызвать серьезные осложнения. Та к, если файл открывается в начале метода и закрывается в конце, то вряд ли кого-нибудь устроит, что код, за­ крывающий файл, будет обойден механизмом обработки исключений. Для таких не предвиденных обстоятельств и служит оператор f i na 11у. Оператор finally образует блок кода, который будет выполнен по заверше­ нии блока операторов try/catch, но перед следующим за ним кодом. Блок опе­ ратора finally выполняется независимо от того, сгенерировано ли исключение или нет. Если исключение сгенерировано, блок оператора finally выполняется , даже при условии, что ни один из операторов catch не совпадает с эти м исключе­ нием. В любой момент, когда метод собирается возвратить управление вызываю­ щему коду из блока оператора t ry /саt ch (через необработанное исключение или явным образом через оператор re t urn), блок оператора f i na 11у выполняется пе­ ред возвратом управления из метода. Это может быть уд обно для закрытия файло­ вых дескрипторов либо освобождения других ресурсов , которые были выделены в начале метода и должны быть освобождены перед возвратом из него. Указы вать оператор finally необязательно, но каждому оператору try требуется хотя бы один оператор catch или finally. Ниже приведен пример программы, в котором демонстрируются три метода, возвращающих управление разными способами. Но ни в одном из них не пропу­ с кается выполнения блока оператора finally. 11 Продемон стрировать приме нение оператора finally class FinallyDemo { 11 сгенериров ать исключение в ме тоде static vo id procA() { try { System. o ut . println ("B теле ме тода procA ()"); throw new Run time Exception ("д eмoнcтpaция " ); finally { System. out .println ( "Блок оператора finally в ме тоде procA() ") ; 11 возвратить упра вление из блока оператора try static vo id procB () { try { System. out . println ("B теле ме тода procB()"); return; finally { System. out .println ( "Блок оператора finally в ме тоде procB() ") ; 11 выполнить блок try, как обычно
276 Часть 1. Язы к Java static void procC () { try { Systern. o ut . println ("B теле ме тода procC() " ); finally { Systern.out . println ( "Блок оператора finally в ме тоде procC() " ) ; puЫic static void ma in (String args [] ) { try { procA(); catch (Exception е) { System.out . println ( "Иcключe ниe перехвачено" ); procB () ; procC () ; В данном примере выполнение метода procA ( ) преждевременно прерывается в блоке оператора try, где генерируется исключение, но блок оператора finally все равно выполняется. Выход из блока оператора try в методе procB ( ) проис­ ходит через оператор return, а бл ок оператора finally выполняется перед воз­ вратом из метода procB ( ) . В методе procC ( ) блок оператора try выполняется обычным образом, когда ошибки отсутствуют, но блок оператора finally выпол­ няется все равно . Помните! Есл и блок оператора finally связан с бл оком оператора try, то бл ок оператора finally будет выполнен по завершении блока оператора try. Ниже приведен результат, выводимый дан ной программой. В теле ме тода procA () Блок оператора finally в ме т оде procA () Исключение перехвачено В теле ме тода procB () Блок оператора finally в ме тоде procB () В теле ме тода procC () Блок оператора finally в ме тоде procC () Встроенные в Java искл ю ч ения В стандартном пакете java.lang определен ряд классов исключений. Некоторые из них использовались в предыдущих примерах. Большинство из этих исключений относятся к подклассам стандартного типа Run timeException. Как пояснялось ранее, эти исключения не обязательно включать в список оператора throws в объявлении метода. В языке Java такие исключения называются непрове­ ряемыми исключениями, поскольку компилятор не проверяет, обрабатываются ил и ге нерируются они в каком-нибудь методе. Непроверяемые исключения , опреде­ ленные в пакете java . lang, приведены в табл. 10.1, тогда как в табл . 10.2 - те
Гл ава 10. Обработка исключений 277 и сключения из пакета j а v а . l ang, которые должны быть включены в список опе­ рато ра throws в объявлении методов, способных генерировать их, но не обра­ батывать самостоятельно. Та кие исключения называются проверяемыми. Помимо и сключений из пакета j ava . lang, вjava определен ряд дополнительных исключе­ ний, относящихся к другим стандартным пакетам. Та бл ица 10.1 . Подклассы н е прове ряемых исключе ний, производн ы е от кл асса RunTimeException и определенные в пакете java . lanq Исключение Arithme ticException Arrayi ndexOutOfВoundsException ArrayS toreException ClassCas tException EnumCon stantNotPresentException Illega lArgumen tException Illega lМonitorStateException IllegalStateException IllegalThreadS ta teException IndexOutOfВoundsException Nega tiveArrayS izeException Nu llPointerException NwnЬerFormatException Securi tyException String i ndexOutOfBounds ТypeNotPresentException UnsupportedOperationException Описание Арифметическая ошибка ( например, деление на нуль) Выход индекса за пределы массива П рисваивание элементу массива объекта несовместимого типа Неверное приведе ние типов П опытка воспользоваться неоп ределенным значением перечисления Упот ребление недопустимого аргумента при вызове метода Недопустимая контрольная операция (например, ожидание незаблокированного потока исполнения) Неверное состояние среды ил и пр�ожения Несовместимость запраш иваемой о ш!рации с текушим состоянием потока исполнения Выход индекса некоторого типа за допустимые пределы Создание массива отрицательного размера Неверное испол ьзование пустой ссылки Неверное преоб разование си м вольной строки в числовой формат П опытка нарушения безопас ности П опытка индексации за пределами сим вол ьной строки Тип не найден Обнаружена неподдерживаемая операция
278 Часть \. Язык Java Та блица 10.2. Проверяемые исключения, определенные в пакете java . lang Искnючение ClassNotFoundException CloneNotSupportedException Illeqal.AccessException InstantiationException InterruptedException NoSuchFieldException NoSuchМethodException ReflectiveOperationException Описание Класс не найден Попьrrка клонировать объект из класса, не реализующего интерфейс CloneaЬle Доступ к классу не разрешен Попьrrка создать объект абстрактного класса или интерфейса Один поток исполнения прерван другим потоком Запрашиваемое поле не существует Запрашиваемый метод не существует Суперкласс исключений, связанных с рефлексией Соэдание собственных подклассов исключений &троенные в Java исключения позволяют обрабатывать большинство рас­ пространенных ошибок. Те м не менее в прикладных программах возможны осо­ бые ситуации, требующие наличия и обработки соответствующих исключений. Для того чтобы создать класс собственного исключения , достаточно определить его как производный от класса Exception, который, в свою очередь, является производн ым от класса ThrowaЬle. В подклассах собственных исключений совсем не обязательно реализовать что-нибудь. Их присугствия в системе типов уже до­ статочно, чтобы пользоваться ими как исключениями. В самом классе Exception не определено никаких методов. Он, безусл овно, на­ следует методы из класса ThrowaЫ e. Та ким образом, всем классам исключений, в том числе и создаваемым самостоятельно, доступ ны методы, определенные в классе ThrowaЫe. Все они перечислены в табл. 10.3. Один или несколько этих методов можно также переопределить в своих классах исключений. В классе Exception определяются четыре открытых конструхтора. Первые два конструхтора поддерживают цепочки исключений, описываемые в следующем раздел е, а два других показаны в общей форме ниже. В первой форме конструхто­ ра создается исключен ие без описания , а во второй форме допускается указывать описание исключения. Exception () Exception (Strinq соо6-енаrе) Зачастую ухазывать описание исключения непосредственно при его создании очень уд обно, но иногда для этого луч ше переопределить метод toString ().Дело в то м, что в варианте метода toString (), определенном в классе ThrowaЫe и на­ следуемом в классе Exception, сначала выводится имя исключения , затем двоето­ чие и, наконец, описание исключения. Переопределив метод toString (), можно вывести только описание исключения, отбросив его имя и двоеточие. В итоге вы­ водимый результат получается более яс ным, что иногда весьма желательно.
Гл ава 1 О. Обработка исключений 279 Та блица 10.З. Методы, определенные в классе ThrowaЬle Метод final void addSup p ressed (ТhrowaЬle uсключение) ТhrowaЬle fillinStacltTraoe () ТhrowaЬle qetcau se () Strinq getLocalizedМ&ssage () Strinq getМessaqe () StacltTraceEleшent [] qetStacltTraoe О final ТhrowaЬle [] qetSuppressed () ТhrowaЬle initCause (ТhrowaЬle причина_иск.лючения) void printStacltTraoe () void printstacltTraoe (Printstream поток_вьuюда) void printstacltTrace ( PrintWriter поток_вьиюда) Описание Добавляет задан ное иск.лЮ14еНUе в список пода­ вляемых исключений, связанный с вызывающим исключением. Предназначен гл авным образом для применения в операторе try с ресурсами Возвращает объект класса ТhrowaЬle, содержащий п олную трассировку стека. Этот объект может быть сгенерирован повто рно Возвращает исключение, положенное в основу текущего исключения. Если такое исключение о·г­ сутствует, то возвращается пустое значение null Возвращает локализованное описание исключения Возвращает описание исключения Возвращает массив, содержащий поэлемент- ную трассировку стека в виде объектов класса StackTraceBlement. На вершине стека находит­ ся метод, который б ыл вызван непосредственно перед генерированием исключения. Этот метод содержится в первом элементе массива. Класс StackTraceElement предоставляет доступ к дан­ ным о каждом элементе трассировки , например. к имени вызванного метода Получает подавленные исключения, связанные с вызывающим исключением, и возвращает мас­ сив, содер:J J< ащий результат. Подавленные исклю­ чения генерируются главным об разом в операторе try с ресурсами Связывает причину_исключения с вызывающим исключением, указывая ее как причину этого вы­ зывающего исключения. Возвращает ссылку на ис­ ключение " Выводит трассировку стека Направляет трассировку стека в зада нный поток вывода Нап равляет трассировку стека в заданный поток вывода void Уст анавливает трассировку стека для заданных setStacltTrace (StacltTraceEleшent элементов. Этот метод предназначен для специаль- эле.менmы[]) ного, а не нормального применения Strinq toString () Возвращает объект типа Strinq, содержащий опи­ сание исключения. Этот метод вызывается из мето­ да println () при выводе объекта типа ThrowaЬle
280 Часть 1. Язык Java В приведенном ниже примере программы сначала объямяется новый подкласс, производн ый от класса Exception, а затем он используется для вывода сообщения об ошибке в методе. В этом подклассе метод toString ( ) переопределяется таким образом, чтобы вывести тщательно подготовленное описание исключения. 11 В этой программе создается специальный тип исключения class MyException extends Exception { private int de tail ; MyEx ception {int а) { detail = а; puЫ ic String toString {) { return "MyException [" + detail + "] "; class ExceptionDemo { static vo id compute (int а) throws MyException { System.out . println {"B ызвaн ме тод compu te {" +а+ ") " ); if{a > 10) throw new MyException (a) ; System.out . println ( "Hopмaл ьнoe завершение ") ; puЫ ic static void main (String args[]) { try { compute (l) ; compute (20) ; catch {MyException е) { System. o ut . println ("П epexвaчeнo ис ключение : "+е) ; В дан ном примере определяется подкласс MyException, производный от клас · са Except ion. Этот подкласс достаточно прост: он содержит только конструктор и переопределенный метод toString (), выводящий значение исключения. В клас· се Except ionDemo определяется метод compu te ( ) , генерирующий объект исключе­ ния типа MyException. Это исключение генерируется, когда целочисленный пара· метр метода compu te ( ) принимает значение больше 10. В методе ma in () сначала устанамивается обработчик исключений типа MyException, затем вызывается ме· тод compute () с правильным (меньше 10) и неправильным (больше 10) значением параметра, чтобы продемонстрировать оба пути выполнения кода. Ниже приведен результат, выводимый дан ной программой. Выз ван метод compute (l) Нормал ьное завершение Выз ван метод compute (20) Перехвачено исключение : MyException [20] Цепо чки искл ю ч ений В версию JDK 1.4 в подсистему исключений было внедрено средство, называе· мое цепочками исключений, которое позволяет связывать одно исключение с другим,
Гл ава 1 О . Обработка исключений 281 чтобы описывать в последнем причину появления первого. Допустим, в методе ге­ нерируется исключение типа Ari thmet icException в связи с попыткой деления на нуль. Но истинная причина состоит в ошибке ввода-вывода, которая и приводит к появлению неверного делителя. И хотя этот метод должен сгенерировать исклю­ чение типа Ari thmeticException, поскольку произошла именно эта ошибка, тем не менее вызывающему коду можно также сообщить, что основной причиной дан­ ного исключения служит ош ибка ввода-вывода. Цепочки исключений позволяют справиться с этой и любой другой ситуацией, когда исключения возникают на раз­ ных уровнях. Чтобы разрешить цепочки исключений, в класс ThrowaЫ e были введены два конструктора и два метода. Ниже приведены общие формы конструкторов. ТhrovaЫe (ТhrovaЫe пр11чяна ясхлJОЧения) ТhrovaЫe (Strinq сообщение , - ТhrоvаЫе пр•чяна _ 11сЖЛJОЧения) В первой форме параметр пр ичина_ исключения обозначает исключение, вы­ звавшее текущее исключение. Та ким образом , параметр пр ичина_исключения обозначает основную причину, по которой возникло текущее исключение. Вторая форма позволяет указать описание вместе с причиной исключения. Оба эти кон­ структора были введены также в классы Error, Exception и Ru ntimeException. Для организации цепочки исключений в класс ThrowaЫ e были также введены методы getCause () и initCause (). Они перечислены в табл . 10.3 среди прочих методо в, но повторяются здесь ради полноты изложения. ТhrovaЫe qetCause () ТhrowaЫe initCause (ТhrowaЫe причяна_яcxлюvEI IOl lJl ) Метод getCause () возвращает исключение, вызывающее текущее исключе­ ние . Если же такое исключение отсутствует, то возвращается пустое значение nul l. Метод initCause () связывает пр ичину_исключения с вызы вающим ис­ ключением и возвращает ссылку на исключение. Та ким образом, причину можно связать с исключением после его создания. tfo причина исключения может быть установлена только од ин раз . Следовательн о, метод ini tCause () можно вызвать для каждого объекта исключения только один раз. Даже если причина исключе­ ния бьша установлена в конструкторе, ее все равно нельзя установить снова, ис­ пользуя метод initCause () . Вообще говоря� метод initCause () служит для уста­ новки причины исключений из устаревших классов, где оба упомянутых выше до­ полнительных конструктора не поддерживаются. В приведенном ниже примере программы демонстрируется применение меха­ низма обработки цепочки исключений. 11 Продемонс трировать цепочки ис ключ ений class ChainExcDemo { static void demoproc () { // создать исключение Null Pointe rException е new Nul lPointe rException ("вepxний уровень") ; 11 добавить причину исключения e.initCause (new Arithme ticException ( "пpичинa " ));
282 Часть 1. ЯзыкJava throw е; puЫic static void ma in (St ring arg s[J ) try { demop roc () ; catch (NullPointerException е) { 11 вывести исключение верхне го уровня System. out .println ( "Пepexвaчeнo ис ключе ние : 11 + е) ; 11 вывести ис ключение , послужившее причиной 11 для исключения верхне го ур овня System. out .println ( "Пepвoпpичинa : 11 + e.getCause () ); Эта программа выводит следующий результат: Перехвачено ис ключение : java .lang . Nul lPointerException : верхний уровень Первопричина : java .lang . ArithmeticException : причина В дан ном примере исключение верхнего уровня относится к типу Nu llPointer Ехсерtiоn. Это исключение дополнено исключением типаАrithmеtiсЕхсерtiоn, по причине которого оно возникает. Когда исключение верхнего уровня генери­ руется в методе demoproc () , оно перехватывается в методе ma in (), где оно выво­ дится, а вслед за ним - исключение, которое послужило причиной для его появле­ ния и которое получается в результате вызова метода getCause (). Цепочки исключений могут быть состаR.Лены на любую гл убину. Это означает, что причина исключения может иметь собственную причину. Но имейте в виду, что слишком дл инные цепочки исключений, скорее всего , свидетельствуют о не­ удачном проектном решении. Цепочки исключений требуются далеко не в каждой программе. Но в тех случаях, когда сведения о причине исключения считаются полезными, цепочки исключений предлагают изящное решение. Недавно внедр енны е с р едства для обработки исключений В версии JDК 7 подсистема исключений вjava была дополнена тремя интерес­ ными и полезными средствами. Первое из них автоматизирует процесс освобож­ дения такого ресурса, как файл, когда он больше не нужен. Оно опирается на рас­ ширенную форму оператора try, называемую оператором try с ресурсами и опи­ сываемую в гл аве 13 при обсуждении во просов обращения с файлами. Второе средство называется многакрат 11:ым перехватом, а третье иногда называют как акон· чатtиъное повторное генерирование исключений или бOJUJe точное повторное генерирование исключений. Два последних средства описаны ниже. Многократный перехват позволяет перехватывать несколько исключений в од­ ном и том же операторе catch. Не так уж и редко в обработчиках нескольких ис­ кл ючений используется одинаковый код, хотя они реагируют на разные исключе­ ния. Вместо обработки исключений каждого типа в отдельности многократный
Гл ава 10. Обработка исключ ений 283 перехват позволяет организовать в одном блоке оператора са tch обработку всех исключений , не дублируя код. Чтобы организо вать многократный перехват, достаточно объединить отдель­ н ые ти пы исключений в операторе catch с помощью логической операции ИЛ И. Каждый параметр многократного перехвата неявно считается завершенным. (При желании можно, конечно, явно указать ключевое слово final, но это совсем не обязательно.) А поскольку каждый параметр многократного перехвата неявно считается завершенным, то ему не может быть присвоено новое значение. Ниже приведен пример оператора catch с многократн ым перехватом. В нем перехватываются оба типа исключений - Ari thmeticException и Ar rayindexO utOfBoundsException. catch (ArithmeticException 1 ArrayindexOutOfBoundsExcept ion е) { В следующем примере программы демонстрируется, каки м образом многократ­ н ый перехват действует на практике: // Продемонстрировать многократный перехват class Mul tiCatch { puЫic static void ma in (String args[] ) { int a=lO, Ь=О; intvals[]={1,2,3}; try int result =а/ Ь; // сгенерировать исключение / / типа Ari thmeticException // vals [lOJ = 19; // сгенерировать исключение 11 типа ArrayindexOutOfBoundsException 11 В этом операторе catch перехватываются оба исключения catch (Arithme ticException 1 ArrayindexOutOfBoundsException е) System.out . println ( "Иcключeниe перехвачено : "+е) ; System. o ut . println ( "Пocлe многократного перехвата .") ; В данном примере программы исключение типа Ari thmeticException гене­ рируется при попытке деления на нуль. Есл и закомментировать операцию деле­ ния и уд алить комментарий из следующей строки кода, будет сгенерировано ис­ ключение типа ArrayindexOutOfBoundsException. Оба исключения перехваты­ ваются одним из тем же оператором catch. Средство более то чного повторного генерирования исключений ограничива­ ет их тип только те ми проверяемыми исключениями, кото рые связаны с блоком оператора try, где они генерируются , но не обрабатываются предыдущим опера­ тором catch и являются подтипом или супертипом его параметра. Потребность обращаться к этому средству может возни кать нечасто, хотя теперь оно доступно для использования. Для того чтобы средство более то чного повторного генериро­ вания исключений возымело действие, параметр оператора catch должен быть фактически завершенным. Это означает, что ему нельзя присваивать новое зна­ чение в блоке оператора catch или же он должен быть явно объя влен как fina l .
28' Часть1.ЯзыкJava П р именение исклю ч ений Обработка исключений является эффекти вным механ измом управления слож­ ными программами, обладающими многими динамическими характеристиками. Операторы try, throws и catch относятся к простым средствам обработки оши­ бок и необычных граничных условий в логике выполнения программы . В отличие от ряда других языков программирования , где в качестве признаков сбоев служат коды ошибок, в Java применяются исключения. Иными словами, когда метод мо­ жет завершиться сбоем, в нем должно быть сгенерировано исключение. Это более простой способ обработки сбойных ситуаций. И последнее замечание: операторы обработки исключений в Java не следует рассматривать как общий механизм нелокального ветвления. Употребляя их имен­ но таким образом, можно лишь усложнить прикладной код и затруднить его сопро­ вождение.
11 Многопоточное программирование В отличие от некоторых языков программирования , в Java предоставляется встроенная поддержка многопотачного програм м ир ования. Многопоточная програм­ ма содержит две или несколько частей, которые могут выполняться одновремен­ но. Каждая часть такой программы называется потоком исполштия, причем каж­ дый поток задает отдел ьный путь исполнения кода. Следовательно, многопоточ­ ность - это особая форма многозадачности. Вам , вероятнее всего , приходилось сталкиваться с многозадачностью на прак­ тике, поскольку она поддерживается практически во всех современных операцион­ ных системах. Те м не менее существуют два отдел ьных вида многозадачности : мно­ гозадач ность на основе процессов и многозадачность на основе потоков. Важно понимать , в чем состоит отличие этих видов многозадачности . Большинству чит а­ тел ей лучше известна многозадачность на основе процессов. Пр ацесс, по существу, является выполняющейся программой. Следовательно, многозадачностъ на основе процессов - это средство, которое позволяет одновременно выполнять две или не­ сколько программ на компьютере. Та к, многозадачность на основе процессов по­ зволяет запускать компилятор Java , работая одновременно в текстовом редакторе или посещая веб-сайт. В среде многозадачности на основе процессов программа оказывается наименьшей единицей кода, которую может диспетчеризировать план ировщик операционной систе мы. В среде многозаiJачности на основе потакав наименьшей единицей диспетчеризируе­ мого кода является поток исполнения. Это означает, что одна программа может вы­ полнять две или несколько задач одновременно. Например, текстовый редактор мо­ жет форматировать текст в то время, как выполняется его вывод на печать, при усло­ вии , что оба эти действия выполняются в двух отдельных потоках исполнения. Та ким образом, многозадачность на основе процессов имеет дело с "общей картиной", тогда как многозадачность на основе потоков - с отдел ьными с деталями. Многозадачные потоки исполнения требуют меньших издержек, чем много­ задач ные процес сы. Процессы являются крупными задачами, каждой из кото­ рых требуется свое ад ресное пространство. Связь между процессами ограничена и обходится дорого. Переключение контекста с одн ого процесса на другой также обходится дорого. С другой стороны, потоки исполнения более просты . Они со­ вместно используют одно и то же адресное пространство и один и тот же крупный процесс. Связь между потоками испол нения обходится недорого , как, впрочем , и переключение контекста с одного потока исполнения на другой. Несмотря на то
286 Часть 1. ЯзыкJava что программы нajava пользуются многозадачными средами на основе процессов, такая многозадачность в Java не контролируется, а вот многопото чная многоза· дачность контролируется. Многопоточность позволяет писать эффективные программы, максимально использую щие доступные вычислительные мощности в системе. Еще одним пре· имуществом многопоточности является сведение к минимуму времени ожидания. Это особенно важно для интерактивных сетевых сред, где работают программы нajava , поскольку простои в таких средах - обычное явле ние. Например, скорость передачи данных по сети много ниже , чем скорость их обработки на компьютере. Даже чтение и запись ресурсов в локальной файловой системе выполняется много медленнее, чем их обработка на центральном процессоре (ЦП). И, конечно, поль­ зователь много медленнее вводит данные с клавиатуры, чем их может обработать компьютер. В однопоточных средах прикладной программе приходится ожидать завер шения таких задач, прежде чем переходить к следующей задаче, даже если бальшую часть времени программа простаивает, ожидая ввода. Многопоточность помогает сократить простои, поскольку в то время , как один поток исполнения ожидает, другой может выполняться. Если у вас имеется опыт программирования для таких операционных систем, как Wi ndows , значит, вы уже владеете основами многопоточного программиро­ ван ия. Но то обстоятел ьство , что в Java можно управлять потоками исполнения, делает многопоточность особенно уд обной , поскольку многие мелкие вопросы ее организации решаются автоматически . Модель потоков исполнения в Java Исполняющая система Java во многом зависит от потоков исполнения, и все библиотеки классов разработаны с учетом многопоточности . По существу, потоки исполнения используются в Java для того , чтобы обеспечить асинхронность ра· боты всей исполняющей среды. Благодаря предотвращению бесполезной траты циклов ЦП уд ается повысить эффективность выполнения кода в целом. Ценность многопоточной среды луч ше понять в сравнении. В однопоточных системах применяется подход , называемый ци'/СЛОм ожидания событий с ипросом. В этой модели единственный поток управления выполняется в бесконечном ци· кле, опрашивая единственную очередь событий, чтобы принять решение, что де­ лать дальше. Как только этот механизм опроса возвращает, скажем, сигнал , что сетевой файл готов к чтению, цикл ожидания событий передает управление соот­ ветствующему обработчику событий. И до тех пор, пока тот не возвратит управ· ление , в программе ничего не может произойти. На это расходует драгоценное время ЦП. Это может также привести к тому, что одна часть программы будет господствовать над другими, не позволяя обрабатывать любые другие события. Вообще говоря , когда в однопоточной среде поток исполнения бл икируется (т.е. приостанавливается) по причине ожидания некоторого ресурса, то приостанав· ливается выполнение и всей программы. Выгода от многопоточности состоит в том, что основной механизм цикличе­ ского опроса исключается. Один поток может быть приостановлен без останов·
Гл ава 11.М ногопоточное программи рование 287 ки других частей программы. Например, время ожидания при чтении данных из сети или вводе пользователем данных может быть выгодно использовано в любом другом месте программы. Многопоточность позволяет переводить циклы анима­ ции в состояние ожидания на секунду в промежугках между соседними кадрам и, не приостанавливая работу всей системы. Когда поток исполнения блокируется в программе на Java, приостанавливается только один заблокированный поток, а вс е остал ьные потоки продолжают выполняться . За последние годы многоядерные системы стали вполне обычным явлением. Безусловно, одноядерные системы все еще широко распространены и применяю1'­ ся. Следует, однако, иметь в виду, что многопоточные средства Jаvа вполне работо­ способны в обоих типах систем. В одноядерной системе одновременно выполняю­ щиеся потоки совместно используют ЦП, получая каждый в отдел ьности некоторый квант времени ЦП. Поэтому в одноядерной системе два или более потока фактиче­ ски не выполняются одновременно, но ожидают своей очереди на использование времени ЦП. А в многоядерных системах два или более потока фактически мо�:уг выполняться одновременно. Как правило, это позволяет увеличить эффективность программы и повысить скорость выполнения некоторых операци й. На заметку! В версии JDK 7 внедрен ка ркас Fo rk/Joiп Fra mework, предоста вляющий мощные сред­ ства для созда ния многопоточ ных приложений с авто матическим масштабированием, по­ зволяющим лучше использовать многоядерные системы. Каркас Fork/Joiп Fra mewo rk явля­ ется частью общей поддержки в Java параллельного программирования, под кото рым понимаются методики оптимизации некоторых видов ал горитмов параллел ьного выпол­ нения в систе мах с несколькими процессорами. Подробнее о каркасе Fork/Joiп Framework и других утилитах параллелизма реч ь пойдет в гл аве 28, а здесь рассматриваются традици­ онные многоnоточ ные возможности Java. Потоки исполнения находятся в нескольких состояниях. Рассмотрим их вкра1'­ це. Поток может вътолнятъся. Он может бь1ть готовъ�м к вътмнению, как только получит время ЦП. Работающий поток может быть приостановлен, что приводит к временному прекращению его активности. Выполнение приостановленного по­ тока может быть возобиов.лено, что позволяет продолжить его выполнение с того ме­ ста, где он был приостановлен. Поток может быть за.блокирован на время ожидания какого-нибудь ресурса. В любой момент поток может быть прерван, что приводит к немедленной остановке его исполнения. Однажды прерванный поток ис полне­ ния уже не может быть возобновлен. Приоритеты п ото ков Каждому потоку исполнения в Java присваивается свой приоритет, кото­ рый определяет поведение данного потока по отношению к другим потокам. Приоритеты потоков исполнения задаются целыми числами, определяющими относительный приоритет одного потока над другими. Абсолютное значение приоритета еще ни о чем не говорит, поскольку высокоприоритетный поток не выполняется быстрее, чем низкоприоритетный, когда он является единственным исполняемым потоком в данный момент. Вместо этого приоритет потока исnолне-
288 Часть 1. Язык Java ния испол ьзуется для принятия решения при переходе от одного потока исполне­ ния к другому. Это так называемое переключение контекста. Правила, которые опре­ деляют, когда должно происходить переключение контекста, достаточно просты и приведены ниже. • Поток может добровольно уступить управление. Для этого достаточно явно уступить очередь на исполнение , приостановить или блокировать по­ ток на время ожидания ввода-вывода. В этом случае все прочие потоки и с­ полнения проверяются , а ресурсы ЦП передаются потоку исполнения , име­ ющему наибольший приоритет и готовому к выполнению. • Один поток исполиенИJI может быть вытеснен другим, более приоритет­ ным потоком. В этом случае низкоприоритетный поток исполнения , кото­ рый не уступает ЦП, просто вытесняется высокоприоритетным потоком, независимо от того, что он делает. По существу, высокоприоритетный по­ ток выполняется, как только это е му потребуется. Это так называемая въ�тес­ няющая многозада'Чностъ (или многозада'Чностъ с приоритетами) . Дело усложняется , если два потока исполнения имеют одинаковый приоритет и претендуют на цикл ЦП. В таких операционных системах , как Windows , потоки исполнения с одинаковым приоритетом разделяют время по кругу. А в операцион­ ных системах других типов потоки исполнения с одинаковым приоритетом дол ж­ ны принудительно передавать управление равноправным с ними потокам . Есл и они этого не делают, другие потоки исполнения не запускаются. Внимание! В силу отличий в способах переключен ия потоковых контекстов в операционных систе­ мах могут возникать трудности переноси мости . Синхрониз ация Многопоточность дает возможность для асинхронного поведения прикладных программ, поэтому требуется каким-то образом обеспечить синхронизацию, когда в этом возни кает потребность. Так, если требуется, чтобы два потока исполнения взаимодействовали и совместно использовали сложную структуру данных вроде связного списка, нуж но найти способ предотвратить возможный конфликт между этими потоками. Это означает предотвратить запись данных в одном потоке и с­ полнения, когда в другом потоке исполнения выполняется их чтение. Для этой цели вjava реализован изящный прием из старой модели межпроцессной си нхро­ низаци и, называемый монитором. Монитор - это механизм управления, впервые определенный Чарльзом Энтони Ричардом Хоаром. Монитор можно рассматри­ вать как маленький ящик, одновременно хранящий только один поток исполне­ ния. Как только поток исполнения войдет в монитор, все другие потоки испол­ нения должны ожидать до тех пор, пока тот не покинет монитор. Та ким образом, монитор может служить для защиты общих ресурсов от од новременного исполь­ зования более чем одним потоком исполнения. Для монитора вjava отсутствует отдел ьный класс вроде Monitor. Вместо этого у каждого объекта имеется свой неявный монитор, вход в который осуществляется
Гл ава 11. М ногопото ч ное прогр аммирован и е 289 автоматически, когда для этого объекта вызывается синхронизированный метод. Когда поток исполнения находится в теле синхронизированного метода, ни один другой поток исполнения не может вызвать какой-нибудь другой синхрон изирован­ ный метод для того же самого объекта. Это позволяет писать очен ь ясный и крат­ кий многопоточный код, поскольку поддержка синхронизации встроена в язык. О бм ен сообщениями Разделив программу на отдел ьные потоки исполнения, нужно организовать их взаимное общение. Для организации взаимодействия потоков исполнения в других языках программирования приходится опираться на средства операционной систе­ мы, а это, конечно, влечет за собой накладные расходы. Вместо этого в Java предо­ ставляется ясный и экономичный способ организации взаимодействия двух или не­ скольких потоков исполнения через вызовы предопределенных методов, которые имеются у всех объектов. Система обмена сообщениями вJ ava позволяет потоку ис­ полнения войти в синхронизированный метод объекта и ожидать до тех пор, пока какой-нибудь другой поток явно не уведомит его об освобождении нужных ресурсов. Класс Thread и и нтерфей с RunnaЬle Многопоточная система в Java построена на основе класса Thread, его методах и дополняющем его интерфейсе RunnaЫe. Класс Thread инкапсулирует поток ис­ полнения. Обратиться напрямую к нематериальному состоянию работающего пото­ ка исполнения нельзя , поэтому приходится иметь дело с его "заместителем" - экзем ­ пляром класса Thread, который и породил его. Чтобы создать новый поток испол­ нен ия, следует расширить класс Thread или же реализовать интерфейс RunnaЫ e. В классе Thread определяется ряд методов, помогающих управлять потоками ис полнения. Некоторые из тех методов, которые упоминаются в этой гл аве , пере­ числены в табл. 11.1 . Табл ица 11.1. М етоды управл е ния пото ками исполне ния иэ класса Thread Метод qetNaJl l& qetPriority isAlive join run sleep start Назначение Получает имя потока исполнения Получает п риоритет потока исполнения Определ яет, вы полняется ли поток Ожидает заверш ения потока исп ол нения Задает точку входа в поток исполнения П риостанавливает выполнение потока на заданное время Запускает п оток исполнения, вызы вая его метода run () Во всех упоминавшихся до сих пор примерах программ использовался еди н­ ственный поток исполнения. А далее поясняется , как пользоваться классом Thread и интерфейсом RunnaЫ e для создания потоков исполнения и управления ими, начиная с гл авного потока , присутствующего в каждой программе нajava.
290 Часть 1. Яэы к Java Гл авны й пото к испол нения Когда программа нa Java запускается на выполнение , сразу же начинает выпол­ няться один поток. Он обычно называется гла.в'НЪIМ потоком программы, потому что он запускается вместе с ней. Гл авный поток исполнения важен по двум причинам . • От этого потока испол нения порождаются все дочерние потоки. • Зачастую он должен быть последним потоком, завершающим выполнение про­ граммы, поскольку в нем производятся различные завершающие действия. Несмотря на то что гл авный поток исполнения создается автоматически при запуске программы, им можно управлять через объект класса Thread. Для этого достаточно получить ссылку на него, вызвав метод currentThread (), который объявляется как открытый и статический ( puЫ ic static) в классе Thread. Его общая форма выглядит следующим образом: static Тhread currentТhread () Этот метод возвращает ссылку на тот поток исполнения, из которого он был вы­ зван . Получив ссылку на главный поток, можно управлять им таким же образом, как и любым другим потоком исполнения . Рассмотрим следующий пример программы: 11 Управление главным потоком исполнения class CurrentThreadDemo { puЫ ic static vo id ma in (String args[] ) Thread t = Thread . currentThread (); System.out . println ("T eкyщий поток исполнения : "+t) ; 11 изменить имя потока исполнения t.setName ("My Thread" ); System.out . println ("П ocлe изменения име ни потока : "+t) ; try { for(intn=5;n>О;n--) System. out . println (n) ; Thread .sleep (1000) ; catch (InterruptedExcept ion е) Syзtem.out . println ( "Глaвный поток исполнения прерван" ); В этом примере программы ссылка на текущий поток исполнения (в данном случае - гл авный поток) получается в результате вызова метода currentThread ( ) и сохраняется в локальной переменной t. Затем выводятся сведения о потоке ис­ полнения. Далее вызывается метод setName () для изменения внугреннего имени потока исполнения. После этого сведения о потоке исполнения выводятся зано­ во. А в следующем далее цикле выводятся цифры в обратном порядке с задержкой на 1 секунду после каждой строки. Пауза орган изуется с помощью метода sleep ( ) . Аргумент метода sleep () задает время задержки в миллисекундах. Обратите вни­ мание на блок операторов try/ catch, в котором находится цикл. Метод sleep ( ) из класса Thread может сгенерировать исключение типа InterruptException , есл и в како м-нибудь другом потоке исполнения потребуется прервать ожидающий
Гл ава 11. Многопоточное программирование 291 поток. В данном примере просто выводится сообщение, если поток исполнения прерывается, а в реальных программах подобную ситуацию придется обрабаты­ ва ть иначе. Ниже приведен результат, выводимый данной программой. Текущий поток исполнения : Thread [main ,5,main] После изменения имени потока : Thread [My Thread, 5,main] 5 4 з 2 1 Обратите внимание на то, что вывод производится тогда, когда переменная t служит в качестве аргумента метода print ln ( ) . Этот метод выводит по порядку имя потока исполнения, его приоритет и имя его группы. По умолчанию гл авный поток исполнения имеет имя ma in и приоритет, равный 5. Именем ma in обозначается так­ же группа потоков исполнения , к которой относите.я данный поток. Гfrj nna потоков исполштия - это структура данных, которая управляет состоянием всего ряда пото­ ков исполнения в целом. После изменения имени потока исполнения содержимое переменной t выводится снова - на этот раз новое имя потока исполнения. Рассмотрим подробнее методы из класса Thread, используемые в приведенном выше примере программы. Метод sleep () вынуждает тот поток, из которого он вызывается , приостановить свое выполнение на указанное количество миллисе­ кунд. Общая форма этого метода выглядит следующим образом: static void sleep ( lonq НJrJUDl l' ceж,yн.zr) throvs InterruptedException Количество миллисекунд, на которое нужно приостановить выполнение, зада­ ет аргумент милли секунд. Метод sleep () может сгенерировать исключение типа Inte rruptedException. У него имеется и вторая , приведенная ниже форма, ко­ торая позволяет точнее задать время ожидания в м ил л и- и наносекундах. static void sleep (lonq ЮU UDl l ceж,yн.zr , lonq .наяосеж.УНR) throvs InterruptedException Вторая форма данного метода может применяться только в тех средах, где предус матривается задан ие промежутков времени в наносекундах. Как показано в предыдущем примере программы, установить имя потока ис­ полнения можно с помощью метода setName ().А для того чтобы получить имя потока исполнения, достаточно вызвать метод getName (),хотя это в данном при­ мере программы не показано. Оба эти метода являются членами класса Thread и объявляются так, как показано ниже, где имя_ потока обозначает имя конкрет­ ного потока исполнения. final void setNaJDe (Strinq ЯЮ1 no!l'oжa) final Strinq qetNёlJl\8 () - Соэдание пото ка испол нения В наиболее общем смысле для создания потока исполнения следует получить экземпляр объекта ти па Thread. В языке Java этой цели можно достичь следующи­ ми двумя способами:
292 Часть 1. Я зык Java • реализовав интерфейс RunnaЫe; • расширив класс Thread. В последующих разделах эти способы рассматриваются по очереди. Реализация интерфейса RunnaЬle Самый простой способ создать поток исполнения состоит в то м, чтобы объ­ явить класс , реализующий интерфейс RunnaЫe. Этот интерфейс предоставляет абстракцию единицы исполняемого кода. Поток исполнения можно создать из объекта любого класса, реализующего интерфейс RunnaЫ e. Для реализации ин­ терфейса Ru nnaЫe в классе должен быть объявлен единственный метод run (): puЫic void run() В теле метода run ( ) определяется код, который , собственно, и составляет но­ вый поток исполнения. Однако в методе run ( ) можно вызывать другие методы, ис­ пользовать другие классы, объявлять переменные таким же образом, как и в гл авном потоке исполнения. Единственное отличие заключается в том, что в методе run ( ) устанавливается точка входа в другой, параллельный поток исполнения в програм­ ме. Этот поток исполнения завершится, когда метод run ( ) возвратит управление. После создания класса, реализующего интерфейс RunnaЬle, в этом классе следует получить экземпляр объекта типа Thread. Для этой цели в классе Thread определен ряд конструкторов. То т конструктор, который должен использоваться в дан ном случае, выглядит в общей форме следующим образом: Тhread ( Run naЫ e объеж� _ по�ожа , Strinq •иir_no�oxa) В этом конструкторе параметр объект_ потока обозначает экземпляр класса, реализующего интерфейс RunnaЫe. Этим определяется место, где начинается вы­ полнение потока. Имя нового потока исполнения передается данному конструкто­ ру в качестве параметра имя_ потока . После того как новый поток исполнения будет создан, он не запускается до те х пор, пока не будет вызван метод start (), объявленный в классе Thread. По суще­ ству, в методе start () вызывается метод run (). Ниже показано, каким образом объявляется метод start (). void start () Рассмотр им следующий пример программы, демонстрирующий создание и за­ пуск нового потока на выполнение: 11 Создать второй поток исполнения class NewThread implements RunnaЫe Thread t; NewThread ( ) { 11 создать новый , вт орой поток исполнения t = new Thread (thi s, "Демо нстрационный поток" ); System. out . println ( "Дoчepний поток создан : "+t) ; t.start(); // запустить поток исполнения
Гл ава 11. М ного п оточ ное п рограммир ование 293 // Точка входа во второй поток исполнения puЫic void ruп() { try { for(inti"'5;i>О;i--){ System. out . println ( "Дoчepний поток : "+i) ; Thread .sleep (500) ; } catch (In terruptedException е) System.out . println ( "Дoчepний поток прерван ."); System. out . println ( "Дoчepний поток завершен ."); class ThreadDemo { puЫ ic static void ma in (String args[]) { new NewThread () ; // создать новый поток try { for(inti"'5;i>О;i--){ System . out .println ( "Глa вный поток : "+i) ; Thread .sleep (1000) ; ) catch (InterruptedExcept ion е) { System.out . println ( "Глaвный поток прерван .") ; System . out .println ( "Глaвный поток завершен .") ; Новый объект класса Thread создается в следующем операторе из ко нструкто­ ра NewThread { ) : t "' new Thread (thi s, "Демонстрационный поток" ); Передача ссьики this на текущий объект в первом аргументе данного конструк­ тора означает следующее: в новом потоке исполнения для текущего объекта по ссьи­ ке thi s следует вызвать метод run { ) . Далее в приведенном выше примере програм­ мы вызывается метод start {), в результате чего поток исполнения запускается , начиная с метода run {). Это, в свою очередь, приводит к началу цикла for в дочер­ нем потоке исполнения. После вызова метода start {) конструктор NewThread {) возвращает управление методу rna in () . Возобновляя свое исполнение, гл авный по­ ток входит в свой цикл for. Далее потоки выполняются параллельно, совместно ис­ пользуя ресурсы процессора в одноядерной систе ме, вплоть до завершения своих ци клов. Ниже приведен резул ьтат, выводимый дан ной программой (у вас он может оказаться иным в зависимости от конкретной исполняющей среды). дочерний поток : Тhrеаd [ Демонстрационный поток, 5,mаin ] Главный поток : 5 Дочерний поток : 5 Дочерний поток : 4 Главный поток : 4 Дочерний поток : 3 Дочерний поток : 2 Главный поток : 3 Дочерний поток : 1 Дочерний поток завершен .
29& Часть1. Язык Java Главный поток : 2 Главный поток : 1 Главный поток завершен . Как упоминалось ранее , в многопоточной программе гл авный поток исполне­ ния зачастую должен завершаться последн им. На самом же деле, если гл авный по­ ток исполнения завершается раньше дочерних потоков, то исполняющая система Java может "зависнугь", что характерно для некоторых старых виртуальных ма· шин JVМ. В приведенном выше примере программы гарантируется , что гл авный поток исполнения завершится последним, поскольку гл авный поток исполнения находится в состоя нии ожидания в течение 1000 миллисекунд в промежутках меж· ду последовательными шагами цикла, а дочерний поток исполнения - только 500 миллисекунд. Это заставляет дочерний поток исполнения завершиться раньше гл авного потока. Впрочем, далее будет показано, как лучше организовать ожида· ние завершения потоков исполнения . Расширение класса Тhread Еще один способ создать поток исполнения состоит в то м, чтобы сначала объ­ явить класс, расширяющий класс Thread, а затем получить экземпляр этого клас· са. В расширяющем классе должен быть непременно переопределен метод run ( ) , который является точкой входа в новый поток исполнения. Кроме того, в этом классе должен быть вызван метод start () для запуска нового потока на исполне­ ние. Ниже приведена версия программы из предыдущего примера, переделенная с учетом расширения класса Thread. // Создать второй поток исполнения , расширив кла сс Тhread class NewТhread exteпds Thread { NewТhread ( ) { 11 создать новый поток исполнения suреr ( "Демонстрационный поток" ); System . out .priпtlп ( "Дoчepний поток : "+this ); start (); // запустить поток на исполнение 11 Точка входа во второй поток исполнения puЬlic void ruп () { try { for(iпti 5;i>О;i--){ System. out . priпtlп ( "Дoчepний поток : "+i) ; Thread .sl eep (500) ; catch (IпterruptedExceptioп е) System.out . priпtlп ( "Дoчepний поток прерван.") ; System.out . priпtln ( "Дoчepний поток завершен .") ; class ExtendThread { puЫ ic static void ma in (Striпg args[]) { new NewТhread (); // создать новый поток исполнения try {
Гл ава 11. М ного п оточ н ое программировани е 295 for(inti=5;i>О;i--){ System. out .println ( "Глa вный поток : "+i) ; Thread .sleep (1000) ; catch (Interrup tedExc ept ion е) { System . out .println ("Г лaвный поток прерван.") ; System. out .println ( "Глaвный поток завершен .") ; Эта версии программы выводит такой же результат, как и предыдущая ее вер­ сия. Как видите , дочерний поток исполнения создается при конструировании объекта класса NewThread, наследующего от класса Thread. Обратите внимание на метод supe r () в классе NewTh read. Он вызывает конструктор Thread (),общая форма которого приведена ниже , где параметр имя_ потока обозначает имя по­ рождае мого потока исполнения. puЬlic Тhread (Strinq иня по�ожа) Выбор способа создания потоков исполнения В связи с изложенным выше могут возникнуть следующие вопросы: почему вjava предоставляются два способа для создания порождаемых потоков исполне­ ния и какой из них лучше? Ответы на эти вопросы взаимосвязаны. В классе Thread определяется ряд методов, которые могут быть переопределены в производных классах. И только один из них должен быть штременио переопределен: метод run ().Безусловно, этот метод требуется и в том случае , когда реализуется интер­ фейс RunnaЫe. Многие программирующие нa java считают, что классы следует расширять только в том случае, если они должны быть усовершенствованы или каким-то образом видоизменены. Следовательно, если ни оди н из других методов не переопределяется в классе Thread, то луч ше и проще реализовать интерфейс RunnaЫe. Кроме того , при реализации интерфейса RunnaЫe класс порождаемо­ го потока исполнения не должен наследовать класс Thread, что освобождает его от наследования другого класса. В конечном счете выбор конкретного способа для создания потоков исполнения остается за вами. Те м не менее в примерах, при­ веденных далее в этой гл аве , потоки будут создаваться с помощью классов, реали­ зующих интерфейс RunnaЫ e. Соэдание м ногих потоков и сполнения В приведенных до сих пор примерах использовались только два потока испол­ нения: гл авный и дочерний. Но в прикладной программе можно порождать сколь­ ко угодно потоков исполнения. Например , в следующей программе создаются три до черних потока исполнения: // Создать не сколь ко потоков исполнения cl ass NewThread implements RunnaЫe { String name ; // имя потока исполнения
296 Часть 1. ЯзыкJava Thread t; NewThread (Striпg threadпame ) { паmе = threadпame ; t = пеw Thread (this , паmе) ; System.out . priпtlп ( "Hoвый поток : "+t) ; t.start(); // запус тить поток на исполнение 11 Точка входа в поток исполнения puЫic void ruп () { try { for(iпti 5;i>О;i--J{ System. out .priпtln (пame + "· "+i) ; Thread .sleep (lOOOJ ; catch (InterruptedException е) { System.out . priпtln (пame + " прерван" ); System. out .priпtlп (пame + " заверше н.") ; class Mu ltiThreadDemo { puЫ ic static void main (String args[]) { пеw NewThread ("Oдин " ); // запустить потоки на исполнение пеw NeWТhread ("Двa" ); пеw NewThread ("Tpи " ); try { // ожидать завершения других потоков исполнения Thread .sleep (lOOOO) ; catch (IпterruptedExceptioп е) { System. out .pr intln ( "Глaвный поток прерван" ) ; System.out . println ( "Глaвный поток завершен.") ; Ниже приведен результат, выводимый данной программой (у вас он может ока­ заться иным в зависимости от конкретной ис полняющей среды ) . Новый поток : Thread [Oдин, 5,main] Новый поток : Thread [Дв a, 5,main ] Новый поток : Thread [Tp и, 5,main ] Один: 5 Два: 5 Три: 5 Один: 4 Два: 4 Три: 4 Один: 3 Три: 3 два: З Один: 2 Три: 2 Два: 2 Один: 1 Три: 1 Два: 1
Один завершен . Два завершен . Три завершен . Главный поток заверш ен . Гл ава 11. М ногопото ч ное программировани е 297 Как видите, после запуска на исполнение всетри дочерних потока совместно ис­ пользуют общие ресурсы ЦП. Обратите внимание на вызов метода sleep ( 10000) в методе ma in (). Это вынуждает гл авный поток перейти в состояние ожидания н а 1 О секунд и гарантирует его завершение последним. При менение методов isAlive () и join () Как упоминалось ранее, нередко требуется, чтобы гл авный поток исполнения завершался последним. С этой цел ью метод s leep ( ) вызывался в предыдущих примерах из метода ma in () с достаточной заде ржкой, чтоб ы все дочерние потоки исполнения завершились раньше гл авного. Но это неудо влетворительное реше­ ние , вызывающее следующий серьезный вопрос: откуда одному потоку исполне­ ния известно, что другой поток завершился? Правда, в классе Thread предостав­ ляется средство, позволяющее разрешить этот вопрос. Определ ить , был ли поток исполнения завершен, можно двумя способами. Во­ первых, для этого потока можно вызвать метод i sAl i ve ( ) , определенный в клас­ се Thread. Ниже приведена общая форма этого метода. final Boolean isAlive () Метод isAlive ( ) возвращает логическое значение true, если поток, для кото­ рого он вызван , еще исполняется . В противном случае он возвращает логическое значение false. И во-вторых, в классе Thread имеется метод join ( ), который применяется чаще, чем метод isAl ive () , чтобы дождаться завершения потока исполнения. Ниже при ведена общая форма этого метода. final void join () throwв InetrruptedException Этот метод ожидает завершения того потока исполнения, для которого он вы­ зван. Его имя отражает следующий принцип: вызывающий поток ожидает, когда указан ный поток присоединится к нему. Дополнительные формы метода j о in ( ) по­ зволяют указывать максимальный промежуток времени, в течение которого тре­ буется ожидать завершения указанного потока исполнения. Ниже приведена ус овершенствованная версия программы из предыдущего примера, где с помощью метода join ( ) гарантируется, что гл авный поток завер­ ш ится последним. В данном примере демонстрируется также применение метода isAlive (). 11 Приме нить ме тод join() , чтобы ожида ть завершения потоков исполнения class NewThread implemeпt s RuппаЫе { Striпg паmе ; // имя потока исполнения Thread t; NewThread (Striпg threadname ) name = threadname ;
298 Часть 1 . Язык Java t = new Thread (this , name} ; System.out . println ("H oвый поток : "+t} ; t.start(} ; // запустить поток исполнения 11 Точка входа в поток исполнения puЫ ic void run(} { try { for(inti 5;i>О;i--}{ System.out . println ( name + "· "+i} ; Thread .sleep (lOOO) ; catch (InterruptedException е) { System.out .println (name + " прерван. ") ; System. out . println ( name + " завершен ."); clas s DemoJoin { puЫ ic static void ma in (String args[] ) { NewThread оЫ new NewThread ("Один " ); NewThread оЬ2 new NewThread ("Двa" ); NewThread оЬЗ new NewThread ("Tpи " ); System.out .println ("П oтoк Один запущен : " + oЫ .t .isAlive()}; System.out . println ( "Пoтoк Два запущен : " + ob2 .t.isAl ive(}); System.out . println ( "Пoтoк Три запущен : " + obЗ .t.isAl ive()); // ожидать завершения потоков исполнения try ( System. out . println ("Oжидaниe завершения потоков ."); оЫ.t .join(); оЬ2.t .join(); оЬЗ.t .join(); catch (InterruptedException е} { System. out . println ( "Глaвный поток прерван" ); System.out . println ("Пoтoк Один запущен : " + oЫ .t .isAl ive() }; System. out . println ( "Пoтoк Два запущен : " + ob2 .t .isAl ive() ); System.out . println ( "Пoтoк Три запущен : " + obЗ .t.isAlive ()) ; System.out . println ("Глa вный поток заверше н. "} ; Ниже приведен результат, вы водимый данной программой (у вас он может ока­ заться и ны м в завис имости от конкретной исполняющей среды) . Новый поток : Thread [Oдни, 5,main ] Новый поток : Thread [Дв a, 5,main] Новый поток : Thread [Tpи, 5,main ] Поток Один запущен : true Поток Два запущен : true Поток Три запущен : true
Гл ава 11. Мно гопото ч н ое прогр аммировани е 299 Ожид ание завершения потоков . Один: 5 Два: 5 Три: 5 Один: 4 Два: 4 Три: 4 Один: 3 Два: 3 Три: 3 Один: 2 два: 2 Три: 2 Один: 1 Два: 1 Три: 1 Два завершен . Три завершен . Один завершен . Поток Один запущен : fal se Поток Два запущен : false Поток Три запущен : false Главный поток завершен . Как видите , потоки прекращают исполнение после то го, как вызовы метода j oin {) возвращают управление. Приоритеты потоков испол нения Планировщик потоков использует приоритеты потоков исполнения , чтобы принять решение, когда разрешить исполнение каждому потоку. Те оретически вы­ сокоприоритетные потоки исполнения получают больше времени ЦП, чем низко­ приоритетн ые. А на практике количество времени ЦП, которое получает поток ис полнения, нередко зависит не только от его приоритета, но и от ряда других факторов. (Например, особенности реализации многозадачности в операционной системе могут оказывать вл ияние на относительную доступность времени ЦП.) Высокоприоритетный поток исполнения может также вытеснять низкоприори­ тетный. Например, когда низкоприоритетный поток ис полняется, а высокопри­ оритетный собирается возобновить свое исполнение, прерванное в связи с при­ остановкой или ожиданием завершения операции ввода-вывода, то он вытес няет низкоприоритетный поток. Те оретически потоки исполнения с оди наковым приоритетом должны получать равный доступ к ЦП. Но не следует забывать, что язык Jаvа предназначен для при­ менения в обширном ряде сред. В одних из этих сред многозадачность реализуется сове ршенно иначе , чем в других. В целях безопасности потоки исполнения с оди­ наковым приоритетом должны получать управление лишь время от времени. Этим гарантируется, что все потоки получат возможность выполняться в среде операци­ онной системы с невытесняющей многозадачностью. Но на практике даже в средах с невытесняющей многозадачностью большинство потоков все-таки имеют шанс для исполнения, поскольку во всех потоках неизбежно возникают ситуации блоки­ ровки , например, в связи с ожиданием ввода-вывода. Когда случается нечто подоб-
300 Часть1. ЯэыкJava ное, ис полнение заблокированного потока приостанавливается, а остальные пото­ ки моrуг исполняться. Но если требуется добиться плавной работы многопоточной програм мы, то полагаться на случай лучше не стоит. К тому же в некоторых видах задач весьма интенсивно используется ЦП. Потоки , исполняющие такие задачи, стремятся захватить ЦП, поэтому передавать им управление СJiедует изредка, чтобы дать возможность выполняться другим потокам. Чтобы устан овить приоритет потока исполнения, следует вызвать метод set­ Priority () из класса Thread. Его общая форма выглядит следующим образом: �inal void setPriori ty(int уроsенъ) где аргумент уровень обозначает новый уровень приоритета для вызывающего потока исполнения. Значение аргумента уровень должно быть в пределах от MIN_ PRIORITY до МAX_ PRIORITY. В настоящее время эти значения равны соответственно 1 и 10. Чтобы возвратить потоку исполнения приоритет по умолчанию, СJiедует ука· зать значение NORМ_ PRIORI ТУ, которое в настоящее время равно 5. Эти приоритеты определены в классе Thread как статические завершенные ( s tat i с f ina1) перемен· ные. Для того чтобы получить текущее значение приоритета потока исполнения, до­ статочно вызвать метод getPriority () из класса Thread, как показано ниже. final int ge tPriority () Разные реалИзации Jаvа моrуг вести себя совершенно иначе в отношении пла· нирования потоков исполнения. Большинство несоответствий возникает при наличии потоков исполнения, опирающихся на вытесняющую многозадачность вместо совместного использования времени ЦП. Наиболее безопасный способ по­ лучить предсказуемое межплатформенное поведение многопоточных программ на Java состоит в том, чтобы использовать потоки исполнения, кото рые добро­ вольно уступают управление ЦП. С инхронизац и я Когда два или более потока исполнения имеют доступ к одному совместно ис· пользуемому ресурсу, они нуждаются в гарантии, что ресурс будет одновременно использован только одн им потоком. Процесс , обеспечивающий такое поведение потоков исполнения, называется синхронизацией. Как будет показано далее , вjava предоставляется особая поддержка синхронизации на уровне языка. Ключом к синхронизации является понятие монитора. Монитор - это объект, используемый в качестве взаимоисключающей блакировки. То лько один поток испол· пения может в одно и то же время владетъ монитором. Когда поток исполнения запраши вает блокировку, то говорят, что он входит в монитор. Все другие потоки исполнения, пытающиеся войти в заблокированный монитор, будут приостанов· лены до тех пор, пока первый поток не выйдет из монитора. Обо всех прочих по­ токах говорят, что они ожидают монитор. Поток, владеющий монитором, может, если пожелает, повторно войти в него. Синхронизировать прикладной код мож· но двумя способами, предусматривающими использование ключевого CJioвa syn ­ chronized. Оба эти способа будуr рассмотрены далее по очереди.
Гл ава 11. М ногопоточ н ое п рограммирова ние 301 При м енение с инх р онизированных м етодов Синхронизация достигается в Java просто , поскольку у объектов имеются свои , неявно связанные с ними мониторы. Чтобы войти в монитор объекта, достаточно вызвать метод , объявленный с модификатором доступа synchron ized. Когда поток исполнения оказывается в теле синхронизированного метода, все другие потоки исполнения или любые другие синхронизированные методы , пытающиеся вызвать его для того же самого экземпляра, вынуждены ожидать. Чтобы выйти из монитора и передать управление объектом другому ожидающему потоку исполнения , владе­ лец монитора просто возвращает управление из синхронизированного метода. Чтобы стала понятнее потребность в синхронизации, рассмотрим сначала про­ стой пример, в котором синхронизация отсутствует, хотя и должна быть осуществле­ на. Приведенная ниже программа состоит Из трех простых классов. Первый из них, Cal lme , содержит единственный метод call (). Этот метод принимает параметр ms g ти па String и пытается вывести символьную cтpoкy msg в квадратных скобках. Любопытно отметить, что после того , как метод са 11 ( ) выводит открывающую ква­ дратную скобку и символьную cтpoкy msg, он вызывает метод Thread . sleep ( 1 О О О) , который приостанавливает текущий поток исполнения на одну секунду. Конструктор следующего класса, Caller, принимает ссылку на экземпляры классов Cal lme и String, которые сохраняются в переменных target и msg со­ ответственно. В этом конструкторе создается также новый поток исполнения , в котором вызывается метод run ( ) для данного объекта. Этот поток запускается немедленно. В методе run () из класса Cal ler вызывается метод call () для эк­ земпляра target класса Callme , передавая ему символьную строку msg. И нако­ нец , класс Synch начинается с создания единственного экземпляра класса Callme и трех экземпляров класса Ca ller с отдельн �1ми строками сообщения. Оди н и тот же экземпляр класса Cal lme передается каждому конструктору Caller (). 11 Эта программа не синхронизирована class Callme { void call ( String ms g) { System.out . print ("[" + msg) ; try { Thread .sleep (lOOO) ; } catch (Interrup tedException е) { System.out . println ("Пpepвaнo" ) ; System. o ut . println ("J ") ; class Caller implement s Ru nnaЫe { String ms g; Callme target; Thread t; puЫic Caller (Callme targ, String s) { target = targ; } msg=s; t = new Thread (this ); t.start () ; puЫic void run() {
302 Часть 1. Я зык Java target .call (msg) ; class Synch { puЫic static void main (String args[] ) { Callme target = new Callme () ; Caller оЫ new Caller ( target, "Добро пожа ловать ") ; Caller оЬ2 new Caller ( target, "в синхронизированный ") ; Caller оЬЗ new Caller ( target , "мир!") ; 11 ожида ть завершения потока исполнения try { оЫ.t.join(); оЬ2 . t.j oin(); оЬЗ . t.join () ; catch (InterruptedException е) { System.out . println ( "Пpepвaнo") ; Ниже приведен результат, выводимый данной программой. Добро пожаловать [в синхронизированный [мир! ] ] ] Как видите , благодаря вызову метода sleep ( ) из метода call () уд ается пере­ ключиться на испол нение другого потока. Это приводит к смешанному выводу трех строк сообщений. В дан ной программе отсутствует механизм , предотвра­ щающий одн овременный вызов в потоках исполнения одного и того же метода для того же самого объекта, или так называемое состояние гоник, поскольку три потока соперничают за окончание метода. В дан ном примере применяется метод sleep ( ) , чтобы добиться повторяемости и наглядности получаемого эффекта. Но, как правило, состояние гонок менее заметно и предсказуемо , поскольку труд­ но предугадать, когда именно произойдет переключение контекста. В итоге про­ грамма может быть выполнена один раз правильно, а другой раз - неправильно. Чтобы исправить гл авный недостаток данной программы, следует упорядичитъ доступ к методу call ().Это означает, что доступ к этому методу из потоков испол­ нения следует разрешить только по очереди. Для этого достаточно предварить объявление метода call () ключевым словом synchronized, как показано ниже. class Callme { synchroni zed void call ( String msg) { Этим предотвращается досrуп к методу call ( ) из других потоков исполнения , когда он уже используется в одном потоке. После ввода модификатора доступа synchroni zed в объявление метода call ( ) результат выполнения данной про­ граммы будет выглядеть следующим образом: [Добро пожало вать ] [в синхронизированный ] [мир!]
Гл ава 11. М ногопоточное про граммиро вание 303 Всякий раз , когда имеется метод или группа методов, манипулирующих вну­ тренним состоянием объекта в многопоточной среде , следует употребить ключе­ вое слово synchroni zed, чтобы исключить состояние гонок. Напомним, что как только поток исполнения входит в любой синхронизированный метод экземпля­ ра, ни один другой поток исполнения не сможет войти в какой-нибудь другой син­ хронизированный метод того же экземпляра. Те м не менее несинхронизирован­ ные методы экземпляра по-прежнему остаются доступными для вызова. О п ератор synchronized Несмотря на всю простоту и эффективность с инхронизации, которую обеспе­ чивает создание синхронизированных методов, такой способ оказывается пригод­ ным далеко не всегда. Чтобы стало понятнее , почему так происходит, рассмотрим следующую ситуаци ю. Допусти м, требуется синхронизировать доступ к объектам класса, не предназначенного для многопоточного доступа. Это означает, что в дан­ ном классе не используются синхронизированные методы. Более того , класс напи­ сан сторонним разработчиком , и его исходный код недоступе н, а следовател ьно , в объявление соответствующих методов данного класса нельзя ввести модифика­ тор доступа synchronized. Как же синхронизировать доступ к объектам такого класс а? К счастью, существует довольно простое решение этого вопроса: заклю­ чить вызовы методов такого кл асса в блок оператора synchronized. Ниже при­ ведена общая форма оператора synchroni zed. synchroni zed ( cCIUl'жa на об�ъеж�) { 11 синхронизируёМые операторы Здесь ссылка_ на_ объект обозначает ссылку на синхронизируемый объект. Блок оператора synchroni zed гарантирует, что вызов метода, являющегося членом того же класса, что и синхронизируемый объект, на который делается ссылка_ на_ объект, произойдет только тогда, когда текущий поток исполнения успешно войдет в монитор данного объекта. Ниже приведена альтернативная версия программы из предыдущего примера, где в теле метода run ( ) используется синхронизированный блок. 11 В этой программе исполь зуется синхронизированный блок class Callme { void call ( Striпg msg) { System.out . print ("[" + msg) ; try { Thread .sleep (lOOO) ; ) catch (InterruptedException е) { System. o ut . println ("Пpepвaнo" ) ; System. out . println ("] ") ; cla ss Caller implements RunnaЫe { String ms g; Callme target; Thread t;
30' Часть 1. Язык Java puЫic Caller ( Cal lme targ , String s) { target = targ ; msg=s; t = new Thread(this) ; t.start(); 11 синхронизированные выз овы ме тода call () puЫic void run() { synchronized ( target) { // синхронизированный блок target .call (msg) ; class Synchl { puЫic static void ma in (String args[]) { Callme target = new Callme() ; Caller оЫ new Caller ( target, "Добро пожаловать ") ; Caller оЬ2 new Caller ( target, "в синхронизированный" ) ; Caller оЬЗ = new Caller ( target, "мир! ") ; 11 ожида ть завершения потока исполнения try { оЫ . t.join(); оЬ2 . t.join(); оЬЗ . t.join () ; catch (Interrup tedEx ception е) { Systern.out . println ("Пpepвaнo" ) ; В данном примере метод cal1 ( ) объявлен без модификатора доступа synchro­ ni zed. Вместо этого используется оператор synchronized в теле метода run () из класса Caller. Благодаря этому получается тот же правильный результат, что и в предыдущем примере, поскольку каждый поток исполнения ожидает завершения предыдущего потока. Взаимодейств ие пото ков испол нения В предыдущих примерах другие потоки исполнения , безусловно, блокирова· лись от асинхронного доступа к некоторым методам. Та кое применение неявных мониторов объектов вJava оказывается довольно эффективным, но более точного управления можно добиться, организовав взаимоде йствие потоков исполнения. Как будет показано ниже, добиться такого взаимодействия особенно просто вjava. Как обсуждалось ранее, многопоточность заменяет программирование циклов ожидания событий благодаря разделению задач на дискретные, логически обосо­ бленные единицы. Еще одно преимущество предоставляют потоки исполнения , ис· ключая опрос. Как правило, опрос реализуется в виде цикла, организуемого для пери· одической проверки некоторого условия. Как только условие оказывается истинным, выполняется определенное действие. Но на это расходуется время ЦП. Рассмотрим в качестве примера классическую задачу организации очереди, когда некоторые дан·
Гл ава 11. М ногопото ч н ое программирование 305 ные поставляются в одном потоке исполнения , а в другом потоке они потребляются. Чтобы сделать эту задачу более интересной , допустим, что поставщик данных должен ожидать завершения работы потребителя, прежде чем сформировать новые данные. В системах с опросом потребитель данных тратит немало циклов ЦП на ожидание данных от поставщика. Как только поставщик завершит работу, он должен начать оп рос, напрасно расходуя лишние циклы ЦП в ожидании завершения работы потре­ бителя данных, и т.д. Ясно, что такая си� нежелател ьна. Чтобы избежать опроса, в Java внедрен изящный механизм взаимодействия пото ков исполнения с помощью методов wait (), not ify () и notifyAll (). Эти методы реализованы как завершенные в классе Ob ject, поэтому они доступ ны всем классам. Все три метода могут быть вызваны только из синхронизированно­ го контекста. Правила применения этих методов достаточно просты, хотя с точки зрения вычислительной техники они принципиально прогрессивны. Эти правила состоят в следующем. • Метод wa i t () вынуждает вызывающий поток исполнения уступить мони­ тор и перейти в состояние ожидания до тех пор, пока какой-ни будь другой поток исполнения не войдет в тот же монитор и не вызовет метод not i fy () . • Метод no tify () возобновляет исполнение потока, из которого был вызван метод wai t ( ) для того же самого объекта. • Метод notifyAl l ( ) возобновляет исполнение всех потоков, из которых был вызван метод wait ()для того же самого объекта. Одному из этих пото­ ков предоставляется доступ. Все эти методы объя влены в классе Ob j ect, как показано ниже. Существуют дополнительные формы метода wa i t ( ) , позволяющие указать время ожидания . final void wait () throws InterrupteclException final void notify () final void notifyAll () Прежде чем рассматривать пример, демонстрирующий взаимодействие по­ токов исполнения, необходимо сделать одно важное замечание. Метод wai t () обыч но ожидает до тех пор, пока не будет вызван метод no tify () или not ify All ( ) . Но вполне вероятно, хотя и в очень редких случаях, что ожидающий поток исполнения может б ыть возобновлен вследствие ложнай активизации. При этом исполнение ожидающего потока возобновляется без вызова метода noti fy () или not ifyAll ( ). (По существу, исполнение потока возобновляется без очевидных причин .) Из-за этой маловероятной возможности в компании Oracle рекомендуют вызывать метод wa i t ( ) в цикле, проверяющем условие, по которому поток ожида­ ет возобновления. И такой подход демонстрируется в представленном далее при­ мере программы. А до тех пор рассмотрим несложный пример программы, неправильно реализу­ ющей простую форму поставщика и потребителя данных. Эта программа состоит из четырех классов: Q-синхронизируемой очереди; Producer - поточного объек­ та , создающего элементы очереди; Consume r - поточного объекта , принимающе­ го элементы очереди ; а также РС - мелкого класса, в котором создаются объекты классов Q, Producer и Consumer. Ниже приведен исходный код этой программы.
306 Часть 1 . Язык Java 11 Неправильная реализация поставщика и потребителя class Q { int n; synchronized int get () { System. o ut . println ("П oлyчeнo : "+n) ; return n; synchronized void put (int n) { this.n = n; System. out .println ( "Oтпpaвлeнo : "+n) ; cl ass Produc er implement s RunnaЫe { Qq; Producer (Q q) { this.q = q; new Thread (this, "Поставщик" ) .start(); puЫ ic void run() inti=О; while(true) { q.put (i++) ; class Consumer implemen ts Ru nnaЫe { Qq; Consume r (Q q) { this.q = q; new Thread (th is, "Потребитель ") .start () ; puЫ ic void run() while (true ) { q.ge t(); class РС { puЫ ic static void ma in (String args (] ) { Qq=newQ(); new Producer (q) ; new Consumer (q) ; System. out . println ( "Для остановки нажмите Ctrl-C .") ; Несмотря на то что методы put () и get ( ) синхронизированы в классе Q, ничто не остановит переполнение потребителя данными от поставщика, как и ничто не помешает потребителю дважды извлечь один и тот же элемент из очереди. В ито­ ге будет выведен неверный результат, как показано ниже (конкретный результат может быть иным в зависимости от быстродействия и загрузки ЦП).
Отправлено : 1 Получено : 1 Получено : 1 Получено : 1 Получено : 1 Получено : 1 отправлено: 2 Отправлено : З Отправлено : 4 Отправле но : 5 Отправлено : 6 Отправлено : 7 Получено: 7 Гл ава 11. Мно го п ото ч н ое программиро вание 307 Как видите, после того, как поставщик ОПiравит значение 1, запускается потре­ битель, который получает это значение пять раз подряд. Затем поставщик продол­ жает свою работу, поставляя значения от 2 до 7, не давая возможности потребителю получить их. Для того чтобы правильно реализовать взаимодействие поставщика и потребителя в рассматриваемом здесь примере программы наJava, следует приме­ нить методы wa i t () и notify () для передачи уведомлений в обоих напраалениях: 11 Пра вильная реализация поставщика и потребителя class Q { iпt п; bo olean valueSet ; false; syпchroni zed int get () whi le ( ! valueSet) try { wait (); catch (InterruptedException е) { Systern . out .println( "Исключение типа InterruptedException перехвачено" ) ; Systern.out . println ( "Пoлyчeнo : "+п) ; valueSet ; false; notify (); return п; syпch roni zed void put (int п) { whi le (valueSet ) try { wait () ; catch (Interrupt edException е) { System.out . println ( this .n n; "Исключ ение типа InterruptedException перехвачено" ); valueSet ; true ; Systern. o ut . printlп ( "Oтпpaвлeнo : "+n) ; notify () ; class Producer irnplernents Ru пnaЫe { Qq; Produce r(Q q) { this.q ; q;
308 Часть 1. Язык Java new Thread {thi s, "Поставщик ") .start {) ; puЫic void run () inti=О; while (true) { q.put ( i++) ; class Consume r implements RunnaЫe { Qq; Consumer (Q q) { this.q = q; new Thread {thi s, "Потребитель ") .start(); puЫic void run () while (true) { q.get (); class PCFixed { puЫic static void main (String args [] ) { Qq=newQ(); new Producer (q) ; new Consume r (q) ; System.out . println ( "Для остановки нажмите Ctrl-C .") ; В методе get () вызывается метод wa it ().Витоге исполнение потока приоста· навливается до тех пор, пока объект класса Producer не уведомит, что данные про­ читаны. Когда это произойдет, исполнение потока в методе ge t () возобновится. Как только данные будуг получены, в методе get () вызывается метод notify (). Этим объект класса Producer уведомляется о том, что все в порядке и в очереди можно разместить следующий элемент данных. Метод wa i t () приостанавливает исполнение потока в методе put () до тех пор, пока объект класса Consumer не из­ влечет элемент из очереди . Когда исполнение потока возобновится, следующий элемент данных размещается в очереди и вызывается метод not ify ().Этим объект класса Consumer уведомляется, что он теперь может извлечь элемент из очереди. Ниже приведен результат, выводимый дан ной программой . Он наглядно пока­ зывает, что теперь синхронизация потоков исполнения действует правильно. Отправлено : 1 Получено : 1 Отправлено : 2 Получено : 2 Отправлено : 3 Получено : 3 Отправлено : 4 Получено : 4 Отправлено : 5 Получено : 5
Гл ава 11. М ногопоточ ное программиро вание 309 Вза имна я блокировка Следует избегать особого типа ошибок, имеющего отношение к многозадачности и называемого взаuмнай блтсировкай, которая происходит в том случае, когда потоки исполнения имеют циклическую зависимость от пары синхронизированных объек­ тов. Допустим, один поток исполнения входит в монитор объекта Х, а другой - в мо­ нитор объекта У. Если поток исполнения в объекте Х попьrгается вызвать любой син­ хронизированный метод для объекта У, он будет блокирован, как и предполагалось. Но если поток исполнения в объекте У, в свою очередь, попытается вызвать любой синхронизированный метод для объекта Х, то этот поток будет ожидать вечно, по­ скольку для получения дос'I)'Па к объекту Х он должен снять свою блокировку с объек­ та У, чтобы первый поток исполнения мог завершиться. Взаимная блокировка являет­ ся ошибкой, которую трудно отладить, по двум следующим причинам. • В общем, взаимная блокировка возникает очень редко , когда исполнение двух потоков то чно совпадает по времени. • Взаи мная блоки ровка может возникнуть, когда в ней участвует бол ьше двух потоков исполнения и двух синхронизированных объектов. ( Это означает, что взаимная блокировка может произойти в результате более сложной по­ следовательности событий, чем в упомянутой выше ситуации.) Чтобы полностью разобраться в этом явлении, его лучше рассмотреть в дей­ ствии. В приведенном ниже примере программы создаются два класса, А и В, с методами foo () и bar ( ) соответственно, которые приостанавливаются непо­ средственно перед попыткой вызова метода из другого класса. Сначала в гл авном классе Deadlock получаются экземпляры классов А и В, а затем запускается второй поток исполнения, в котором устанавливается состояние взаимной блокировки. В методах foo () и bar () испол ьзуется метод sleep (), чтобы сти мулировать по­ явление взаимной бло кировки . 11 Пример взаимной блокировки class А { syпchroпi zed void foo (B Ь) St riпg пате = Thread . curreпtThread () .getName() ; System. out .priпtlп (пame + " вошел в метод A. f o o() " ) ; try { Thread .sleep (1000) ; } catch ( Exceptioп е) { System.out .priпtlп ( "Клacc А прерван") ; System.out . priпtlп (пame + " пытается вызвать ме тод B.last()"); b.last() ; syпchroпized void last () { System.out . priпtlп ("B ме тоде A. last() " ) ; class В { syпchroni zed void bar (А а) {
310 Часть 1. Яэык Java String name = Thread . currentThread () .getName() ; System.out . println (name + " вошел в ме тод B.bar ()") ; try { Thread .sl eep (1000) ; catch ( Exception е) { System. out . println ("Клacc В прерван" ); System.out . println (name + " пытается вызвать ме тод A.last ()") ; a.last() ; synchroni zed void last () { System. out . println ("B ме тоде A.last()"); class Deadlock implements RunnaЫe { Аа newА(); ВЬ=newВ(); Deadlock() { Thread . currentThread () . setName ("Глa вный поток" ); Thread t = new Thread (this, "Соперничающий поток") ; t.start (); а.fоо (Ь) ; // получи ть блокировку для объекта а 11 в этом потоке исполнения System.out . println ( "Haзaд в главный поток" ); puЫ ic void run() { b.ba r(a) ; // получить блоки ровку дл я объе кта Ь 11 в другом потоке исполнения System.out . println ( "Haзaд в другой поток " ); puЫ ic static void ma in (String args []) { new Deadlock () ; Запустив эту программу на выполнение, вы получите следующий результат: Главный поток вошел в ме тод A. fo o() Сопернич ающий поток вошел в ме тод B.bar () Главный поток пыта ется вызвать метод B.last () Сопернич ающий поток пытается вызвать ме тод A.last {) В связи со взаимной блокировкой придется нажать комбинацию клавиш <Ctrl+C>, чтобы завершить данную программу. Нажав комбинацию клавиш <Ctrl+Pause> на ПК, можно увидеть весь дамп ( вывод из оперативной памяти) по· тока и кеша монитора. В частности , Соперничающий поток владеет монитором объекта Ь, тогда как он ожидает монитор объекта а. В то же время Гла вный поток владеет объектом а и ожидает получить объект Ь. Следовательно, программа ни­ когда не завершится . Как демонстрирует дан ный пример, если многопоточная программа неожиданно зависла, то прежде всего следует проверить возможность взаимной блоки ровки.
Глава 11. Многопоточное программирование 311 Приоста новка , возобновл е ние и остановка потоков исполнения Иногда возникает потребность в приостановке исполнения потоков. Например, отдел ьный поток исполнения может служить для отображения времени дня . Если пользователю не требуется отображе ние текущего времени, этот поток исполнения можно приостановить. Но в любом случае приостановить исполнение потока совсем не трудно. Выполнение приостановленного потока может быть легко возобновлено. Механизм временной или окончательной остановки потока исполнения, а также его возобновления отличался в ранних версиях jаvа, например, Jаvа 1.0, от современных версий, начиная с Java 2. До версии Java 2 методы suspend ( ) и re sume ( ) , определенные в классе Thread, использовались в программах для при­ остановки и возобновления потоков исполнения. На первый взгляд применение этих методов кажется вполне благоразумным и уд обным подходом к управлению выполнением потоков. Те м не менее пользоваться ими в новых программах нajava не рекомендуется по следующей причине: метод suspend ( ) из класса Thread не­ сколько лет назад был объявлен не рекомендованным к употреблению, начиная с версии jаvа 2. Это было сделано потому, что иногда он способен порождать се­ рьезн ые системные сбои. Допустим, что поток исполнения получил блокировки для очень важных структур дан ных. Если в этот момент приостановить исполне­ ние да нного потока, блокировки не будут сняты. Другие потоки исполнения , ожи­ дающие эти ресурс ы, могут оказаться взаимно блокированными. Метод resume ( ) также не рекомендован к употреблению. И хотя его приме­ нение не вызовет особых осложнений, тем не менее им нельзя пользоваться без метода suspend (), который его дополняет. Метод stop ( ) из класса Thread также объявлен устаревшим с версии Java 2. Это было сделано потому, что он может иногда послужить причиной серьезных системных сбоев. Допусти м, поток выполняет запись в критически важную струк­ туру данных и успел произвести лишь частичное ее обновление. Если его остано­ вить в этот момент, структура данных может оказаться в поврежде нном состоя­ нии. Дело в том , что метод s top ( ) вызывает снятие любой бл окировки, устанав­ ливаемой вызывающим потоком исполнения . Следовател ьно, поврежде нные дан­ н ые могут быть использованы в другом потоке исполнения, ожидающем по той же самой бло кировке . Есл и методы su spend (), resume ( ) или stop ( ) нельзя использовать для управ­ ле ния потоками исполнения, то можно прийти к выводу, что теперь вообще нет никакого механ изма для приостановки , возобновления ил и прерывания потока исполнен ия. К счастью, это не так. Вместо этого код управления выполнением потока должен быть составлен таким образом, чтобы метод run ( ) периодически проверял , должно ли исполнение потока быть приостановлено, возобновлено или прервано. Обычно для этой цел и служит флаговая переменная, обозначаю­ щая состояние потока исполнения . До тех пор, пока эта флаговая переменная содержит признак "выполняется", метод run ( ) должен продолжать выполнение. Если же эта переменная содержит признак "приостановить", поток исполнения должен быть приостановлен. А если флаговая переменная получает признак "оста-
312 Ч асть 1. Яэык Jаvа новить" , то поток исполнения должен завершиться. Безусловно, имеются самые разные способы написать кода управления выполнением потока , но основной принцип остается неизменны м для всех программ. В приведенном ниже примере программы демонстрируется применение методов wa i t ( ) и notify (), унаследованных из класса Obj ect, для управления выполнени­ ем потока. Рассмотрим подробнее работу этой программы. Класс NewThread содер­ жит переменную экземпляра suspendFlag типа boolean, используемую для управ­ ления выполнением потока. В конструкторе этого класса она инициализируется ло­ гическим значением false. Метод run ( ) содержит блок оператора s ynchronized, где проверяется состояние переменной suspendFlag. Если она принимает логиче­ ское значение true, то вызывается метод wa it ( ) для приостановки выполнения потока. В методе my suspend ( ) устанавливается логическое значение true перемен ­ ной suspendFlag, а в методе myresime () -логическое значение false этой пере­ менной и вызывается метод no tify (), чтобы активизировать поток исполнения. И наконец, в методе ma in () вызываются оба мeтoдa -my suspend () и myresime (). 11 Приостановка и возобновление исполнения потока современным способом class NewThread implements RunnaЫe { String name ; // имя потока исполнения Thread t; boolean suspendFlag; NewThread (String threadname ) name = threadname ; t = new Thread (this, name) ; System.out . println ( "Hoвый поток : "+t) ; suspendFlag = false ; t.start (); // запус тить поток исполнения 11 Точка входа в поток исполнения puЫic void run () { try { for(inti=15;i>О;i--) System.out . println (name + "· "+i) ; Thread .sleep (200) ; synchroni zed (this) { } wh ile ( suspendFlag) wait () ; catch (InterruptedException е) { System. o ut . println ( name + " прерван ."); System. out . println ( name +"завершен .") ; synchroni zed void my suspend () suspendFlag = true ; synchroni zed void myresume () suspendFlag = false ; notify ();
class SuspeпdRe sume { Гл а ва 11. М ногопоточное п рограммировани е 313 puЫ ic static vo id ma iп (Striпg args [] ) { NewThread оЫ пеw NewThread ("Один " ); NewThread оЬ2 = пеw NewThread ("Двa" ); try { Thread .sleep (lOOO) ; оЫ . mysuspeпd (); System.out . priпtlп ("Пpиocтaнoвкa потока Один" ); Thread .sl eep (l000) ; оЫ .myresume () ; System.out . priпtlп ("B oзoбнoвлeниe потока Один" ) ; ob2 .mysuspeпd () ; System.out . priпtlп ("Пpиocтaнoвкa потока Два " ); Thread .sleep (l000) ; ob2 . myre sume (); System.out . priпtlп ("B oзoбнoвлeниe потока Два" ); catch (Iпte rruptedExceptioп е) { System.out . priпtlп ( "Глaвный поток прерван") ; 11 ожидать завершени я потоков испол нения try { System.out . priпtlп ("Oжидaниe завершения потоков.") ; оЫ.t.jоiп(); оЬ2 . t.j оiп(); catch (IпterruptedExceptioп е) { System.out . priпtlп ( "Глaвный поток прерван" ); System.out . priпtlп ( "Глaвный поток завершен" ); Если запустить эту программу на выполнение, то можно увидеть, как исполне­ ние потоков приостанавливается и возобновляется. Далее в этой книге будут пред­ ставлены другие примеры, в которых применяется современный механизм управ­ ления исполнением потоков. И хотя этот механизм не так прост, как прежний, его следует все же придерживаться , чтобы избежать ошибок во время выполнения. Именно такой механизм должен применяться во всяком новом коде. Получ ение состоя ния пото ка испол нения Как упоминалось ранее в этой гл аве, поток исполнения может находиться в не­ с кол ьких состоя ниях. Для того чтобы получить текущее состоя ние потока испол­ н е ния, достаточно вызвать метод getState (), определенный в классе Thread, следующим образом: Thread .State ge tState () Этот метод возвращает значение типа Thread . State, обозначающее состо­ яние потока исполнения на момент вызова. Перечисление State определено в классе Thread. (Перечисление представляет собой список именованных кон­ стант и подробно обсуждается в главе 12.) Значения, которые может возвратить метод ge tState (), перечислены в табл. 11. 2.
314 Часть 1. Язык Java Та блица 11. 2. Значения, возвращаемые методо м qets tate О Значение Состояние BLOCКED Поток приостановил вы полнение, поскольку ожидает получения блокировки NEW П оток еще не начал выполнение RUN NAВ LE П оток в настоящее время выполняется или начнет выполняться, когда п олучит доступ к ЦП TERМINATED Поток заверш ил выполнение TIМED WAIT ING Поток приостановил выполнение на определенный промежуrок времени, например, после вызова метода sleep () . Поток переходит в это состояние и при вызове м етода wai t () или join () WA ITING Поток приостановил выполнение, п оскольку он ожидает некоторого действия, например, вызова версии метода wai t () или j oin () без заданного времени ожидания На рис. 11.1 схематически показана взаимосвязь различных состояний потока исполнения. BLOCКED Ожидание бnокировки Блокировка получена NEW Ожидан ие WAПING RUN N ABLE или ТIМED_WAПING i Ожидание окончено --. ._. .. .,. .. .. .,_. .,. .._., ,, !:1 1 .. . !i 1 � ТERМINAТED Рис. 11.1 . Состояния потока исполнения
Гл ава 11. Мно гопото ч ное прог раммирование 315 Имея в своем распоряжении экземпляр класса Thread, можно вызвать метод getState (}, чтобы получить состояние потока исполнения . Например, в следую­ щем фрагменте кода определяется, находится ли поток исполнения thrd в состо­ янии RUNNAB LE во время вызова метода getState (}: Thread .State ts = thrd . getState () ; if (ts == Thread .State . RUNNABLE ) // ... Следует, однако , иметь в виду, что состояние потока исполнения может изме­ ниться после вызова метода getState (}. Поэтому в зависимости от обстоятельств состоя ние, полученное при вызове метода ge tState (}, мгновение спустя может уже не отражать фактическое состояние потока исполнения. По этой и другим причинам метод ge tState ( ) не предназначен для синхронизации потоков испол­ нен ия. Он служит прежде всего для отладки или профилирования характеристик потока во время выполнения. Применение много п ото ч ности Чтобы эффективно пользоваться многопоточными средствами в Java, нужно научиться мыслить категориями параллельного , а не последовательного выпол­ нения операций. Та к, если в программе имеются две подс истемы, которые могут выполняться одновременно, оформите их в виде отдел ьных потоков исполнения. Благоразумно применяя многопоточность, можно научиться писать довольно эф­ фективные программы. Но не следует забывать , что, создав слишком много пото­ ков исполнения , можно снизить производительность программы в целом, вместо того чтобы повысить ее. Следует также иметь в виду, что переключение контекста с одного потока на другой требует определенных издержек. Если создать очень много потоков исполнения, то на переключение контекста будет затрачено боль­ ше времени процессора, чем на выполнение самой программы ! И последнее заме­ чан ие: для создания прикладной программы, предназначенной для интенсивных вычислений и допускающей автоматическое масштабирование с целью задейство­ вать имеющиеся процессоры в многоядерной системе, рекомендуется воспользо­ ваться каркасом Fork/Join Framewo rk, описанным в гл аве 28.
12 Переч исления, автоуп аковка и аннота ции (метаданные) В этой главе рассматриваются три относ ительно новых дополнения языка Java: перечисления , автоупаковка и аннотации (называемые также метаданными). Каждое из них увеличивает эффективность этого языка, предлагая изящный под­ ход к решению часто возникающих задач программирования . В этой гл аве обсуж­ даются также оболочки типов данных вjava и рефлекс ия. Перечисления До версииJDК 5 явно недоставало перечислений - одного изязыковых средств , потребность в котором остро ощущали многие программирую щие нa Java. В про­ стей шей форме перечисление представляет собой список именованных констант. Несмотря на то что в J ava имеются другие языковые средства с похожими функ­ циональными возможностями , например завершенные переменные, многим про­ грам мирующим нajava все же не хватало принципиал ьной ясности перечислений, особенно пото му, что они применяются во многих других языках программиро­ ван ия . В версии JDК 5 перечисления были внедрены вJava и наконец-то стали до­ ступны для программирования на это м языке. В простейшей форме перечисления в Java похожи на перечисления в других языках программирования, но это поверхностное сходство . В языках вроде С++ перечисления просто явля ются списками целочисленных констант. А в J ava пере­ числения определяют тип класса. Благодаря тому что перечисления реализованы в виде классов, само понятие перечисления значительно расширяетс я. Например, перечисления в Java могут иметь конструкторы , методы и переменные экземпля­ ра. И несмотря на то что внедрения перечислений пришлось ждать несколько лет, реали зация их вJava того стоила. Ос новные п ол оже ния о п еречислениях Переч исления создаются с помощью ключевого слова enum. В качестве приме­ ра ниже показано простое перечисление сортов яблок. 11 Перечисление сортов яблок enum App le { Jonathan, GoldenDel , RedDel, Wine sap, Cortland
318 Часть 1.Язык Java Идентификато ры Jona than, GoldenDel и так далее называются коистаитами перечис.л:имого типа. Каждая из них явно объявл ена как открытый статический ко­ нечный член класса App le. Более того , они относятся к типу того перечисления, в кото ром объя влены (в данном случае - App le) . В языке Java такие константы называются самотипизироваииъ�ми, причем префикс само относится к охватываю­ щему их перечислению. Объявив перечисление, можно создавать переменные данного типа. Но, не­ смотря на то , что перечисления определяют тип клас са, получать экземпляры класса типа enum с помощью оператора new нельзя. Вместо этого переменная перечисления объявляется и применяется практически так же , как и переменные при митивных типов. В приведенном ниже примере объявляется переменная ар перечислимого типа App le. Apple ар ; Переменная ар относится к типу Арр l е, и поэтому ей можно присвоить только те значения , которые определены в перечислении. В следующем примере пере­ менной ар присваивается значение RedDe l: ар = App le .RedDel; Обратите внимание на то, что значению RedDe l предшествует тип App le. Две коliстанты перечислимого типа можно проверять на равенство с помощью опера­ ции отношения ==. Например, в следующем условном операторе переменная ар сравнивается с константой Apple . GoldenDe l: if (ар == App l e.GoldenDel ) // ... Значения перечислимого типа можно также использовать в упрамяющем опе­ раторе switch. Разумеется, для этого во всех выражениях ветвей case должны использоваться константы из того же самого перечисления , что и в самом опе­ раторе swi tch. Например, следую щий оператор swi tch составлен совершенно правильно: 11 Исполь зовать перечисление для упра вления оператором switch switch (ар) { case Jonathan : 11... case Winesap : // ... Обратите внимание на то , что в выражениях ветвей case имена констант ука­ зываются без уточнения имени их перечислимого типа, например Winesap, а не App le . W i n е s а р . Дело втом, что тип перечисления воператоре swi tсh уже неявно задает тип перечисления для выражений в ветвях case. Следовательно, уточнять имена констант в выражениях ветвей case не нужно. В действительности любая попытка сделать это приведет к ошибке во время компиляции. Когда выводится константа перечислимого типа, например, методо м println (),то отображается ее имя. Например, в следующей строке кода: System. out . println (Apple . Wine sapp ) выводится имя Winesapp.
Гл ава 12. Переч исл ения, автоупако вка и аннотаци и (метаданные) 319 Приведенный ниже пример программы подытоживает все сказанное выше, де­ монстрируя применение перечисления App le. 11 Перечисление сортов яблок enum App le { Jonathan, GoldenDel , RedDel, Winesap, Cortland cl ass EnumDemo { puЫ ic static void main (String args[] ) { Apple ар ; ар = App le .RedDel; 11 вывести значение перечислимого типа System.out . println ("З нaчeниe ар : " + ар ) ; System.out . println (); ар = App le .GoldenDel; 11 сравнить два значения перечислимого типа if (ap == App le . GoldenDel ) System. out .println ( "Пepeмeннaя ар содержит GoldenDel .\n" ) ; 11 приме нить перечисление для управления оператором switch switch (ар) { case Jonathan : System. out .println ( "Copт Jonathan красный.") ; break; case GoldenDel : System. out . println ( "Copт Golden De licious желтый.") ; break; case RedDel : System.out . println ( "Copт Red De licious красный. ") ; break; case Wine sap : System. out .println ( "Copт Wine sap кр асный.") ; break; case Cortland : System.out . println ( "Copт Cortland красный.") ; break; Эта программа выводит следующий результат: Значение ар : RedDe l П еременная ар содержит GoldenDel . Сорт Golden Delicious желтый . Методы values () и valueOf' () Перечисления автоматически включают в себя два предопределе нных метода: va lues ( ) и va lueOf ().Ниже приведена их общая форма.
320 Часть 1. Язы к Java puЫic static 'l'IOI Dep8'1JirCJ J &юr• [] values () puЫic static 'l'IOI=D epevжcлeюu valueOf (String С!l.'JЮЖВ) Метод va lues () возвращает массив, содержащий список констант перечисли­ мого типа. А метод va lueOf ( ) возвращает константу перечислимого типа, значе­ ние которой соответствует символьной строке, переданной в качестве аргумента строка . В обоих случаях тип_ перечисления обозначает тип конкретного пере­ числения. Та к, если вызвать метод Appl e.valueOf ( " Winesapp ") для упоминав­ шегося ранее перечисления Apple, то он возвратит значение константы перечис­ лимого типа Wine sapp. В следующей программе демонстрируется применение методов va lues () и valueOf (): 11 Восполь зоваться вс троенными в перечисление ме тодами 11 Перечисление сортов яблок enurn Apple { Jona than , GoldenDel, RedDe l, Wine sap, Cortland class EnurnDemo2 { puЫ ic static vo id ma in (String args[]} { Apple ар; System. out . println ("K oнc тaнты перечислимого типа Apple :") ; 11 приме нить ме тод values () Ap ple allappl es [] = Appl e.values () ; for (Apple а : allapples} System.out . println (a} ; System. out . println (} ; 11 приме ни ть ме тод valueOf () ар = App le .valueOf ("Wine s ap ") ; System. out . println ( "Пepeмeннaя ар содержит "+ар ) ; Эта программа выводит следующий результат: Ко нстанты перечислимо го типа App l e: Jonathan GoldenDel Re dDel Wine sap Cortland Переменная ар содержит Winesap Обратите внимание на то , что для перебора массива констант, возвращаемых ме­ тодом va lues (),в данной программе используется цикл for в стиле for each. В це­ лях демонстрации создается переменная allapples, которой присваивается ссьwка на массив значений перечислимого типа. Но это совсем не обязательно, поскольку цикл for можно написать и без переменной allapp les следующим образом:
Гл а ва 12. Пер ечислени я, а втоупако вка и аннота ц ии (метаданные) 321 for (Apple а:Apple .values ()) System. o ut . println (a) ; Обратите также внимание на получение значения , соответствующего имени Winesapp, в результате вызова метода va lueOf (): ар = Appl e.valueOf ( "Wine sap" ); Как пояснялось ранее , метод va lueOf () возвращает значение перечислимого типа, связанное с именем константы того же типа, передаваемым этому методу в виде символьной строки. Перечисления в Java относятся к типам классов Как пояснялось ранее , перечисление в Java относится к типу класса. Создать экземпляр перечисления с помощью оператора new нельзя , но в остальном пере­ числение обладает всеми возможностями, которые имеются у других классов. То т факт, что перечисление определяет класс, наделяет перечисления в Java огром­ ным потенциалом, которого лишены перечисления в других языках программи­ рован ия. В частности , перечисления допускают предоставление конструкторов, добавление переменных экземпляров и методов и даже реализацию интерфейсов. Важно понимать, что каждая константа перечислимого типа является объектом класса своего перечисления. Та к, если для перечисления определяется конструк­ тор, он вызывается всякий раз, когда создается константа перечислимого типа. Кроме того , у каждой константы перечислимого типа имеется своя ко пия любой из переменных экземпляра, объявленных в перечислении. Рассмотрим в качестве примера следующую версию перечисления App le: 11 Исnоль зовать конструктор , nеременную экземпляра и метод в перечисле нии enum App le { Jona than (lO) , GoldenDel (9) , RedDe l (l2) , Winesap (15) , Cortland (8) ; private int price ; // цена яблока каждого сорта 11 Конструктор Apple(int р) { price = р; } int getPrice () { return price ; class EnumDemo З { puЫic static void main (String args[] ) { Apple ар; 11 вывести цену на яблоко сорта Winesap System.out . println ( "Яблoкo сорта Winesap стоит " + Apple . Winesap .getPrice () + " центов . \n") ; 11 вывести цены на все сорта яблок System.out . println ( "Цeны на все сорта яблок:") ; for {Apple а : Apple.values ()) System. out .println (a + " стоит " + a.getPrice () + " центов.") ;
322 Часть 1. Язык Java Ниже приведен результат, выводимый при выполнении данного кода. Яблоко сорта Wine sap стоит 15 центов . Цены на все сорта яблок : Jonathan стоит 10 центов . GoldenDel стоит 9 центов . Re dDel стоит 12 центов . Wi nesap стоит 15 центов . Cortland стоит В центов . В данной версии перечисления App le добавлено следующее . Во-первых, пере­ менная экземпляра price, которая служит для хранения цены яблока каждого сорта. Во-вторых, конструктор App le () , которому передается цена на яблоко. И в-третьих, метод getPrice (),возвращающий значение цены. Когда в методе ma in () объявляется переменная ар, конструктор App le ( ) вы­ зывается один раз для каждой объявл енной константы . Обратите внимание на то, что аргументы ко нструктору передаются в скобках после каждой перечисляемой константы , как показано ниже. Jona than (lO) , GoldenDel (9) , RedDel (12) , Wine sap (15) , Cortland (B) ; Эти значения передаются параметру р конструктора Ap p le (), который затем присваи вает их переменной экземпляра pr ice. Опять же конструктор вызывается один раз для каждой константы перечисли мого типа. У каждой константы перечислимого типа имеется своя копия переменной эк­ земпляра price, поэтому для получения цены на определенный сорт яблок доста­ то чно вызвать метод ge tPrice (). Например, цена на сорт яблок Wine sap получа­ ется в результате следующего вызова в методе ma in ( ) : App le .Winesap .getPrice () Цены на все сорта яблок получаются при переборе перечисления в цикле for. Копия переменной экземпляра price существует для каждой константы перечис­ лимого типа, поэтому значение, связан ное с одной константой , отделено и отли­ чается от значения, связанного с другой константой. Столь эффекти вный прин­ цип оказывается возможным только благодаря реализации перечислений в виде классов, как это и сделано в языке Jаvа. В предыдущем примере перечисление содержит только один конструктор, но на самом деле в перечислении могут быть предоставлены две или более перегру­ жаемых формы конструкторов, как и в любом другом классе. Например, в приве­ де нной ниже версии перечисления App le дополнительно предоставляется кон­ структор по умолч анию, инициализирующий цену значением -1, которое озна ча· ет, что цена не указана. 11 Использовать конс трукторы в перечислении enum Apple { Jonathan (lO) , GoldenDel (9) , RedDe l, Winesap (l5) , Cortland (B) ; private int price; // цена яблока каждого сорта // Конструктор Apple(int р) { price = р; } // Перегружаемый конструктор
Гл ава 12. Перечисления, автоуп аковка и аннота ции (метада нные) 323 Apple() { price = -1; } int getPrice () ( return price ; Обратите внимание на то , что в этой версии перечисления App le констан· те RedDe l не передается аргумент. Это означает, что вызывается конструктор по умолчанию и в переменной price для цены на яблоко сорта RedDe l устанавли­ вается значение -1 . Однако на перечисления накладываются два ограничения. Во-первых, пере­ числение не может наследоваться от другого класса. И во-вторых, перечисление не может быть суперклассом. Это означает, что перечисление не может быть рас­ ширено. А в остальном перечисление ведет себя так же, как и любой другой тип класса. Самое гл авное - не забывать, что каждая константа перечислимого типа является объектом класса, в котором она определена. Перечисления наследуются от кпасса Enum Несмотря на то что при объявлении перечисления нельзя наследовать супер­ класс, все перечисления автоматически наследуют от класса j ava . lang . Enum. В этом классе определяется ряд методов, доступных для использования во всех перечислениях. Класс Enum подробнее рассматривается в части 11 данной книги , но три его метода требуют хотя бы краткого описания уже теперь. Вызвав метод ordinal (), можно получить значение, которое обозначает пози­ цию константы в списке констант перечислимого типа. Это значение называется порядковым и извлекается из перечисления так, как показано ниже. final int ordinal () Этот метод возвращает порядковое значение вызывающей константы . Порядковые значения начинаются с нуля . Та к, в перечислении Apple констан­ та Johnatan имеет порядковое значение О, константа GoldenDel - 1, константа RedDel - 2 и т.д. С помощью метода compareTo ( ) можно сравнить порядковые значения двух констант одного и того же перечислимого типа. Этот метод имеет следующую об­ щую форму: final int СО1 11р аrето ( �JrD_переvжслеюr.1 1 е) где тип_ перечисления обозначает тип конкретного перечисления, а е - кон­ станту, которую требуется сравнить с вызывающей константой. Напомним, что обе константы (вызывающая и е) должны относиться к одному и тому же пере­ ч ислимому типу. Если порядковое значение вызывающей константы меньше, чем у константы е, то метод compareTo () возвращает отрицательное значение. Если же порядковые значения обеих констант одинаковы, возвращается нуль. А если порядковое значение вызывающей константы больше, чем у константы е, то воз­ вращается положительное значение. Вызвав метод equa ls (), переопределяющий аналогичный метод из класса Obj ect, можно сравнить на равенство константу перечислимого типа с любым другим объектом. Несмотря на то что метод equal s ( ) позволяет сравнивать кон-
32' Часть 1. Язы к Java станту перечислимого типа с любым другим объектом , оба эти объекта будут рав­ ны только в том случае, если они ссылаются на одну и ту же константу из одного и того же перечисления. Простое совпадение порядковых значений не вынудит метод equals ( ) возвратить логическое значение true, если две константы отно­ сятся к разным перечислениям. Напомним, что две ссылки на перечисления можно сравнивать на равенство с помощью операции =. В следующем примере программы демонстрируется при­ менение методов ordinal (), compareTo () и equa ls (): 11 Продемонс трировать приме нение ме тодов ordinal () , сошраrеТо () и equals () 11 Перечисление сортов яблок enurn Apple { Joпathaп , GoldenDel , RedDel , Winesap , Cortland class EnurnDemo 4 { puЫ ic static void ma in (String args [] ) { Apple ар, ар2, арЗ; 11 получи ть все порядковые значения с помощь ю ме тода ordinal () System. out . println ("B ce константы сортов яблок " + "иих порядковые значения : ") ; for (Apple а : App le .values () ) System.out . priпtln (a +""+a.ordinal () ); ар = App le .RedDel; ар2 = Ap ple.GoldenDel; арЗ = Apple .RedDel ; System. out .println () ; 11 продемонс трировать приме нение методов сошраrеТо () и equals () if (ap.compareTo (ap2 ) < 0) System.out . println (ap + " предшествует " + ар 2) ; if (ap.compareTo (ap2 ) > 0) System.out . println (ap2 + " предшествуе т " + ар ) ; if (ap.compareTo (apЗ) == 0) Systern.out . println (ap +"равно "+арЗ) ; Systern.out .pr iпtln () ; if (ap.equals (ap 2) ) Systern.out . println ("Oшибкa !"); if (ap.equals (ap 3) ) Systern.out . println (ap + " равно "+арЗ) ; if(ap == арЗ) Systern.out . printlп {ap + "= ="+ арЗ); Ниже приведен результат, выводимый данной программой. Все константы сортов яблок и их порядко вые значения : Jonathan О
Гл ава 12. Пере ч исления, автоупаковка и аннота ц и и (метаданные) 325 Gol denDel 1 RedDe l 2 Winesap 3 Cortland 4 Golde nDel предшествует Re dDel RedDe l равно RedDe l Re dDel равно Re dDe l Re dDe l == RedDel Еще оди н прим ер перечисления Перед тем как продолжить дальше, рассмотрим еще один пример, в котором при меняется перечисление. В гл аве 9 расс матривался пример программы для ав­ томатического принятия решений. В той ее версии переменные NO, YES, МАУ ВЕ, LA TER, SOON и NEVER объявлялись в интерфейсе и использовались для представле­ ния возможных ответов. И хотя в этом нет ничего формально неверного , в дан ном случае луч ше подходит перечисление. Ниже приведена усовершенствованная вер­ сия дан ной программы, в которой для представления ответов используется пере­ числение An swers. Можете сравнить эту версию с первоначальной из гл авы 9. 11 Усов ерше нствованная версия программы прин ятия решений 11 из главы 9. В этой версии для представления возможных ответов 11 исполь зуе тся перечисление , а не переменные экземпл яра import java .ut il . Random ; 11 Перечисление возможных ответов enum Answers { NO, YES , МАУВЕ , LATER, SOON , NEVER class Que stion { Random rand = new Random( ); Answers ask() { int prob = (int ) (100 * rand . nextDouЬle () ); if (prob < 15) return An swers .МAY BE; 11 15% else if (prob < 30) return An swe rs .NO; 11 15% else if (prob < 60) return An swe rs .YES ; 11 30% else if (prob < 75) return Answe rs . LA TER; 11 15% else if (prob < 98) return An swe rs .SOON ; 11 13% else return Answers . NEVER; 11 2% class As kМe { static void answer (Answers result ) { switch ( result) { case NO : System. out . println ("Heт " ); break;
326 Часть 1. Яэык Java case YES : Systern.out . println ("Дa " ); break; case МАУВЕ : Systern. o ut . println ("B oзмoжнo" ); break; case LATER : Systern.out . println ("П oзднee ") ; break; case SOON : Systern.out . println ( "Bcкope") ; break; case NEVER : Systern.out . println ( "Hикoгдa") ; break; puЫ ic static void rnain (String args [J ) Question q = new Question (); answer (q.as k() ); answer (q.as k() ); answer (q.as k() ); answer (q.ask()); Оболочки типов Как вам должно быть уже известно, в Java для хранения основных типов дан­ ных, поддерживаемых в этом языке программирования, используются прими­ ти вные (или простые) ти пы вроде int или douЫe. Примитивные типы данных, в отличие от объектов, используются для хранения простых значений из сооб­ ражений производительности. Применение объектов для хранения таких значе­ ний приводит к нежелательным издержкам даже при простейших вычислениях. Поэтому примитивные типы данных не являются частью иерархии объектов и не наследуются от класса Obj ect. Несмотря на то что примитивные ти пы данных обеспечивают выигрыш в про­ изводительности, иногда может понадобиться объектное представление этих типов данных. Например, данные примитивного типа нельзя передать методу по ссьшке. Кроме того, многие стандартные структуры да нных, реализованные в Java, опе­ рируют объектам и, а это означает, что такие структуры данных нельзя применять для хранения примитивных типов. Для выхода из таких (и подобных им) сиrуаций в Java предоставляются оболочки типов, которые представляют собой классы, заклю­ чающие примитивный тип данных в оболочку объекта. Классы-оболочки типов под­ робнее описываются в части 11 данной книги , но поскольку они имеют непосред­ ственное отношение к автоупаковке вjava, то здесь они представлены вкратце. К оболочкам ти пов относятся классы DouЫ e, Float, Long, Integer, Short, Byte, Cha racter и Boolean, которые предоставляют обширный ряд методов, по­ зволяющих полностью интегрировать примитивные типы в иерархию объектов вjava. В последующих разделах каждый из них рассматривается вкратце.
Гл ава 12. Переч исления, а втоу п а ко вка и аннота ции (метаданные) 327 Класс Character Служит оболочкой для типа char. Конструктор Character () имеет следующую общую форму: Character (char е1 1 нsол) где параметр симв ол обозначает тот символ, который будет заключен в оболочку при создан ии объекта типа Character. Чтобы получить значение типа char, со­ держащееся в объекте типа Character, достаточно вызвать метод charValue () , как показано ниже. Этот метод возвращает инкапсулированный символ . char charValue () Кла сс Boolean (',л ужит оболочкой для логических значений типа boolean. В нем определены следующие конструкто ры: Boolean (Ьoolean лог•чесжое эначеняе) Boolean (String лоГJtчесжая_ ё!l'РОжа) В первом конструкто ре логиче ское_ зна чение должно быть равно true или fa lse. А во втором конструкторе новый объект типа Boo lean будет содержать ло­ гическое значение true, если логическа я_ строка содержит символьную строку "true " (в верхнем или нижнем регистре). В противном случае этот объект будет содержать логическое значение false. Чтобы получить логическое значение типа boolean из объекта ти па Boolean, достато чно вызвать метод booleanVa l ue (), как показано ниже. Это метод возвра­ щает значение типа boolean, эквивалентное вызывающему объекту. bool ean booleanVa lue () Оболочки числовых типов Безусловно, наиболее часто употребляемыми являются оболочки числовых ти­ пов. К ним относятся классы Byte, Short, Integer, Long, Float и DouЫe. Все обо­ лочки числовых типов наследуют абстрактный класс NumЬe r. В этом классе объ­ являются методы, возвращающие значение объекта в разных числовых форматах. Все эти методы перечислены ниже. Ьуtе ЬyteValue () douЫe douЬleValue () float floatValue () int intValue () long longValue () short shortValue () Например , метод douЬleValue () возвращает значение объекта в виде типа douЬle, метод floatVa lue () - значение объекта в виде типа float и т.д . Все эти методы реализованы каждой из оболочек числовых типов. В классах оболочек всех числовых типов определяются конструкторы, позво­ ля ющие создавать объекты из задан ного значения или строкового представления
328 Часть 1 . Язык Java этого значения. В качестве примера ниже приведены конструкторы, определен­ ные в классе Integer. Если строка не содержит числовое значение, то генериру· ется исключение типа NurnЬerFormatException. Integer (int чясло) Inteqer (Strinq строжа) В классах оболочек всех числовых типов переопределяется метод toString ( ) . Он возвращает удо бочитаемую форму значения , содержащегося в оболочке, что позволяет, например, выводить значение, передавая объект оболочки типа мето­ ду println () без дополнительного преобразования в примитивный тип. В следу· ющем примере программы показано , как пользоваться оболочкой числового типа для инкапсуляции числового значения и последующего его извлечения: // Продемонстрировать ооолочку числового типа cla ss Wrap { puЫ ic static void ma in (String args[] ) Intege r iOb = new Integer (lOO) ; int i = iOb. intValue (); System.out . println (i +" "+iOb) ; // выводит значения 100 и 100 В этой программе целое значение 100 размещается в объекте iOb класса Integer. Затем это значение получается в результате вызова метода intValue ( ) и размещается в переменной i. Процесс инкапсуля ции значения в объекте называется у паковкой. Та к, в следую­ щей строке кода из рассматриваемой здесь программы значение 100 упаковывает­ ся в объекте типа Integer: Integer iOb = new Integer (100) ; Процесс извлечения значения из оболочки типа называется распаковкой. Например, в приведенной ниже строке кода из рассматриваемой здесь програм· мы целочисленное значение распаковывается из объекта iOb. int i = iOb. intValue (); Описываемая здесь общая процедура упаковки и распаковки значений стала применяться с самой первой версии Java. Но в версию Java J2SE 5 были внесены существенные усовершенствования благодаря внедрению автоупаковки , которая рассматривается ниже. Автоупа ковка С версииJDК 5 вJava внедрены два важных средства: автоупакоика и автораспа­ ковка. Автоупаковка - это процесс , в результате которого примитивный тип авто· матически инкапсулируется (упаковы вается ) в эквивалентную ему оболочку типа вся кий раз, когда требуется объект данного типа. Благодаря этому отпадает необ­ ходимость в явном создании объекта. Автораспаковка - это процесс автоматиче· ского извлечения значения упакованного объекта (распаковки) из оболочки типа,
Гл а ва 12. Переч и сления, автоупако вка и аннота ц ии (метаданные) 329 когда нужно получить его значение. Благодаря этому отпадает необходимость вы­ зывать методы вроде intValue () или douЫ eVa lue (). Внедрение автоматической упаковки и распаковки значительно упрощает ре­ ализацию некоторых алгоритмов, исключая необходимость в ручной упаковке и распаковке значений. Это также помогает предотвратить ошибки и очень важно для обобщений, которые оперируют только объектами. И наконец, автоупаковка существенно облегчает работу с каркасом коллекций Collection Fгarnewoгk, рас­ сматриваемым в части 11. С появлением автоупаковки отпадает необходимость в ручном создании объ­ ектов для заключения примитивных типов в оболочку. Для этого достаточно при­ своить значение примитивного типа переменной ссЬVIки на объект оболочки этого типа, а сам этот объект будет создан средствами Java автоматичес ки. В качестве при­ мера ниже приведен современный способ создания объекта типа Integer и заклю­ чения в его оболочку целочисленного значения 100. Обратите внимание на то , что объект не создается явно оператором new. Это делается в Java автоматически. Intege r iOb = 100 ; // автоуп аковка значения типа int Чтобы распаковать объект, достаточно присвоить ссылку на него переменной соответствующего примитивного типа. Например, в следующей строке кода цело­ численное значение извлекается из автоматически распаковываемого объекта iOb: int i = iOb; // автораспаковка Ниже приведена версия программы из предыдущего примера, переделанная с целью продемонстрировать автоупаковку и автораспаковку. // Продемонс трировать ав тоупаковку/автораспако вку class AutoBox { puЫ ic static vo id ma in (String args[] ) Intege r iOb = 100; // автоупаковка значения типа int inti=iOb; // автораспаковка значения типа int System.out . println (i +" "+iOb) ; // выводит значения 100 и 100 Автоу паковка и м етоды Помимо простых случаев присваивания, автоупаковка происходит автомати­ чески всякий раз, когда примитивный тип должен быть преобразован в объект. Автораспако вка происходит всякий раз, когда объект должен быть преобразован в примитивный ти п. Та ким образом, автоупаковка и автораспаковка может произ­ водиться , когда аргумент передается методу или значение возвращается из мето­ да. Рассмотрим следующий пример программы: 11 Автоупаковка /автораспаковка происходит при передаче 11 параме тров и возврате значений из ме тодов class Au toBox2 // принять параметр типа Integer и возвратить
330 Часть 1. Язы к Java // значение типа int; static int m ( Integer v) { return v ; 11 автораспаковка значения типа int puЫ ic static void ma in (String args[] ) { 11 Передать значение типа int ме тоду ш() и присвоить // возвращаемое значение объекту типа Integer . // Здесь значение 100 аргумента автома тически 11 упаковывается в объе кт типа Integer . 11 Возвращаемое значение также упаковы вается 11 в объе кт типа Integer . Integer iOb ; m(100) ; System.out . println (iOb ) ; Эта программа выводит следующий результат: 100 Обратите внимание на то , что в этой программе метод m ( ) объявляется с па· раметром типа Integer и возвращает результат типа int. В теле метода ma in () ч исловое значение 100 передается методу m ().Апоскольку метод m ( ) ожидает получить объект типа Integer, то числовое значение 100 автоматически упако­ вывается. Затем метод m () возвращает эквивалент своего аргумента типа int. Это приводит к автораспаковке возвращаемого значения v типа int. Далее это зна­ чение присваивается переменной iOb типа Integer в методе ma in (), что снова приводит к автоупаковке возвращаемого значения типа int. Автоупако в ка и автораспако в ка в выражениях В общем, автоупаковка и автораспаковка производятся всякий раз , когда тре­ буется взаимное преобразование значения примитивного типа и объекта оболоч­ ки этого типа. Это, как правило, происходит в выражениях, где автоматически рас паковывается объект оболочки числового типа. Результат вычисления такого выражения снова упаковывается по мере надобности . Рассмотрим в качестве при­ мера следующую программу: // Автоупаковка /распаковка происходит в выраже ниях class AutoBoxЗ { puЫ ic static void ma in (String args [] ) { Intege r iOb, i0b2 ; int i; iOb ; 100; System.out .println ("И cxoднoe значение iOb : "+iOb ) ; // В следующем выражении автоматически распаковывается // объе кт iОЬ, вып олн яется приращение получ аемого значения, 11 которое затем уп аковывается обратно в объект iOb ++iOb; System. out . println ("Пocлe ++iOb : " + iOb) ;
Гл а ва 12. Пер ечислени я, автоупаковка и аннотац ии (метаданные) 331 // Здесь объе кт iOb распаковывается , выражение вычисляется, // а резуль тат снова упаковывается и присваивается объекту iОЬ2 iОЬ2=iОЬ+(iОЬ/3); System.out . println ("i OЬ2 после выражения : "+iОЬ2 ); // Здесь вычисляется то же само е выражение , // но резуль тат не упаковывается i=iОЬ+(iОЬ/3); System. o ut . println ("i после выражения : "+i) ; Ниже приведен результат, выводимый данной программой. исх одное значение iОЬ : 100 После ++iОЬ : 101 i0b2 после выражения : 134 i после выражения : 134 Обратите особое внимание на следующую строку кода изданной программы: ++iОЬ ; В этой строке значение в переменной iOb увеличивается на 1. А происходит это следующим образом: объект iOb распаковывается , получаемое в итоге значе­ ние увел ичивается на 1, а результат упаковывается обратно в тот же самый объект. Автоматическая распаковка позволяет также сочетать числовые объекты раз­ ных типов в одном выражении. Как только числовое значение будет распако вано, вступают в действие стандартные правила продвижения и преобразования типов данных. Например, следующая программа написана совершенно верно: class AutoBox4 { puЫ ic static void ma in (Striпg args[] ) ( Integer iОЬ = 100; DouЫe dOb = 98 .б; dОЬ=dOb+iOb; System. out . println ("dOb после выражения : "+dOb) ; Ниже приведен результат, выводимый данной программой. dOb после выражения : 198.6 Как видите , оба объекта (из переменных dOb типа DouЫe и iOb типа Integer) участвуют в сложении, а результат повторно упаковывается и сохраняется в объ­ екте dOb. Благодаря автоупаковке появляется возможность применять числовые объек­ ты ти па Integer для управления оператором switch. Рассмотрим в качестве при­ мера следующий фрагмент кода: Integer iOb 2; swi tch (iOb ) case 1: System. out . println ( "oдин") ; break;
332 Часть 1. Язык Java case 2: System. out . println ("двa" ); break; default : System. o ut . println ("o шибкa") ; Когда вычисляется оператор swi tch, объект iOb распаковывается и возвраща­ ется его значение типа int. Как показывают приведенные выше примеры про­ грамм, благодаря автоупаковке и автораспаковке применение числовых объек· тов в выражениях становится интуитивно понятным и значительно упрощается. Прежде для написания подобного кода приходилось выполнять приведение ти· пов и вызывать методы вроде intVa lue (). Автоупаковка и распаковка значени й из кла ссов Boolean и Character Как упоминалось ранее, в Java также поддерживаются оболочки для типов boo lean и char в классах Boolean и Character соответстве нно. Автоупаковка и автораспаковка применимы к оболочкам и этих типов данных. Рассмотрим в ка· честве примера следующую программу: // Автоупаковка/распаковка значений из кл ассов Вoolean и Character class Au toBoxS { puЫ ic static void ma in (String args[] ) { // Автоупако вка/распаковка логического значения типа boolean Boolean Ь true ; 11 объе кт Ь автоматически распаковывается , // когда он упо требляется в усло вном операторе if if (Ь) System. out . println ("b равно true") ; 11 Автоупако вка /распаковка значения типа char Character ch = ' х'; // упаковать значение типа char char ch2 = ch; // распако вать значение типа char System.out . println ("ch2 равно "+ch2) ; Ниже приведен результат, выводи мый данной программой. Ь равно true ch2 равно х Самое важное и достойное упоминания в этой программе - это автораспаковка объекта Ь в условном операторе if. Напомним, что в результате вычисления ус · ловного выражения , управляющего оператором if, должно возвращаться логиче· ское значение типа boo lean. Благодаря автораспаковке логическое значение типа boolean, содержащееся в объекте Ь, автоматически распаковывается при вычис· лении условного выражения. Та ки м образом, с появлением автоупаковки и распа· ковки появилась возможность применять объекты типа Boolean для управления условным оператором i f.
Гл ава 12. Переч исления, автоупаковка и аннотаци и ( метадан ные) ЭЗЭ Благодаря автоупаковке и автораспаковке объект типа Boolean можно также применять для управления операторами всех циклов в Java. Когда объект типа Boolean применяется в качестве условия в операторе цикла whi le, for или do / wh ile, он автоматически распаковывается в свой эквивалент типа boo lean. Например , следующий фрагмент кода теперь вполне допустим: Bo olean Ь; 11 ... while (Ь) { // ... Автоупаковка и а вто рас паковка п омогают п редотвратить оши б ки Помимо тех уд обств, которые предоставляют автоупаковка и распаковка , эти языковые средства могут также оказать помощь в предотвращении ошибок. Рассмотрим в качестве примера следующую программу: // Ошиб ка , порождаема я ручной распаковкой class UnboxingError { puЬl ic static void rnain (String args[] ) Integer iOb = 1000; 11 автоупаковка значения 1000 int i = iOb . byteValue () ; // ручная распаковка значения 11 как относящегося к типу byte ! ! ! Systern. out . println (i) ; // значение 1000 не выводится ! Эта программа выводит не предполагаемое значение 1000, а -24! Дело в том , что значение в оболочке объекта iOb распаковывается вручную при вызове ме­ тода byteVa l (), что приводит к усечению значения 1000, храня щегося в этом объекте. В итоге получилось неверное значение -2 4, которое было присвоено переменной i. Автораспаковка предотвращает подобные ошибки , поскольку зна­ чение из объекта iOb всегда будет автоматически распаковываться в значение, со­ вместимое с типом int. Предупреждение Благодаря внедрению автоупаковки и автораспаковки возникает соблазн при­ менять исключительно объекты оболочек типов наподобие Integer или DouЫ e, пренебрегая примитивными типами данных. Те перь можно, например, написать такой код: // Неудачное применение автоупаковки и автораспаковки ! DouЫe а, Ь, с; а 10 .0; ь 4.0; с = Math.sqrt(a*a + Ь*Ь) ; Systern.out . println ( "Гипoтeнyзa равна "+с) ;
334 Часть 1. Язы к Java В данном примере кода в объектах типа DouЫe хранятся значения, которые ис­ пользуются для вычисления гипотенузы прямоугольного треугольника. Несмотря на то что этот код формально правильный и вполне работос пособный, он все же де­ монстрирует весьма неудачное применение автоупаковки и автораспаковки. Такой код менее эффективен, чем аналогичный код, в котором применяется примитивный тип douЬ le. Дело в то м, что автоупаковка и автораспаковка влекут за собой дополни­ тельные издержки, которых можно избежать, применяя элементарные типы данных. Аннота ц ии (метаданны е) С версии JDК 5 вJava поддерживается языковое средство, позволяющее встра­ ивать справочную информацию в исходные файлы. Эта информация называется аннотацией и не меняет порядок выполнения программы. Это означает, что анно­ тация сохраняет неизменной семантику программы. Но эта информация может быть использована различными инструментальными средствами на стадии раз­ работки или развертывания прикладных программ нa Java. Например, аннотация может обрабатываться генераторами исходного кода. Для обозначения этого язы­ кового средства служит также те рмин метаданные, но более описательный терми н ан·нотация употребляется намного чаще. Осн овы аннотирования п рограмм Аннотации создаются с помощью механизма, основанного на интерфейсе. Начнем их рассмотрение с простого примера. Ниже приведено объя вление анно­ тации MyAnno. 11 Простой тип аннотации . @interface MyAnno { String str(); int val(); Прежде всего обратите внимание на знак @, предваряющий ключевое слово interface. Этим компилятору указывается, что объявлен тип аннотации. Далее обратите внимание на два метода - str () и va l ().Все аннотации состоят тол ько из объя влений методов. Но тела этих методов в них не определяются. Вместо эт о­ го они реализуются средствамиJаvа. Более того , эти методы ведут себя аналогич­ но полям, как станет ясно в дальнейшем. Объявление аннотации не может включать в себя ключевое слово extends . Но все аннотации автоматически рас ширяют интерфейс An notation. Это означает, что An no tation является суперинтерфейсом для всех аннотаций. Он объявлен в пакете j ava . lang . annotat ion. В интерфейсе An notation переопределяются методы hashCode (), equa ls () и toString ( ) , определенные в классе Ob ject. В нем также объявляется метод annotationType (), возвращающий объект типа Class, представляющий вызывающую аннотацию. Как только аннотация будет объя влена, ею можно воспользоваться для анноти­ рования любых элементов прикладного кода. До версии JDK 8 аннотации можно
Гл ава 12. Переч исления, автоупако вка и а ннотации [ метада нные) 335 бьuю использовать только в объявлениях, с чего и будет начато их подробное рас­ смотрение. А в версии JDK 8 появилась возможность аннотировать применение типов данных, как поясняется далее в этой гл аве. Но обе разновидности аннота­ ций применяются одним и тем же способом. Аннотацию можно связать с любым объявлением. Например , аннотировать можно классы, методы, поля , параметры и константы перечислимого типа. Аннотированной может быть даже сама анно­ тация. Но в любом случае аннотация предшествует остальной части объявления. Когда применяется аннотация , ее членам присваиваются соответствующие значения. В качестве примера ниже приведен вариант применения аннотации MyAnno к объявлению метода. /! Анн отиро вание ме тода @MyAnno (str = "Пример аннотации" , val = 100) puЫic static void myMe th () { // ... Эта аннотация связана с методом myMe th (). Обратите особое внимание на ее синтаксис. Сразу за именем аннотации, которому предшествует знак @, следует список инициализируе мых ее членов в скобках. Чтобы установить значение члена аннотации, достаточно присвоить его имени данного члена. Та ким образом, в дан­ ном примере строка " Приме р аннотации " присваивается члену str аннотации MyAnno . Обратите также внимание на то , что в этом присваивании нет никаких скобок после имени члена str. Когда члену аннотации присваивается значение, используется только его имя. В данном контексте члены похожи на поля . Правила удержания аннотаций Прежде чем продолжить рассмотрение аннотаций, следует обсудить пр авила удф жания аннотаций. Правила удержания определяют момент, когда аннотация отбра­ сывается . В Java определены три такие правила, инкапсулированные в перечисление java . lang . annotat ion . Re tentionPolicy. Это правила SOURCE, CLASS и RUNT IME. Аннотации по правилу уде ржания SOURCE содержатся только в исходном файле и отбрасываются при компиляции. Ан нотации по правилу уде ржания CLASS сохра­ няются в файле с расширением . class во время компиляции. Но они недоступны для виртуальной машиныJVM во время выполнения. Аннотаци и по правилу уд ер­ жания RUNT IME сохраня ются в файле с расширением .class во время компиля­ ции и остаются доступными для виртуальной машиныJVМ во время выполнения. Это означает, что правило уд ержания RUNT IME предоставляет аннотации наибо­ лее высокую степень сохраняемости. На заметку! Аннотации объявлен ий локал ьных переменных не уде ржи ваются в файле с расш ире· нием • class. Правило уд ержания аннотации задается с помощью одной из встроенных анно­ тацийJаvа: @Retent ion, общая форма которой приведена ниже. @Retention (npaJil llr дo_yд epжSIUfя) Здесь пр авило_ уд ержа ния должно быть обозначено одной из описанных ранее констант. Если для аннотации не указано никакого правила удержания, то приме­ няется правило уде ржания CLAS S.
336 Часть 1. Язы к Java В следующем примере аннотации MyAnno правило уд ержания RUNT IME устанав­ ливается с пом ощью аннотации @Retention. Это означает, что аннотация MyAn no будет доступна для виртуальной машиныJVМ во время выполнения программы. @Retention (RetentionPolicy . RUNT IME) @interface MyAnno { String str() ; int val(); Получение аннотаций во время выполнения с помощью рефлексии Аннотации предназначены в основном для использования в инструментальных средствах разработки и развертывания прикладных программ на Java. Но если они задают правило уде ржания RUNT IME, то могут быть опрошены во время вы­ полнения в любой программе нaJava с помощью рефлексии. Рефлексия - это языко­ во е средство для получения сведений о классе во время выполнения программы. Прикладной программный интерфейс (API) для рефлексии входит в состав пакета j а va . lang . re f lect. Пользоваться рефлексией можно самыми разн ыми способа­ ми , но мы, к сожалению, не имеем здесь возможности , чтобы рассмотреть их. Те м не менее обратимся к нескольким примерам применения рефлексии, имеющим отношение к аннотациям. Первый шаг с целью воспользоваться рефлексией состоит в получении объек­ та типа С 1 а s s. Этот объект представляет класс, аннотацию кото рого требуется по­ луч ить. А С 1 а s s относится к числу встроенных в J ava классов и определен в пакете j ava . lang. Более подробно он рассматривается в части П данной книги . Имеютс я разные способы получения объекта типа Class. Самый простой из них - вызвать метод ge tClass (), определенный в классе Ob ject. Его общая форма приведена ниже. Этот метод возвращает объект типа Class, который представляет вызыва­ ющий объект. final Class<?> ge tClass() На заметку! Обратите внимание на знаки <?>, следующие за именем Class в приведенном выше объя влении метода getClass ().Это обозначение имеет отношение к обобщениям в Java. Метод getClass ( ) и нескол ько других связанных с рефлексией методо в, обсуждаемых в этой гл аве, используются в обобщениях, подробнее описываемых в гл аве 14. Но для того чтобы уя снить основополагающие принципы рефлексии, разбираться в обобщениях совсем не обязател ьно. Имея в своем распоряжении объект типа Class, можно воспользоваться его методами для получения сведений о различных элементах , объявленных в классе, включая и его аннотацию. Если требуются аннотации, связанные с определенным элементом, объявленным в классе , сначала следует получить объект, представляю­ щий этот элемент. Например, класс Class предоставляет (среди прочего) методы ge tMe thod (),getField () и getConstructor (), возвращающие сведения о мето­ де , поле и конструкторе соответственно. Эти методы возвращают объекты ти па Me thod, Field и Constructor.
Гл ава 12. Перечисл ени я, автоупаковка и аннота ции (метада нные) 337 Чтобы понять этот процесс , рассмотрим в качестве примера получен ие аннота­ ций, связанн ых с методом . Для этого сначала получается объект типа Class, представ­ ляющий класс, затем вызывается метод ge tMe thod () для этого объекта с указанным именем искомого метода. У метода ge tMethod () имеется следующая общая форма: Кethod qetмethod (Strinq яюr_не!l'ода , Сlавв<?> ... !l'JШм_паране!l'роа) Имя искомого метода передается в качестве аргумента имя_ме тода. Есл и этот мето д принимает аргументы, то объекты типа Class, представляющ ие их ти пы, должн ы быть также указан ы в качестве аргумента типы_ параме тров. О братите внимание на то , что аргумент типы_ параме тров представляет с обой список ар­ гументов перемен ной дл ины. Это позволяет задать столько типов параметров , сколько требуется , в том числе и не указывать их вообще . Метод getMe thod () воз­ вращает объе кт типа Me thod, который представляет метод. Если метод не уд ается н айти , то генерируется исключение типа NoSuchMe thodException. Из объекта ти па Class, Method, Field или Constructor можно получить кон­ кретн ые аннотации, связанные с этим объектом, вызвав метод ge tAnnotation ( ) . Его общая форма приведена ниже . <А extends Arшotation> qetArшotation (Class<A> !l'JШ_aняo!l'a.wrж) Здесь параметр тип_ аннотации обозначает объект типа Class, предста вля­ ющий требующуюся аннотаци ю. Этот метод возвращает ссылку на аннотаци ю. Используя эту ссылку, можно получить значения , связанные с членами аннотации . Метод ge tAnno tation () возвращает пустое значение nul l, если аннотация не н айдена. В этом случае у искомой ан нотации отсутствует аннотация @Retent ion, устанавливающая правило удержания RUNT IME. Н иже приведен пример програм­ м ы, подытоживающий все сказанное выше. В это й программе рефлексия приме­ няется для вывода аннотации , с вязанной с конкретным методом . import java.lang . annotation .*; impo rt java.lang . re flect .*; 11 Объявление типа аннотации @Retention (RetentionPolicy . RUNT IME ) @interface MyAnno { String str (); int val (); class Meta { 11 аннотировать ме тод @MyAnno (str = "Пример аннотации", val 100) puЫic static void myMe th () { Meta оЬ = new Meta (); 11 получить аннотацию из ме тода 11 и вывести значения ее членов try { 11 сначала получить объект типа Class , 11 представл яющий данный класс Class<?> с=ob .getClass () ; 11 затем получить объект типа Мethod, 11 предста вляющий данный ме тод Method m = c.getMethod ( "myMeth");
338 Часть 1. Язык Java 11 далее получить аннотацию дл я данного кл асса MyAnno anno = m . getAnnotation ( MyAnno .class) ; 11 и наконец, вывести значения членов аннотации System.out . println (anno .st r() +""+anno .val () ); catch (NoSuchMe thodException ехс ) { System.out . println ( "Meтoд не найден .") ; puЬlic static vo id ma in (String args[]) { myMe th (); Ниже приведен резул ьтат, выводимый данной программой. Пример аннотации 100 В этой программе рефлексия применяется , как описано выше, для получения и вывода значений переменных str и va l из аннотации MyAnno, связанной с мето­ дом myMe th () из класса Me ta. Здесь следует обратить особое внимание на следую­ щее. Во-первых, это выражение MyAnno.class в следующей строке кода: MyAn no anno = m .ge tAnпotation ( MyAnno .class ); Это выражение вычисляется как объект Class, относящийся к типу MyAnno , т. е. к искомой аннотации, и называется литершwм класса. Та кое выражение мож­ но использовать всякий раз, когда требуется объект Class известного класса. Например, в следующем операторе получается объект Class для класса Me ta: Class<?> с = Me ta .class ; Безусловно , такой подход годится лишь в то м случае, если заранее известно имя класса искомого объекта, что возможно далеко не всегда. В общем, литерал класса можно получить для классов, интерфейсов, примитивных типов и масси­ вов. ( Напомним, что синтаксис <?> имеет отношение к обобщениям в Java, рас­ сматриваемым в гл аве 14.) И во-вторых, это способ получения значений, связанных с переменными str и va l, когда они выводятся в следующей строке кода: System. out . println (anno .st r() +""+anno .val () ); Обратите внимание на то , что для обращения к ним применяется синтаксис вызова методов. То т же самый подход применяется всякий раз, когда требуется получить член аннотации. Вто рой п рим ер п рим енения рефлекс ии В предыдущем примере у метода myMe th ( ) отсутствовали параметры. Иными словами, когда вызывался метод ge tMe thod ( ) , передавалось только имя myMe th искомого метода. Но для того , чтобы получить метод , у которого имеются пара­ метры, следует задать объекты класса, представляющие типы этих параметров , в виде аргументов метода ge tMe thod (). Например, ниже приведена немного из­ мененная версия программы из предыдущего примера.
Глава 12. П еречисления, автоупако вка и аннота ции (метаданные) 339 import java .lang . annotation.*; import java .lang . reflect .*; @Retention (RetentionPolicy . RUNTIME ) @interface MyAnno { String str (); int va l(); class Me ta { шуМеth () теnерь имеются два аргумента "Два параметра", val = 19) 11 У метода @MyAnno (str puЫic static { void myMeth ( String str, int i) Meta оЬ new Meta (); try Class<?> с=ob . getClass () ; /! Здесь указыв аются типы параметров Method m = c .getMethod ("myMeth" , String .class, int .class ); MyAnno anno = m. ge tAnnotation ( MyAnno .class ); System. out .println (anno .st r() +""+anno .val () ); catch (No SuchMe thodException ехс ) { System.out . println ( "Meтoд не найден .") ; puЫic static void ma in (String args [] ) { myMeth ("Tecт", 10) ; Ниже приведен результат выполнения этой верс ии программы. Два параме тра 1 9 В этой версии метод myMe th () принимает параметры типа String и int. Чтобы получить сведения об этом методе , следует вызвать метод ge tMe thod {) так, как показано ниже , где объекты Class, представляющие типы String и int, передаются в виде дополнительных аргументов. Method m = c.getMethod ("myMeth", St ring .class, int .class); Получение всех аннотаций Для того чтобы получить сразу все аннотации, имеющие аннотацию @Retent ion с установленным правилом удержания RUNT IМE и связанные с искомым элементом , достаточно вызвать метод getAnnotations {) для этого элемента. Ниже приведена общая форма метода getAnno tations (). An n otation [J getAn n otations () Метод getAnnotations () возвращает массив ан нотаций. Этот метод может быть вызван для объектов типа Class, Method, Constructor и Field.
3'0 Часть 1. Язы к Java Ниже приведен еще один пример применения рефлексии, демонстрирующий получение всех аннотаций, связанных с классом и методом. В программе из это­ го примера сначала объя вля ются две аннотации, которые затем используются для аннотирования класса и метода. 11 Пока зать все аннотации для класса и ме тода import jav a.lang . annotation .*; import java .lang . reflect .*; @Retention (RetentionPolicy . RUNT IME ) @interface MyAnno { String str() ; int val(); @Retention (RetentionPolicy . RUNT IME) @interface What { String description () ; @What (description = "Аннотация тес тового класса ") @MyAnno (str "Meta2 ", val = 99) class Me ta2 { ,@What (description = "Аннотация тестового ме тода ") @MyAnno (str = "Testing", val = 100) puЫic static void myMeth() { Meta2 оЬ = new Meta2(); try Annotation annos [] = ob . getClass () .getAnnotations () ; 11 вывести все аннотации дл я кла сса Меtа2 System.out . println ("Bce аннотации для кл асса Meta2:") ; for (Annotation а : annos ) System.out .pr intln (a) ; System.out . println (); 11 вывести все аннотации дл я ме тода шуКеth () Method m = ob . getClass ( ).getMethod ( "myMeth") ; annos = m. getAnnotations () ; System.out . println ("Bce аннотации для ме тода myMeth() :") ; for (Annotation а : annos ) System.out . println (a) ; catch (NoSuchMethodException ехс ) { System.out . println ( "Meтoд не найден .") ; puЬlic static void main (String args[] ) { myMe th (); Ниже приведен результат выполнения данной программы . Все аннотации для кл асса Me ta2 : @What (description=Aннoтaция тестового класса ) @MyAnno (str=Meta2 , val=99)
Гл ава 12. Пер е ч исления, автоупаковка и аннота ц и и (метаданные! 31.1 Все аннотации для ме тода myMe th () : @What (description=Aннoтaция тестового ме тода ) @MyAnno (str=Testing , val=lOO) В этой программе метод getAnno tations () используется для получения мас­ сива всех аннотаци й, связанных с классом Me ta2 и методом myMe th (). Как по­ яснялось ранее , метод getAnno tations () возвращает массив объектов ти па An no tation. Напомним, что Аnnо tаtiоn является супери нтерфейсом для всех ин­ терфейсов аннотаций и что в нем переопределяется метод toString () из класса Ob ject. Та к, если выводится ссылка на интерфейс Annotation, то вызывается его метод toString () для создания символьной строки , описывающей аннотаци ю, что и демонстрирует предыдущий пример. И нтepфeй cAnnotatedElement Методы ge tAnnotation () и getAnnotations (), использованные в предыду­ щем примере , определены в интерфейсе Anno tatedEleme nt, который входит в со­ став пакета j ava . lang . re flect. Этот интерфейс поддерживает рефлексию для ан­ нотации и реализуется в классах Method, Field, Constructor, Class и Package. Кроме методов getAnno tation () и ge tAnno tations (), в интерфейсе An notatedElement определяются два других метода. Первый метод, getDeclare­ d.Annotations (), имеет следующую общую форму: An n otation [] getDeclarec\An n o tations () Данн ый метод возвращает ненаследуемые аннотации, присутствующие в вызы­ вающем объекте. А второй метод , isAnnotationPresent (), имеет такую общую форму: Ьoolean isAn n otationPresent (Clas s<? extends An n otation> �я.п _ а.н н о�ацяж) Этот метод возвращает логическое значение true, если аннотация, заданная в виде аргумента тип_ аннотации, связана с вызывающим объектом. В против­ но м случае возвращается логическое значение false. В версии JDK 8 эти мето­ ды дополнены методами ge tDeclaredAnnotation (), ge tAnno tationsByType () и getDeclaredAn notationsByType (). Два последних метода предназначаются для по вторяющихся аннотаци й, которые рассматриваются в кон це этой гл авы . Исп ользование значений по умолчанию Для членов аннотации можно указать значения по умолчанию, которые будут в ыбираться при применении аннотации, если для них явно не заданы другие зна­ чения. Чтобы указать значение по умолчанию, достаточно ввести ключевое сл ово de faul t в объявлении •шена аннотации. Ниже п риведена общая форма для указа­ н ия значен ий членов аннотации по умолчанию. !!'Jl l n memЬer () default .зна чение ; Здесь зна чение и тип должны быть совместимы по типу данн ых. Ниже показано, как выглядит аннотация @MyAnno, переделанная с учетом значений по умолчанию.
3'2 Часть 1. Язык Java 11 Объявление типа аннотации , включающее значения по умолчанию @Retention (RetentionPolicy . RUNT IME) @interface MyAnno { String str () de fault "Тестирование "; int val () de fault 9000; В этом объявлении определяются значения по умолчанию "Тес'.1.'ирование " и 9000 для членов str и va l аннотации @MyAnno соответственно. Это означает, что значения ни одного из членов аннотации @MyAnno указывать необязательно, когда она применяется. Но любому из них или обоим сразу можно, если требуется, присвоить конкретное значение явным образом. Та ким образом, имеются четыре способа применения аннотации @MyAnno: @MyAnn o () // значения str и val принимаются по умолчанию @MyAnn o (str "Некоторая строка ") // значение val - по умолчанию @MyAnno (val = 100) // значение str - по умолчанию @MyAnno (str = "Тестирование ", va l = 100) // значения не по умолчанию В следующем примере программы демонстрируется использование значений членов аннотации по умолчанию: import java.lang . annotation.*; import java .lang . reflect .*; // Объявление типа аннотации , включая значения ее членов по умолчанию @Retention (RetentionPolicy . RUNTIME ) @inter face MyAnno { String str () de fault "Тестирование "; int val () de fault 9000; class Ме tаЗ { // аннотировать ме тод, используя значения по умолчанию @MyAnno () puЫ ic static void myMeth () МеtаЗ оЬ = new МеtаЗ (); // получить аннотацию для данного метода 11 и вывести значения ее членов try { Class<?> с=ob . getClass () ; Method m c.ge tMe thod ( "myMe th" ); MyAnno anno = m.ge tAnnotation ( MyAn no .class ); System.out . println ( anno .str () + " "+anno .val ()); catch (NoSuchМethodException ехс ) { System.out . println ( "Meтoд не найден ."); puЫ ic static void main (String args []) { myMeth(); Ниже приведен результат, выводимый данной программой. Тестирование 9000
Гл ава 12. Пере ч исления, автоупаковка и аннотации ( метада нные) 343 Аннотации- м аркеры Это - специальный вид аннотаций, которые не содержат членов. Единственное назначение аннотации-маркера - пометить объявление, для чего достаточно нали­ чия такого маркера, как аннотации. Лучший способ выяснить, присутствует ли в прикладном коде аннотация-маркер, - вызвать метод isAnnotationPre sent (), определенный в инте рфейсе Anno tatedEleme nt. Рассмотрим пример применения аннотаци и-маркера. У такой аннотации отсут­ ствуют члены, поэтому достаточно определить, присутствует ли она в прикладном коде: import java .lang . anno tation .*; import java .lang . reflect .*; 11 Анно тация-маркер @Retention (RetentionPol icy . RUNT IME) @interface MyMa rker { ) class Marker { 11 аннотировать ме тод с помощь ю мар кера 11 Обратите внимание на обязательность скобок () @MyMa rker puЫic static void myMeth () { Marker оЬ = new Marker(); try { Method m = ob . getClass () .getMe thod ( "myMe t h" ); 11 определить наличие аннотации if (m .isAn пotatioпPreseпt (MyMarker .class) ) System. out .printlп ( "Aннoтaция-мapкep MyMa rker присутствует .") ; catch (NoSuchMe thodException ехс ) { System.out . printlп ( "Me тoд не найден.") ; puЬlic static void ma iп (Striпg args[] ) { myMeth (); Привед енный ниже результат подтверждает наличие аннотации-маркера @MyMarker. Анно тация -маркер MyMa rker присутст вуе т. Применяя аннотацию-маркер @MyMarker, совсем не обязательно указывать скобки после ее имени. Это означает, что аннотация-маркер @MyMarker применя­ е тся просто по ее имени, как показано ниже. И хотя указан ие скобок после имени аннотации-маркера не считается ошибкой, делать это совсем не обязательно. @MyMa rker Одночле н ные аннота ции Оче видно , что одиачлеиная аииотация состоит из единственного члена. Она дей­ ствует подобно обычной аннотации, за исключением того , что допускает сокращен-
344 Часть 1. Язык Java ную фор му ука :щн и я :ш ачения 'VICHa. Когда в та кой ашюта�щи нршунтвуст тол1.ко один член, д остато'1110 задать его зна'lенис, а ко111а она нримсняетс я, указы вать имя ее 'lлена неuбяаател ыю. Но для применения этой сокращенной формы единствен­ ный член ашюта 11ии должен иметь имя va l ue. Ниже приведен пример нрограммы, де монстрирующий создание и применение одночленной а н н отании. import java . lang .annotation . * ; imp o rt java .lang . reflect .*; // Одночле нная аннотация @Retention ( Re tentionPol icy . RUNTIME) @interface MyS ingle { int val ue ( ) ; 11 эта переменная должна име�ъ имя value class Single { 11 аннотировать ме тод одночленной аннотацией @MySingle(lOO) puЬlic static void myMe th () { Single оЬ = new Single () ; try { Method m = ob . getClass () . getMethod(" myMeth" ) ; MyS ingle anno = m. getAnnotation (My Si ngle . class ); System. out .println (anno .value () ); // выводит значение 100 catch (NoSuchMe thodException ехс) ( System.out . println ( "Meтoд не найден.") ; puЫic static void main (Striпg args[ J) { myMeth(); Как и п р с;щолаг<UЮ<Ъ, эта 111ю1·рамма выводит ::1Шt'lе11ие 100. �Jдс<ъ од ночлен­ ная аннота11ия @MySingle применяется для аннотирования метода myMetr1 (),как показано н и же . Обратите внимание на то , что ука:\ы nап. операцию присваивания value = нсобязатеш.но. @MyS ingle (l00) Синтаксис о;1ночле1шых аннотаций можно ис1 юл 1,:ю ват1, и в то м случае, когда применяетс я аннота ци я с другими членами, 110 вес о<т<u1 ы 1 ыс •1лс11ы ;�олжны имст1, значения 110 умолчанию. Например, в приведен ном ниже фрагменте кода в ашюта­ цию вво;1ится донолнительный •шен xyz с пулевым :шачсписм 110 умолчанию. @interface SomeAnno { int va lue() ; int xyz () default О; Если же требуется ипющ,:ювап, :шачснис 110 умолчанию )\ЛЯ •1лс11а xyz, мож­ но 11римени·1ъ аннота цию @SomeAnno, как 1 1 о к а :1а н о ниже, просто ука за в значение члена va lue с 1 ю м о щью синтаксиса о;що•шс1шых ашюта1щй. @SomeAnno ( ВВ)
Гл а ва 12. Пер е ч исления, автоупако вка и аннота ции ( метаданные) 3'5 В этом случае член xyz по умолчанию принимает нулевое значение, а член value - значение 88. Разумеется , чтобы задать другое значение члена xyz , при­ дется инициировать оба члена аннотации явным образом , как показано ниже . Напомним, что для применения одн очленной аннотации ее член должен непре­ менно иметь имя va lue. @SomeAn no (value = 88, xyz = 99) Встроенные анно та ции В Java определено немало встроенных аннотаций. Большинство встроенных аннотаций имеют специальное назначение, но девять из них - общее назначение. Следующие четыре аннотации из этих девяти импортируются из пакета java . lang . anno tation: @Retention, @Docurnented, @Target и @In herited. А еще пять аннотаций - @Ov e r r ide, @ Depr eca t e d , @Functionallnterface, @Sa feVa rargs и @SuppressWarn ings - входят в состав пакета java.lang. Каждая из них описана ниже . На заметку! В версии JDK 8 па кет java . lang . annotation был допол нен аннотациями @RepeataЫe и @Native. Первая из них служит для поддержки повто ряющихся аннота­ ций, как поясняется далее в этой гл аве, а вторая - для аннотирования полей, доступ ных из платформенно-ориенти рова нного кода . Аннота ция @Retention Предназначена для применения только в качестве аннотации к другим аннота­ циям. Она определяет правило уде ржания, как пояснялось ранее в этой гл аве . Аннота ция @Documented Служит маркерным интерфейсом, сообщающим инструментальному средству разработки , что аннотация должна быть документирована. Она предназначена для применения только в качестве аннотации к объявлению другой аннотации. Аннота ция @Targe t Задает типы элементов, к которым можно применять аннотацию. Она пред­ назначена для применения только в качестве аннотации к другим аннотациям. Аннотация @Target принимает один аргумент, который должен быть констан­ той из перечисления Elerne ntType . Этот аргумент задает типы объявляемых элементов, к которым можно применять аннотацию. Эти константы приведе ны в табл . 12. 1 вместе с типами объявляемых элементов, к которым они относятся . В аннотации @Target можно задать одно или несколько значений этих кон­ стант. Чтобы задать несколько значений, их следует указать списком, заключив в фигурные скобки . Например, чтобы указать, что аннотация применяется только к поля м и локальн ым переменным, достаточно определить следующую аннотацию @Target: @Target ( { ElementType .FIELD, ElementType . LOCAL_VAR IABLE } )
3'6 Часть 1. Язы к Java Та бл ица 12.1. Конста нты из переч исл ения ElementType Целевая константа AN N OTAT ION ТУРЕ CONSTRUCTOR FIELD LOCAL VARIAВLE МЕТНОD РАСКАGЕ PARAМETER ТУРЕ ТУРЕ PARAМETER ТУРЕ USE Объявляемый элемент, к которому можно применять аннотацию Другая а н нотация Конст руктор Поле Локал ы1ая 11сремс1111ая Метод Пакет Параметр Класс, интерфейс ил и 11срсчисл е11ис Параметр типа (добавлено в вс 1ки11 J I>К Н) Использован ие типа (добавлено в всрсииjl>К Н) В отсутствие обозначения @Target а11нота11и ю можно приме11я1ъ к любому объя вл яемому элементу, за ИСJ()IЮчением параметров тшюв. Именно ноэтому зача­ стую луч ше ука:Jьшать целевые константы явным обра:юм, чтобы ясно обозначить на:шачение аннотаци и. Аннота ция @ Inherited Это - ашюта1111я-маркер, которую можно 11рименять только в другом объявлении аннотации. Более того , она оказывает во:щсiiствнс толы<о на те аннотаци и, которые будут применяться в объявлениях классов. Аннота ция @ I пheri ted обусловливает на­ следование аннота ции из суперкласса в 110;1классе. Так, есл и кон кретная ашю·г,щ ия за праши вается в 1ю;1ю�ассе , то в отсутствие :�той аннота ции в нодклассе нроверяется ее присутствие в сунерклассе. Есл и занрашивае:v�ая ашюта1 1ия нрисугствуст в супер­ классе и ашютирована ка к @Inher i ted, то она бу11ет во:шращена по :J<tпpocy. Аннота ция @Override Это - ан11ота1 1ия-маркер, которую можно нримешпъ тол ько в мспщах. Метод, анноти рованный юш @Ove r r ide, /\олжсн переопределять метод 11 :1 суперкласса. Есл и он этого не с11еласт, во время ком1шляции 1юз1шю1ст ошибка . Эта ашюта�1ия служ ит дл я гарантии то1·0 , что мето11 иа суперкласса бу;1ет действител ыю перео­ пределен, а не просто перегружен. Аннота ция @ Dерrесаtеd Эта аннотаци я-маркер обоаначает, что объя вление устарело и должно быть за­ менено более новой формой. Aннoтa ция @ Functional interface Эта аннотация-маркер внедрена в верс ии JI>K Н и нрс;ща:шачена дл я при­ менения в инте рфейсах. Она обо:шачает, что аннотируемый интерфейс явля-
Гл ава 12. Переч исления, автоупаковка и аннота ции (метада нные) 31.7 ется функцШJналън'ым, т. е . содержит один и только один абстрактный метод. Фун кциональные инте рфейсы применяются в лямбда-выражениях (подробнее о тех и других речь пойдет в гл аве 15). Если же аннотируе мый интерфейс не яв­ ляется функциональным, то во время компиляции возникает ошибка. Следует, однако, иметь в виду, что для создания функционального интерфейса аннотация @Functionalinterface не требуется. Следовател ьно, эта аннотация носит ис­ ключительно информативный характер. Аннота ция @Saf'eVararqs Это - аннотация-маркер, применяемая в методах и конструкторах. Она указы­ вает на отсутствие каких-нибудь небезопасных действий, связанных с параметром переменной длины. Эта аннотация служит для подавления непроверяемых преду­ преждений, возникающих в коде, который в остальном является безопасным, в свя­ зи с применением неовеществляемых типов аргументов переменной длины и полу­ чением экземпляра параметризированного массива. (Неовеществляемый тип - это , по существу, обобщенный тип, как поясняется в главе 14, посвященной обобщени­ ям.) Эту аннотацию следует применять только к методам или конструкторам с пере­ менным количеством аргументов, объявляемым как static или final. Aн нoтaци я@Suppre s sWarninqs Эта аннотация обозначает, что следует подавить одно или несколько преду­ прежде ний, которые могут быть выданы компилятором. Подавляемые предупреж­ дения указываются по имени в строковой форме. Ти п овые аннота ц ии В версии JDK 8 рас ширены места, в которых могут применяться аннотаци и. Раньше аннотации допускались только в объявлениях, как бьшо показано в пре­ дыдущих примерах. Но с выпуском версии JDK 8 появилась возможность указы­ вать аннотации везде , где применяются типы данных. Такая расширенная возмож­ ность применения аннотаций называется типовой аннотацией. Например, анно­ тировать можно ти п, возвращаемый методо м, тип объекта по ссылке this в теле метода , приведение типов, уровни доступа к массиву, наследуемый класс , опера­ тор throws , а также обобщен ные типы, включая границы параметров и аргумен­ ты обобщенного типа (более подробно обобщения рассматриваются в гл аве 14). Типовые аннотации важны потому, что они позволяют выпол нять дополни­ тельные проверки прикладного кода различными инструментальными средства­ ми на стадии разработки , чтобы предотвратить ошибки. Следует иметь в виду, что эти проверки , как правило, не проиэводятся ком пилятором по команде javac. Для этой цел и служит отдел ьное инструментальное средство , хотя оно и могло бы действовать в качестве модуля, подключаемого к компилятору. В типовую ко мпиляцию должна быть включена целевая константа ElementType . ТУРЕ_USE. (Как пояснялось ранее, достоверные целевые константы аннотаций указываются с помощью аннотации @Targe t.) Ти повая аннотация п р и-
348 Часть 1. Язы к Java меняется к тому типу данных, которому она предшествует. Так, если обозначить типовую аннотацию как @TypeAnno , то приведенная ниже строка кода считается вполне допустимой. В этой строке кода аннотация @TypeAnno аннотирует исклю­ чение типа Nu llPointerException в операторе throws: void myMe th () throws @TypeAnno NullPointe rException { // ... Аннотировать можно также тип объекта по ссылке this (так называемого п олу­ 'Чателя) . Как вам должно быть уже известно, ссьика this является неявным аргу­ ментом во всех методах экземпляра и делается на вызывающий объект. Для анно­ тирования такого типа данных требуется еще одна новая возможность, появившая­ ся в версииJDK 8. Те перь ссьику thi s можно явным образом объявлять в качестве первого параметра метода. В этом объявлении тип объекта по ссьике this должен соответствовать типу его класса, как показано в приведенном ниже примере. class SomeClass { int myMeth { Some C lass this, int i, int j) { // ... В дан ном примере типом объекта по ссьике this является класс SomeClass, поскольку метод myMe th () определен в этом классе. С помощью такого объявле­ ния теперь можно аннотировать тип объекта по ссьике this. Та к, если снова обо­ значить типовую аннотацию как @TypeAnno, то приведенная ниже строка кода считается вполне допусти мой. int myMeth (@TypeAnno Some C lass this, int i, int j) { // ... Однако если объект по ссылке this не анноти руется, то объявлять его совсем не обязательно. Ведь если объект по ссьике thi s не объявляется , он все равно передается неявным образом. И это положение в версии JDK 8 не изменилось. Кроме того , явное объявление объекта по ссьике this никоим образом не меняет сигнатуру метода , поскольку такое объявление все равно делается неявно по умол­ чанию. Опять же объект следует объявлять по ссылке this лишь в том случае, если к нему требуется применить аннотаци ю. И в этом случае ссьика this даitжна быть указана в качестве первого параметра метода. В приведенном ниже примере программы демонстрируется ряд мест, где мож­ но применять типовую аннотацию. В этой программе определяется ряд аннота­ ций, среди которых имеются типовые аннотации. Имена и целевые константы ан нотаций приведены в табл . 12.2 . Табл ица 12.2. Имена и целевые ко н ста нты аннота ций Аннотация @ТypeAn n o @NaxLen @NotZeroLen @Unique @What @EDlptyOK @Recoll ll l8 nded Целевая ко нста нта EleшentТype .TYPE _ USE ElementТype .TYPE _ USE EleшentТype .TYPE _ USE EleшentТype .TYPE _ USE ElementТype .TYPE _ PARAМEТER EleшentТype .FIELD ElementТype . МETBOD
Гл ава 12. Пер еч исления, автоуп аковка и аннота ции (метаданные) 3'9 Следует заметить, что аннотации @Emp tyOK, @Recomme nded и @What не явля­ ются типовыми. Они включен ы в табл. 12.2 лишь для целей сравнения. Особый интерес представляет аннотация @Wha t, которая применяется для аннотирования объявля емого параметра типа и является еще одним новым средством аннотиро­ вания, внедренным в версии JDK 8. Примене ние каждой аннотации поясняется в ко мментариях к приведенному ниже примеру программы. 11 Продемон стрировать приме нение несколь ких типовых аннотаций import java .lang . annotation .*; impo rt java .lang .reflect . *; 11 Ан нотация -мар кер , которую можно применить к типу данных @Target ( Eleme ntType .TYPE USE ) @inte rface ТуреАпnо { } - 11 Еще одна а н нотация-маркер, которую можн о приме ни ть к типу данных @Target ( ElementType . TYPE USE) @interface Not ZeroLen { - } 1 1 И еще одна аннотация -маркер , которую можно приме нить к типу данных @Target ( ElementType .TYPE USE) @interface Unique { } - 1 1 Параме тризированная аннотация, которую можно приме ни ть к типу данных @Target ( ElementType .TYPE USE) @inte rface MaxLen { - int value (); 11 Аннотаци я, которую можн о применить к параме тру типа @Target (Eleme ntType .TYPE PARAМETER) @interface What { - String de scription () ; 11 Ан нотация, ко торую можно применить в объявлении поля @Target ( ElementType .FIELD) @inter face EmptyOK { } 11 Ан нотация, которую можно приме нить в объя влении ме тода @Target ( ElementType . METHOD ) @inter face Re commended { } 11 приме нить аннотацию в параме тре типа class TypeAnnoDemo<@What(description = "Данные обобще нного типа" ) Т> { 1 1 применить типовую аннотацию в конструкторе puЫ ic @Unique TypeAnnoDemo () {} 11 аннотировать тип (в данном случае - Strinq) , но не поле @TypeAnno String str; 11 аннотировать тест поля @Emp tyOK String test; 1 1 применить типовую аннотацию для аннотиров ания 1 1 ссылки this на объе кт (получатель ) puЫic int f(@ Typ eAnno TypeAnnoDemo<T> this, int х) { return 10;
350 Часть 1. Язык Java 11 аннотировать возвращаемый тип puЫ ic @TypeAnno Integer f2 (int j, int k) ( return j+k; 11 аннотировать объявление ме тода puЫic @Recoпunended Integer fЗ ( String str) ( return str . length () / 2; 11 приме нить типовую аннотацию в операторе throwв puЬlic void f4 () throwз @TypeAnno Null PointerException 11 ... 11 аннотировать уровни доступа к ма ссиву String @MaxLen (lO) [] @Not ZeroLen [] w; 11 аннотировать тип элемента массива @TypeAnno Integer [] vec ; puЫ ic static void myMeth (int i) 11 применить типовую аннотацию в аргуме нте типа TypeAnnoDemo<@TypeAnno Integer> оЬ = new TypeAnnoDemo<@TypeAnno Integer> (); 11 применить типо вую аннотацию в операторе new @Unique TypeAnnoDemo<Integer> оЬ2 = new @Unique TypeAnnoDemo<Integer>() ; Ob ject х = new Integer (lO) ; Intege r у; 11 применить типо вую аннотацию в приведении типов у = (@TypeAnno Integer) х; puЫ ic static void ma in ( String args (] ) ( myMe th (lO) ; // применить типовую аннотацию в выражении наследования class SomeClass extends @TypeAnno TypeAnnoDemo<Boolean> ( } Большинство аннотац ий в приведенном вы ше примере программы самооче­ видны, тем не менее некоторые из них требуют дополнительных разъяснений. Это относится прежде всего к сравнению аннотации типа, возвращаемого мето­ до м , с аннотацией объя вления метода . О братите особое в ниман ие н а об ъя вление следующих двух методов в рассматриваемом здесь примере программы: 11 аннотировать возвращаемый тип puЫ ic @TypeAnno Integer f2 (int j, int kJ ( return j+k; // аннотиро вать объявление ме тода
Гл ава 12. Пер е ч исления, автоупако вка и аннотации (метаданные) 351 puЫic @Recommended Integer fЗ (String str ) { return str . length () / 2; Как видите , в обоих случаях аннотация предшествует типу, возвращаемому ме­ тодом (в данном случае - Integer). Но в обоих случаях аннотируются два разных элемента. В первом случае аннота ция @TypeAnno аннотирует тип, возвращаемый методом f2 (), поскольку в ней указана целевая константа Elerne ntType . ТУРЕ_ USE, а это означает, что она служит аннотацией к использован ию типов. А во втором случае аннотация @Recornrne nded аннотирует объявление самого метода, поскольку в ней указана целевая константа ElernentType . ME THOD. В итоге аннота­ ция @Recornrne nded применяется к объявлению метода , а не к типу, возвращаемому этим методом. Та ким образом, указание целевой константы позволяет устранить кажущуюся неоднозначность в толковании аннотации объявления метода и анно­ тации типа, возвращаемого этим методом. В отношении аннотирования типа, возвращаемого методом, следует также за­ метить, что аннотировать возвращаемый тип vo id нельзя . Не меньший инте рес вызывают и аннотации полей, как показано ниже. 11 аннотировать тип (в данном случае - Strinq) , но не поле @TypeAnno String str; 11 аннотировать тест пол я @EmptyOK String test; В дан ном случае аннотация @TypeAnno аннотирует тип String, тогда как ан­ нотация @Еmрt уОК -тест поля . Несмотря на то что обе аннотации предшествуют все му объявлению, у них разные целевые константы , указываемые в зависимо­ сти от типа целевого элемента. Та к, если в аннотации указана целевая константа El ernentType . TYPE_USE, то аннотируется тип. А если в ней указана целевая кон­ станта Elerne ntType_FIELD, то аннотируется поле. Следовательно, рассматрива­ емый здесь случай похож на описанный выше случай аннотирования метода те м, что в нем отсутствует неоднозначность. То т же самый механизм позволяет устра­ н ить неоднозначность и в аннотациях локальных переменных. Обратите далее внимание на аннотирование объе кта по ссылке this (полу­ чателя ): puЫ ic int f(@TypeAnno TypeAnnoDemo<T> this , int х) ( В данном случае ссьшка this указывается в качестве первого параметра и относит­ ся к типу TypeAnnoDerno , т.е . к классу, членом которого является метод f ( ) . Как пояс­ нялось ранее, начиная с версии JDК 8 ссылку this можно явно указывать в качестве пар аметра в объявлении метода экземпляра ради применения типовой аннотации. И наконец, рассмотрим аннотирован ие уровней доступа к методу в следующей строке кода: String @MaxLen ( 10) [] @Not ZeroLen [] w; В этом объявлении аннотация @MaxLen аннотирует тип первого уровня доступа к методу, тогда как аннотация @NotZeroLen - тип второго уровня. А в приведе н­ ном ниже объя влении аннотируется элемент массива типа Integer. @TypeAnno Integer [J vec;
352 Часть 1. Язык Java П овторяющ иеся аннота ц ии Еще одна новая возможность, появившаяся в версии JDK 8, позволяет повто ­ рять аннотации в одном и том же элементе. Та кие аннота ции называются повmfГ ряющимися. Для того чтобы сделать аннотацию повторяющейся , ее следует снаб­ дить аннотацией @RepeataЬle, определенной в пакете java.lang . anno tation . В ее поле va lue указывается тип коптейперадля повторяющейся аннотации. Такой контейнер указывается в виде аннотации, для которой поле val ue является масси­ вом типа повторяющейся аннотаци и. Следовательно, чтобы сделать аннотацию повторяющейся, прежде нужно создать контейнерную аннотацию, а затем указать ее тип в качестве аргумента аннотации @RepeataЫe. Для доступа к повторяющимся аннотациями с помощью такого метода, как, на­ пример, ge tAnno tation ( ) , следует воспользоваться контейнерной , а не самой повторя ющейся аннотацией. Именно такой подход и демонстрируется в приве­ денном ниже примере программы. В этой программе представленная ранее ве р­ сия аннотации MyAnno преобразуется в повторяющуюся аннотацию, а затем де­ монстрируется ее применение. 11 Продемонстрировать примене ние повторяющейся аннотации import java . lang . annotation .*; import java .lang . reflect .*; 11 сделать аннотацию МyAn n o повторяющейся @Retention (RetentionPolicy . RUNT IME ) @RepeataЬle(M yRepeatedAnnos . class ) @interface MyAnno ( String str() de fault "Тестирование "; int val () default 9000 ; 11 Это контейнерная аннотация @Retention (RetentionPolicy . RUNT IME) @interface MyRepeatedAnnos ( MyAnno [) va lue () ; class Rep eatAnno ( 11 повторить аннотацию МyAn n o в методе myМeth() @MyAnno (str = "Первая аннотация" , val = -1) @MyAnno (str = "Вторая аннотация" , val = 100) puЫic static void myMeth ( String str, int i) RepeatAnno оЬ = new RepeatAnno () ; try { Clas s<?> с= ob . getClass () ; 11 получить аннотации для ме тода шуМеth () Me thod m = c.ge tMe thod ( "myMe th" , String .class, int .class ); 11 вывести повторяющиеся аннотации МyAn n o An notation anno = m. getAnn otation (MyRepeatedAnnos .class ); System.out .println (anno) ;
Гл ава 12. Переч и сл ения, а втоупаковка и аннота ц ии (метада ннь1 е ) 353 catch (NoSuchMe thodException ехс ) { System. o ut . println ( "Meтoд не найден ."); puЫ ic static void ma in (String args[]) { my Meth ("тecт" , 10) ; Ниже приведен результат, выводимый данной программой. @MyRep eatec!Annos (value= [ @MyAnno (st r=Пepвaя аннотация, val=-1) , @MyAnno (str=Bтopaя аннотация, val= lOO) ]) Как пояснялось ранее , чтобы сделать аннотацию MyAn no повторяющейся , ее нужно снабдить аннотацией @RepeatаЫе, указывающей ее контейнерную аннота­ ци ю, которая называется MyRepeatedAnnos. В данной программе для доступа к по­ вторя ющимся аннотациям вызывается метод ge tAnnotation (), которому переда­ ется класс контейнерной аннотации, а не самой повторяющейся аннотации:Как следует из результата выполнения данной программы, повторя ющиеся аннотации разделяются запятой. Они не возвращаются п о отдельности. Еще один способ получить повторя ющиеся аннотации состоит в том, чтобы воспользоваться одним из новых методов, оперирующих непосредственно по­ вторя ющейся аннотацией и внедренных в интерфейс An notatedEleme nt в вер­ сииJDК 8. Это методы ge tAnno tat ionsByType () и getDeclaredAnno tationsBy Туре ( ) . Ниже приведена общая форма первого из этих методов. <Т extends An n otation> Т[ ] qetAnnotationsВy'l'ype ( Class<T> �•п_ан н о�ацяи) Этот метод возвращает массив аннотаций, имеющих тип_ аннотации и связан­ н ых с вызывающим объектом. Если же аннотации отсутствуют, этот массив будет иметь нулевую длину. Ниже приведен пример применения метода ge tAnnota t i onsByType ( ) для получения повторя ющихся аннотаций MyAnn o, представлен­ н ых в предыдущем примере программы. Annotation [J annos = m. ge tAnno tationsByType ( MyAnno .class) ; for (Annot ation а : annos ) System. o ut . println (a) ; В данном примере ти п повторяющейся аннотации MyAnno передается методу ge tAnno tationsByType (). Возвращаемый в итоге массив содержит все экземпля­ ры аннотации MyAnno , связанные с методом myMe th () (в данном примере их два) . Каждая повторяющаяся аннотация доступна в массиве по индексу. В данном случае каждая аннотация MyAn no выводится при выполнении цикла в стиле for each. Некото рые о гра ничения н а а ннотации Существует ряд ограничений, накладываемых на объявл ения аннотаций. Во­ первых, одна аннотация не может наследовать другую. Во-вторых, все методы, объявленные в аннотации, должны быть без параметров. Кроме то го, они должны воз вращать один из перечисленных ниже ти пов:
354 • • • • • Часть 1. Язы к Java примитивный тип наподобие int или douЫe; объект класса String или Class; перечислимый тип; тип другой аннотации; массив одного из предыдущих ти пов. Аннотаци и не могут быть обобщенными. Иными словами, они не могут прини· мать параметры типа. (Обобщения рассматриваются в гл аве 14.) И нако нец, при объявлении методов в аннотациях нельзя указывать оператор throws.
13 Ввод-вывод, а плеты и прочие вопросы Эта гл ава посвящена двум наиболее важным пакетам вjava: io и applet. Пакет io поддерживает базовую систему ввода-вывода в Java, вкл ючая файловый ввод­ вывод, а пакет applet - аплеты . Поддержка ввода-вывода и аплетов осуществля­ ется библиотеками базового прикладного программного интерфейса (API), а не ключевыми словами языка jаvа. По этой причине углубленное обсуждение этих вопрос ов приведено в части 11 данной книги, где рассматриваются классы при­ кладного программного интерфейсаjаvа API. А в этой гл аве представлены основы этих двух подсистем с целью показать, каким образом они интегрированы в язык Java и вписываются в общий контекст программирования на нем и его исполня­ ющую среду. В гл аве рассматриваются также оператор try с ресурсами и недав­ но внедренные ключевые слова Jаvа: transient, volatile, instanceof, nati ve , strictfp и assert. И в завершение описаны статический импорт и особенности применения ключевого слова this, а также дано введение в компактные профи­ ли, внедренные в версииJDК 8. Основы ввода-вывода Читая предыдущие 12 гл ав этой книги , вы, вероятно, обратили внимание на то , что в приведенных до сих пор примерах программ было задействовано не так много операций ввода-вывода. По существу, никаких методов ввода-вывода, кроме print () и println (), в этих примерах не применялось. Причина этого проста: большинство реал ьных прикладных программ на Java не являются текстовыми консольными программами, а содержат графический пользовательский интеrr фейс (ГПИ) , построенный на основе библиотек АWТ, Swing или jаvаFХ для взаи­ модействия с пользователем , ил и же они являются ве6-приложениями. Те кстовые консольные программы отлично подходят в качестве учебных примеров, но они имеют весьма незначительное практическое применение. К тому же поддержка консольного ввода-вывода в Java ограничена и не очень уд обна в употреблении - даже в простейших программах. Та ким образом, текстовый консольный ввод­ вывод не имеет большого практического значения для программирования нajava. Несмотря на все сказанное выше, в J ava предоставляется сильная и универсаль­ ная поддержка файлового и сетевого ввода-вывода. Система ввода-вывода в Java целостна и последовательна. Если усвоить ее основы , то овладеть всем остальным
356 Часть 1. Язы к Java будет очсш, 1 1 росто . Здесь дается лиш ь общий об:юр вво11а-вьшода, а нодробпое его он исанис нриводится в гл авах 20 и 21. Пото ки ввода- вывода В про гра ммах нa java создаются потоки ввода-вывода. Потак ввода-вывода - это абстракция, кото рая поставл яет ил и потребл яет информацию. Поток ввода-выво­ да связан с физическим устройством чере:� с исте му ввода-вывода в Java. Вес потоки в вода-вы вода ведут себя один аково , нес мотря на отл и ч ия в конкретных фи:шческих устройствах, с которыми они связаны. Таким образом, 0;1ни и те же классы и ме­ тод ы ввода-вывода применимы к ра:�ноти пным устрой ства м . Это озпачае-1; что аб­ стракция 11отока ввода может охватывать ра:ш ыс ти н ы шюда : из файла на /\иске, клавиатур ы или сетевого соединения. Аналогично ноток вывода может обращаться к консоли, файлу на диске или сетевому соединен ию. Потоки ввода-вывода нрсдо­ ста вляют ясный способ организации ввода-вывода, и :3бавюш от необход имости раз­ бираться в отп ичиях, например, кл ави<нуры от сети . В я:1ыкс . Java потоки ввода-вы­ вода реал и зуются в пределах иерархии клжтов, 011 рсдсле11 11ых в шtкетс j а v а.io. На заметку! Помимо потокового ввода -вывода , оп ределенного в пакете j ava . io, в Java предо­ ста вл яется та кже буферный и канал ьный ввод- вывод, оп ределенный в пакете j ava . nio и его подчиненных пакетах. Эти разновидности ввода-вывода рассматриваются в гл аве 21. Потоки ввода-вывода байтов и символов B.Java определяются два вида потоков ввода-вы вода: байтов и си мволов . Потоки ввода-в ывода ба йтов предоставля ют у11о б 11 ы с срс11ства для унравлсния вводо м и вы­ водо м отдел ьных байтов. Эти потоки исполь:Jуются , н а п ример , при чтении и за­ писи двоичных да нных. Потоки (l(Юда-в ыаода ги.мл олов предоставляют удобн ые средства уп равления вводом и выводо м отдел ьных символ ов. С этой цел ью в них нриме нястся коди1ювю1. в К) никодс , дону<,кающая интс рн;щионал и:Jацию. Кроме того, потоки ввода-в ывода символов ока:Jы ваются но рой более эффективными, чем потоки ввода-вывода байтов. В пер воначальной версииjаvа 1.0 пото ки ввода-вывода символов отсутствова· ли, и поэтому весь ввод-вывод и мел байтовую орrани:ыцию. Потоки ввода-выво­ да символов были внедрены в всрсии .Jа\'а 1.1, сдела в нс рекомендованным к упо­ треблению некоторые кл ассы и методы, ноддсрживавшис ввод-вы вод с б ай товой органи:шцией . Ус таревший код, в котором нс применя ются потоки ввода-вывода байтов, встречается вес реже, но иногда он все еще нримснястся . Как правило, устаревший код н рих од и тс я обновлять, если это во :J можно , чтобы воспользовать­ ся преи муществами пото ков ввода-вы вода символов. Слt.·дуст также иметь в виду. что на самом ниаком уровне весь ввод-вывод по­ прежнему имеет байтовую о р га низацию. Л потоки ввода- вывода с и м вол ов л и шь предоставляют удобные и эффе ктивные средства для обращения с символами. В последующих разделах дастся краткий обзор потоков ввода-вывода с баi1товой и символьной органи:Jацией.
Глава 13. Ввод-вывод, аплеты и прочие вопросы 357 Классы потоков ввода-вывода байтов Потоки ввода-вывода байтов определены в двух иерархиях классов. На верши­ не этих иерархий находятся абстрактные классы Input Stream и OutputStream. У каждого из этих абстрактных классов имеется несколько ко нкретных подклас­ сов , в которых учитываются отличия разных устройств , в числе файлов на диске , сетевых соединений и даже буферов памяти. Классы потоков ввода-вывода байтов из пакета j ava . io перечислены в табл . 13.1. Одни из этих классов описываются н иже, а другие - в части П да нной книги. Не следует, однако , забывать о необхо­ димости импортировать пакет j ava . io, чтобы воспользоваться классами потоков ввода-в ывода. Та бл ица 13.1. Классы пото ко в ввода-вывода байтов из пакета java . io класс пото ка ввода-вывода BufferedinputStream ВUfferedOutputS tream ВyteArrayinputS tream ВyteArrayOutputStream DatainputStream DataOutputStream FileinputStream FileOutputS tream Fil ter inputStream Fil terOutpu tStream Inpu tstreaш OЬjectinputStream OЬjectOutputStream OutputStream Pipedinpu tStream PipedOutputS tream PrintStreaш PushЬackinputStream Sequence inputStream Назначение Буферизированный поток ввода Буфе ризированный поток вы вода Поток ввода, читающий байты из массива Поток вывода, за писывающий байты в массив П оток ввода, содержащий методы для чтения да нных стандартных типов, определенных вjava Поток вывода, содержащий методы для записи данных стандартных типов, определенных вjava П оток ввода, читающий дан ные из файла П оток вывода, записывающий данные в файл Реализует абстрактный класс InputStream Реализует абстрактный класс OutputStream Абстрактны й класс, описывающий поток ввода Поток ввода объектов Поток вывода объектов Абстрактный класс, описывающий поток вывода Канал ввода Канал вывода Поток вывода, содержащий методы print () иprintln () П оток ввода, поддержи вающий возврат одного байта об­ ратно в поток ввода П оток ввода, состоящий из двух и более потоков ввода, данные из которых читаются по очереди В абстрактных классах Inp utStream и Ou tputStream определяется ряд клю­ чевых методов, реализуемых в других классах потоков ввода-вывода. Наиболее важными среди них являются методы read () и wr i te () , читающие и зап исыва-
358 Часть 1. Я зык Java ющие байты данных соответственно. Оба эти метода объявлены как абстрактные в классах InputStream и OutputStream, а в производных классах они переопре· деляются. Классы пото ков ввода-вывода сим вол ов Потоки ввода-вывода символов также определены в двух иерархиях классов. На вершине этих иерархий находятся два абстрактных класса - Re ader и Wr i ter. Эти абстрактные классы управляют потоками символов в Юникоде. Для каждого из них в Java предусмотрен ряд конкретных подклассов. Классы потоков ввода-вы · вода символов перечислены в табл. 13.2 . Табл ица 13.2. Классы потоков ввода-вы в ода с имв олов из пакета java .io Класс потока ввода­ вывода BufferedReader Buffered1friter CharArrayReader CharArrayWriter FileReader File1friter FilterReader FilterWriter InputS trea.Reader LineNwlЬerReader OutputStreaJIWriter PipedReader Pipedlfriter PrintWriter PushЬackReader Reader StringReader StringW'riter Writer Назначение Буферизированный поток ввода символов Буферизированный поток вывода символов Поток ввода, читающий символы из массива Поток вывода , записывающий символы в массив Поток ввода, читающий символы из файла Поток вывода, записывающий символы в файл Фильтрованный поток чтения Фильтрованный поток записи Поток ввода, преобразующий байты в символы Поток ввода, подсчитывающий строки Поток вывода, преобразующий символы в байты Канал ввода Канал вывода Поток вывода, содержащий методы print () и println () Поток ввода, позволяющий возвращать символы обратно в поток ввода Абстрактный класс, описывающий поток ввода символов Поток ввода, читающий символы из строки Поток вывода, записывающий символы в строку Абстрактн ый класс, описывающий поток вывода символов В абстрактных классах Reade r и Wr iter определяется ряд ключевых методов, реализуемых в других классах потоков ввода-вывода. Наиболее важными среди них являются методы read () и wr ite (), читающие и записывающие байты дан· пых соответственно. Оба эти метода объявлены как абстрактные в классах Reader и Wr iter, а в производных классах они переопределяются.
Глава 13. Ввод-вывод, аплеты и прочие вопросы 359 Предопределенные потоки ввода-вывода Как вам долж но бьпъ уже и:шестно, все программы на Java автоматически им­ порти руют шt кет j ava . lang. В этом 11акете определен класс System, инкансул и­ рующи й некоторые свойства исполняющей среды Java. Испол ьзуя некоторые из его методо в, можно, например, получ ит�, те кущее время и настройки различных параметров, связанных с системой . Класс System содержит также три перемен­ ные предо пределенных потоков ввода-в ывода: in, out и err. Эти переменные объ­ я вле ны в кл ассе System как puЫ ic, static и final. Это означает, что они могут быть испол ь:юван ы в любой другой части прикладной программы беа обращения к конкретному объекту класса S у s t em. Переменная System . out пъ�лается на стандартный поток вывода . По умолча­ нию это ко нсол1" Переменная System. in сс ылается на стандартн ый пото к ввода , которым 110 умолчанию явш1ето1 кл авиату ра. А переменная System.err ссыл ает ­ ся на стандартн ый поток вывода ошибок, которым по умолчанию также является консол ь. Но эти станда ртн ые потоки могут быть перенанравж�ны на любое совме­ стимое устройство ввода-вывода. Переменная System. in содержит объект тина InputStream, а переменные System.out и System.err соде рж ат объекты типа PrintStream. Это потоки ввода-вывода байтов, хотя они , как нравило, испол ьзуются для чтения символов с консоли и записи символов на консоль. Ка к будет показано далее, их можно, если требуется , закл ючить в оболочки потоков ввода-вывода символов. В примерах, 11риведенных в нредьщущих гл авах , использовался стандартный поток вы вода System.out.. Ашuюгичным образом можно воспол1,зо 1ш·1ъся и стан­ дартным потоком вывода System. err . Как поясняется в следующем разделе, поЛ I,­ зоваты:я потоком System. in немного сложнее. Чтение данных, вводимых с консол и Органи:ю ва ть вво;( /(аtшых с консол и в версии]аvа 1.0 можно было только с по­ мощью 1ютока ввода байтов. Та кое но-прежнему возможно и теперь, но для ком­ мерческого примене ния чтение да нных, вводимых с ко нсоли, предпочтител ьнее органи:ювать с 1юмощью потока ввода символов. Это значител ьно упрощает воа· мож 1юсти инте рнациопалиаации и сопро вождения разрабатываемых про грамм. В Java да нные, вводимые с консоли, читаются иа стандартного потока ввода System . in. Чтобы нолуч ить поток ввода символов, присоеди нив его к консоли, следует эаключить стандартн ый поток ввода S ystem . in в оболочку объе кта кл асса Bu fferedReader, 11011.де рживающего буферизованный поток ввода. Ниже приве­ ден чаще всего и п10л к1 уе м ы й 1<онп·ру1<тор этого класса. BufferedReader (Reader лоток_чтения_вводиных_данНiZХ) Здесь параметр ПО 'l'ОК_ чтения_вводимых_данных обозначает ноток, который связывается с со;щаваемым экаемнляром кл асса BufferedReade r. Класс Re ader является абстрактн ым. Одн им из нро изводн ых от него кон1<ретных подклассов яв­ ляется кл асс InputStreamReade r, преобразующий байты в символ ы. Лля rюлуче·
360 Часть 1. Язык Java ния uб·�:,екта тина Inp utStreamReader, <· вя аа ш ю го со стандартным 1юто ком ввода System. in, служ и т следующий конструктор: InputStreamReader ( InputStream по�ок_ввода ) Перемсшшя System.in ссылается на объект класса lnputStream и ноэто· му долж на быть указ;ша в качестве н ара мстра по ток _ ввода. В конечном итоге rюлучастсн 1 1ри веде шшя ниже <: трока кода , в которой со:щается объект типа Bu fferedRe ade r, с вязанны й с клавиатурой . П осле вы1юл11е11ю1 этой стро ки кода п ерем е н ная :� кзсм11ляра br буд<->т содержа1ъ ното к в вода <· имво;ю в, свя:ш1111ый с конс ол ь ю через стандартный поток ввода System. in. Bu ffe redReade r br = new Bu ffe redReader (new InputStrearnReader (Sys tern.in) ); Чтение символов Для чтения с и м вола из потока ввода ти па BufferedReader служ ит метод read (). Н иже п оказан а версия метода read () , которая бу1�t.·т ип юл 1,:юваться в п р иведенных далее примерах 1 1ро гра мм. int read () throws IOException Каждый раз, к огда вызывается метод read (),он •штаст с и м вол и:� 11оток<1 ввода и возвращает его в виде целочисленного значения . Пu достижени и конца нотока возвращается :ш <l•tсние -1 . Как видите , метод read () м ожет <:генериро ват ь исклю­ чение ти на IOException. В приведенном ниже примере нрограммы демонстрируется 11римененис мсто· да read ( ) для чтес'ния символ ов с консоли до тех нор, 1ююt 11оль:юв<�тсль не введет символ " q ". Следует заметить, что любые и<·клю•1с1нн1 , во:шикающие нри вводе· выводе , п ро сто 1·t.·нерируются в мето1�е ma in ( ).'Еtкой 110/\Х<Щ рап 1 ростра11сн нри •пении дан н ых с консоли в простых п ри мерах программ, а11а1101·и чиых представ­ ленным в это й кн иге, но в более слож н ых 11риклаю1ых 11ро1·раммах исключе11ия м ожно обрабаты вать явным образом. 11 Использовать кл асс BufferedReader для чтения символов с кон соли irnport jav a.io.*; cla ss BRRead { puЬlic static void rnain (St ring args[] ) throws IOExcept ion { char с; Bu fferedReader br = new Bu ffe redReade 1· (new Input StrearnRe ader ( Systern.in) ); Sys tern.ou t.println ( "Bвeдитe симв олы , 'q' - для выхода."); 11 читать симв олы do( с= (char) br. read(); Systern.out . println (c) ; wh ile (c != 'q'); Ниже при веден пример выполнения данной 11ро1·раммы.
Гл ава 13. Ввод-вы вод, аплеты и прочие вопросы 361 Введите симв олы , 'q' - для выхода . 123abcq 1 2 3 а ь с q Результат выполнения дан ной программы может выглядеть не совсем так, как предполагалось, потому что стандартный поток ввода System. in по умолчанию является буферизованным построчно. Это означает, что никакие вводимые дан­ н ые на самом деле не передаются программе до тех пор, пока не будет нажата клавиша <Enter>. Нетрудно догадаться, что эта особенность делает метод read () малопригодн ым для организации ввода с консоли в диалоговом режиме. Чтение символьных строк Для чтения символьных строк с клавиатуры служит версия метода readLine (), который является членом класса Bu ffe redReade r. Его общая форма приведена н иже. Как видите , этот метод возвращает объект типа String. Strinq readLine () throws IOException В приведенном ниже примере программы демонстрируется применение клас­ са Bu ffe redReader и метода readLine (). Эта программа читает и выводит тек­ стовые строки текста до тех пор, пока не будет введено слово "стоп". 11 Чтение символь ных строк с консоли средствами класса ВufferedReader import java.io.*; class BRReadLines puЫ ic static vo id ma in (String args[] ) throws IOException { 11 создать поток ввода типа ВUfferedReader , 11 исполь зуя стандартный поток ввода Systeш. in BufferedRe ade r br = new BufferedReade r(n ew InputStreamRe ader ( System. in) ); String str; System. out .println ("B вeдитe строки текста.") ; System. out . println ("B вeдитe 'стоп ' для завершения .") ; do{ str = br. readLine(); System.out . println (str) ; } while (!str .equals ("c тoп" )); В следующем примере программы демонстрируется простейший текстовый редактор . С этой целью сначала создается массив объектов типа String, а затем читаются текстовые строки, каждая из которых сохраняется в элементе масс ива. Чтение производится до 100 строк или до тех пор, пока не будет введено слово "стоп". Для чтения данных с ко нсоли применяется класс Buffe redReader.
362 Часть 1. Язык Java 11 Простейший текстовый редактор import java.io. *; class TinyEdit { puЫic static void ma in (String args[J ) throws IOException { 11 создать поток ввода типа BufferedReader, 11 исполь зуя стандартный лоток ввода Sys teJ1 1 .in BufferedRe ade r br = new BufferedRe ader (new Input StreamRe ader (System. in) ); String str [J = new String [lOO] ; System.out . println ("B вeдитe строки текста.") ; System. out . println ("B вeдитe 'стол ' для завершения .") ; for ( int i=O ; i<lOO; i++) { } str(i] = br. readLine (); if (str [i] .equals ( "cтoл" )) break; System.out . println ("\nCoдepжимoe вашего файла:"} ; 11 вывести те кстовые строки for (int i=O ; i<lOO; i++) { if (st r[i] .equals ( "cтoл" )) break; System. out . println (str [i] ); Ниже приведен пример выполнения данной программы. Введите строки те кста . Введите 'стол ' для завершения . Это строка один . Это строка два . Java упрощает работу со строками . Просто создайте объе кты типа String . стол Содержимое вашего файла : Это строка один . Это строка два . Java упрощает работу со строками . Просто создайте объе кты типа String . Запись данных, вы водим ых на консоль Вывод данных на консоль проще всего организовать с помощью упоминавших­ ся ранее методов print ( ) и println () , которые применяются в большинстве примеров из этой книги. Эти методы определены в классе PrintStream ( он явля­ ется типом объекта , на который ссылается переменная Systern. out) . Несмотря на то что стандартный поток Systern. out служит для вывода байтов, его можно вполне применять в простых программах для вывода данных. Те м не менее в сле­ дующем разделе описывается альтернативный ему поток вывода символов. Класс Pr in tStrearn описывает поток вывода и является производным от клас са OutputStrearn, поэтому в нем реализуется также низкоуровневый метод wri te ( ) . Следовательно, метод wr i t е ( ) можно применять для записи данных, выводимых на консоль. Ниже приведена простейшая форма метода wr i te () , определенного в классе PrintStrearn.
Гл ава 1 З. Ввод-вы вод, аплеты м прочие вопрось1 363 void vrite (int dd!l'0.8Q8_.sшtveюre) Этот метод записывает байт, передаваемый в качестве параметра байтовое_ зна чение. Несмотря на то что параметр байтовое_ зна чение объявлен как цело­ 'UIСJiенный, записываются только 8 его младших бит. Ниже при веден короткий пример , в котором метод wr i te ( ) применяется для вывода н а экран буквы '1 А" с последующим переводом строки. // Продемонстрировать применение ме тода Syst&JD.out . vrite () class WriteDemo { puЫic static void ma in (String args[]) { int Ь; Ь= 'А'; System. o ut . write (b) ; Syзtem. out .write ( ' \n '); Пользоваться методом write () для вывода данных на консоль приходится не­ часто, хотя иногда это и уд обно. Ведь намного проще применять для этой цели методыprint() иprintln(). Кл асс PrintWr iter Нес мотря на то что стандартным потоком Syst e m . ciut вполне допустимо по,ль­ зоВаться для вывода данных на консоль, он все же подходит в большей степени дЛя отладки или примеров программ, аналогичных предсТавленным в данной кй�� re. А для реальных программ рекомендуемым средством вывода данных на кон­ соль служит поток записи, реализованный в классе PrintWriter, относящемся к категории символьных классов. Применение такого класса для консольного вы­ вода упрощает интернационализацию прикладных программ. В классе PrintWr iter определяется несколько. �оиструкторов. Ниже приведен один из тех конструкто ров, которые применяются в' раtсматриваемых далее прИ­ м�рах. 'iint1friter (OutputStreaa DО!l'ОЖ_.1 1Ш1О да, Ьo o lean oua!l'.кa) · Здесь параметр поток_ выв ода обозначает объект типа OUtput Stream, а пара­ ме'tр очистка - очистку потока вывода всякий раз, когда вызьtвается (среди про­ чих) метод p r i n t ln (). Если параметр очистка принимает логическое значе1U1е t rue, то очистка потока вывода nроисходит автоматически , а иначе - вручную. В классе Pr intWriter поддерживаются методы print ( ) и println (). Спедовательно , их можно использовать таким же образом, как и в стандартном rtотоке вывода System. out . Если аргумент зтих методов не относится к проСТ()Му mny, то для объекта типа PrintWriter снача.!Iа вызывается метод toString () , а затем выводится результат. Чтобы вывести данные на консоль, используя класс PrintWriter, следует ука­ зать стандартный поток System.out для вы вода и его автоматическую очистку.
361. Часть 1. Язык Java Например, в следующей строке кода создается объект типа PrintWriter, кото­ рый связывается с консольным выводом: PrintWri ter pw = new PrintWriter{ Sysiem . out , tru e) ; В приведенном ниже примере программы демонстрируется применение клас­ са PrintWriter для управления выводом данных на консоль. 11 Продемо нс трировать приме нение кла сса PrintWriter import java.io.*; puЬlic class PrintW riterDemo puЫic static void ma in (St ring arg s[J ) { PrintWriter pw = new PrintWri ter ( System.out, tru e) ; pw . println ("Этo строка") ; inti= -7; pw . println (i) ; douЫe d = 4.5е-7; pw . println (d) ; Ниже приведен результат, выводи мый данной программой . Это строка -7 4.5Е-7 Напомним, что стандартный поток System.out вполне пригоден для вывода простого текста на консоль на стадии овладения языкомjаvа ил и отладки приклад­ ных программ, тогда как класс PrintWriter упрощает интернационализацию ре­ альных программ. Но поскольку применение класса PrintWriter не дает никаких преимуществ в примерах простых программ, то для вывода данных на консоль бу­ дет и далее применяться стандартный поток System. out. Чтение и запись данных в файлы В Java предоставляется немало классов и методов, позволяющих читать и за­ писывать данные в файлы. Прежде всего , следует заметить, что тема ввода-вывода данных в файлы весьма обширна и подробно обсуждается в части 11 данной книги . А в этом разделе будут представлены основные способы чтения и записи данных в файл. И хотя для это й цели применяются потоки ввода-вывода б айтов, упомина­ емые здесь способы нетрудно приспособить и под потоки ввода-вывода символов. Для ввода-вывода дан ных в файлы чаще всего применяются классы Filelnput Stream и Fi leOu tputStream, которые создают потоки ввода-вывода байтов, связан­ ные с файлами. Чтобы открыть файл для ввода-вывода данных, достаточно создать объект одного из этих классов, указав имя файла в качестве аргумента конструктора. У обоих классов имеются и дополнительные конструкторы, но в представленных да­ лее примерах будут употребляться только следующие конструкторы: FileinputStream (Strinq ЯН1l файла ) throws FileNotFoundException FileOUtputStream ( Strinq янЯ_ФаJiла) throws FileNotFoundException
Глава 1З. Ввод-вывод, аплеты и прочие вопросы 365 где параметр имя_ файла обозначает имя того файла, который требуется открыть. Если при создании потока ввода файл не существует, то генерируется исключе­ ние типа Fi leNotFoundException. А если при создании потока вывода файл нельзя открыть ил и создать, то и в этом случае генерируется исключение типа FileNotFoundException. Класс исключения FileNotFoundException являет­ ся производным от класса IOException. Когда файл открыт для вывода, любой файл, существовавший ранее под те м же самым именем, ун ичтожается. На заметку! В тех случаях, когда присутствует диспетчер защиты, некоторые фа йловые классы, в том числе Fi leinputStream и FileOutputStream, генерируют исключение типа SecurityException, есл и при попытке отк рыть файл обнаруживается нарушен ие за­ щиты . По умолчанию в прикладных программах, запускаем ых на выполнение по ко ман­ де java, диспетч ер защиты не применяется. Поэтому в примерах, де монстрирующих ор­ га низацию ввода-вывода в да нной книге, вероятность генерирования исключения ти па SecurityExcept ion не отсл еж ивается. Но в других видах при кладных программ (напри­ мер, аплетах) диспетчер защиты обычно применяется, и поэтому операци и ввода-вывода да нных в файлы впол не могут привести в них к исключению типа SecurityExcept ion. В та ком сл учае следУет организовать соответствующую обработку этого исключения. Завершив работу с файлом, его нужно закрыть. Для этой цел и служит метод close (),реализованный в классах File inputStream и FileOutputStream: void close () throws IOException Закрытие файла высвобождает выделенные для него системные ресурс ы, по­ зволяя использовать их дл я других файлов. Неуда чный исход закрытия файла мо­ жет привести к "утечкам памяти", пос кольку неиспользуемые ресурсы оператив­ ной памяти останутся выделенными. На заметку! Начиная с верси иJDК 7 метод сlоsе () определяется в инте рфейсе Аu tоСlоsеаЫе из пакета j ava . lang. Интерфейс AutoCloseaЫe насл едУет от интерфейса CloseaЫe из пакета j ava . io. Оба инте рфейса реал и зуются кл ассами пото ко в ввода-вывода , вклю­ чая классы Fi leinputStream и Fi leOutput Stream. Следует заметить, что имеются два основных способа закрытия файла, когда он больше не нуже н. При первом , традиционном способе метод close () вызы­ вается явно , когда файл бол ьше не нуже н. Именно такой способ применялся во всех версиях jаvа до JDK 7, и поэтому он присутствует во всем коде , написанном до версии JDК 7. А при втором способе применяется оператор try с ресурсами, внедренный в'версии JDК 7. Этот оператор автоматически закрывает файл , ког­ да он бол ьше не нужен . И в этом случае отпадает потребность в явных вызовах метода close ().Но поскольку существует немалый объем кода, который написан до версииJDК 7 и все еще эксплуатируется и сопровождается, то по-прежнему важ­ но знать и владеть традиционным способом закрытия файлов. Поэтому сначала буде т рассмотрен именно этот способ , а новый, автоматизированный способ опи­ сывается в следующем разделе.
366 Часть 1. Язы к Java Чтобы прочитать данные из файла, можно воспользоваться версией метода read (), определенной в классе Fileinp utStream. Та его версия, которая приме­ няется в представленных далее примерах, выглядит следующим образом: int read () throws IOException Всякий раз , когда вызывается метод read ( ) , он выполняет чтение одного бай­ та из файла и возвращает его в виде целочисленного значения. А если достигнуr конец файла, то возвращается значение -1 . Этот метод может сгенерировать ис­ ключение ти па IOException. В приведенном ниже примере программы метод read ( ) применяется для вво­ да из файла, содержащего текст в коде ASCII, который затем выводится на экран. Имя файла указывается в качестве аргумента командной строки. /* Отображение содержимо го те кстового файла . */ Чтобы восполь зоваться этой программой , укажите имя файла , ко торый требуе тся просмотреть . Например , чтобы просмотре ть файл TEST .TX'l', введите в командной строке следующую команду : java ShowFile ТЕSТ .ТХТ import jav a.io.*; class ShowFi le { puЫic static void ma in ( String args[] ) { int i; Fi leinputStream f in; 11 сначала убедиться, что имя файла указано if(args.length != 1) { System.out . println ( "Иcпoльзoвaниe : ShowFi le имя_файла" ); return; // Попытка открыть файл try { fin = new Fi leinputStream (args [O] ); catch (FileNot FoundException е) { System.out . println ("Heвoзмoжн o открыть файл " ); return; 11 Теперь файл открыт и готов к чтению . 11 Далее из него читаются символы до тех пор , 11 пока не встретится признак конца файла try { do i = fin . read(); if (i != -1) System.out . print ((char) i) ; } while(i != -1); catch (IOExcept ion е) { System.out . println ( "Oшибкa чтения из файла" ) ; 11 закрыть файл try {
fin . close (); Гл ава 13. Ввод -вывод, аплет ы и прочие в опрос ы 367 catch (IOException е) { System. o ut . println ("Oши бкa закрытия файла") ; Обратите внимание в дан ном примере программы на блок операторов try/ са tch, обрабатывающий ошибки , которые могут произойти при вводе-выводе. Каждая операция ввода-вывода проверяется на наличие исключений, и если исклю­ чение возникает, то оно обрабатывается. В простых программах или примерах кода исключения, возникающие в операциях ввода-вывода, как правило, ге нерируются в методе та in ( ) , как это делалось в предыдущих примерах ввода-вывода на консоль. Кроме того, в реальном прикладном коде иногда оказывается полезно, чтобы ис­ ключение распространялось в вызывающую часть программы, уведомляя ее о не­ уда чном исходе операции ввода-вывода. Но ради демонстрации в большинстве при­ меров файлового ввода-вывода, представле нных в данной книге, все исключения, возникающие в операциях ввода-вывода, обрабатываются явным образом. В приведенном выше примере поток ввода закрывается после чтения из файла, но имеется и другая возможность, которая нередко оказывается удо бной . Она под­ разум евает вызов метода close () из блока оператора finally. В таком случае вс е ме тоды, получающие доступ к файлу, содержатся в блоке оператора try, тогда как блок оператора finally служит для закрытия файла. Та ким образом, файл будет закрыт независимо от то го , как завершится блок оператора try. Если обратиться к приведенному выше примеру, то в свете только что сказан ного блок оператора try, где читаются данные из файла, может б ыть переписан следующим образом: try { do{ i = fin .read() ; if (i != -1) System. o ut . print ((char) i) ; 1 while(i != -1); catch (IOException е) { System.out . println ("Oшибкa чтения из файл а" ) ; final ly { 11 закрыт ь файл при вых оде из блока оператора try try { fin .close () ; catch (IOException е) { Sys tem . out .println ("Oши бкa закрытия файла" ); В данном случае это не так важно. Те м не менее одно из преимуществ такого подхода состоит в том, что если выполнение кода , в котором происходит обраще­ н ие к файлу, прекращается из-за каких-ни будь исключений, не связанных с опера­ циями ввода-вывода , то файл все равно будет закрыт в блоке оператора finally. Иногда проще заключить все части программы, открывающие файл и получа­ ющие доступ к его содержимому, в один блок оператора try, вместо того чтобы ра зделять его на два блока, а затем закрыть файл в блоке оператора f ina 11у. В ка­ честве иллюстрации ниже показан другой способ написания программы ShowFi le из предыдущего примера.
368 Часть 1. Язык Java /* ОтоОражение содержимо го текстового файла . */ ЧтоОы восполь зоваться этой программ ой , укажите имя файла, который треОуе тся просмотреть . Например, чтоОы просмотреть файл ТЕSТ .ТХТ, введите в командной строке следующую команду : java ShowFile ТЕSТ .ТХТ В этом варианте програм мы код , открыв ающий и получающий доступ к файлу , заключ ен в один Ол ок оператора try . Файл за крыв ается в Ол оке оператора finally . import jav a.io.*; class ShowFi le { puЫ ic static void main (String args[]) { int i; Fi leinpu tStream fin = null; 11 сначала уО едить ся, что имя файла указано if (args . length != 1) { System.out . println ( "Иcпoльзoвaниe : ShowFi le имя_файла ") ; return ; // В следующем коде сначала открывается файл , а затем 11 из него читаются символы до тех пор , пока не встретится 11 признак конца файла try { fin = new Fi leinputStream (args [O] ); do i = fin. read(); if(i != -1) System.out . print ((char ) i); while (i != -1) ; catch (FileNotFoundException е) { System.out . println ("Фaйл не найден.") ; catch (IO Exception е) { System.out . println ("Пpoизoшлa ошиОка ввода- вывода" ) ; finally { 11 закрыт ь файл в люОом случае try { if(fin != null) fin. close(); catch (IOException е ) { System. out . println ("O шиOкa закрытия файла" ) ; Как видите, при таком подходе объект fin инициализируется пустым значе­ нием null. А в блоке оператора finally файл закрывается только в том случае, если объект fin не содержит пустое значение null. Та кой подход оказывается ра­ ботос пособным потому, что объект fin не будет содержать пустое значение nu ll только том случае, если файл ус пешно открыт. Та ким образом, метод close () не вызы вается , если при открытии файла возникает исключение.
Глава 13. Ввод-вывод, аплеты и прочие вопросы 369 Последовательность операторов try/catch в приведенном выше примере можно сделать более краткой. Класс исключения FileNotFoundExcept ion явля­ ется производным от класса IOExcept ion, и поэтому обрабатывать отдел ьно его ис ключение совсем не обязательно. В качестве примера ниже приведе на пере­ деланная последовательность операторов try/саtch без перехвата исключения типа FileNot FoundException. В данно м случае отображается стандартное сооб­ щен ие об исключительной ситуаци и, описывающее возникшую ошибку. try fin = new Fi lelnput Stream(args[O]); do{ i = fin. read(); if(i != -1) System.out . print ((char) i) ; 1 while(i != -1); catch (IOException е) { System.out . println ( "Oши бкa ввода- вывода : " + е) ; finally { // закрыт ь файл в любом случае try { if(fin != null) fin . close (); catch (IO Exception е) { System.out . println ( "Oшибкa за крытия файла") ; При таком подходе любая о шибка, в том числе и ошибка открытия файла, об­ рабатывается одним операто ром catch. Благодаря своей краткости такой п одход употребляется в большинстве примеров организации ввода-вывода, представлен­ н ых в данной книге. Но этот подход не годится в тех случаях, когда требуется ина­ че отреагиро вать на неудачный исход открытия файла, например , когда пользова­ тель может неправильно ввести имя файла. В таком случае можно было бы, напри­ мер, запросить правильное имя файла, прежде чем переходить к блоку оператора try, где происходит обращение к файлу. Для зап иси в файл можно воспользоваться методом wri te ( ) , определенным в классе Fi l eOutputStream. В своей простейшей форме этот метод выглядит сле­ дующим образом: void write (int с1ай!1'оsое_sяа vенве) throws IOException Этот метод записывает в файл байт, переданный ему в качестве параметра ба йтовое_ зна чение. Несмотря на то что параметр байтовое_ зна чение объявлен как целочисленный, в файл записываются только его младшие восемь бит. Если при записи возникает о шибка, генерируется исключение типа IOException. В следую­ щем примере программы метод wr i te ( ) применяется для копирования файла: / * Копиро вание файла . */ Ч т обы восполь зоваться этой программой , укажите имена исходного и целевого файлов . Например , чтобы скопировать файл FIRST .TX'l в файл SECOND .ТXT, введите в кома ндной строке следующую команду : java CopyFile FIRST .ТXТ SECOND .ТXТ
370 Часть1. ЯзыкJava import jav a.io.*; class CopyFi le { puЫic static void ma in ( String args[] ) throws IOException { int i; Fi leinputStream fin = null; FileOutputStream fout = nu ll ; 11 сначала убедиться , что указаны име на обоих файлов if(args.length != 2) { System.out . println ( "Иcпoль зoвaниe : CopyFi le от куда куда" ); return ; 11 копировать файл try { 11 попытаться открыть файлы fin = new Fileinput Stream (args [O] ); fout = new FileOutput Stream (args [l] ); do{ i = fin. read(); if (i != -1) fout .write (i) ; } while(i != -1); са tch ( IOException е) { System.out . println ( "Owибкa ввода- вывода : "+е) ; final ly { try { if(fin != null) fin . close (}; } catch (IOException е2 ) { System. out . println ("Owибкa закрытия файла ввода") ; } try { if ( fout != null) fout .close (); catch (IOException е2 } { System. out . println ( "Owибкa закрытия файла вывода" ) ; Обратите внимание на то , что в данном примере программы при закрытии файлов используются два отдел ьных блока оператора try. Этим гарантируетс я, что оба файла будут закрыты, даже если при вызове метода f in . close ( ) будет сге­ нерировано исключение. Обратите также вн имание на то , что все потенциальн ые ошибки ввода-вывода обрабатываются в двух приведенных выше программах с по­ мощью исключений, в отличие от других языков программирования, где для уве­ домления о файловых ошибках используются коды ошибок. Исключения вJava не только упрощают обращение с файлами , н о и позволяют легко отличать условие дос тижения конца файла от файловых ошибок во время ввода. Автоматическое закрытие файла В примерах программ из предыдущего раздела метод close () вызывался явно, чтобы закрыть файл , как только он о кажется ненужным. Как упоминалось ранее,
Гл ава 1 З. Ввод-вы вод, аплеты и прочие вопросы 371 такой способ закрытия файлов применялся до версииJDК 7. И хотя он все еще допустим и применим, в версии JDK 7 появилась новая возможность, предлагаю­ щ ая иной способ управления такими ресурсами, как потоки ввода-вывода в файлы: автомати ческое заверш ение процесса. Эту возможность иногда еще называют ав­ томати-ческим упр авлением ресурсами (АRМ), и основывается она на усовершенство­ ванной верс и и оператора try. Гл авное преимущество автоматического управле­ ния ресурсами заключается в предотвращении ситуаций, когда файл ( или другой ресурс) н е освобождается по невнимательности, если он больше не нужен . Как по­ яснялось ранее, если забыть по какой-нибудь при чине закрыть файл, это может п ривести к угечке памяти и другим осложнениям . Как упоминалось выше, автоматическое управление ресурсами основывается на усовершенствованной форме оператора try. Н иже приведе на его общая форма. try ( спецхф•жаЦJU__ресурса) { 11 использование ресурса Здесь специфика ция_ресурса обозначает оператор, объявляющий и инициа­ лизирующий такой ресурс, как поток ввода-вывода в файл. Он состоит из объявле­ ния переменной, где переменная инициализируется ссьшкой на управляемый объ­ ект. По заверш ении блока оператора try ресурс автоматически осво бождается . Для файла это означает, что он автоматически закрывается, а следовательно, от­ падает необходимость вызывать метод close () явно. Безусловно, эта ·новая фор­ ма оператора try может также включать в себя операторы finally и catch. Она называется оператором try с ресурсами. Оператор try с ресурсами применяется только с теми ресурсами, которые ре­ ализуют интерфейс Au toCloseaЫe, определенный в пакете j ava . lang . В этом интерфейсе определяется метод close (). Инте рфейс Au toCloseaЫe наследует­ ся интерфейсом CloseaЫe из пакета j ava . io. Оба интерфейса реализуются клас­ сами потоков ввода-вывода. Таким образом, оператор t ry с ресурсами может быть использован п ри обращении с потоками ввода-вывода, включая и потоки ввода­ вывода в файлы. В качестве первого примера автоматического закрытия файла рассмотрим переделанную версию представленной ранее программы ShowFi le. /* В этой версии программы ShowFile оператор try с ресурсами применяется для автома тического закрытия файла Примечание : для выполнения этого кода требуе тся версия JDK 7 */ import java.io.*; class ShowFi le { puЫic static void ma in (String args[] ) { int i; 11 сначала уб едить ся, что имя файла указано if(args . length != 1) { System. out . println ( "Иcпoль зoвaниe : ShowFi le имя_файла") ; return ;
372 Часть 1. Язы к Java 11 Ниже оператор try с ресурсами применяется 11 сначала для открытия , а затем для автоматического 11 закрытия файла по заверше нии бл ока этого оператора try ( Fi leinpu tStream fin = new Fileinput Stream (args [O] )) do{ i = fin. read(); if (i != -1) System.out . print ((char) i) ; } while(i != -1); catch (FileNot FoundException е) { System.out . println ("Фaйл не найден ."); catch (IOException е) ( System.out . println ( "Пpoизoшлa ошибка ввода- вывода" ) ; В привед енном выше примере программы обратите особое внимание на от­ крытие файла в бл оке оператора try: try ( Fi leinpu tSt ream fin = new Fileinput Stream (args [O] )) { Как видите , в той части оператора try, где указывается спецификация ресур­ са, объявляется экземпляр fin класса FileinputStream, которому затем присва· ивается ссылка на файл , открытый его конструктором. Та ким образом, в данной версии программы переменная f in является локальной по отношению к блоку оператора try, в начале которого она создается. По завершении блока try поток ввода-вывода, связанный с переменной f in, автоматически закрывается в резуль­ тате неявного вызова метода close ().Этот метод не нужно вызывать явно, а сле­ довател ьно, исключается возможность просто забыть закрыть файл. В этом и со­ стоит гл авное преимущество применения оператора try с ресурсами. Однако ресурс, объявляемый в операторе try, неявно считается завершенным. Это означает, что присвоить ресурс после того, как он бьm создан, нельзя. Кроме того, область действия ресурса ограничивается пределами оператора t ry с ресурсами. В одном операторе try можно организовать управление несколькими ресурса· ми. Для этого достаточно указать спецификацию каждого ресурса через точку с за· пятой. Примером тому служит приведенная ниже версия программы CopyFi le, переделанная таким образом, чтобы использовать один оператор try с ресурсами для управления переменными fin и fout. /* Версия пр ограммы CopyFi le , исполь зующа я оператор try сре сурсами . */ Она демон стрирует управление двумя ресурс ами (в данном случае - файлами ) в одном операторе try import jav a.io.*; class CopyFi le { puЬlic static void ma in ( String args [] ) throws IOException { int i; 11 сначала убедиться, что заданы оба файла if (args . length != 2) { System.out . println ( "Иcпoльзoвaниe : CopyFile от куда куда" );
return ; Глава 13. В вод-вывод, аплеты и проч ие вопрос ы 373 11 открыть два файла и управлять ними в операторе try try (FileinputS trearn fin = new Fi leinputStrearn (args [O] ); Fi l eOutputSt rearn fout = new FileOutputStrearn (args [l])) do{ i = fin. read(); · if (i != -1) fout .write (i) ; } while(i != -1); catch (IOException е) { Systern.out . println ("Oшибкa ввода- вывода : "+е) ; Обратите внимание на то, как файлы ввода и вывода откр ы ваются в блоке опе­ ратора try. try (Fileinput Strearn fin = new Fileinpu tSt rearn (args [O] ); Fi l eOutputStrearn fout = new FileOutputStrearn (args [l])) 11 ... По завершении этого блока оператора try будут закрыты оба ресурса в пере­ м енных f in и f ou t. Если сравнить эту версию программы с предыдущей, то можно заметить, что она значительно ко роче. Возможность упростить исходный код яв­ ляется допол нительным преимушеством автоматического управления ресурсами. У оператора try с ресурсами имеется еще одн а особенность, о которой стоит упомянуть. В общем, когда выполняется блок оператора try, существует вероят­ ность того , что исключение, возни кающее в блоке оператора t ry, приведет к дру­ гому исключению, которое произойдет в тот момент, когда ресурс закрывается в блоке оператора f inal 1 у. Если это "обычный" оператор t ry, то первоначальное исключение те ряется, будучи вытесненным вторым исключением. А если исполь­ зуется оператор try с ресурсами, то второе исключение подавляется, но не теряет­ ся . Вместо этого оно добавляется в список подавленных исключений, связанных с первым исключением. Доступ к списку подавле нных исключений может быть по­ лучен с помощью метода getSuppressed ( ) , определенного в кл ассе ThrowaЫe. Благодаря упомя нутым выше преимушествам оператор try с ресурсами приме­ няется во многих, но не во всех примерах программ, представленных в дан ной книге. В не которых примерах по-прежнему применяется традиционный способ за­ крытия ресурсов. И на то есть несколько причин. Во-первых, существует немалый объем широко распространенного и эксплуатируемого кода, в котором применя­ ется традиционный способ и с которым хорошо знакомы все программирующие нa Java. Во-вторых, не все разрабатываемые проекты будут немедленно переведе­ ны на новую версию JDK. Некоторые программисты , вероятно, какое-то время продолжат работать в среде , предшествующей версииJDК 7, где рассматриваемая здесь улучшенная форма операто ра try недоступ на. И наконец, возможны случаи, когда явное закрытие ресурса вручную оказывается лучше, чем автоматическое. По этим причинам в некоторых примерах из этой книги будет и далее применять-
37' Часть 1. Язык Java ся тради ционный способ явного вызова метода close () для закрытия ресурсов вруч ную. Помимо того , что эти примеры программ демонстрируют традицион­ ный способ управления ресурсами, они могут быть откомпилированы и запущены всеми читателя ми данной книги во всех системах, которыми они пользуются. Помните! Дл я цел ей демонстра ции в некоторых примерах программ из данной книги намеренно применяется традицион ный способ закрытия файлов, широко распространенный в ун ас­ ледованном коде. Но при написан ии нового кода рекомендуется при менять новый способ авто матического управления ресурса ми, который поддерживается в тол ько что описанном операторе try с ресурса ми. Осно в ы создания апл етов Во всех предыдущих примерах демонстрировались програм мы, относящиеся к категории консольных прикладн ых программ нa Java. Но это лишь одна из кате­ горий прикладных программ нa Java. К другой кате го рии относятся аплеты . Как упоминалось в гл аве 1 , аплет - это небольшая прикладная программа, находящая· ся на веб-сервере , откуда она загружается, автоматически устанавли вается и запу­ скается как сосrавная часть веб-документа. Как только аплет поя вится у клиента, он получает ограниченный доступ к ресурсам, чтобы предоставить графический пользовательский интерфейс (ГПИ) и выполнить различные вычисления, не под­ вергая клиента риску вирусной атаки или нарушения целостности его данных. Начнем рассмотрение аплетов с простейшего примера, приведенного ниже. import java .aw t.*; import java . applet .*; puЫ ic class Simpl eApplet extends Appl et puЫ ic void paint ( Graphics g) { g.drawS tring ("Пpocтeйwий аплет" , 20, 20) ; Этот аш1ет начинается с двух операторов impo rt. Первый из них импортиру­ ет классы библиоте ки Abstract Window To olkit (АWТ). Аплеты взаимодействуют с пользователем через библиотеку АWТ, а не через классы консольного ввода-вы­ вода. Библиотека АWТ поддержи вает элементарный окон ный ГПИ и служит здес ь в качестве введения в программирование аплетов. Нетрудно догадатьСя, что би­ блиотека АWТ значител ьно крупнее и сложнее, и пол ное обсужде ние ее возможно­ стей занимает несколько гл ав в части 11 данной книги. Правда, приведенный выше п1юстой аплет весьма ограниченно использует возможности библиоте ки АWГ . (Для поддержки ГПИ в апл етах можно также пользоваться библиотекой Sw iпg, но такой подход рассматривается далее в книге.) Второй оператор impo rt импорти· рует пакет applet, в котором находится класс Applet. Каждый создаваемый аплет должен быть подклассом, прямо ил и косвенно производным от класса App let. В следующей строке кода из рассматриваемого здесь примера простейшего аплеrа объявляется класс Simp leApplet. Этот класс должен быть объя влен откры­ тым ( puЫ ic) , чтобы быть доступным для кода за пределами аплета.
Глава 13. Ввод-вывод, аплеты и прочие вопросы 375 В классе Simp l eApp let объявляется метод paint (), который определен в би­ блиотеке АWГ и должен быть переопределен в аплете. Метод раint ( ) вызывается вся кий раз , когда аплет должен перерисовать результат, выводимый графическим способом. Та кая ситуация может возникнуть по ряду причин. Например, окно , в котором запущен аплет, может быть перекрыто другим окном, а затем вновь от­ крыто или же свернуто , а затем развер нуто. Метод paint () вызывается в то м слу­ чае, когда аплет начинает свое выполнение. Независимо от конкретной причины, вся кий раз, когда аплет должен перерисовать выводимый графическим способом результат, вызывается метод paint (), принимающий единственный параметр типа Graph ics. Этот параметр содержит графический контекст, описывающий графическую среду, в которой действует аплет. Гр афический контекст использует­ ся всякий раз, когда из аплета требуется вывести результат. В методе pa int ( ) вызывается метод drawString () из класса Graphics. Этот метод выводит строку на позиции, задаваемой координатами Х,У. Он имеет следу­ ющую общую форму: void drawString (String сообщение , int х, int у) где параметр сообщение обозначает символьную строку, которая должна быть вы­ ведена начиная с позиции, определяемой коорди натами х,у. В Java верхний ле­ вый угол окна имеет координаты 0,0. В результате вызова метода drawString () ваплете выводится символьная строка "Простейший аплет", начиная с позиции, определяемой координатами 20,20. Обратите внимание на то , что в аплете отсутствует метод ma in (). В отличие от обычных программ нajava, выполнение аплета не начинается с метода mа in (). На самом деле этого метода нет у большинства аплетов. Вместо этого выполнение аплета начинается, когда имя его класса передается средству просмотра аплетов или сетевому браузеру. После ввода исходного кода аплета Simp leApplet его компиляция выполняет­ ся таким же образом, как и обычных программ. Но запуск аплета Simp leApp let осуществляется иначе . По существу, запустить аплет можно двумя способами: • в совместимом cjava браузере; • средством просмотра аплетов, например, стандартным инструменталь­ н ым средством appletviewer . Это средство выполняет аплет в своем окне. Обычно это самый быстрый и простой способ проверки работоспособно­ сти аплета. Кажд ый из этих способов запуска аплетов подробно описывается ниже. Для того чтобы выполнить аплет в веfНJраузере, достаточно, в частности, создать ко роткий НТМL-файл, который должен содержать соответствующий дес криптор. В настоящее время компания Огасlе рекомендует использовать для этой цели дескрип­ тор APPLET, хотя может быть также использован дескриптор OBJECT. Подробнее о методиках разверты ван ия аплетов речь пойдет в гл аве 23. В качестве примера ниже приведен НТМL-файл с дескриптором APPLET для запуска аплета Simp leApplet. <applet code= "Simp leApplet" width=200 height=бO> </app let>
376 Часть 1. Язы к Java Атр ибуты width и he ight разметки этого НТМL-файл обозначают размеры области отображения, используе мой аплетом. (У дес криптора APPLET имеются и другие параметры, более подробно рассматриваемые в части 11.) Создав рассма­ тр иваемый здесь НТМL-файл , следует запустить браузер, а затем загрузить в нем этот файл , в результате чего и будет выполнен аплет Simp l eApp let. На заметку! Начиная с обновления 21 версии Java 7, аплеты в Java должны подписываться во из­ бежание предупреждений о нарушении защиты при их выпол нении в браузере, хотя иногда требуется предотв ратить выполнение апл ета. К этим переменам особенно чувствительны аплеты , хра нящиеся в локал ьной файловой системе, в том числе и те, что получа ются в ре· зул ыате ко мпиляции примеров из да нной кн иги. Та к, для выполнения локал ьного аплета в браузе ре, скорее всего, придется настроить пара метры защиты на панели упра вления Java (Java Control Pa nel). На момент написания этой книги в компании Oracle рекомендо· вали пользоваться не локальными аплетами, а те ми, что выполняются через веб-сервер. А в будущем ожидается , что выпол нение неподписанных локальных аnлето в будет бл оки ро· ваться . В ко нечном итоге подписание аплето в, распространяемых через Инте рнет, станет обязательным. И хотя принципы и методики подп исания аплетов (и других видов приклад· ных программ на Java) выходят за ра мки рассмотрения этой книги, на веб-са йте компании Oracle можно найти немало подробных сведений по да нному вопросу. И, наконец, чтобы опробовать предста вленные здесь примеры аплетов, проще всего воспользоваться упоми· навшимся ранее средством просмотра апл етов app letviewer. Чтобы опробовать аплет Simp leApp let в средстве просмотра аплетов, следует также выполнить приведенный выше НТМL-файл . Та к, если этот НТМL-файл на· зы вается RunApp . h tml , то для его запуска на выполнение достаточно ввести в ко­ мандной строке следующую команду: C:\>appletviewer RunApp .html Но существует более уд обный способ, ускоряющий проверку аплетов. Для это­ го достаточно ввести в начале исходного файла аплета комментарий, содержащий дес криптор APPLET. В итоге исходный код аплета будет документирован прото­ типом необходимых операторов HTML, и скомпилированный аплет можно буд ет проверить , запуская средство просмотра аплетов вместе с исходным файлом апле­ та. Если применяется именно такой способ проверки аплетов, то исходный файл аплета Simp leApp let должен выглядеть следующим образом: import jav a.awt . *; import java.applet .*; /* <appl et code= "SimpleAppl et" width=2 00 height= бO> </applet> */ puЫ ic class Simp l eApp let extends Applet puЫ ic void paint ( Graphics g) { g.drawS tring ("Пpocтeйший аnлет", 20, 20) ; Подобным способом можно очень быстро проходить стадии разработки апле­ то в, если выполнить следующие действия .
Глава 13. Ввод-вывод, аплеты и прочие вопросы 377 • Отредактировать файл исходного кода аплета. • Откомпилировать исходный код аплета. • Запустить средство просмотра аплетов вместе исходн ым файлом аплета . Средство прос мотра аплетов обнаружит дескриптор APPLET в комментарии к исходному коду аплета и запустит его на выполнение. Окно аплета Simp leApp let, отображаемое с редством просмотра аплетов, при· ведено на рис. 13.1 . Разумеется , фрейм средства просмотра аплетов может отли­ чаться своим внешним видом в зависимости от конкретной исполняющей среды. Поэтому моментальные снимки экранов, приведенные в данной книге, отражают разные исполняющие среды. · = Applet Viewer: Simp... �l � l8J Ap plet А Bimple Applet Applet sta t1e d. Рис. 13 .1 . Окно аплета SimpleApplet, отобра жаемое средств ом просмотра а плетов Вопросы создания аплетов будут обсуждаться далее в данной книге, а до тех пор укажем гл авные их особенности , о которых нужно знать те перь. • Аплеты не нуждаются в методе ma in (). • Аплеты должны запускаться из средства просмотра аплетов или совмести­ мого с J ava веб-браузера. • Пользовательский ввод-вывод в аплетах не организуется с помощью классов потоков ввода-вывода . В место этого в аплетах применяется ГПИ, предостав­ ляемый соответствующими библиотеками. Модифи каторы доступа transient и volatile В языке jаvа определяются два интересных модификатора доступа: transient и vo l atile. Эти модификато ры предназначены для особых случаев. Когда пере­ менная-экземпляр объявлена как trans ient, ее значение не должно сохраняться, когда сохраняется объект: class т { transi ent int а; 11 н е сохранится int Ь; // сохранится
378 Часть 1. Язык Java Если в данном примере кода объект ти па т записывается в область постоянного хранения , то содержимое переменной а не должно сохраняться, тогда как содер­ жимое переменной Ь должно быть сохранено. Модификатор доступа vo latile сообщает компилято ру, что модифицируе­ мая им переменная может быть неожидан но изменена в других частях програм­ мы. Одна из таких ситуаций возникает в многопоточных программах, где иногда у двух или более потоков исполнения имеется совместный досrуп к одной и той же переменной. Из соображений эффективности, в каждом потоке может храниться своя закрытая копия этой переменной. Настоящая (или глмиая) копия перемен­ ной обновляется в разные моменты , например, при входе в синхронизированный метод. Та кой подход вполне работоспособен, но не всегда оказывается достаточ но эффективным. Иногда требуется, чтобы гл авная копия переменной постоянно от­ ражала ее те кущее состояние. И для этого достаточно объявить переменную как volatile, сообщив те м самым компилятору всегда использовать гл авную копию этой переменной (или хотя бы поддержи вать любые закрытые ее копии обновля­ емыми по гл авной копии, и наоборот) . Кроме того, доступ к гл авной копии пере­ менной должен осуществляться в том же порядке, что и к любой закрытой копии. Применение оператора instanceof Иногда тип объекта полезно выяснить во время выполнения. Например, в од­ ном потоке исполнения объекты разных типов моrут формироваться , а в другом потоке исполнения - использо ваться. В таком случае удо бно выяснить тип каждо­ го объекта , получаемого в обрабатывающем потоке исполнения. Тип объекта во время выполнения не менее важно выяснить и в том случае, когда требуется при· ведение типов. В Java неправильное приведение типов влечет за собой появление ошибки во время выполнения. Большинство ошибок приведения типов может быть выявлено на стадии компиляции. Но приведение типов в пределах иерархии классов может стать причиной ошибок, которые обнаруж иваются тол ько во вре­ мя выполнения. Например, суперкласс А может порождать два подкласса: В и С. Следовательно, приведение объекта класса В или С к типу А вполне допустимо , но приведение объекта класса В к типу С (и наоборот) - неверно. А поскольку объект типа А может ссылаться на объекты типа В и С, то как во время выполнения узнать, на какой именно тип делается ссылка перед те м, как выполнить приведе ние к типу с? Это может быть объект ти п а А, В или С. Если это объект типа В, то во время вы­ полнения будет сгенерировано исключение. Для разрешения это го вопроса вjava предоставляется оператор времени выполнения instanceof, который имеет сле­ дующую общую форму: саzлжа на o6r.ex!1' instanceof !l'ял где ссылка_ на _ объект обозначает ссылку на экземпляр класса, а тип - конкрет­ ный тип этого класса. Если ссылка_на_ объект относится к указанному типу или может быть приведена к нему, то вычисление оператора instanceof дает в итоге логическое значение true , а иначе - логическое значение false. Та ким образом , оператор instanceof - это средство, с помощью которого программа может по-
Гл ава 13. Ввод-вы вод, аплеты и прочие вопросы 379 лучить сведения об объекте во время выполнения. В следующем примере програм­ мы демонстрируется применение оператора ins tance о f: // Продемонс трировать применение оператора instanceof class А { iпt i, j; class в int i, j; class С extends А { int k; class D extends А { int k; class InstanceOf { puЫic static void ma in (String args [] ) { Аа new А(); ВЬ newВ(); Сс newС(); Dd newD(); if (a instanceo f А) System.out . println ("a является экземпляром А" ); if (b instanceof В) System . out .println ("b является экземпляром В" ) ; if (c instanceof С) System. o ut . println ("c являе тся экземп ляром С" ); if (c instanceof А) System.out . println ("c можно привести к типу А" ); if (a instanceof С) System. out . println ("a можн о привести к типу С") ; System. out . println ( ); // сравнить с порожд енными типами А оЬ; оЬ=d;//ссылканаобъектd System .out . println ("ob теперь ссылае тся на d") ; if (ob instanceof D) System.out . println ("ob является экземпл яром D" ) ; System. o ut . println () ; оЬ=с;11ссылканас System. out .pr intln ("ob теперь ссыла ется на с") ; if (ob instanceo f D) System.out . println ("ob можн о при вести к типу D" ) ; else S ystem. out . println ("ob нель зя приеести к типу D" ); if (ob instanceof А) System. out . println ("ob можн о привести к типу А" ); System. out . println () ;
380 Часть 1. Язы к Java 11 все объе кты мо г ут быть приведены к типу Ob ject if (a instanceof Obj ect ) System. out . println ("a можн о прив ести к типу Obj ect") ; if (b instanceof Ob j ect ) System. out . println ("b можн о привести к типу Obj ect") ; if(c instanceof Obj ect) System. o ut . println ("c можн о привести к типу к Ob jec t") ; if (d instanceof Obj ect ) System. out . println ("d можн о привести к типу к Ob jec t"); Эта программа выводит следующий результат: а является экземпляром А Ь являе тся экз емпляром В с является экз емпляром С с можно привести к типу А оЬ теперь ссылается н а d оЬ является экземпл яром D оЬ теперь ссылае тся на с оЬ нель зя привести к типу D оЬ можно при вести к типу А а можн о привести к типу Obj ect Ь можн о привести к типу Ob j ect с можн о привести к типу Ob ject d можн о привести к типу Obj ect Большинство программ не нуждается в операторе instanceof, поскольку типы объектов обычно известн ы заранее. Но этот оператор может пригодиться при разработке обобщенных процедур, оперирующих объектами из сложной ие­ рархии классов. Модификатор до ступа strictfp С появлением версии J ava 2 модель вычислений с плавающей точкой стала чуть менее строгой . В частности , эта модель не требует те перь округления некото­ рых промежуточных результатов вычислений. В ряде случаев это предотвращает переполнение. Объявляя класс, метод или инте рфейс с модификатором доступа strictfp, можно гарантировать, что вычисления с плавающей точкой будут вы­ пол няться таким же образом, как и в первых версияхJаvа. Если класс объявляется с модифи катором доступа strictfp, все его методы автоматически модифициру­ ются как strictfp. Например, в приведенной ниже строке кода ко мпиляторуJava сообщается, что во всех методах, определенных в классе MyClass, следует использовать исходную модель вычислений с плавающей то чкой. Откровенно говоря , большинству про­ граммистов вряд ли понадобится модификатор доступа strictfp, поскольку о н касается лишь небол ьшой категории задач. strictfp class MyClass { // ...
Глава 13. Ввод-вывод, аплеты и прочие вопросы 381 Платформенно-ориентированные методы Иногда , хотя и редко , возникает потребность вызвать подпрограмму, написан­ ную на другом языке , а не на Java. Как правило, такая подпрограмма существует в в иде исполняемого кода для ЦП и той среды , в которой приходится работать, т. е . в виде платформенно-ориентированного кода. Такую подпрограмму, возмож­ но, потребуется вызвать для повышения скорости выполнения. С другой сторо­ ны, может возникнуть потребность работать со специализирован ной сторонней библиотекой, например, с пакетом статистических расчето в. Но пос кольку про­ граммы нajava компилируются в байт-код , кото рый затем интерпрети руется (или динамически компилируется во время выполнения) исполняющей системой jаvа, то вызвать подпрограмму в платформенно-ориентированном коде из программы нa java, на первый взгляд, невозможно. К счастью, этот вывод оказы вается лож­ ным. Для объявления платформенно-ориентированных методов вjava предусмо­ трено ключевое слово na tive . Однажды объя вленные как native, эти методы могут быть вызваны из прикладной прогр аммы нajava таким же образом, как и вы­ зывается любой другой метод. Чтобы объявить платфо рменно-ориентированный метод, его имя следует пр едварить модификато ром доступа na tive , но не определять тело метода , как показано ниже. puЫic native int meth() ; Объя вив платформенно-ориентированный метод , нужно написать его и пред­ принять ряд относительно сложных шагов, чтобы связать его с кодом Java. Большинство платформенно-ориентированных методов пишутся на С. Механизм интеграции кода на с и программы наJ ava называется интерфейсомJNI аava Native Interfac e). Подр обное описание интерфейса JNI выходит за рамки данной кн и­ ги, но предложенное ниже краткое описание дает достаточное представле ние для простого применения этого интерфейса. На заметку! Кон кретн ые действия, которые следУет предп ринять, зависят от применяемой сре­ ды Java, а та кже от языка, испол ьзуемого для реализации платформенно-ориентированных методов. Приведенный ниже пример опирается на cpeдy Wi пdows дл я реал изации платфор­ менно-ориентированного метода на языке С. Кроме то го, в рассмат риваемом здесь при­ мере применяется динамически подкл ючаемая библиотека , но в версии JDK 8 появилась возможность созда вать и стати чески подключаемую библиотеку. Самый простой способ понять процесс - расс мотреть его на конкретном при­ мере. Ниже представлена короткая программа, в кото рой испол ьзуется платфор­ менно-ориентированный метод test ().Можете ввести ее исходн ый те кст. / / Прос той пример применени я платформенно- ориентированного ме тода puЫ ic class Nat ive Demo { int i; puЫ ic static void ma in ( String args[] ) Nat iveDemo оЬ = new NativeDemo () ; ob.i = 10;
382 Часть 1. Яэы к Java System.out . println ( "Coдepжимoe переменной ob .i перед вызовом платформенно-ориентированного ме тода :" + ob .i) ; ob .test (); // вызвать пл атформенно-ориентиров анный ме тод System. out . println ("C oдepжимoe переменной ob .i после вызова пл атформенно-ориентированного ме тода :" + ob . i) ; 11 объявить платформенно-ори ентированный ме тод puЫ ic native void test () ; 11 загрузить библи отеку DLL , содержащую статический ме тод static { System. loadLibrary ( "Native Demo" ) ; Обратите внимание на то , что метод test () объя в.лен как native и не имеет тел а. Это метод, который будет реализован далее на языке С. Обратите также в ни­ мание на блок оператора static. Как пояснялось ранее, блок, объявленный как static, выполняется только один раз при запуске программы, а точнее говоря, при первой загрузке ее класса. В данном случае он служит для загрузки динами­ чески подключаемой библиотеки , которая содержит реализацию метода test (). (Ниже будет показано, как создать такую библиотеку. ) Динамически подключаемая библиотека загружается методом loadL i b rary (), входя щим в состав класса S y stem. Общая форма этого метода приведена ниже. static vo id loadLibrary (String имя_файпа) Здес ь параметр имя_ файла обозначает символьную строку, в которой задает­ ся имя файла, содержащего библиоте ку. Для среды Windows предполагается , что этот файл имеет расширение . DLL . Введя исходный текст рассматриваемой здесь программы, скомпилируйте ее, чтобы получить файл Native Demo . class. Затем воспол ьзуйтесь инструменталь­ н ым средством javah . exe , чтобы создать заголовочный файл Native Demo .h (это инструментальное средство входит в состав JDK) . Файл Nati ve Demo . h вам предстоит включить в свою реализацию метода test (). Чтобы получить файл Na tive Demo . h, выполните следующую команду: javah -jni NativeDeшo Эта команда создает файл N а t i ve Demo . h, который должен быть включен в ис­ ходный файл С, реализующий метод test ().Ниже приведен результат выполне­ ния приведенной выше команды. / * DO NOT EDIT THIS FILE - it is machine generated */ fi i ncl ude <jni .h> /* Heade r for class Native Demo */ #i fnde f Included Native Demo #de fine Included - Nat ive Demo #ifde f cplusplus extern "С" { #endi f /* * Class : Nat ive Demo * Method : test
* Signature : ()V */ Гл ава 1 З. Ввод-вывод, аплеты и проч ие вопросы 383 JNIEXPORT void JN I CALL Java_NativeDemo test (JNIEnv *, jobj ect) ; #ifdef �c plusplus ) #endi f #endi f Обратите особое внимание на следующую строку кода, которая определяет прототи п создаваемой вами функции test (): JN IEX PORT void JNI CALL Java _ NativeDemo _ test (JNIEnv *, jobject) ; Следует иметь в виду, что функция будет называться Java_Nat ive Demo_test (). Именно так и следует называть реализуемую вам и платформенно-ориентирован­ ную функцию. Это означает, что вместо функции t е s t ( ) вы создаете на С функцию Java_N ative Demo_ test (). Часть Native Demo в префиксе добавляется, поскольку она обозначает, что метод test () является членом класса Native Demo . Не следует забывать, что в другом классе можно объявить свой метод t е s t ( ) , совершенно не похожий на тот, который объявлен в классе Na t i ve Demo . Поэтому для различения разных версий этого метода в его имя в качестве префикса включается имя класса. Как правило , платформенно-ориентированным функциям присваивается имя, со­ держащее в качестве префикса имя класса, в кото ром он объявлен. Создав требующийся заголовочный файл , можете написать свою реализацию метода test () и сохранить ее в файле Native Demo . с. Ниже приведен пример ре­ ал и зации этого метода на С. /* Этот файл содержит версию ме тода test () на С. * / #include <j ni . h> #include "Native Demo .h" #incl ude <std io .h> JNIEXPORT void JNICALL Java _ NativeDemo_test (JNIEnv *env , jobj ect obj ) { jclass cl s; j fieldI D fid; jint i; printf ("З aпycк платформенно-ориентированного ме тода.\n ") ; cls = (*e nv ) ->GetObj ectClass (env, ob j ); fid = (*e nv ) ->GetFieldID(env , cls, "i", "I") ; if(fid == 0) { рrintf ("Невозможно получить поле id . \n ") ; retur n; i (*env) ->GetintField (env, obj , fid) ; printf ("i = %d\n" , i) ; (*env) - >Seti ntField ( env, obj , fid, 2*i) ; рrintf("З авершение платформенно- ориентиро ванного ме тода .\n ") ; Как видите, этот файл включает в себя заголовок j ni . h, содержащий интерфейс­ ную информацию. Этот файл поставляется вместе с компиляторомJava. Напомним, что заголовочный файл Na t i ve Demo . h создан ранее по команде j avah.
384 Часть 1. Язык Java В этой функции метод GetObj ectClass () служит для получения структу· ры С, содержащей данные о кл ассе Native Demo . Метод GetFieldID () воз· вращает структуру С, соде ржащую данные о поле i из этого кл асса. А метод Ge tintField () извле кает исходное значение этого поля и сохраняет его обнов· ленное значение (дополнительные методы, управля ющие другими типами дан· ных,см.вфайлеjni.h). Создав исходный файл Nat i v e Demo . с, его следует скомпилировать, а затем сформировать динамически подключаемую библиотеку. Для того чтобы сделать это с помощью комп илятора Microsoft С/С++, введите в командной строке следу· ющую команду, дополнительно указав, если потребуется , путь к файлу j ni . h и его подчиненному файлу j ni _md . h: Cl /LD NativeDemo .c По этой команде будет создан файл Nati veDemo . d l l . И только после этого мо­ жете запустить программу нaJava, которая выдаст следующий результат: С одер жим ое переменной ob .i перед вызовом платформе нно-ор иентир ованного ме тода : 10 Запуск пла тформе нно- ориентированного ме тода . i=10 За вершение пл атформе нно-ор ие нтированног о метода . Соде р жимое пер еменной ob .i после выз о ва пла тформенно-ор иентированного м етода : 20 Трудности, связанные с платформенно­ ориентированными методами Платформенно-ориентированные методы выглядят многообещающе, посколь­ ку позволяют получить доступ к существующей базе библиотечных подпрограмм , а также надеяться на высокую скорость работы программ. Но с этими методами связаны две значительные трудности . • Потенциальный риск нарушения безопасности. Платформенно-ориенти· рованный метод выполняет конкретный машинный код, поэтому он может получ ить доступ к любой части системы. Это означает, что платформенно· ориентированный код не ограничивается только исполняющей средойjаvа, а следовательно, он несет в себе, например, угрозу заражения вирусами. Именно по этой причине в аплетах нел ьзя использовать платформенно-о ри· ентированные методы . Кроме того, загрузка динамически подключаемых библиотек может быть ограничена и произведена тол ько с разрешения дис· петчера защиты . • Потеря переносимости . Платформенно·ориентированный код содер· жится в динамически подключаемой библиотеке , поэтому он должен б ыть представлен на той машине, которая выполняет программу нa Java. Более того , каждый платформенно-ориентированный метод зависит от конкрет· наго процессора и операционной системы, а следовательно, каждая дина· мически подкл ючаемая библиотека оказывается непереносимой. Та к им
Глава 1 3. В вод- вывод, аплеты и прочие вопросы 385 образом, прикладная программа на Java, в которой применяются плат­ форменно-ориентированные методы, может выполняться тол ько на той машине, на которой ус тановлена совместимая динамически подключаемая библиотека. Применение платформенно-ориентированных методов должно быть ограни­ чено, пос кольку они делают прикладную программу наJava непереносимой и пред­ ставляют существенный риск нарушения безопасности . Применение ключевого слова assert Еще одн им относ ительно новым дополнением языка jаvа является ключевое слово assert. Оно используется на стадии разработки программ дл я создан ия так называемых у тверждений - условий, которые должн ы быть истинными во время выполнения программы. Например, в программе может быть метод, ко­ торый всегда возвращает положител ьное целое значение. Его можно проверить утверждением, что возвращаемое значение больше нуля, используя оператор assert. Если во время выполнения программы условие оказывается истинным, то никаких де йствий больше не выполняется. Но если условие окажется ложным , то ге нерируется исключение типа As sertion Error. Ут верждения часто приме­ няются с цел ью проверить, что некоторое ожидаемое условие действительно вы­ полняется. В коде окончател ьной верс ии программы утверждения, как правило , отсутствуют. Ключевое слово assert имеет две формы. Первая его форма выглядит следую­ щим образом: assert условие ; где условие обозначает выражение, в результате вычисления которого должно быть получено логическое значение. Есл и это логическое значение true, то ут­ верждение истинно и никаких действий больше не выполняется. Если же вычис­ ление условия дает логическое значение false, то утверждение не подтверждает­ ся и по умолчанию гене рируется объект исключения типа AssertionError. Ниже приведе на вторая форма операто ра assert. as sert усло8Же : 1Шражение ; В этой верс ии выражение обозначает значение, кото рое передается конструк­ то ру класса исключения As sertionError. Это значение преобразуется в строко­ вую форму и выводится , есл и утвержде ние не подтверждается. Как правило , в ка­ честве выражения задается символьная строка, но, в общем, разрешается любое выражение, кроме типа void, при условии , что оно допускает приемлемое строко­ вое преобразован ие. Ниже приведен пример программы, де монстрирующий применение операто­ ра assert. В этом примере проверяется , что метод getnum () возвращает положи­ тельное значение. 11 Продемонстрировать применение опера тора assert class As sertDemo {
386 Часть 1.Яэык Java static int val = З; // возвратить целочисленное значение static int getnum () ( return val - -; puЫ ic static vo id ma in (String args [J ) ( int n; for(int i=O; i < 10; i++) ( n = getnum( ); assert n > О; 11 не nодтвердит ся, если n == О System.out . println ("n равно " + n) ; Чтобы разрешить проверку утверждений во время выполнения , следует указать параметр -еа в ко мандной строке . Например, для проверки утверждений в классе As sertDemo нужно ввести следующую команду: java -еа AssertDemo После компиля ции и запуска только что описанным образом эта программа вы· водит следующий результат: n равно З n равно 2 n равно 1 Exception in thread "main" java .lang .AssertionError at As sertDemo .main (AssertDemo. jav a: l 7) <Исключ ение в пото ке "main" java .lang .AssertionError при вызове As sertDemo .main (AssertDemo. jav a: l 7) > В методе ma in ( ) выполняются повторяющиеся вызовы метода getnum {), воз· вращающего целочисленное значение. Это значение присваивается переменной n, а затем проверяется в операторе assert, как показано ниже . assert n >О; // не подтвердится, если n == О Исход вычисления этого оператора окажется неудачным, т. е. утверждение не подтвердится , если значение переменной n будет равно нулю, что произойдет П()с еле четвертого вызова метода ge tnum ().Икогда это произо йдет, будет сгенериро­ вано исключение. Как пояснялось выше, имеется возможность задать сообщение, которое выво­ дится , если утверждение не подтверждается. Та к, если подставить следующее уr­ верждение в исходный код из предыдущего примера программы: assert n > О : "n отрица тель ное !"; то будет выдан такой результат: n равно З n равно 2 n равно 1
Гл ава 1 З. Ввод- вывод, аппеты и прочие вопросы 387 Exception in thread "main" java .lang .AssertionError : n отрицательное! at As sertDemo .main (AssertDemo.java :l7) Для правильного понимания уrверждений важно имеТь ·в виду следующее: на них нельзя полагаться для выполнение каких-щ1будь конкретных действий в программе. Дело в том, что отлаженный код окончательной версии программы будет выполняться с отключенным режимом проверки уrверждений. Рассмотрим в качестве примера следующий вариант предыдущей программы: // Неудачное применение оператора assert! !! class As sertDemo { // получи ть генератор случайных чисел static int val = 3; // возвратить целочисленное значение static int getnum() { return val -- ; puЬlic static void main ( St ring args [] ) { intn=О; for(int i=O; i < 10; i++) assert (n = getnum( )) >О; 11 Неудачная идея ! System.out . println ("n is " + n) ; В этой версии програм м ы вызов метода getnum () перенесен в оператор assert. И хотя такой прием оказывается вполне работоспособным, когда акти­ визирован режим проверки угверждений, отключение этого режима приведет к неправильной работе програм м ы, потому что вызов метода getnum () так и не произойдет! По существу, значение переменной n должно быть теперь инициали­ зировано, поскольку компилятор выявит, что это значение может и не быть при­ своено в операторе assert. Ут верждения являются полезным нововведением в Java, потому что они упро­ щают проверку ошибок, часто выполняемую на стадии разработки . Так, если до поямения уrверждений нужно бьvю проверить, что переменная n имеет в при­ веденном выше примере программы положительное значение, то пришлось были написать последовательность кода, аналогичную следующей: if(n<0){ System. out .println ("n отрицательное !") ; return ; // или сгенерировать исключение А с поямением уrверждений для той же самой проверки требуется только одна строка кода. Более того, строки кода с оператором assert не придется удалять из окончательного варианта прикладного кода.
388 Часть 1. Язык Java Параметры включения и отключения режим а п р оверки утве.рждений При выполнении кода можно отключить все угверждения , указав параметр - da . Включить или отключить режим проверки угверждений можно для конкрет­ ного пакета (и всех его внутренних пакетов), указав его имя и три точки после па­ раметра -еа или -da . Например, чтобы включить режим проверки угверждений в пакете MyPack, достаточно ввести следующее: -еа: МуРасk ..• А для того чтобы отключить режим проверки угверждений, ввести следующее: -da:МуРасk •.. Кроме того , класс можно указать с параметром -еа или -da . В качестве приме­ ра ниже показано, как включить режим проверки угверждений отдельно в классе AssertDemo . -еа : AssertDel ll o Статически й и мпорт В Java имеется языковое средство, расширяющее возможности ключевого сло­ ва import и называемое статu'Ческuм импортом. Оператор import, предваряемый ключевым словом static, можно применять для импорта статических членов класса или интерфейса. Благодаря статическому импорту появляется возмож· ность ссылаться на статические члены непосредственно по именам, не угочняя их именем класса. Это упрощает и сокращает синтаксис, требующийся для работы со статическими членами. Чтобы стала понятнее польза от статического импорта; начнем с примера, в котором он не используется. В приведенной ниже программе вычисляется гипо­ тенуза прямоугольного треугольника. С этой целью вызываются два статических метода из встроенного вjava класса Ma th, входящего в пакет java . lang. Первый из них - метод Маth . pow () - возвращает числовое значение, возведенное в ука· занную степень, а второй - метод Math . sqrt () - возвращает квадратный корень числового значения аргумента. 11 Вычисли ть длину гипотенузы прямоугол ьного треугольника class Hypot { puЫ ic static void main (String args []) { douЫ e sidel, side2 ; douЫe hypot ; sidel = 3.0; side2 = 4.0; 11 Обратите внимание на то , что име на ме тодов sqrt () и pov () 11 должны быть уточнены именем их класса - Мath hypot = Ma th .sqrt (Math .pow ( sidel, 2) + Math .pow(side2 , 2) ); System. o ut . println ( "Пpи заданной длине сторон " +
Гл а ва 1 З. Ввод -вывод, аплеты и прочие вопросы 389 sidel+"и"+side2+ "гипотенуза равна " + hypot ); Методы p ow () и sqrt () являются статическими, поэтому они должны быть вы­ званы с указанием имени их класса - Маth. Это приводит к следующему громоздко­ му коду вычисления гипотенузы: hypot = Ma th .sqrt (Math .pow (sidel, 2) + Math .pow (side2, 2) ); Как показывает данный простой пример, очень неудобно указывать каждый раз имя класса при вызове методов p o w () и sqrt () или любых других встроенных вJava методов, выполняющих математические операции наподобие sin (),cos () и tan (). Подобных неудобств можно избежать, если воспользоваться стати ческим им­ портом, как показано в приведенной ниже версии программы из предыдущего примера. // Восполь зоваться статическим импортом для доступа // к встроенным в Java методам sqrtО и powО import static java.lang .Math .sqrt; import static java . lang .Math . pow; // вычисли ть гипотенузу пр ямоуг ол ьного треугольника class Hypot { puЫ ic static void main (String args[]) { douЫ e sidel, side2; douЫe hypot ; sidel = 3.0; side2 = 4.0; 11 Здесь ме т оды sqrt () и pow () можно вызывать 11 непосредственно , опуская имя их класса hypot = sqrt (pow (sidel, 2) + pow (side2, 2) ); System. out . println ( "Пpи заданной длине сторон " + sidel+"и"+side2+ " гипотенуза равна " + hypot ); В этой версии программы имена методов sqrt ( ) и p o w ( ) становятся видимы­ ми благодаря оператору статического импорта, как показано ниже. import static java .lang .Math .sqrt ; import static java .lang .Math . pow; После этих операторов больше нет нужды уточнять имена методов pow ( ) и sqrt () именем их класса. Таким образом, вычисление гипотенузы может быть выражено более удобн ым способом, как показано ниже . Как видите, эта форма не тол ько упрощает код, но и делает его более удобочитаемым. hypot = sqrt (pow (sidel, 2) + pow (side2, 2) );
390 Часть 1. Язык Java Имеются две основные формы оператора import static. Первая форма, упо­ треблявшаяся в предыдущем примере программы, делает видимым единственное имя. В общем виде эта форма статического импорта такова: i.Jlport static паже!I'. -- !l'JШa .- _c!l'a!l'ЖVEICltaro_ vлема ; где имя_ типа обозначает имя класса или интерфейса, который содержит требуе­ мый статический член. Полное имя его пакета указано в части па кет, а имя чле­ на - в части имя статического члена. - - Вторая форма статического импорта позволяет импортировать все статиче- ские члены данного класса или интерфейса. В общем виде эта форма выглядит следующим образом: i.Dport static паже!l' .- _!l'JШа .*; Если предполагается применять много статических методов ил и полей, определенных в классе, то эта форма позволяет сделать их доступными, не ука· зывая каждый из них в отдельности . Та к, в предыдущем примере программы с помощью ед инственного оператора impo rt можно сделать доступными мето­ ды pow () и sqrt (), а также все осталъные статические чле ны класса Math, как показано ниже. import static java.lang .мath .*; Разумеется , статический импорт не ограничивается только классом Ma th или его методами. Например, в следующей строке становится доступным статическое поле System. out. import static java.lang . System.out ; После этого оператора данные можно выводить на консоль, не уточняя стан· дартный поток вывода out именем его класса System, как показано ниже. out .println ( "Импopтиpoвaв стандартный поток вывода System. out , " + "можно польэоваться им непосредственно .") ; Те м не менее приведенный выше способ импорта стандартного потока вывода Sy s tem. ои t столь же удо бен, сколь и полемичен. Несмотря на то что такой способ сокращает исходный текст программы, тем, кто его читает, не вполне очевидно, что out обозначает System. out. Следует также иметь в виду, что, помимо импор­ та статических членов классов и интерфейсов, определенных в прикладном про­ граммном интерфейсеJаvа .Аfl, импортировать статическим способом можно так· же статические члены своих собственных классов и интерфейсов. Каким бы удобным ни был статический импорт, очень важно не злоупотре­ блять им. Не следует забывать, что библиотечные классы Java объединяются в пакеты для того, чтобы избежать конфликтов пространств имен и непреднаме· ренного сокрытия прочих имен. Если статический член используется в приклад· ной программе только один или два раза, то его лучше не импортировать. К тому же некоторые статические имена, как, например, System.out, настолько при· вычны и узнаваемы, что их вряд ли стоит вообще импортировать. Статический импорт следует оставить на тот случай , если статические члены применяются многократно, как, например , при выполнении целого ряда математических вы·
Гл ава 1 З. Ввод-вы вод, аплеты и прочие вопрось1 391 числений. В сущности , этим языковым средством сто ит пользо ваться, но только не злоупотреблять им. Вызов перегружаем ых конструкторов по ссылке this () Пользуясь перегружаемыми конструкторами , иногда удо бно вызывать один конструктор из другого. Для этого вJava имеется еще одна форма ключевого слова thi s. В общем виде эта форма выглядит следующим образом: this ( с:пжсох_арr,УНен!l'оа) По ссылке this () снач ала выполняется перегружаемый ко нструктор, кото­ рый соответствует заданному списку_ аргументов , а затем - любые операто­ ры, находящиеся в теле исходного конструктора, если таковые имеются. Вызов конструктора п о ссыл ке this () должен быть первым оператором в конструк­ торе. Чтобы стал о понятнее, как пользо ваться ссылкой this (), обратимся к кратко­ му примеру. Рассмотрим сначала приведенный ниже прим�р класса, в котором ссылка this () не используется. class MyClass { int а; int Ь; 11 инициализировать поля а и Ь по отдельности MyClas s (int i, int j) { а i; ь=j; 11 инициализировать поля а и Ь одним и тем же значением MyClass ( int i) { а i; ь=i; 11 присвоить полям а и Ь нуле вое значение по умолчанию MyClass ( ) а О; Ь=О; Этот класс содержит три конструктора, каждый из которых инициализирует значения полей а и Ь. Первому конструктору передаются отдельные значения для инициализации полей а и Ь. Второй кон структор принимает только одно зна­ чение и присваивает его о боим полям, а и Ь. А третий присваивает полям а и Ь нулевое значение по умолчанию. Используя ссылку this (), приведенный выше класс MyClass можно перепи­ сать сл едую щим образом:
392 Часть 1. Язык Java class MyClass int а; int Ь; 11 инициализировать поля а и Ь по отдельности MyClass (int i, iпt j) { а i; ь=j; // инициализировать поля а и Ь одним и тем же значением MyClass (iп t i) { this (i, i) ; // по этой ссылке вызывается // конструктор МyClaaa (i , i) ; // присвоить полям а и Ь нулевое значение по умолч а нию MyClass( ) { this (O) ; // а по э той ссылке вызывается 11 конс труктор .МуСlааа (О) В этой версии класса МуС las s значения полям а и Ь в действительности присва· иваются только в конструкторе MyClass (int , int ) . А два других конструктора просто вызывают первый конструктор (прямо или косвенно) по ссылке this ( ) . Рассмотрим, например , что произойдет, если выполнить следующий оператор: MyClasз mc = new МуСlазз ( В ); Вызов конструктора MyClass ( 8) приводит к выполнению ссылки this ( 8, 8), которая преобразуется в· вызов конструктора MyClass ( 8, 8), поскольку именно эта версия конструктора класса MyClass соответствует списку аргументов, переда· ваемому по ссылке this ().Атеперь рассмотрим следующий оператор, в котором используется конструктор по умолчанию: MyClasз mc2 = пеw МуСl азз () ; В данном случае происходит обращение по ссылке this (О) , приводящее к вы· зову конструктора MyClass (О) , поскольку именно эта версия конструктора соот­ ветствует списку параметров. Разумеется , конструктор MyClass (О) вызывает да· лее конструктор MyClass (О,О) , как пояснялось выше . Одной из причин, по которой стоит вызывать перегружаемые конструкторы по ссылке this ( ) , служит потребность избежать дублирования кода. Зачастую со­ кращение дубл ированного кода ускоряет загрузку классов, поскольку объектный код становится компактнее. Это особенно важно для программ, доставляемых че­ рез Интернет, когда время их загрузки критично. Применение ссылки this ( ) по­ зволяет также оптимально структурировать прикладной код, когда конструкторы содержат большой объем дублированного кода. Следует, однако , иметь в виду, что конструкторы, вызывающие другие кон· структоры по ссылке this () , выполняются медленнее, чем те, что содержат весь код, необходимый для инициализации. Дело в то м, что механизм вызова и возвращения, используемый при вызове второго конструктора, требует до­ полнительных издержек. Если класс предполагается употреблять для создания
Гл ава 1 З. Ввод- вывод, аплеты и прочие вопросы 393 небольшого количества объектов или если его конструкторы, вызывающие друг друга по ссылке this (), будут использоваться редко, то снижение производи­ тельности во время выполнения, скорее всего , окажется незначительным. Но если во время выполнения программы предполагается создание большого коли­ чества (порядка тысяч) объектов такого класса, то дополнительные издержки, свя зан ные с вызовом одних конструкторов из других по ссылке this () , могут отрицательно сказаться на производительности. Создание объектов затрагивает всех пользователей такого класса, и поэтому придется тщательно взвесить пре­ имущества более быстрой загрузки в сравнении с увеличением времени на созда­ н ие объекта. Следует также иметь в виду, что вызов очень коротких конструкторов, как, на­ пример, из класса MyClass, по ссьшке this () зачастую лишь незначительно уве­ личивает размер объектного кода. (В некоторых случаях никакого уменьшения объема объектного кода вообще не происходит.) Дело в том, что байт-код, кото­ рый устанавливается и возвращается из вызова конструктора по ссылке this (), добавляет инструкции в объектный файл. Поэтому в таких случаях вызов кон­ структора по ссылке this (),несмотря на исключение дублирования кода, не даст значительной экономии времени загрузки , но может повлечь за собой дополни­ тельные издержки на создание каждого объекта. Поэтому применение ссьшки this () больше всего подходит для вызова тех конструкто ров, которые содержат большой объем кода инициализации, а не тех, которые просто устанавливают зна­ чен ия в нескольких полях. Вызывая конструкторы по ссьшке this (), следует учитывать следующее. Во­ первых, при вызове конструктора по ссьшке t his () нельзя использовать пере­ менные экземпляра класса этого конструктора. И во-вторых, в одном и том же конструкто ре нельзя использовать ссылки super () и this (), поскольку каждая из них должна быть первым оператором в конструкторе. Компактные профили Java API В версии JDK 8 внедрено средство, позволяющее организовать подмножества библиотеки прикладных программных интерфейсов API в так называемые ком­ пактные профили. Они обозначаются следующим образом: cornpactl, cornpact2 и cornpactЗ. Каждый такой профиль содержит подмножество библиотеки. Более то го , компактный профиль cornpact2 включает в себя весь профиль cornpa ctl, а компактный профиль cornp a ctЗ - весь профиль cornpact2. Следовательно, каж­ дый последующий компактный профиль строится на основании предыдущего. Преимущество компактных профилей заключается в то м, что прикладной про­ грамме не нужно загружать библиотеку полностью. Применение компактных профилей позволяет сократить размер библиотеки, а следовател ьно, выполнять некото рые категории прикладных программ на тех устройствах, где отсутствует полн ая поддержка прикладного программного интерфейса Java API . Благодаря компактным профилям уд ается также сократить время , требующееся для загрузки программы. В документации на прикладной программный интерфейсJava API ука-
39' Часть 1. Язык Java зывается, к какому именно элементу этого прикладного интерфейса принадлежит компактный профиль, если это вообще имеет место. На стадии компиляции программы с помощью параметра командной строки - profile можно указать, какие именно эл ементы прикладного программного ин· терфейса Java API , определяемые компактн ым профил ем, следуе т использовать в программе. Н иже приведена общая форма команды, по которой программу мож· но скомпилировать именно таким способом. javac -prof'ile 11JUЯ_npoфжлsi 11JUЯ_ lJPOZ'PВ НNl l Здес ь имя_ пр офиля обозначает конкретный профил ь: compact 1, compact2 или comp a ctЗ. Напр имер: javac -prof'ile COl lp &ct2 Teat .java В этом примере команды компиляции указан профиль comp a ct2. Если же нс· ходный файл программы Te st . java содержит элемен ты прикладного программ­ ного интерфейсаJаvа АРI, отсугствующие в компактном профиле comp a ct2, то во время комп иляции возникнет ошибка.
Обобщения После выхода в 1995 году первоначальной версии 1.0 вjava было внедрено не­ мцо новых яэыковых средств. Одним иэ наиболее эначительных нововведений, повлиявших на дальнейшую судьбу Java, стали обобщ ения. Во-первых, их появлецие означало внедрение новых синтаксических элементов в яэык. Во-вторых, они nо­ цеюш эа собой иэменения во многих I<Лассах и методах баэового приI<ЛаДного программного интерфейса API. Ныне обобщения являются неотъемлемой частью программирования нa Java, и поэтому требуется ясное понимание этого важного я�ыкового средства. И в этой гл аве оно исследуется во всех подробностях. Применение обобщений поэволило соэдавать классы, интерфейсы и методы, работающие беэопасным по отношению к типам способом с раэнообраэными !!Идами данных. Многие алгоритмы логически идентичны, неэависимо от того, � АШfНЫМ каких типов они применя ются. Например, механиэм, поддерживаю­ щий стеки, является одним и тем же в стеках, храня щих элементы ти па Integer , String, Ob j e ct или Thread. Благодаря обобщениям можно определить алгоритм оди н раэ неэависимо от конкретного типа данных, а эатем применять его к об­ wирному раэнообраэию типов данных без каких-нибудь дополнительных ус илий. Впечатляющая эффективность внедренных в Java обобщений коренным образом изменила способы написания кода на этом яэыке программирования. Вероятно, од ним иэ средств Jаvа, которое в наибольшей степени испытало на себе влияние обобщений, является каркас 1щмекций Collections Framework. Этот каркас является частью прикладного программного интерфейса Java API и под­ робно обсуждается в гл аве 18, но стоит дать хотя бы краткое пояснение его назна­ чения. Кол.лекция - это группа объектов, а в каркасе коллекций определяется ряд К.l laC COB для управления такими коллекциями, как, например, списки и отображе­ ния. Классы коллекций всегда бьши способны обращаться с объектами любых ти­ пов. Но выгода от внедрения в Java обобщений в том и состоит, что классы коллек­ ций можно теперь применять с пол ной гарантией типовой беэопасности . Так им образом, обобщения явля ются не только эффективным яэыковым средством, но и способны эначительно усовершенствовать уже существующие средства. Именно поэтому обобщения являются столь значительным дополнением языка jаvа. В этой гл аве описываются синтаксис , теория и практика применения обоб­ щений . В ней будет показано, каким обраэом обобщения обеспечивают типовую беэопасность в некоторых трудных прежде случаях. Прочитав эту главу, вы, ско­ рее всего, эахотите ознакомиться с материалом гл авы 18, посвященной каркасу
396 Часть 1. Язык Java коллсКJ (И Й Collectio11s l;1·aшewoгk, где вы наiiдете немало нримеров применения обобщений на нрактике. Что та кое обобщения По существу, обоби;,ения - это пара.нппр и.юаm 11 1 ые типы. 'lакие типы важны, 110- cкoJJ J,кy они шкщол яют объя влять класс ы, интерфейсы и методы, где тин ;щнных, которыми они оперируют, ука:1ан в виде ш1.раметра . ИсноJJ J,зуя обоб щения, м ож­ но, например, соз;(а'IЪ единственный класс, который бу;(ст автомати чески обра­ щап,ся с разноти пными дан ными. Классы, интерфейсы или мст<щы, оперирую­ щие 11араметр11:юва1111 ыми типами, называютс я обо61 11,1' 1111ы.мu. Сле;(ует замети·1·1" что в Java всегда прсдоста влялжъ во:�можнос1ъ со:ща вать в то й или иноii степени обобщенные классы, интерфейсы и мето;\ы , опериру­ ющие ссыл кам и типа Ob j ect. А поскольку класс Ob j ect служ ит сунсрклассом для всех оста.11 ы1ых классов, то он по:�воляет обращаты·я к объе кту л юбо1·0 типа. Следонател ыю. в старо м коде ссылки тип<1. Obj ect ипюлt.:ювашнъ в обобщенных классах, инте рфейсах и методах с цел ью опериро вап, ра:ш отшшыми объектами. Но дело в то м, что они не м01:11 и обеснечить тшювую бс:юпас11ос1ъ. И:1.1снно обобщения внесли в язык типовую бс:юпапюсть ти 1юв, кото рой так недослш<1ло прежде . Они также упростили процесс вы110л 11с11ия, попюю,ку те­ пер1, нет нужды в явном приведении типов дл я нреобра:юванин объектов типа Obj ее t в конкретн ые ти пы обрабатыв<1емых да нных. l:iJia ГO/ (apя обобщениям все оперании прш1е;1ения типов вы пол няю·1тя авто мати 11ески и неявно. Та ким обра­ зо м, обобщения расширили во:�можности повторного ип юш.:ювания кода , позво­ лив делап. это легко и безопас но. На заметку! Программирующим на С++ следует иметь в виду, что обобщения и шаблоны в С++ - это не одно и то же, хотя они и похожи. У этих двух подходов к обобщенным типам есть ряд принципиальных отличий. Есл и у вас имеется некоторый опыт программирования на С++, не спешите дел ать поспешные выводы о то м, как обобщения действуют в Java. Про сто й п р имер обобщения Начнем с простого примера обобщенного класса. В 111ншсде шюй ниже нро­ грамме определяются два класса. Первый и:1 них - обобщенный класс Gen, вто­ рой - де монстрационный класс GenDemo , в кото ром 11с1юл 1,ауt�то1 обобщенный класс Gen. 11 П р остой обобщенный кл асс . 11 Здесь Т обозначает пар аметр типа , 11 который будет заменен реальным типом 11 при создании объекта типа Gen class Gen<T> { Т оЬ; // объявить объект типа Т 11 пер едать констр уктор у ссыл ку на объе кт типа Т Gen(TО) {
оЬ о; 11 � озвратить объе кт оЬ Т getob() { return оЬ ; Глава 14. Обобщения 397 11 показать тип Т void showType () { Systern.ou i .println ( "Tипoм Т является "+ob . getClass () .getNarne() ); 1 1 продемо нс трировать приме нение обобщенного кл асса class GenDerno { puЫ ic static void main (String args[)) { 11 Создать ссылку типа Gen дл я целых чисел Gen< Integer> iOb; 11 Создать объект типа Gen<Integ8r> и присвоить 11 ссылку на него переменной iОЬ . Обратите внимание на 11 приме нение автоупаковки дл я инкапсуляции значения 88 11 в объекте типа Inteqer iOb = new Gen< Integer> (88) ; 11 показать тип данных , хранящихся в переменной iОЬ iOb.showType () ; 11 получить значение переме нной iОЬ . Обратите внимание на то, 11 что для этого не требуе тся ни какого приведения типов int v = iOb.getob (); Systern. out . println ( "Знaчeниe : "+v) ; Sys tern. o ut . println (); 11 создать объе кт типа Gen для символьных строк Gen<String> strOb = new Gen<String> ("Тест обобщений" ) ; 11 показать тип данных , хранящих ся в переме нной strOЬ strOb . showType () ; 11 получ ить значение переме нной strOЬ . И в этом случае 11 приведение типов не требуе тся String str = strOb. ge tob () ; Systern.out . println ("З нaчeниe : "+str) ; Ниже приведен результат, выводимый данной программой. тхпом Т является java .lang . Integer Значение : В В Типом Т является java . lang . St ring Зна чение : Тест обобще ний Внимательно проанализируем эту программу. Обратите внимание на объявле­ ние класса Gen в следующей строке кода: c�ss Gen<T> {
398 Часть 1. Язык Java где Т обозначает имя параметра типа. Это имя используется в качестве заполнител.w� вместо которого в дальнейшем подставляется имя конкретного типа, передаваеМО! го классу Gen при создании объекта. Это означает, что обозначение Т применяетЦ в классе Gen всякий раз, когда требуется параметр типа. Обратите внимание на ТОi что обозначение Т заключено в угловые скобки ( <> ) . Этот синтаксис может бЬ1'1 1� обобщен. Всякий раз. когда объявляется параметр типа, он указывается в угл овыа скобках. В классе Gen применяется параметр типа, и поэтому он является обобщеlt ным классом, относящимся к так называемому пар аметризоваииаму типу. Далее тип Т используется для объявления объекта оЬ: Т оЬ ; // объя вить объект типа Т Как упоминалось выше , параметр типа Т - это место для подстановки коНJс� ного ти па, который указывается в дальнейшем при создании объекта класса Gen. Это означает, что объект оЬ станет объектом того типа, кото рый будет передаlr в качестве параметра типа Т. Та к, если передать тип String в качестве параметра типа Т , то такой экземпляр объекта оЬ будет иметь тип S t r ing. Рассмотрим далее конструктор Gen ( ) . Его код приведен ниже. Gen(Т о) { оЬ=о; Как видите , параметр о имеет тип Т. Это означает, что конкретный тип пара-] метра о определяется с помощью параметра типа Т. передаваемого при созданИй; объекта класса Gen. А поскольку параметр о и переменная экземпляра оЬ oтнOCJrl l- i ся к.типу Т , то они получают одинаковый конкретный тип при создании объеК'm; класса Gen. , Параметр типа Т может быть также использован для указания типа, возвраща-1 емого методом, как показано ниже на примере метода getob (). Объект оЬ также , относится к типу Т, поэтому его тип совместим с типом, возвращаемым методом getob (). Т getob() { return оЬ ; Метод showT ype () отображает тип Т, вызывая метод getName () для объекта ти па Class, возвращаемого в результате вызова метода getClass () для объекта' оЬ. Метод getClass ( ) определен в классе Obj ect, и поэтому он является членщ1 всех классов. Этот метод возвращает объект типа Class, соответствующий типу того класса объекта, для которого он вызывается. В классе Class определяСТС. метод getName () , возвращающий строковое представление имени класса. " Класс GenDemo служит для демонстрации обобщенного класса Gen. Сначала в нем создается версия класса Gen для целых чисел, как показано ниже. Gen< Integer> iOb; Проанализируем это объявление внимательнее. Обратите внимание на то, � тип Integer указан в угловых скобках после слова Gen. В данном случае Integer :,:. это аргумент типа. который передается в качестве параметра типа Т из класса Gen. Это объявление фактически означает создание версии класса Gen, где все ссьvtки
Глава 14. Обобщения 399 натип Т преобразуются в ссылки натип Integer. Та ким образом, в данном объявле­ нии объект оЬ относится к типу Integer, и метод gеtоЬ () возвращает тип Integer. Прежде чем продолжить дальше, следует сказать, что компилятор Java на са­ иом деле не создает разные версии класса Gen или любого другого обобщенно­ го класса. Те оретически это было бы удобно, но на прцтике дело обстоит иначе. Вместо этого компилятор удаляет все сведения об обобщенрых типах, выполняя необходимые операции приведения типов, чтобы сделать поведение прикладного кода таким, как будто создана конкретная версия класса Gen . Таким образом, име­ ется только одна версия класса Gen , которая существует в прикладной программе. Процесс удаления обобщенной информации об обобщенных типах называется стиранием, и мы еще вернемся к этой теме далее в главе. В следующей строке кода переменной iOb присваивается ссылка на экземпляр целочисленной версии класса Gen: iOQ = new Gen< Integer> (88) ; Обратите внимание на то , что , когда вызывается конструктор Gen ( ) , аргумент типа Integer также указывается. Это необходимо потому, чТо объект (в данном случае - iOb}, которому присваивается ссылка, относится к типу Gen<Integer>. �довательно, ссылка , возвращаемая оператором new, также должна относиться к rnny Gen< Integer>. В противном случае во время компиляции возникает ошиб­ ка. Например, следующее присваивание вызовет ошибку во время компиляции: iOb = new Gen<DouЬl e> (BB.0) ; // ОШИ БКА ! Переменная iOb относится к типу Gen< Integer>, поэтому она не может быть использована для присваивания ссылки типа Gen<DouЫe>. Такая проверка типа является одним из основных преимуществ обобщений, потому что она обеспечи­ вает типовую безопасность. Не заметку! Как будет показано далее в этой гл аве, в версии JDK 7 появилась возможность упо­ треблять сокращен ный синтаксис для созда ния экземпляра обобщенного класса . Но ради ясности изложения в да нном случае испол ьзуется полный синтаксис. Как следует из комментариев к данной программе, в приведенном ниже при­ сваивании выполняется автоупаковка для инкапсуляции значения 88 типа int в объекте типа Integer. iOb 2 new Gen< Integer> (BB) ; 'Такое присваивание вполне допустимо, поскольку обобщение Ge n< Integer> соэдает конструктор, принимающий аргумент типа Integer. А поскольку пред­ полагается объект типа Integer, то значение 88 автоматически упаковывается в этом объекте. Разумеется, присваивание может быть написано и явным образом, как показано ниже, но такой его вариант не дает никаких преимуществ. iOb = new Gen<Integer> (new Integer (BB) ); •' Затем в данной программе отображается тип объекта оЬ в переменной iOb (в данном случае - тип Integer) . А далее получается значение объекта оЬ в следу­ ющей строке: int v = iOb .getob() ;
400 Часть 1. Язы к Java Метод getob () возвращает обобщенный тип Т, который был заменен на тип Integer при объявлении переменной экземпляра iOb. Поэтому метод getob () также возвращает тип Integer, который автоматически распаковывается в тип int и присваивается переменной v типа int. Следовательно, тип, возвращаемый методом getob (),нет никакой нужды приводить к типу Integer. Безусловно, вы· полнять автоупаковку необязательно, переписав предыдущую строку кода так, ках показано ниже . Но автоупаковка позволяет сделать код более компактным. int v = iOb .getob () .intValue (); Далее в классе GenDemo объявляется объект типа Gen<String> следующим об­ разом: Gen<String> strOb = new Gen<String> ("T ecт обобщений" ) ; В качестве аргумента типа в данном случае указывается тип S tring, подставля­ емый вместо параметра типа Т в обобщенном классе Gen. Это , по существу, приво­ дит к созданию строковой версии класса Gen, что и демонстрируется в остальной части рассматриваемой здесь программы. Обобщения дей ствуют только со ссылочными ти пами Когда объявляется экземпляр обобщенного типа, аргумент, передаваемый в ка­ честве параметра типа, должен относиться к ссылочному типу, но ни в коем случае не к примитивному типу наподобие int или char. Например, в качестве параме­ тра Т классу Gen можно передать тип любого класса, но нельзя передать примитив­ ный тип. Та ким образом, следующее объявление недопустимо: Gen< int> intOb = new Gen<int> (53) ; // ОШИБКА ! Использовать // прими тивные типы нельзя ! Безусловно, отсутствие возможности использовать примитивный тип не яв• ляется серьезным ограничением, поскольку можно применять оболочки типов данных (как это делалось в предыдущем примере программы) для инкапсуляции примити вных типов. Более того, механизм автоупаковки и автораспаковки вJava делает прозрачным применение оболочек типов данных. Обобщенные типы различаются п о аргумента м типа В отношении обобщенных типов самое гл авное понять, что ссылка на одну кон· кретную версию обобщенного типа несовместима с другой версией того же самого обобщенного типа. Так , если ввести следующую строку кода в предыдущую про­ грамму, то при ее компиляции возникнет ошибка: iOb = strOb ; // НЕВЕРНО ! Несмотря на то что переменные экзем пляра iOb и strOb относятся к типу Gen<T>, они являются ссылками на разные типы объектов, потому что их пара­ метры типов отличаются. Этим, в частности , обобщения обеспечивают типовую безопасность, предотвращая ошибки подобного рода.
Гл ава 14. Обобщения '01 Каким образом о бобщения повышают ти п овую безопас ность В связи с изложенным выше может возн икнуть следующий вопрос: если те же самые функциональные возможности, которые были обнаружен ы в обобщенном классе Gen, могут быть получены и без обобщений, т. е . простым указанием класса Obj ect в качестве типа данных и правильным приведением типов, то в чем же польза от того, что класс Gen является обобщенным? Дело в том, что обобщения автоматически гарантируют типовую безопасность во всех операциях, где задей­ ствован обобщенный класс Gen. В процессе его применения исключается потреб­ ность в явном приведении и ручной проверке типов в прикладном коде. Чтобы стали понятнее выгоды от обобщений, рассмотрим сначала следующий пример программы, в которой создает необобщенный эквивалент класса Gen: // Класс NonGen - функциональный эквивалент класса Gen без обобщений class NonGen { Ob ject оЬ ; // объе кт оЬ теперь име ет тип OЬject // передать конструктору ссылку на объе кт типа OЬject NonGen (Obj ect о) { оЬ=о; // возвратить тип OЬj ect Ob ject ge tob () return оЬ ; // показать тип объе кта оЬ vo id shoWТype () { System.out . println ( "Oбъe кт оЬ относится к типу " + оЬ . getClass () . getName () ); 11 продемонстрировать необобщенный класс class NonGenDemo { puЫic static void ma in (String args[] ) NonGen iОЬ; 11 создать объе кт типа NonGen и сохранить в нем 11 объе кт типа Integer . Вып олн яется автоупаковка iОЬ = new NonGen (88) ; // показать тип данных , хранящихся в переменной iОЬ iOb.sh owType () ; // получить значение переменной iОЬ , // на этот раз требуе тся при ведение типов int v = (Integer) iOb.getoЬ() ; System. o ut . println ( "Знaчeниe : " + v) ; System.out . println () ; // создать другой объе кт типа NonGen и 11 сохранить в нем объе кт типа Strinq NonGen strOb = ne w NonGen ("Tecт без обобщений" );
402 Часть 1. Язы к Java !! показать т и п данных , хран ящихся в перемен ной strOb st rOb . showType (); / / получи�'ь значение переме нной strOb , !/ И в этом случае потребуетсq приведение типов .St ring str = (String) s trOb . getob() ; System.out .println ("З нaчeниe : "+str) ; 11 э�·от код компилируется , но он при нципиально не1.-,ерньrй ! iOb = strOb ; v = (In tege r ) iOb.getoЬ() ; // Ошибка во время выполнени я! В это ii верс ии 11рограммы обращает на сс6я в11има11ис ря;\ и1псрсс ных момен· то в. Прежде всего, в ющссс NonGe n вес ссылю1 на ти н Т :за мс11с11ы ("СЬJЛ 1«1 ми на ти п Obj ect. :-1 то 1юзвш1яст хранить в классе NonGen о6ъскгы любо1·0 ти на, ка к и в обоб· щс111юм кл а<тс Gen. l lo это нс 11о:шоляст ком1шлн·1 ·о ру Ja\!a 11олу1 1ить к;:�кие-нибудь под;ш1111ыс све;\с ния о тине данных, фа1<Т1Р1ес ки сохраняемых в о6ъсктс класса NonGen, что 11лохо 1ю двум 11ричинам. Во-1 1ервых, J\Л Н 11:шж.·11е1шя сохра11е1111ых дан ных требуется явное 11риведе 11 ис тшюв. И во-втор ых, м1юп1с ошибки нссоот· ветспн1я тшюв не мшуг бы1ъ обнаружены 110 11рсме1111 вы 1юл11е11ия. Рассмотри�� каждый иэ этих недостатков подробнее. Обратите в11има�11н.· на слс;\ующую строку ко,�1а : int v = (In teger) iOb.getob() ; Мепщ getob () воэв1м11щст ти11 Ob ject. шхно му его 11уж110 щн1 1ктп1 к типу I n tege r, ч тобы выпоm 1ить авторас ш1 ко11ку и сохра 111п1, :ша•1с1 111е 11 11срсменной v. Есл и убрать 11риведе11ис ти11011, программа 11е с1<омниш1рустся. Есл и в се версии с о6общешшми приведение тшюв 11ро11:шод11тс н нсш11ю, то в версии беа обобщс· ний приведе ние ;\олж1ю 6ьпъ сделано я11110. Это нс тоm.ко нсу,:1об110, но 11 и1уж1 п 11оте11циа;1ы1ым ист(>чником ошибок. " lt'11ep1, рассмотрим следующий фра1·мснт IO>i\a в ко1 щс ;1а шюii 11ро1·1ыммы: 11 Этот код компилируется, но он принципиально неверньlli ! i0l) = strOb ; v = I I n t eger) iOb.getob(); // Ошибка во время выполнения' Э;1с<ъ неремешюй экаемпляра iOb нрисваи вастся :111а11с11ис не1 кмсшюii :� к:�ем· пл яра s t rOb. l lo 11среме1111ая экасмнляра s t rOb ссылается на объе кт, со;\сржащий с им1юл ы1у ю строку, а нс целое •шс;ю . Такое 11рис11а 11 11анис сшпаксически коррск· пю, нотому что все ссылки ти11а NonGen оди 11<1ковы н любан ссылюt тина NonGen может укааы ват1. на любой другой объект ти на NonGen. Но семантически эта ош.� рация нрисваиванин неверна, •по и отражено в слс;\ующсй строке ко;(е. Э;\е<ъ тин, во:шра щасмый методо м ge tob (),111ншодится к т и ну Iпt:eger, а аатс м ;\сл астен по­ пытка 11рисво1пъ нолучешюс ;111а11еш1е переменно�i v. Дело в то м. что 11среме11 11ая акаемш1яра iOb те 11ср1. ссылается на объе кт, хранящиii /\а1111ые тина String, а не I n tege r. К сожалению, беэ обобщений 1< ом1111ляп>р Ja\·a 11ро сто не в состояш1и обна руж и п . Э'f)' оши6ку. Вместо :�то н> 1ю вре:-.ш вы1ю;ше1 1ия ге нерируется исклю· 11е11ие 11ри 1ю11ытке привести к тину Integer· . llo как вам , л.олжно бы·1ъ, уже из· вестно. 11оявле11 ие ошибок во времн вы1юл11е11ия кода 11е11рш:млсмо!
Гл ава 14. Обобщения '°З Подобной бы ошибки не произошло , если бы в данной программе использова­ лись обобщения. Уп омянугая выше попытка присваивания разнотипных объектов в обобщенной верс ии данной программы была бы обнаружена уже на стадии ком­ пиля ции и не привела бы к серьезной ошибке во время выполнения. Возможность создавать типизированный (т.е . обеспечивающий типовую безопасность) код, в котором ошибки несоответствия типов перехватыва.Ются компилятором, явля­ етс я глав ным преимуществом обобщений. Несмотря на то что поль.зоваться ссыл­ ками на тип Ob ject для создания "псевдообобщенного" кода можно было всегда, не следует забывать, что такой код не обеспечивает типовую безопасность, а злоу­ пот ребление им приводит к исключениям во время выполнения. Обобщения пре­ дотвращают подобные осложнения. По существу, благодаря обобщениям ошибки, возникающие во время выполнения, преобразуются в ошибки , обнаруживаемые во время компиля ции. В этом и заключается гл авное преимущество обобщений. Обобщенный класс с двумя параметрами типа Для обобщенного типа можно об'Ьявлять не только один параметр. Два или бо- 11-ее параметра типа можно указать списком че�з запя'I)'Ю. Например, приведен­ ,Щ>IЙ н}Ц(е класс TwoGen является переделанным вариантом класса Gen, принимаю­ щим два параметра ти па. 1 1 Простой обобще нный класс с двумя параме трами типа : Т и V class TwoGen<T , V> { Т оЫ; V оЬ2; 11 передать конструктору ссылки на объе кты типа Т и V TwoGen (T ol, V о2) { оЫ ol; оЬ2 = о2; 11 показать типы Т и V vo id showТype s() { System.out .println ( "Tип Т: "+oЫ . getClass () .getName() ); System.out . println ( "Tип V: "+ob2 . getClass () . getName() ); т getoЫ () return оЫ ; v getob2 () return оЬ2 ; 11 продемо нстрировать приме нение класса ТwoGen class SimpGen { puЫic static void ma in (String args[] ) { TwoGen< Integer, String> tgObj = new TwoGen< Integer, String> (88, " Обобщени я") ;
404 Часть 1. Язы к Java 11 показать типы tgObj . showTypes () ; 11 Получить и показать значения int v = tgObj . getoЫ (J; System. out . println ( "Знaчeниe : "+v) ; String str = tg0bj . getob2 () ; System. out .pr intln ("З нaчeниe : "+str) ; Ниже приведен пример выполнения данной программы. Тип Т: java . lang . Integer Тип V: java . lang. String Значение : 88 Значение : Обобщения Обратите вниман ие на следующее объявление класса TwoGen: class TwoGen<T, V> { В это м объявлении два параметра типа Т и V задаются списком через запятую. А поскольку в объявлении этого класса указаны два параметра типа, то при созДа' нии объекта типа TwoGen должны быть переданы два аргумента типа, как по:каз<lн'6 rJ_I ниже . TwoGen< Integer, String> tgOb j = new TwoGen< Integer, S tring> (88, "Обобщения ") ; В этом случае тип Integer подставляется вместо параметра типа Т, а тип String - вместо параметра типа V. В данном примере оба аргумента типа отличаются , тем не менее вполне допу­ стимо передавать в качестве параметров два одинаковых типа. Например, следую­ щая строка кода написана правильно: TwoGen<String, String> х = new TwoGen<String, String> ("А" , "В") ; В этом случае оба аргумента, V и Т, будуг иметь тип String. Ясно, что если оба аргумента ти па всегда од инаковы, то два параметра типа не нужны. Общая форма обобщенного кл а сса Синтаксис, предсгавленный в предыдущих примерах программ, может быть обобщен. Ниже показано, как выглядит синтаксис объявления обобщенного класса. class ах._жла сса< сп•сох_параNе!l'рОв_!IWna> { / / . .. А синтаксис объявления ссылки на обобщенный класс и создание его экземпля­ ра полностью выглядит следующим образом: ах. жласса< сп•сож apryxeн 'l'oa !IWпa> 1U Ul перенен н ой= 081f IOd_жла сса<сmrсож_В�8Н'l'ОВ_ 'l'ea> ( CП•CO.Jr_8prYJ1�708_Jt'OНC!l'&Н'l') ;
Ограниченны е тип ы Гл ава· 14. Обобщен ия '05 В предыдущих примерах программ параметры nmoв могли быть заменены ти­ пами любых классов. Это подходит для многих целей, но иногда уд обно ограничить перечень типов, передаваемых в качестве параметров. Допустим, требуется создать обобщенный класс с методом, возвращающим среднее значение массива чисел. Более того, с помощью этого класса требуется по.лучить среднее значение из целых чисел, а также чисел с пл авающей точкой одинарной и двойной точности. Та ким образом, тип числовых данных требуется указать обобщенно, используя параметр пша. Попытаться создать такой класс можно, например , следующим образом: // Кл асс Stats - пример бе зуспеwой попытки создать // обобщенный кл асс для вычи сления средне го значения // ма ссива чисел заданного типа 11 / / Этот кл асс содержит оmи.бку ! class Stat s<T> { Т[] пums ; // nuшв - это массив элементов типа Т // передать конструктору ссылку на ма ссив значений типа Т Stats (T[] о) { nums = о; // возвратить значение типа douЬle в любом случ ае douЫe average () { douЫe surn = О.О; for ( int i=O ; i < nums .length; i++) sum += nums [i] .douЫeValue () ; // ОШИБ КА ! !! return sum / nums .length; Метод average {) из класса Stats пытается получить версию типа douЫe для каждого числа из массива nums , вызывая метод douЬ leValue {). Все классы оболочек числовых типов дан ных, в том числе Integer и DouЫe, являются под­ классами, производными от класса NumЬe r, а в классе NumЬe r определяется метод douЫeValue {), поэтому данный метод доступен во всех классах оболочек чис­ ловых типов данных. Но дело в тt;>м , что компилятор не может знать, что автор программы намерен создавать объекты типа Stats, используя только числовые типы данных. Поэтому, когда класс Stats компилируется, выдается сообщение об ошибке , уведомляющее , что метод douЬleVa lue {) неизвестен. Чтобы устра­ нить эту ошибку, придется найти какой-нибудь способ сообщить компилятору, что в качестве параметра Т предполагается передавать числовые типы. Кроме того , требуется каким-то образом гарантировать, что передаваться будут талько число­ вые ти пы данных. Для подобных случаев в Java предоставляются ограни'Че'Нньtе типы. Указывая па­ раметр типа, можно наложить огран ичение сверху в виде верхней границы , где объявляется суперкласс, от которого должны быть унаследованы все аргументы типов. С этой целью вместе с параметром указывается кл ючевое слово extends , как показано ниже . <Т extends супер�rласс>
'°6 Часть 1. Язык Java Это означает, что параметр типа Т может быть заменен только указанн..Щ суперкла ссом или его подклассами . Следовательно, суперкла сс объявляет верх· нюю границу включительно. Наложив ограничение сверху, можно исправить упо­ мянугый выше класс Stats, указав класс NumЬe r в виде верхней границы для пара­ метра типа. 11 В этой версии класса Stata аргумент типа Т долже н быть // J<Jiaccoм Nu8Ьer или наследуемым от него классом class Stats<T exteпds NumЬe r> { Т[] nums ; // массив J<Jiacca Nu8Ьer или его пoдJ<Jiacca // передать конструктору CCЬl.I I KY на ма ссив элементов 11 класса NwlЬer или его подкласса Stats (T[] о) { nums = о; // возвратить значение типа douЫe в любом случае douЫe ave rage () { douЬle sum = О .О; for(int i=O; i < nums . length; i++) sum += nums [i] . douЫ eValue () ; returп sum / nums . length ; // продемонстрировать приме нение J<Jiacca Stats class Bounds Demo { puЫ ic static void ma in (String args []) { Integerinums[]={1,2,3,4,5}; Stats<Integer> iob = new Stats<Integer> ( inums) ; douЫe v = iob. average (); System.out . println ( "Cpeднee значение iob равно " + v) ; DouЫednums[]={1.1,2.2,З.З,4.4,5.51; Stats<DouЫe> dob = new Stats<DouЫe> ( dnums ); douЫe w = dob . average (); System.out . println ( "Cpeднee значение dob равно " + w) ; 11 Этот код не скомпилируе тся , так как кла сс Strinq // не является производныw от класса NU.Ьer //Stringstrs[J={ "1", "2", "3", "4", "5" }; // Stats<String> strob = new Stats<String> (strs) ; 11 douЬle х = strob . average () ; 11 System.out . println ( "Cpeднee значение strob равно " + v) ; Эта программа выводит следующий результат: Среднее значение iob равно 3.0 Среднее значение dob равно 3.3 Обратите внимание на то , что класс Stats объявляется теперь сле�щим об­ разом: class Stats<T extends NumЬer> {
Гл ава 14. Обобщения '07 Тип Т ограничивается сверху классом Nurnhe r, а следовательно, компилято­ ру Java теперь известно, что все объекты типа Т могут вызывать метод douЫe Va lue (}, поскольку это метод класса Nurnh e r . И само по себе это уже серьезное преимущество . Но кроме того , ограничение параметра типа Т предотвращает создание нечисловых объектов типа Stats. Та к, если попытаться убрать коммен­ тарии из строк кода в ко нце данной программы и перекомпилировать ее, то во время ко мпиляции будет выдана ошибка, поскольку класс String не является про­ изводным от класса Nurnhe r. В виде ограничения можно накладывать не только тип класса, но и тип интер­ фейса. Более того , такое ограничение может включать в себя как тип класса, так и типы одного или нескольких интерфейсов. В этом случае тип класса должен быть задан первым. Когда ограничение включает в себя тип инте рфейса, допусти­ мы только аргументы типа, реализующие этот интерфейс. Накладывая на обоб­ ще нный тип ограничение , состоящее из класса и одного или нескольких интер­ фейсов , д.Тlя их объединения следует воспользоваться логической операцией &: class Gen<T extends MyClass & Myinterface> { // . .. где параметр типа Т ограничивается классом MyClass и интерфейсом My lnteface. Та ким образом, любой тип, передаваемый параметру Т, должен быть подклассом, производным от класса MyClass и реализующим интерфейс My inte face. Применение метас имвол ьны х аргум е нто в Типовая безопасность уд обна не только сама по себе, но иногда она позволяет получить идеально подходящие конструкции. Например, в классе Stats, рассмо­ тренном в предыдущем разделе, предполагается, что в него требуется ввести ме­ тод same Avg (}, который определяет, содержат ли два объекта типа Stats масси­ вы , дающие оди наковое среднее значение независимо от типа числовых значений в них. Та к, если один такой объект содержит значения 1,О, 2,О и З,О типа douЫe, а другой - целочисленные значения 2, 1 и З, то их среднее значение будет одина­ ковым. Для того чтобы реализовать метод same Avg (}, можно, в частности , пере­ дать ему аргумент типа Stats, а затем сравнить средние значения в этом методе и вызывающем объекте, возвращая логическое значение true , если они равны. Кроме того , необходи мо иметь возможность вызывать метод sameAvg (}, напри­ мер, следующим образом: Integerinurns[]={1,2,3,4,5}; DouЫe dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 ); Stats<Integer> iob = new Stats<Integer> ( inums ); Stats<DouЫ e> dob = new Stats<DouЫe> (dnurns ); if (iob . sameAvg (dob) ) System.out . println ("Cpeдниe значения одинаковы. ") ; else Sys tem.out . println ( "Cpeдниe значения отличаются .") ; На первый взгляд, написать метод sameAvg (}совсем не трудно. Ведь класс S tats является обобщенным , и поэтому его метод ave rage (} может оперировать
t.08 Часть 1. Язы к Java ра:шотш шыми объе кта м и юшсс<1 Stats. К сож<u1е11 ию, труд1юп·и 1ю5шлн ются , стоит тоm,кu 11011ыт<1тьсн объя вить 1шр<1метр ти па Stats. Есл и Stа t. s - шtраметри· :юв<1 1 н1 ый ти11, то юtкой ти п ш1раме1·р<1 <·ледует ука:ыть дл я Sta ts 11ри объя влении пар аметра эт01·0 типа? Вполне подходя щим может 1юк<1аа·1ъся рс11н:11ис исполь:ю· вать Т в ка честве нара метра тина следующим обра:юм: // Это решение не годится! // Определит ь равенст во дву х средних значений Ьооlеап sameAvg (Stat s<T> о Ь) { it(average() == ob. average()) return true; ret:urn fa1se; l lс;((ктаток та кого решения аа кл ючастсн в том, что 0110 1·одито1 тол 1.ко дл я :1ру· гого объекта ю1;нта Stats, тин кото рого совнадаст с вы:1ы ва ющи м объектом. Т1 к , есл и вы:и,ша ющиii объект относится к пшу Stats<Int:eger>, то и нара мстр оЬ ю>лжен оп юситы· я к ти ну Stats<Integer>. E1·u 11сл1,: 1я нримсшпъ, шш римср, дл я сравнения сред него :шачения пша Stat s<ГJ оuЫе > со срсюшм :ш ;р1ением ти па Stats<Short>. Та ким обра:юм, ;1а111юе рс 111t:·11 ие 11рю·ол110 тол 1.ко в оче нь 01·рани•1е1нюм контексте и не являетс я об щим, а сле;1о вател ыю, и обобщенным. 'Iтобы со:щаты>боб щенную версию метода sameAvg (), сле;1ует воп ющ,:юваться други :\-1 сре;1п·вом обобщений.Jаvа - метасиJ�11111л ы1 ы.л1 rфгу.л tl'11 11иш. Метас имвол ы1ый ар1уv1с11т обо:значастсн :шаком ? и нрс;1ставл яет неи:шестн ыii т1111. П римсняя мета­ СИ:\-1Вол ы1ыii аргумент, метод sameAvg () можно ш111 ио1·1ъ, 11а1 1ример, следующим обра:ю:\-1: // Определить равенство двух средних значений . 11 Обра тите в н и м а ние на применение метасимв о ла boolean sameAvg (Stats<?> оЬ) { if(average() == ob. average()) re turп tru e; re turn fal se; 1·де мстас имвол ы1ый <tргумент тина Stat.s<?> совн;щает <' любым объс1по м клас­ са S ta t s, что 11о:шоляет сравнивап, с реюн1 е :шачсния любых двух объектов ю1асса Stat.s. В сл с;1ующем нримсрс программы де монстрируется такое приме н е ние ме· таС И:\-IВ<>ЛЫIОГО аргумента. // Приме нение ме тасимвол а class Stats<T extends NumЬe r> Т [] nums ; / / ма сси в класса NumЬer или его rюдкласса 1 1 передат ь конструктору ссылку на массив /! элементов к ласса NumЬer или его п о дкласса Stats(T[J о) { ПllffiS = О; 11 возвра тить з начение ти па douЫe в любом случ ае douЫe ave rage () douЫe sum = О .О;
for (int i=O; i < nums . length ; i++) sum += nums [i] .douЬl eVa lue () ; return sum / nums .length; // Определи ть равенство двух средних значений . // Обратите внимание ' на приме нение ме тасиывола boolean sameAvg (Stats<?> оЬ ) { if(average () == ob. average ()) return true ; return false; 11 Продемонс трировать применение ме тасимв ола class WildcardDemo { puЫ ic static vo id ma in (String args[] ) { Гл ава 1 ". Обобщения '09 Integerinums[]={1,2,3,4,5}; Stats<Integer> iob = new Stats<Integer>(i nums ) ; douЫ e v = iob.ave rage (}; System.out . println ("C peднee значение iob равно "+v} ; DouЫe dnums [] = { 1.1, 2.2, 3.3, 4.4, 5.5 } ; Stats<DouЫe> dob = new Stats<DouЫe>(dnums }; douЫe w = dob . average (}; System. o ut . println ("Cpeднee значение dob равно " + w) ; Float fnums [] = { l.OF, 2.0F, 3.0F, 4.0F, 5.0F } ; Stat s<Float> fob = new Stats<Float> (fnums} ; douЬle х = fob . ave rage (}; System. out . println ("Cpeднee значение fob равно " + х} ; 11 выяснить , какие массивы имеют одина ковые средние значения System.out . print ( "Cpeдниe значе ния iob и dob "} ; if(iob. sameAvg(dob) ) System.out . println ( "paвны. ") ; else System.out . println ("o тличaютcя ."}; System. out . print ( "Cpeдниe iob и fob "} ; if (iob . sameAvg (fob ) } System.out . println ("o динaкoвы. "} ; else System.out . println ("oтличaют cя ."} ; Ниже приведен результат выполнения данной программы. Среднее значение iob равно 3.0 Сред нее значение dob равно 3.3 с,едне е значение fob равно is 3.0 Gредние .значения iob и dob отличаются . Средние значения iob и fob одинаковы . И еще одно, последнее замечание: следует иметь в виду, что метасимвол не ока­ зывает никакого влияния на тип создаваемых объектов класса Stats. Это опре­ деляется оператором extends в объявлении класса Stats. А метасим вол просто совпадает в любым достовериъ�м объектом класса Stats.
'10 Часть 1. Язык Java О гра ниченные м ета с имвопьн ы е аргум е нты Метасимвольные аргументы могут быть ограничены почти таким же образом, как и параметры типов. Ограничивать метасимвольный аргумент особенно важно при создании обобщенного типа, оперирующего иерархи ей классов. Чтобы это стало понятнее, обратимся к конкретному примеру. Рассмотрим следующую ие­ рархию классов, инкапсулирующих координаты: 11 Двумерные координ аты class TwoD { int х, у; TwoD(int а, int Ы { х а; у=Ь; 11 Трехмерные координаты class ThreeD extends TwoD int z; ThreeD (int а, int Ь, int с) { super(a, Ь); z=с; 11 Четырехме рные координаты class FourD extends ThreeD { int t; FourD(int а, int Ь, int с, int d) { super(a, Ь, с); t=d; На вершине этой иерархии находится класс Two D, инкапсулирующий двумер­ ные координаты ХУ. Его наследует класс T h ree D, вводя третье и змерение и обра· зуя координаты XVZ. Класс ThreeD наследует от класса FourD, вводящего четвер­ тое измерение ( время) и порождая четырехмерные координаты. Ниже приведен обобщенный класс Coords, хранящий массив координат. 11 Этот класс хранит ма ссив координатных объектов class Coords<T extends TwoD> { Т [] coords ; Coords (T [] о) { coords =о; Обратите внимание на то , что в классе Coords задается параметр типа, orpa• ниченный классом Two D. Это означает, что любой массив, сохраняемый в объекте типа Coords , будет содержать объект класса Two D ил и любого из его подклассов. · · А теперь допусти м, что требуется написать метод, выводящий координаты Х и У каждого элемента массива coords в объекте класса Coords. Это нетрудно сдl! ! лать с помощью метасимвола, ка.к показано ниже , поскольку все типы объекто• класса Coords имеют, как минимум, пару координат (Х и У) . ·1
static void showX Y (Coords<?> с) { System.out . println ( "Koopдинaты Х У: ") ; Гл ава 14. Обобщения 411 for {int i=O ; i < c. coords .length ; i++) System.out . println (c. coords [i] .х +" "+c.coords [i] .у) ; System. out . println () ; Класс Coords относится к ограниченному обобщенному типу, для которого ука­ зан класс TwoD в качестве верхней границы, поэтому все объекты , которые можно ис пользовать для с оздания объекта типа Coords, будут массивами класса Two D или наследуемых от него классов. Та ким образом , метод s h owXY ( ) может выводить со­ держимое любого объекта типа Coords. Но что, если требуется создать метод, выводящий координаты Х, У и Z объекта типа T h reeD или Fou rD? Дело в том , что не все объекты типа Coords будут иметь три координаты, поскольку объект типа Coords<Two D> будет иметь только коор­ динаты Х и У. Как же тогда написать метод, который будет выводить координаты Х, У и Z для объектов типа Coords<ThreeD> и Coords<FourD>, и в то же время не допустить использование этого метода с объектами типа Coords<Two D>? Ответ за­ ключается в использовании ограни'Че'НН'ЬtХ метасимвалъных аргументов. Ограниченный метасимвол задает верхнюю или нижнюю границу для аргумен­ та типа. Это позволяет ограничить типы объектов, которыми будет оперировать метод. Наиболее распространен метас имвол , который накладывает ограничение сверху и создается с помощью оператора extends почти так же , как и ограничен­ ный тип. Применяя ограниченные метасимволы, нетрудно создать метод, выводящий координаты Х, У и Z для объекта типа Coords, если эти три координаты действи­ тельно имеются у этого объекта. Например , приведенный ниже метод showXY Z ( ) выводит координаты элементов, сохраняемых в объекте типа Coords, если эти элементы относятся к классу T h reeD (или к наследуемым от него классам ). static void showX YZ ( Coords <? extends ThreeD> с) Sys tem.out . println ("X У Z Coordinates :") ; for (int i=O ; i < c.coords .length; i++) System. o ut . println (c.coords [i] .x +" "+ Sys tem. out . println () ; c.coords [i] .y + + c.coords [i] . z) ; Обратите внимание на то , что оператор extends введен в метасимвол при объявлении параметра с. Это означает, что метасимвол ? может совпадать с лю­ бым типом , при условии, что он относится к классу T h reeD или наследуемому от него классу. Та ким образом, оператор extends накладывает ограничение сверху на совпадение с метасимволом ? . Вследствие этого ограничения метод showXYZ ( ) может быть вызван со ссылками на объекты типа Coords<Th reeD> или Coo r ds<FourD>, но не со ссылкой на объект типа Coords<Two D>. Попытка вызвать метод sho wX YZ ( ) со ссылкой на объект типа Coords<Two D> приведет к ошибке во время компиляции. Те м самым обеспечивается типовая безопасность. Ниже приведена полная версия программы, демонстрирующей действие огра­ ниченных метасимвольных аргументов.
'12 Часть l. Яэык Jаvа 11 Ограниченные ме тасимв оль ные аргуме нты 11 Двуме рные координаты class TwoD { int х, у; TwoD (int а, int Ь) { х а; у=Ь; 11 Трехме рные координаты class ThreeD extends TwoD int z; ThreeD (int а, int Ь, int с) { super (а, Ь); z=с; 11 Четыре хме рные координаты class FourD extends ThreeD { int t; FourD(int а, int Ь, int с, int d) { super(a, Ь, с) ; t=d; 11 Этот кл асс хранит массив координатных объе ктов clas s Coords<T extends TwoD> { Т[] coords ; Coords (Т [] о) { coords о;) 11 Продемонстрировать применение огранич енных ме тасимволов class BoundedWildcard { static void showXY (Coords<?> с) { System.out . println ("Koopдинaты Х У:") ; for ( int i=O ; i < c.coo rds .length; i++) System. out . println (c.coords [i] .x +" "+ c.coords [i] .у) ; System.out . println () ; static void showXYZ (Coords<? extends ThreeD> с) { System.out . println ("Koopдинaты Х У Z:") ; for (int i=O ; i < c.coords . length; i++) System. out . println (c.coords [i] .х + " " + c.coords [i] .y +""+ c.coords [i] .z) ; System.out . println () ; static void showAl l (Coords<? extends. Fou rD> с) System.out . println ("Koopдинaты Х У Z Т:") ; for ( int i=O ; i < c.coords . length; i++)
Гл ава 14. Обобщения '13 System.out . println (c.coords [i] .x +" "+ c.coords[i].у + " " + c.coords[i].z + " " + с.coords[iJ •t); System.out . println (); puЫic static void main (String args(] ) { TwoD td[] = { }; new TwoD(O, 0), new TwoD(7, 9), new TwoD (18, 4) , new ТwoD (-1, -23) Coords<TwoD> tdlocs = new Coords <TwoD> (td) ; System. o ut . println ( "Coдepжимo e объе кта tdlocs .") ; showXY (tdlocs ) i // Верно , это тип ТWoD // showXYZ ( tdlocs ); // Ошибка , это не тип ТhreeD // showAll ( tdlocs ); // Ошибка , это не тип FourD // а теперь создать нескол ько объе ктов типа FourD FourD fd[] = { }; new FourD(l, 2, 3, 4), new FourD(б, 8, 14, 8}, new FourD (22, 9, 4, 9} , new FourD(3, -2 , - 23, 17) Coords<FourD> fdlocs = new Coords<FourD> (fd) ; System. out . println ( "Coдepжимoe объе кта fdlocs .") ; 11 Здесь все верно showXY (fdl ocs ); showXYZ (fdlocs ); showAll (fdlocs ); Результат выполнения этой программы выглядит следующим образом: Содержимое объе кта tdlocs . Координаты Х У : оо 1'/9 .i i 1,4 -1 -23 •\I;•' �оnержимое объекта fdlocs . Координаты Х У: 12 6'в 129 З�; -:2 tЬординаты Х У Z: 123 6в14 2294 3-2 -23
1.11. Часть 1. Яэы к Java Координаты Х У Z Т: 1234 68148 22949 3-2 -23 17 Обратите внимание на следующие закомментированные строки: 11 showX YZ (tdlocs ); // Оши бка , это не тип ТhreeD 11 showA ll (tdloc s ); // Ошибка , это не тип FourD Объект tdlocs относится к типу Coords<Two D>, и его нельзя использовать для вызова метода s h owXY Z {) или s h owAl l {),поскольку этому препятствуют огра· ниченные метасимвольные аргументы в их объявлении. Чтобы убедиться в этом, попробуйте убрать комментарии из приведенных выше строк кода и скомпилиро­ вать данную программу. В итоге вы получите ошибку компиляции из-за несоответ­ ствия типов. В общем, чтобы установить верхнюю границу для метасимвола, следует вос­ пользоваться приведенной ниже формой метас имвольного выражения: <? extends супвржласс> Здесь суперкла се обозначает имя класса, который служит верхней границей. Не следует забывать, что это включающее выражение, а следовательно, класс, за­ данный в качестве верхней границы (т.е. суперкла сс) , также находится в преде­ лах допустимых типов. Имеется также возможность указать нижнюю границу для метасимвольного ар­ гумента, введя оператор supe r в его объявление. Ниже приведена общая форма наложения ограничения на метасимвольный аргумент снизу. <? super nодJСЛасс> В данном случае допустимыми аргументами могут быть только те классы, кото­ рые являются суперклассами для указанного подкла сса . Это исключающее выра­ жение, поскольку оно не включает в себя заданный подкла сс. Соэда н и е обобще нного м етода Как было показано в предыдущих примерах , в методах обобщенного класса можно использовать параметр ти па, а следовательно , они становятся обобщеиньi­ ми относительно параметра типа. Но можно объявить обобщенный метод, в кото­ ром непосредственно используется один или несколько параметров типа. Более того, можно объявить обобщенный метод, входя щий в необобщенный класс. Начнем рассмотрение обобщенных методов с конкретного примера. В приведен· ной ниже программе объявляется необобщенный класс GenMe t h Demo , а в нем -сrа­ тический обобщенный метод i s I n { ) . Этот метод определяет, является ли объект чле­ ном массива. Его можно применять к любому типу объектов и массивов, при условии, что массив содержит объекты, совместимые с типом искомого объекта. 11 Продемонс трировать простой обобщенный ме тод class GenМethDemo {
11 определить , содержится ли объе кт в массиве static <Т extends ComparaЬle<T> , V extends Т> boolean is!n (T х, V[] у) { for ( int i=O; i < y.length; i++) if (x. equals (y[i] )) return true ; return false ; puЫic static void ma in ( String args []) { 11 применить ме тод isin () для целых чисел Integernums[]={1,2,З,4,5}; if (is!n (2, nums) ) Глан 14. Обобщения 1.15 System. out .println ( "Чиcлo 2 содержится в ма ссиве nums") ; if (!i s!n (7, nums) ) System. o ut . println ( "Чиcлo 7 отсутствует в масси ве nums") ; System.out . println (); 11 приме нить ме тод isin () дл я симв оль ных строк String strs [] = { "один" , "два" , "три", "че тыре fl , "пять" }; if (isin ( "двa ", strs) ) System. o ut . println ("двa содержится в ма ссиве strs" ); if ( !is!n ( "ceм ь", strs )) System.out . println ("c eмь отсутствует в ма ссиве strs" ); 11 Не скомпилируе тся ! Типы должны быт ь совме с тимы 11 if (is!n ( "двa" , nums) ) 11 System. out . println ( "двa содержится в массиве strs ") ; Ниже приведен результат выполнения данной программы. Число 2 содержится в ма ссиве nums Число 7 отсутствует массиве в nums дв а содержится в ма ссиве strs семь отсутствует ма ссиве в strs Рассмотрим метод i s I n ( ) подробнее. Прежде всего обратите внимание на объ­ явление этого метода в следующей строке кода: st atic <Т extends ComparaЫe<T>, V extends Т> boolean is!n (T х, V[] у) ( Параметр типа объявляется до типа, возвращаемого методом. Обратите так­ же внимание на то , что тип Т расширяет обобщенный тип Compa raЫe<T>, где ComparaЫe - это интерфейс , объявляемый в пакете j ava . lang. В классе, реали­ эуJОщем интерфейс ComparaЬle, определяются объекты, которые моrут быть упо­ рядочены. Следовательно, указание интерфейса ComparaЫe в качестве верхней �ицы гарантирует, что метод isln () вполне применим к объектам, которые можно сравнивать. Интерфейс Compar аЫе является обобщенным, а параметр его rnп a обозначает тип сравниваемых объектов. (Далее в этой гл аве будет показано, как создается обобщенный интерфейс .) Обратите далее внимание на то , что тип
'16 Часть 1. Язы к Java V ограничен сверху ти пом Т. Это означает, что тип V должен быть тем же типом, что и Т, или же ти пом его подкласса. Та кая взаимосвязь подразумевает, что метод isin ( ) может быть вызван только с совместимыми аргументами. И наконец, об­ ратите внимание на то , что метод isin ( ) объявлен как статический, что позволя· ет вызывать его независимо ни от какого объекта. Следует, однако , иметь в виду, что обобщенные методы могут быть как статическими, так и нестатическими. Никаких ограничений на этот счет не существует. А те перь обратите внимание на то , что метод isin ( ) вызывается из метода ma in ( ) с нормальным синтаксисом вызовов, не требуя указывать аргументы тип а. Дело в то м, что типы аргументов различаются автоматически , а типы Т и V coo'i" ветственно подстраиваются. Например, в первом вызове этого метода. if (isin (2, nums) ) первый аргумент относится к типу Integer (благодаря автоупаковке ), поэто­ му вместо типа Т подставляется тип Integer. Второй аргумент также относится к типу Integer, который подставляется вместо типа V. Во втором вызове данного метода оба аргумента относятся к типу String, который и подставляется вместо типов Т иV. Для вызовов большинства обобщенных методов, как правило, достаточно и вы· ведения типов, но если требуется, то аргументы типа можно указать явно. В каче­ стве примера ниже показано, как должен выглядеть первый вызов метода i s In (), если явно указаны оба аргумента типа. GenМethDemo .<Integer, Integer>is!n (2, nums ) Очевидно, что явное указание аргументов типа в данном случае не дает ника· ких преимуществ. Более того , выведение типов в отношении методов было усо­ вершенствовано в версииJDК 8. Таким образом , указывать аргументы типа явным образом требуется лишь в крайне редких случаях. А теперь обратите внимание на приведенный ниже закомментированный код из рассматриваемой здесь программы. 11 if (isin ("двa" , nums )) 11 System.out . println ("двa содержится в ма ссиве strs ") ; Если убрать комментарии из этих строк кода, а затем попытаться скомпили· ровать данную программу, то возникнет ошибка. Дело в том , что параметр тица V ограничивается типом Т в выражении extends из объявления параметра типа , � Это означает, что параметр типа V должен иметь тип Т ил и же тип его подкласса, В данном же случае первый аргумент относится к типу String, а следовательно, второй аргумент также должен относиться к типу String, хотя на самом деле он относится к типу Integer. который не является производн ым от класса Str inф В итоге во время компиляции возникает ошибка несоответствия типов. Та кая спО: собность обеспечивать типовую безопасность является одним из самых гл авныi преимуществ обобщенных методов. Синтаксис, использованный для создания метода isln (), можно обобщить. Ниже приведена общая синтаксическая форма обобщенного метода. <стrсож параме!rрОВ !l'ИПа> .во.з.арцаеJ&di !l'яn Жн. . _ ..,8'1'ода (cmrcoж_napaмe!l'J)Oa) -{ // . • .
Гл ава 14. Обобщения '17 В любом случае список_параметров_ типа обозначает разделяемый запятыми список параметров типа. Обратите внимание на то, что в объявлении обобщенно­ го метода список параметров типа предшествует возвращаемому типу. Обобщенные ко нструкторы Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются. Рассмотрим в качестве примера следующую короткую программу: 11 Исполь зовать обобще нный конструктор class GenCons { private douЫ e val ; <Т extends NumЬer> GenCons (T arg) { val = arg . douЬleValue (); vo id showval () { System.out . println ("val : "+val ) ; cla ss GenCons Demo { puЫ ic static void ma in (String args [] ) { GenCons test = new GenCons (lOO ) ; GenCons test2 = new GenCons (123 .5F) ; test . showval (); test2 . showval (); Эта программа выводит следующий результат: val : 100.О val: 123.5 В конструкторе GenCons () задается параметр обобщенного типа, который мо­ жет быть производным от класса NurnЬe r, поэтому конструктор GenCons () можно вызывать с любым числовым типом, включая Integer, Float или DouЫ e. И несмо­ тря на то , что класс GenCons не является обобщенным, его конструктор обобщен . Обобще нные и нтерфей с ы Помимо классов и методо в, обобщенными можно объявлять интерфейсы. Обобщенные инте рфейсы объя вляются таким же образом, как и обобщенные классы. Ниже приведен характерный тому пример. В нем создается обобщенный интерфейс MinMax, где объявляются методы min ( ) и max () , которые, как предпо­ лагается , должны возвращать минимальное и макс имальное значения из некото­ рого множества объектов. 11 Пример прЩ4енения обобщенного интерфейса 1 1 Обобще нный интерфейс МinМа.х для определения
'18 Часть 1. Яэык Jаvа 1 1 !>4ИНимального и .максимал ьного значений inter face Mi nMax<T extends ComparaЬle<T>> Т min (); Т max (); 11 реализовать обобщенный интерфейс Мinм&х class MyClass<T extends ComparaЫe<T>> implements Mi nмax<T> { Т[] val s; MyClass(T[] о) { vals • о; 1 11 возвратить минимал ьное значение из массива vals puЫic Т min() { т v = vals[O]; for(int i=l; i < vals.length; i++) if (vals [i] .compareTo (v) < 0) v = vals [i] ; return v; 11 возвратить .ма ксимальное значение из массива vals puЫicТ max(){ Т v = vals[O]; for ( int i=l; i < vals. length; i++) if (vals [i] .compareTo (v) > О) v return v; class GenI FDemo { vals[i]; puЫ ic static void ma in ( String args[] ) { Integerinums[] = {З, 6, 2, 8, 6 }; Character chs[] = {'Ь ' , 'r' , 'р', 'w' }; MyCl ass<Integer> iob = new MyCl ass<Integer> ( inums ); MyCl ass<Character> соЬ = new MyClass<Character> (ch s) ; System. out . println ( "Maкcимaльнoe значение в .ма ссиве inums : " + iob.max()); System.out . println ( "МИНимaль нoe значение в массиве inums : " + iob.min() ); System. out . println ( "Maкcимaльнoe значение в ма ссиве chs : " + cob.max ()); System. out . println ( "Минимaльнoe значение в массиве chs : " + cob .min () ); Ниже приведен результат выполнения дан ной программы. Максимальное значе ние в ма ссиве inums : 8 Миниыальное значение в массиве inums : 2 ма ксимальное значение в ма ссиве chs : w Минимальное значение в ма ссиве chs : Ь Бмьшую часть этой программы нетрудно понять, но некоторые ключевые Ъ( менты следует все же пояснить. Прежде всего обратите внимание на следующс;� объявление обобщенного интерфейса MinMax: inte rface MinMa x<T extends ComparaЬle<T>> {
Гл ава 14. Обобщения '19 Как правило, обобщенный интерфейс объяWIЯет<:я таким же .образом, как и обобщенный класс. В данном случае параметр типа т ограничивается сверху ин· терфейсом ComparaЫe. Как пояснялось выше, интерфейс ComparaЫe определен в пакете j ava . lang для целей сравнения объектов. Параметр его типа обозначает тип сравниваемых объектов. Затем интерфейс MinMax реализуется в классе MyClass. Объявление класса М.yClass выглядит следующим образом: class MyClass<T extends Compa raЫ e<T>> impleme nts HinМax<T> { Обратите особое внимание, каким образом параметр типа Т сначала объявляет­ ся в классе MyClass, а затем передается интерфейсу MinMa x. Интерфейсу MinMax требуется тип класса, реализующего интерфейс Cornpa raЬle, поэтому в объявле­ н ии класса, реализующего этот интерфейс (в данном случае - класса MyClass), должно быть наложено такое же ограничение. Более того, однажды наложенное ограничение уже не нужно повторять в операторе irnplernent s. В действитель­ ности это было бы даже неверно. Например. приведенная ниже строка неверна и не может быть скомпилирована. Однажды установленный параметр типа просто передается интерфейсу без последующих видоизменений. 11 Неверно ! cl ass MyClass<T extends Compa raЫe<T>> impleme nts MinMax<T extends Compa raЫe<T>> Как правило , класс , реализующий обобщенный интерфейс , должен быть так­ же обобщенным - по крайней мере , в тех случаях, когда он принимает параметр типа, передаваемый далее интерфейсу. Например , следующая попытка объявить класс MyClass приведет к ошибке: class MyClass implements MinМax<T> { // Неверно ! В классе MyClass параметр типа не объявляется, поэтому передать его интер­ фейсу MinMax никак нельзя . В данном случае идентификатор параметра типа Т просто неизвестен, и поэтому компилятор выдаст ошибку. Безусловно, если класс реализует конкретный тип обобщенного интерфейса, то реализующий класс не обя зан быть обобщенным, как показано ниже. class MyClass implements MinМax<Integer> { // Верно Обобщенный интерфейс дает два преимущества. Во-первых, он может быть реализован для разных типов данных. И во-вторых, он позволяет наложить огра­ ничения на типы данн ых, для которых он может быть реализован. В примере ин­ терфейса Mi nMax вместо параметра типа Т могут быть подставлены только типы классов, реализующих интерфейс Compa raЫe. Ниже приведена общая синтаксическая форма обобщенного интерфейса. interface .__Жl l !l'ер.Мса< са•сож_параме!l'рО•_!l'JШa> { / / ••• Здесь список_ параметров_ типа обозначает разделяемый запятыми список па­ раметров типа. Когда реализуется обобщенный интерфейс, следует указать аргу­ менты типа, как показано ниже. claвs ._ жласса<сп.сож пap&Jle!l'pOВ !l'JШa> :iJlplei ie nts ---Жl l !l'вРФмса<са•сож_арrунея !l'оа_-па>
1.20 Часть 1. Язы к Java Базовые типы и унасл едо ван ный код Поддержка обобщений отсуrсtвовала до версии JDK 5, но требовался какой· нибудь способ переноса старого кода, разработанного до появления обобщений. На момент написания этой книги существовал большой объем унаследованного кода, который должен оставаться функциональным и бьrгь совместимым с обобще­ ниями. Код, предшествующий обобщениям, должен иметь возможность нормально взаимодействовать с обобщенным кодом, а обобщенный код - с унаследованным. Чтобы облегчить плавный переход к обобщениям, в Java допускается приме­ нять обобщенные классы без аргументов. Это приводит к созданию баз ового типа (иначе называемого "сырым") для класса. Этот базовый тип оказывается совме­ стимым с унаследованным кодом, где синтаксис обобщений неизвестен. Гл авный недостаток применения базового типа заключается в том , что при этом уграчива· ется типовая безопасность. В приведенном ниже примере базовый тип демонст­ рируется в действии. 11 Продемонс трировать базовый тип class Gen<T> { Т оЬ; // объявить объект типа Т 11 передать конс труктору ссылку на объект типа Т Gen(Т О) { оЬ=о; 11 возвратить объект оЬ т getob() { return оЬ; 11 Продемонс трировать применение базового типа class RawDemo { puЫic static void maiп ( Striпg args []) { 11 создать объе кт типа Gen для целых чисел Gen< Integer> iOb = new Gen< Integer> (88) ; 11 создать объе кт типа Gen для симв ольных строк Gen<String> strOb = new Gen< String> ("Tecт обобщений" ); 11 создать объе кт базового типа Gen и прис воить ему 11 значение типа DouЫe Gеп raw = пеw Gen (пew DouЬle (98.6) ); 11 Требуется приведение типов, поскольку тип неизвестен douЫ e d = (DouЬle) raw . getob(} ; System. out . priпtlп ("З нaчeниe : "+d} ; 11 Приие нение 6а зовых типов може т выз вать исключения во 11 время выполнения . Ниже представлены некоторые тому примеры. 11 Следующее приведение типов вызовет ошибку во время выполнения ! 11 int i = (Iпteger} raw.getob() ; // ОШИБКА во время выполнения ! 11 Следующее присваивание наруwает типо вую безопасность
Гл ава 14. Обобщения 421 strOb = raw; // Верно , но потенциально ошибочно // Striпg str = strOb . getob () ; // ОШИБКА во время выполне ния ! 11 Следующее присваив ание также наруп�ает типовую безопасность r aw = iOb ; // Верно , но потенциально ошибочно // d = (DouЬl e) raw. getob() ; // ОШИБКА во время выполнения ! С этой программой связано несколько интересных моментов. В следующем объявлении создается класс Gen базового типа: Gen raw = new Gen ( new DouЬ le (98.6) ); Обратите внимание на то , что никаких аргументов типа в этом объявлении не указывается. По существу, в нем создается объект класса Gen, тип Т которого за­ меняется типом Ob ject. Базовые типы не обеспечивают нужной безопасности. Это означает, что пере­ менной базового типа можно присвоить ссылку на любой тип объектов масса Gen. Возможно и обратное: переменной конкретного типа Ge n можно присвоить ссЫJ1- ку на объект базового типа Gen. Но обе операции потенциально небезопасны, п о­ скольку они выполняются в обход механизма проверки типов. Недостаток типовой безопасности ил л юстрируется закомментированными строками кода в конце данной программы. Рассмотрим каждую из них. // int i = (Integer) raw .getob() ; // ОШИБКА во время выполнения ! В этой строке кода получается значение объекта оЬ из объекта raw, и это зна­ чение приводится к типу Intege r. Дело в том, что объект raw содержит значе­ ние типа Do uЫe вместо целочисленного значения. Но этого нельзя обнаружить на стадии компиляции, поскольку тип объекта raw неизвестен . Следовательно, вы­ полнение этой строки кода приведет к ошибке во время выполнения. В следующем фрагменте кода переменной s trOb , ссьщающейся на объект типа Gen< S t r ing >, присваивается ccЬVIIЩ на объект типа Gen: strOb = raw; // Верно , но потенциаль но 9wибочно /1 String str = strOb .getob() ; // ОШИБКА во время выполне ния ! Такое присваиван ие само по себе синтаксически верно, но сомнительно. {1еременная strOb относится к типу Gen<String>, поэтому предполагается, что она содержит символьную строку. Но после присваивания объект, на который ссылается переменная strOb, содержит значение типа DouЫ e. Следовательно, во время выполнения , когда предпринимается попытка присвоить переменной str содержимое переменной strOb, происходит ошибка, поскольку объект, на кото­ рый ссылается переменная strOb , теперь содержит значение типа DouЫe. Та ким образом , присваивание базовой ссьтки обобщенной ссылке происходит, минуя механизм проверки типов. Следующий фрагмент кода предстамяет совершенно противоположный случай : raw = iOb; // Верно , но потенциально ошибочно /1 d = (DouЫe ) r aw .getob() ; // ОШИ БКА во время выполнения ! где обобщенная ссылка присваивается переменной базовой ссылки. И хотя такое присваивание синтаксически верно , оно может также привести к ошибкам, как
'22 Часть 1. Язык Java показывает вторая строка кода. В данном случае переменная raw ссьшается на объ­ ект, содержащий значение типа Integer, но в операции приведения типов пред­ полагается, что он содержит значение типа DouЫe. Та кую ошибку нельзя предот­ вратить на стадии компиляции, поэтому она проявляется во время выполнения. В связи с тем что базовые типы представляют опасность, по команде javac вы­ водятся неnр оверяе.м :ые nре(;упреждения, когда компилятор обнаружи вает, что их при· менение способно нарушить типовую безопасность. Появление таких предупреж· де ний вызовуг следующие строки кода из рассматриваемой здесь программы: Gen raw = new Gen( new DouЫe (98.6) ); strOb = raw ; // Верно , но п отенциально ошибочно В первой строке кода происходит вызов конструктора Gen ( ) без аргумента ти па, что приводит к появлению предупреждения. А во второй строке базовая ссылка присваивается переменной обобщенной ссылки , что также приводит к по­ яв.лению предупреждения. На первый взгляд может показаться , что приведенная ниже строка кода таюке должна порождать предупреждение, но этого на самом деле не происходит. raw = iOb ; // Верно , но п отенциаль но ошибочно Здесь компилятор не выдает . никаких предупреждений, потому что присваи� ван не не вызывает никакой доnаянитмъной потери типовой безопасности , кроме той, что уже произошла при создании объекта raw. И еще одно, последнее замечание: применение базовых типов следует ограни· чивать теми случаями, когда унаследованный код приходится сочетать с новым; обобщенНЬlм кодом. Базовые типы служат лишь для переноса кода, а не средсmоМJ которое следует применять в новом коде. Иерархии обобщенных классов Обобщенные классы могуг быть частью иерархии классов, как и любые другие необобщенные классы. Это означает, что обобщенный класс может действовать в качестве суперкласса ил и подкласса. Гл авное отличие обобщенных иерархиj\ от необобщенных состоит в том, что в обобщенной иерархии любые аргументiJ типа, требующиеся обобщенному суперклассу, должны передаваться всеми по�; классами вверх по иерархии. Это похоже на порядок передачи аргументов кон: структорам в верх по иерархии. ' П римен ение обобщенного супер кпа сса Ниже приведен пример иерархии, в которой применяется обобщен ный супер: : класс . 11 Пр остая иерархия обобще нных классов class Gen<T> { Т оЬ; Gen (T О)
оЬ о; / / возврати·гь объе кт оЬ Т getob() { return оЬ ; 11 Подкл асс , производный от кл асса Gen class Gen2<T> extends G е п <Т> { Gеп2(Т о) { super (o) ; Глава 14. Обобщения 423 В этой иерархии класс Gen2 р<tсширяет обобщенный кл асс Gen. Обратите вни­ мание на объя вление ю�асса Gen2 в следующей строке кода: class Gen2 <T> extends Gen<T> { Параметр тина Т ука:�ан в объявлен ии класса Gen2 и передастся классу Gen в вы­ ражении extends. Это о:шачает, что ти п, передаваемый кл ас су Gen2 , бу;(ет также переда н классу Gen. 1 Iанример, в объявлении Gen2<Integer> пum = new Geп2<Integer> (100) ; тин Integer перещ1стс я в ка честве ш1раметра тина классу Gen. Та ким образом, объект оЬ в ч аст и Gеп кл асса Gen2 будет имеп, тин Integer. Следует та кже иметь в виду, что параметр тш1а Т ис1юш,:iуется в юшссе Gen2 тол ько для подде ржки его супсркласса Gеп. Лаже сел и подкласс обобщенного суперкласса совсем не обяза­ тел ьно ;tолжен бып. обобщенным, в н е м все же должн ы быт1, уюваны пара:\1етр ы тина, требующиеся е1·0 обобщенному суперклассу. Разумеется, подкласс может, есл и требуется , бып, допол н е н и своими параме­ тра м и тина. В качестве н р и мсра ниже rюкааан вариант предыдущей иерархии, где в класс Gen2 вводитсн свой параметр типа. // В подкласс могут быть введены свои параметры типа class Gеп<Т> { Т оЬ; // объявить объект типа Т / / передать конс1· руктору ссылку на объе кт типа Т Gеп(Т О) { оЬ=о; // возвратить ссылку оЬ т (jetob() { retнrп оЬ ; } // Подкла сс , производный от класса Gen , где // определяется второй параметр типа V class Ge n2<T, V> extends Gen<T> { V оЬ2; Gеп2(То, V о2) Sllpe r (o) ;
lt21t Часть 1. Язык Java } оЬ2 = о2; v getob2 () return оЬ2 ; 11 создать объе кт типа Gen2 class HierDemo { puЫ ic static vo id ma in ( String args[J) { // создать объекты типа Gen2 для символь ных строк целых чисел Gen2<String, Integer> х = new Gen2 <String, Integer> ("Значение равно : ", 99) ; System.out . print (x .getob()); System.out . println (x. getob2 () ); Обратите внимание на объя вление класса Gen2 , приведенное в следующей строке кода: class Gen2<T, V> extends Gen<T> { где Т обозначает тип, передаваемый· классу Gen, а V - тип , характерный для класса Gen2 . Параметр ти па V используется при объя влении объекта оЬ2 , а также в каче­ стве ти па, возвращаемого методом ge tob2 (). В методе ma in ( ) создается объект класса Gen2 , в котором nш S t r ing подставляется вместо параметра типа т, а тип Integer - вместо параметра типа V. Данная программа выдает следую щий вполне ожидаем ый результат: Значение равно : 99 Обобщенный п од кл асс Суперклассом для обобщенного класса вполне может служить и необобщенный класс. Рассмотрим в качестве примера следующую программу: 11 Необобще нный класс може т быть суперклассом 11 для обобщенного подкласса 11 Необобще нный класс class NonGen { int num; NonGen (int i) num=i; int getnum () { return num; // Обобщенный подкласс class Gen<T> e xtends NonGen { Т оЬ ; // объявить объе кт типа Т
11 передать конструктору объе кт типа Т Gеп(Т о, int i) { super (i); оЬ=о; 11 возвратить о бъе кт оЬ Т getob() { returп оЬ ; 11 создать объе кт типа Gen class HierDemo2 { puЫic static void ma in (St ring arg s[] ) { Глава 1 4. Обобщения '25 11 созда ть объе кт типа Gen для символь ных строк Geп<String> w = new Gen< String> ( "Дoбpo пожало вать ", 47 ) ; System. out .print (w.getoЬ() +" ") ; System.out .println (w .getnum() ); Ниже нриведсн резул ьтат вы1юл11ения данной программы. Добро пожаловать 47 Обратите внимание, ка ким обра:ю м в этой программе клас с Gen н асл едуетс я от класса NonGen: class Gen<T> extends NonGen { Класс NonGen явю1 ется необобщенным, поэтому никаких аргументов типа в не м нс указываетс я. И даже если в классе Gen объявляется параметр типа Т, то о н не требуется ( и не может бьпъ исполь:юван) в классе NonGen. Так им образом, класс Gen насл едуется от класса NonGen обычным образом. Никаких с пециальн ых условий дл я э то го нс требуется. Сравнениетипов в обобщенной иерархии во время выполнения Напомним, что для 11 олучс1 1 и я с11е;(е 11 ий о типе во в ремя выполпсния служит операто р instanceof, они са нны й в гл аве 13. Как пояснялось ранее, оператор i n stanceo f о п редел яет, является л и объект экземпляром класса. Он возвращает логическое :ш ачепие true, есл и объе кт относится к указа нному ти пу ил и может быть п рив еде н к :пому типу. О11ератор instanceof можно применять к объектам обобщен ных кл ассов. В следующем нримере кл асса демонстрируются некото р ые посл едствия совмесгимости тш юв в обобщенных иерархиях: 11 Исполь зовать оператор ins tanceof в иерархии обобщенных кл ассов class Gen<T> { Т оЬ; Gen (Т о) оЬ=о;
'26 Часть 1. Язык Java 11 возвратить объе кт оЬ Т getob() { return оЬ ; 11 Подкласс , производный от кл асса Gen class Gen2<T> ext ends Gen<T> { Gen2(Т О) { super (o) ; 11 продемонс трировать последствия динамической идентификации 11 типов в иерархии обобщенных классов class HierDemo З { puЫic static vo id ma in (String args [] ) 11 создать объект типа Gen для целых чисел Gen<Integer> iOb = new Gen<Integer> (88) ; 11 создать объе кт типа Gen для целых чисел Gen2 <Integer> i0b2 = new Gen2<Integer> (99) ; 11 создать объе кт типа Gen2 дл я симв ол ь ных строк Gen2<String> str0b2 = new Gen2 <String> ("Tecт обобщений") ; 11 проверить , является ли объе кт iei>2 какой- нибудь 11 из форм класса Gen2 if ( iOb2 instanceof Gen2<?>) System.out . println ( "Объект i0b2 явля ется экзеМIμJяром класса Gen2") ; 11 проверить , является ли объе кт iОЬ2 какой- нибудь 11 из форu класса Gen if (i0b2 instanceof Gen< ?>) System.out .pri ntln ( "Объе кт i0b2 явля ется экземпляром класса Gen" ); System.out . println () ; 11 проверить , является ли •1;%0Ь2 рбъе��� :класса a.D2 if (str0b 2 instanceof Gen2<?>) · System.out . println ( "Объе кт strOb2 является экземпляром кл асса Gen2") ; 11 проверить , является ли etr0b2 объектом кл асса Gen if ( str0b2 instanceof Gen<?>) System.out . println ( "Объект &t r0b2 является экземпляром класса Gen"J ; System.out . println () ; 11 проверить, является ли iОЬ экземпляром .класса Gen2 , 11 что совсем не так if (iOb instanceof Gen2 <?>) System. out . println ( "Объе кт iOb является экземпляром кл асса Gen2") ; 11 проверить , является ли iOb экз емпляром класса Gen , 11 что так и есть
if(iOb instanc eof Gen<?>) System. o ut . println ( Гл ава 14. Обобщения 427 "Объе кт iOb является экземпляром класса Gen" ); 11 Следующий код не скомпилируе тся , так как сведения об 11 обобщенном типе отсутствуют во время выполнения 11 i f (iOb2 instanceof Gen2 <Integer>) 11 System.out . println ( 11 "Объе кт i0b2 является экземпляром класса Gen2<Integer>" ) ; Ниже приведен результат выполнения данной программы. Объект iOb2 является экземпляром кл асса Gen2 Объект iOb2 является экземпляром класса Gen Объект str0b 2 является экземпляром кл асса Ge n2 Объект str0b2 является экземпляром кла сса Gen Объект iOb являе тся экземпл яром класса Gen В дан ной программе класс Gen2 является производн ым от класса Gen, которь1й о бобщен по параметру типа Т. В методе main () создаются три об�. Первый: - объект iOb типа Gen< Integer>, второй - объект i0b2 типа Gen2 <Integer>, тре­ тий - объект str0b2 типа Gen2 <String>. Затем в данной программе выполняются следующие проверки типа объекта i0b2 с помощью оператора instance of: 11 проверить , является ли объе кт i0b2 какой- нибудь из форм класса Gen2 if ( iOb2 i nstanceof Gen2<?>) System.out .println ("Oбъe кт iOb2 являе тся экземпляром класса Gen2") ; 11 про верить , являетс я ли объе кт i0b2 какой-нибудь из форм класса Gen if ( i0b2 i nstanceof Gen< ?>) System. o ut . println ( "Oбъ eкт iOb2 являе тся экземпляром класса Gen" ); Как показывает результат выполнения дан ной программы, обе проверки за· вершаются ус пешно. n первом случае объект i0b2 проверяется на соответст�ие обобщенному типу Gen2 <?>. Эта проверка завершается успешно, поскольку она просто подтверждает, что объект i0b2 относится ка.кому-то из типов Gen2 . С тто­ мощью метасимвола оператор instanceof определяет, относится ли объект i0b2 к какому-то из типов Gen2. Далее объект iOb проверяется на принадлежность типу суперкласса Gen< ?>. И это верно, поскольку объект iOb2 является некоторой фор­ мой суперкласса Gen. В ряде последующих строк кода из метода ma in () выполня­ ется та же самая последовательность проверок, но уже объекта str0b2 с выводом соответствующих результатов. Далее объект iOb, являющийся экземпляром суперкласса Gen< Integer>, п ро­ веряется в следующих строках кода: 11 проверить , является ли iOb экземпляром класса Gen2 , что совсем не так if ( iOb instanceof Gen2<?>) System. o ut . println ( "Oбъe кт iOb является экземпл яром класса Gen2") ; 11 проверит ь, является ли iOb экземпляром класса Gen, что так и есть if (iOb instanceof Gen<?>) System . out .println ("Oбъe кт iOb является экземпл яром класса Gen " );
'28 Част1а1.ЯзыкJava Первый условный оператор if возвращает логическое значение false, по­ скольку объект iOb не является ни одной Из форм типа Gen2. Следующая проверка завершается ус пешно, потому что объект iOb относится к одному из типов Gen. А те перь внимательно проанализируем следую щие закомментированные стро­ ки кода: 11 Следующий код не скомпилируется, так как сведения об 11 обобщенном типе отсутствуют во время выполнени я 11 if (i0b2 iпstaпceof Geп2 <Iпteger>) 11 System. o ut . priпtlп ( 11 "Объе кт i0b2 является экземпляром кла сса Geп2 <Iпteger>") ; Как следует из комментария , эти строки кода не компилируются , потому что в них предпринимается попытка сравнить объект i0b2 с конкретным типом Gen2 (в дан ном случае с типом Gen2 <Integer>). Напомним, что во время выполнения всякие сведения об обобщенном типе отсутствуют. Та ким образом , оператор in­ stanceof ни коим образом не может выяснить, является ли объект iOb2 экземпля­ ром типа Gen2<Integer> или нет. П риведение ти пов Тип одного экземпля ра обобщенного класса можно привести к другому ТОЛJ•КQ в том случае, если они совместимы и их аргументы типа одинаковы. Например , следующее приведение типов из предыдущего примера программы: (Geп<Iпteger> ) i0b2 // допус тимо вполне допустимо, потому что объект iOb2 является экземпляром типа Gen< Integer>. А следующее приведение типов: (Geп<Loпg>) iOb2 // недопустимо недопустимо , поскольку объект iOb2 не является экземпляром типа Gen<Long>. Переопр едел ение методов в обоб щенном кл а ссе Метод из обобщенного класса может быть переопределен, как и любой другой метод. Рассмотрим в качестве примера следующую программу, в которой · переО' пределяется метод getob (): { 11 Переопределение обобщенного ме тода в обобщенном классе clas s Gen<T> { Т оЬ ; // объя вить объект типа Т 11 передать конс труктору ссЬ1Лку на объе кт типа Т Gеп(Т о) { оЬ=о; 11 возвратить объе кт оЬ т getob() { System.out . print ( "Meтoд getob () из кла сса Gеп : "); returп оЬ ; .;:
11 Подкласс , производный от класса Gen и 11 переопределяющий ме тод getoЬ () class Gen2 <T> extends Gen<T> { Gen2(То) { super (o) ; 11 переопредели ть ме тод qetob () Т getob() { Глава 14. Обобщения '29 System. out .print ( "Meтoд getob () из класса Gen2 : ") ; return оЬ ; /1 продемонс трировать переопределение обобщенных ме тодов class Ove rrideDemo { · puЫ ic static void main (String args[J ) { 11 создать объе кт типа Gen дл я целых чисел Gen<Integer> iOb = new Gen< Integer> (88) ; 11 создать объе кт типа Gen2 для целых чисел Gen2 <Integer> iOb2 = new Gen2<Integer> (99) ; 11 созда ть объе кт типа Gen2 для символьных строк Gen2 <String> str0b2 = new Gen2<String> ("Тест обобщений " ) ; System. o ut . println (iOb . getob () ); System. out . println ( i0b2 ;getob () ); System.out . println (str0b 2 .getob() ); Ниже приведен ре3ультат выполнения данной программы. Метод getob () из кл асса Gen : 88 Ке тод getob () из класса Gen2 : 99 Ме тод ge tob () из класса Gen2 : Тест обобщений Как подтверждает полученный результат, переопределенная версия метода getob () вызывается для объекта типа Gen2 , тогда .как для объектов типа Gen вы- 3ывается его версия из суперкласса. Выведение типов и обобщения Начиная с версииJDК 7 можно использовать сокращенный синтаксис для со3- дания экземпляра обобщенного типа. Для начала рассмотрим следующий обоб­ щенный класс: class MyClass<T, V> { Т оЫ; V оЬ2; MyClass(Т ol, V о2) { оЫ ol; оЬ2 = о2;
'ЗО Часть 1. Язык Java 11 До версии JDК 7 для получения экземпляра класса MyClass пришлось бы вое· пользоваться оператором, аналогичным следующему: MyCl ass<Integer, String> mcOb = new MyCl ass<Integer, String> (98, "Строка" ); где аргументы типа (Integer и String) определяются дважды: когда объявляет­ ся объект mcOb и когда экземпляр класса MyClass создается с помощью операто­ ра new. Со времени появления обобщений в версии JDК 5 эта форма была обяза· тельна для всех ве рсий языкаJаvа вплоть до JDK 7. Хотя в этой форме нет ничего неправильного, она, по существу, несколько более многословна, чем требуется. В операторе new тип аргументов типа может быть без особого труда выведен из типа объекта mcOb, поэтому указывать их во второй раз на самом деле нет никаких причин. Чтобы разрешить эту ситуацию, в версииJDК 7 был внедрен синтаксиче­ ский элемент, позволяющий избежать повторного указания аргументов типа. Те перь приведенное выше объявление может быть переписано следующим об­ разом: MyClass<Integer, String> mcOb = new MyCl ass<> (98, "Строка" ) ; Обратите внимание на то, что в правой части приведенного выше оператора, где создается экземпляр, просто указываются угловые скобки (<>) , обозначаю­ щие пустой список аргументов типа и называемые ромбооиднъlМ оператором. Этот оператор предписывает компилятору вывести тип аргументо в, требующихся кон· структору в операторе new. Гл авное преимущество синтаксиса выведения типов заключается в том, что он короче и иногда значительно сокращает очень дли11ц1ые операторы объявления. Обобщим все сказанное выше. Когда выполняется выведение типов, синтаксис; объявления для обобщенной ссылки и создания экземпляра имеет приведеllнуt) ниже общую форму, где список аргументов типа конструктора в операторе new пуст. _. .хла сса<СDа'оож •prJIJl8Н !l'08 !l'JШa> JOUl перенен н оJi• -new JD1.1 1 _.хла ссi°<> (сп•сох_арг унен!l'о•_:-хонс!rрух!l'ора) ; Выведение типов можно также выполнять и при передаче параметров. Так, если в класс MyClass вводится следующий метод: boolean isSame (MyClass<T, V> о) 1 if (oЫ == о.оЫ && оЬ2 == о.оЬ2) return true ; else return false ; то приведенный ниже вызов считается вполне допустимым. if (mcOb.is Same (new MyCl ass<> (l, "test ") )) System. out . println ("Same") ; В данном случае ар гументы типа для аргумента, передаваемого методу isSame () ,могут быть выведены из параметров типа. В связи с тем что синтаксис выведения типов был внедрен в версииJDК 7 и не поддерживается устаревшими компиляторами, в последующих примерах про­ грамм будет использоваться полный синтаксис объявления экземпляров обоб-
Гл ава 14. Обобщения 1.31 щенных классов, чтобы эти примеры можно было отко мпилировать любы м ком­ пилятором java , поддерживающи м обобщения. Кроме того , употребление полно­ го синтаксис а позволяет яснее понять. что именно создается, а это очень важно для понимания исходного кода примеров, представленных в да нной книге. Но при написании прикладного кода употребление синтаксиса выведения типов зна­ чительно упростит объявления. Стира ние Обычно подробно знать, каким образом компиля тор Java п реобразует исход­ ный текст программы в объектный код , нс нужно. Но что касается обобщен и й , то некоторое о бщее представление об этом процессе все же следует и м еть , поскольку оно объясняет принцип действия механизма обобщений, а также п ри ч ины, по ко­ торым он иногда ведет себя не совсем обычно. Поэтому ниже описывается вкрат­ це, каким образом обобщения реализованы в языке java. Важное ограничение, которое было наложено на способ реализации обобще- 11ий в Java, состояло в том , что требовалось обес печить с овмести м ос ть с преды­ дущими версиями Java. Проще говоря , обобщенный код должен быть совмести м с п режним кодо м, существовавшим до появлен ия обобщений. Это означает, что любые изменения в синтаксисе языка Java или ви ртуальн ой машине JVМ долж­ ны вноситься , не нарушая старый код. Способ, которым обобщения реализуются в Java для удовлетворения этому требованию, называетс я стиранием. Рассм отрим в общих чертах принцип действия стирания. П ри компиляции прикладного кодаjаvа все с ведения об обобщенных типах удал яютс я ( сти раютс я). Это означает, что параметры типа сначала заменяются их ограничивающим ти­ пом, которым является ти п Ob ject, есл и никакого явного ограничения не указа­ но. Затем вы полня ется требуемое приведение ти пов, определяемое аргументами типа, для обеспече н и я совмести мости с ти пами, указанными в эти х ар1-уменпtх. Компилятор также обеспечивает эту совместимость типов. Та кой подход к обоб­ ще ниям означает, что никаких сведений о типах во время выполнения не суще­ ствует. Это просто механизм автоматической обработки исходного кода . Мостовые методы Иногда ком пилятору приходится вводить в класс так назы вае м ый мостовой ме· тод в качестве выхода из положен ия в тех случаях, когда результат стирания ти­ пов в перегружаемом методе из подкласса не совпадает с тем, что получается пр и сти ран ии в анало ги ч ном методе из суперкласса. В этом случае со3Дается метод, который испол ьзует стирание типов в суперклассе и вызывает соответствующий метод из подкласса с указан ным сти ран ием типов. Безуслов но , мостовые методы появляются только на уровне байт-кода , они недоступны для программиста и не могут быть вызваны непос редственно. Несмотр я на то что программистам обычно не приходится иметь дело с мосто­ выми методами, поскольку они им не особенно нужны, все же полезно рассмотреть с итуац и ю , в кото рой они создаются. Расс мотрим следую щий нример нрограммы:
'32 Часть 1. Язык Java 11 Ситуация, в которой создается мостовой ме тод class Gen<T> { Т оЬ; // объявить объе кт типа Т 11 передать конструктору ссыпку на объект типа Т Gen(T О) ( оЬ=о: 1 1 возвратить объект оЬ т getob() { return оЬ; 11 Подкласс, производный от класса Gen class Gen2 extends Ge n<String> f Gen2 (String о) super (o) ; 11 перегрузить ме то д getoЬ () для получения символь ных строк String getoЬ () ( Syз tem.out . print ( "Bызaaн метод String getob() : ") ; return оЬ ; 11 пр одемо нстрировать си туацию, когда требуе т ся мостовой метод сl азз Bridge Derno { puЫic зtatic void ma in (String arg s[J) ( 11 созда ть объе кт типа Gen2 для сиыаольных с трок Gen2 str0b2 z пеw Gе п2 ("Тест обобщений" ) ; System. out . print ln lstrOb2 .getoЬ{) ); В зтой программе класс Gen2 расширяет класс Gen, но делает это с помощью спе­ циальной строковой версии масса Gen , как показывает следующее объяаление: class Gen2 extends Gen<String> f Более того , в клас се Gen2 переопределяется метод getob ( ) с возвращаемым типом String, как показано ниже. 11 перегрузить метод getoЬ () дпя получе ни я символьных строк String getob () ( System.out. print ( "Bызвaн ме тод String get oЫ J: ") ; return оЬ ; Все это вп ол не допустимо. F,д инственное затруд нение состоит в том, что из-за сти рания типов ожидаемая форма метода getob () будет выглядеть следующим образом: Object getoЫ ) 1 // ...
Гл ава 14. О бобще ния '33 Чтобы разрешить это затруднение, компилятор создает мостовой метод с упо­ мянугой выше сигнатурой , который вызывает строковый вариант метода get оЬ ( ) . Та к, есл и исследовать и нтерфейс класса Gen2 по команде javap , то в нем можно обнаружить следующие методы: class Gen2 extends Gen<j ava .lang.St ring> { Gen2 (java.lang . Strin gJ ; java.lang . String getob(); java.lang .Obj ect getob(); // мо стовой ме тод Как видите. в их число входит и мо стовой метод . (Комментар ий добавлен авто­ ром, а н е командо й javap , а результат ее выполнения может меняться в зависимо­ сти от используемой версииjаvа . ) И наконец, обратите внимание на то , что оба варианта метода getob () отли· чаются лишь типом возвращае мого значения. Обычно это приводит к ошибке, но поскольку происходит не в исходном коде, то никаких осложнений не во зникает, и виртуальная машинаJVМ находит правильный выход из данного положения. Ошибки неодн о з начности Включение в язык обобщений породило неод-нотачностъ - новый вид ошибок, от кото рых приходится защищаться. Ошибки неоднозначности происходят, когда стирание типов приводит к тому, что два внеш не разн ых объя мения обобщений разрешают один и тот же стираемый ти п, вызывая конфликт. Рассмотрим следую­ щий пр и м ер программы, в которой выполняется пер егрузка методов: 11 неоднозначность возника ет в результате стирания 11 типо в пере гружаемых методов class MyGenClass<T, V> 1 Т оЫ; V оЬ2; 1/ ... // Эти два пере гружаемых метода неоднозначны и не компилируют ся void set (T о) оЫ=о; void set (V о) оЬ2=о; Обратите внимание на то , что в классе MyGenClass объя вля ются два обоб щен· ных типа - Т и V. В классе MyGenClass предпринимается попытка перегрузить метод set (), исходя из параметров ти па Т и V. На первый взгляд это вполне обо­ с нованно. потому что параметры Т и V вроде бы относятся к разным типам . Но именно здес ь возникают две проблемы неоднозначности . Первая проблема состоит в следующем: судя по тому, как нап исан класс MyGenClass, ничто не требует, чтобы параметры Т и V относилис ь к разн ым ти·
434 Часть 1. Язык Java пам. В п ринци п е объект класса MyGenC lass вполне во зможно создать, например, следующим образом: MyGenClass<St ring, S tri ng> obj = new MyGenClass<String , St ring> () В этом случ ае оба п араметра типа Т и V будут за менены типом String. Благодаря :� тому оба ва рианта метода set () оказываются одинаковыми, что , безусловно , считаетс я ошибкой . Вто рая, боле е фундаментал ьн ая проблема состоит в то м, что стирание типов приводит оба варианта метода se t ( ) к следующей форме: void set (Object о) ( // ... Это означает, что 11ерегруз ка метода set (), которую пытается осуществить класс MyGenClas s, в де й ствительности неоднозначна. Ош ибки неоднозначности трудно ис править. Та к, если заранее известно, что пара­ метр типа V всегда будет получать некоторый подтип класса NщnЬе r, то можно попы­ таться исправить класс MyGenC lass, переписав его объяаление следующим образом: class MyGenClass<T , V extends NumЬer > 1 // почти верно! Это исправление по зволит скомпилировать класс MyGenC lass, и можно будет даже получать экземпляры его объектов так, как показано ниже. MyGenClass<String, NumЬe r> х = new MyGenClass<S tring, NumЬer> () ; Та кой код впол не работоспособен , поскол ьку в Java можно точно определ ить, како й именно метод следует вызвать. Но неоднозна чность возн и кнет при попыт­ ке выполнить следующую строку кода: MyGe nCl ass<NumЬe r, NumЬe r> х = new MyGenClass<NumЬe r, NumЬer> (); Есл и параметры тип а Т и V получают тип NumЬe r. то какой и менно вариант ме­ тода set (} следует вызвать? Вызов метода set () теперь неоднозначен . Откров ен н о говоря, в предыдущем примере было бы лучше объя вить два мето­ да с разн ыми именами , вместо того чтобы перегружать метод set (}. Разрешение неоднознач ности зачастую требует реструктуризации кода , потому что неодно­ значность свидетельствует о принципиальной проектн ой ошибке. Некоторые ограничения, присущие обобщениям Имеются н екото рые ограничения, о кото рых сJi едует помнить , применяя обоб­ щен ия. К их числу относ ится создан ие объектов по п араметрам типа. статических членов, искл юче н и й и массивов. Каждое из этих ограничений рассматривается далее по отдельности. Получить экземпляр по параметру типа неn ьэя Создать экземпля р по параметру типа нельзя. Расс мотр им в качестве примера такой класс:
/ / Создать экземпляр типа Т нель зя class Gen<T> { Т оЬ; Gen() { оЬ � пеw Т() ; // Недопустимо !!! Гл ава 14. Обобщения '35 В дан н ом примере п редпринимается недопустимая попытка создать экземпляр типа Т. Причину недопустимости та ко й 011ераци и н етрудн о понять: ко мпилятору неизвестен тип создаваемого объекта. Ведь параметр типа Т - это просто место для подс тан овки конкретного ти па. Ограничения на статические чпены Ни в одном из стати ческих членов нельзя использовать параметр ти па, о бъ­ являемый в его классе. Например, оба статических члена следующего класса не­ допусти мы: cla ss Wrong<T > { // Не верно , нель зя созда ть статические переменные типа Т static Т оЬ; // Неверно, ни в одном из статическим методов нельзя 1 1 использовать параметр типа Т static Т getob 1) ( return оЬ ; Если объя вить стати ческие члены по параметру типа, объявленному в их клас­ се, нельзя, то объявить статические обобщенные методы со своими параметрами типа все же можно, как демонстр иро валось в пр имерах , п редставленных ранее в этой главе. Ограничения на обобщенные массивы И меются два важ н ых огран ичения , накладываемые на обоб ще ния и касающие­ ся массивов. Во-первых, нельзя с оздать экземпля р массива, тип эле мента кото рого определяется параметром ти па. И во-втор ых, нел ь. . "\я создать массив специфиче­ с ких для ти па обобщенных ссылок. Оба эти ограничения демонстр и руются в сле­ дующем кратком примере п рограммы: // Обобщения и ма сси вы class Gen<T ехtепdз NumЬe r> ( 'Г оЬ; Т vals[]; // Верно ! Gen{'Го, Т[]nurnз) { оЬ=о; 11 Этот оператор не верен 11 vals = new Т[10]; // нельзя создать массив типа Т
'36 Часть 1. Язык Java // Тем не менее этот опера тор верен vals = пums; // можно присвоить ссылку существующему массиву class GeпAr rays ( puЫic static void ma in ( String args[] ) ( Integern[]=(1,2,З,4,5}; Gen< Integer> iOb = new Gen< Integer> (50, n) ; 11 Нель зя создать ма ссив специфических для типа обобщенных ссылок 11 Gen<Integ er> g ens [J = new Gen<Integer> [lO] ; / / Неверно ! 11 А это верно! Gen<?> gens [] = new Gen<?>[lOJ; // Верно ! Как показы вает дан ный пример программы, объявлять ссьшку на массив типа Т до пустим о , как это сделано в следующей строке кода: Т vals[]; // Верно! Те м не менее нел ьзя получить экземпляр массива типа Т, как показано в при· веденной ниже закомменти рованной строке кода. // vals = new T[lO] ; // нельзя создать массив объе ктов типа Т Создать массив типа Т нелЬ3Я потому, что тип Т не существует во время выпол­ н е н ия , а следовател ьно, компилятор не в состоянии выяснить, массив элемен тов какого ти па требуется в действительности создавать. Тем не менее конструктору Gen ( ) можно передать ссылку на совместимый по типу массив при созда ни и объекта и присвоить эту ссылку переменной vals, ка к это делае-n:я в приведенной ниже строке кода. va ls = nums ; // мо жн о присвоить ссылку существующему ма сси в у Это вполне до пустимо, поскольку масс ив, переда вае м ы й классу Gen, имеет изве с тн ый ти п, совпадающий с типом Т на момент создания объекта. Следует. однако , иметь в виду. что в теле метода ma in () нельзя объявить мас сив ссылок на объекты конкретного обобщенного ти па. Та к, следую щая строка кода не под­ лежит ко мпиляции : 1 1 Gen< Integer> gens [J = new Gen< Intege r> [lOJ ; // Неверно ! Те м не менее массив ссылок на обобщен ный тип можно со3дать , если воспоЛJ,­ зоваться метасимвол ом, как показано ниже. Посrупить так луч ше, чем применять массивы базовых типов. Ведь, по крайней мере, некоторые проверки ти пов мoiyr быть по-прежнему выпол нены компилятором. Gen<?> gens [] = new Gen<?>[lO); // Верно ! Ограничения на обобщенные исключения Обобщенный класс не может расширять класс ThrowaЫ e. Это оз нач ает, что создать обобщенные кл ассы исключений нельзя .
15 Лямбда-выражения В ходе 11с111н·ры111 rо 111 ю,11ол жа ю1t\t'ii('Я ра:.1работк и и ра :ш 1 пия 11 .Ja,·a был 11 1ш е- 11 1х.·11 ы MllO/'Иt' s1 :1i.t KOl\bl(' <' J >t'J\П'Ш\, 11<1'/ИllаЯ с исходн ой /lt'JK Ш1 1.0. l lo ;111а IJ:l llllX сж·дует 11ылсл ит1, особо. 1юс ко11 ы<у 01н1 ко ре 1 111ы м образnм 11:1ме1111;111 :·пот язык, а слсд1н �атсл ыю. и 1юрЯi\ОК 11а1111са11 11я 11а нем кnда . Пер вым и:1 этих 1101юnве;1,е11иii ст;uш обобще1111я , в11е;1рс1ш 1м· 11 11с рп 111 .J ОК 5 и рассмотре 1111ыс n 1:11anc 14, а 11тn­ рым - 11 я мuit<1·ш.1 ражс11ш1. кото р ы м 1юпн1 щсна ш\стонщ<НI rл а11а. Л11 мfi;1а·вы р ажеш1я и свя:1а1111ые с 1111\нt cpcl1.CГB<t , 1шt�;1.ре11ныс 11 не1к1н1 .J l>К 8. з11а•1 итt· J1ыю усо 11с р 111е1 1ство11ал 11 я:1 ык.J а\·а 110 СЛ<.'/1.}' ЮЩ11м r1 \>1Р11111ам. В о · 1 1ср в ы х , ОШI llLIO.'\llT 11 п111такс11с 1101\bl(.' :M ('M('J /Tbl , l/Ollbl/llaющ11e BЫJ ><IЗllТ(.'JJ ЫIY IO п1лу яаыка. 01111, 1ю ч· 111t·1·т 11у. у11рощают 11оря;1о к реализа1 11н1 некоторых оGщнх язы­ ковых кo1 1cтpyю (l l ii. И во- вторы х. 1111c;1pt'1111c 1н 1мб;1а·выр<tжсн11ii 11о:шол11ло на­ дt•;нпъ llOl\ЬIMll 111 1:\MOЖ llOCTЯТll б11Gт ютt·ку llJHfКJli\Дl/01'0 111ю1·pa\t M HOl'O и11те р· фeika ЛРI. К их •1 11.-;1�· от1юопо1 во:1мож1юсл, у11росппь 11аралл<'J1 1,11ую обработ· ку 11 \tlJO l'OH,1 ('JНIЫX .-рс;1ах, особе1 11111 11 IНIКЛИ'IСС КИХ 011еран11ях. Ш.НIОЛIJЯ('МЫХ в с·п1л<· t- �J r Cdct1, а также n 11о�1ом 111н1 кла,11,ном програ ммном ш1·1·<· p1/ 1ciice ЛРI 1юто коr1 1шо:1а -nы110;1а . l'/IC 1ю;щ<· р жи 11а юто1 коtшейерные 011t�ра ни11 с ;1а1111ы,111. В 11ед р сш1е лн :">1fi;1а-выражt·1111i·i 1юи1уж шю также катал иэ.атор ом J\JJ ll в11с;tрt'1шя 11 Ji\·a ;1 руп1 х 1ю11ых сре;1ст11, 11кл ю • 1 а я 11 н�тсщы 1ю умолчанию, р асс мотре1111ые n 1·лаnе !1 ll 11o:ШOJIЯIOЩll<.' Oll(J<'.'\('Jl li'IЪ (' 'J'<l JJ;\il ) Hl lOC JIO BCДl.'IШ(' 11 11·1 ·e pфej.i <'IIOI'() \te· то;щ, а такжt• 01 нtсыва<·мые 11 :-iтoii 1·л;шс 1тыл1<11 Шt методы. 1tо:шолто111111 . · ссы· латьсн на метол . нt· 11ы 1ю;111ян tто . Помн:-.ю 11р<'И \1у�11е1тв . 1ютn рыс лямбда-выражения в1 ю<"ят в-'"'"' · имееп·я еще одна 11ри•11111а. 1ю кото роii 011 11 яnляю 1·1 ·s1 оче111. 11аж11ым до 11оm1с11111.�м :-:1того я:1ык<1 11ро�·1ыым111ю11а1111н. Ja 1 1осж:;11111е 11ескол1.ко летлямбда-выражс11 1 1я стал 11 11рсдме­ том 1л;ш1 ю1·0 1ш 11:-.ы1�ня 11 обласп1 ра:1работю1 язы ко в 11ро1·рамм11рtша1 11ш. В част- 1 юсп1 , 01111 бы;1 11 11111.·дрены 11 та к �н· HJ1>1t01, как С# и С++. А 11х 11к;1юче111н· 11 состав Вt'J)(' И1 1 .J()K Н 110:111щ1 11Jю сохрашпъ жшюil , 11о ватор ск11й хара1п1:р я:н,1 ка . f а,·а, к ко­ торо му ужt· 11\ Hl llЫ KЛ ll Tt:, кто 11<1 llC�I llJ>Ol'\M\tMllpycт_ В ко11еч 1ю\1 1по1·<' ля :1.·1б;р-11ыражс11 11я 11:1менилн соврсме1 1 1 1 ы �i 1111;1, языкаj<1\ а та­ ю 1 м же обра:юм, к;,� 1,; :по с;\слал и обобщ1.·1н1я llC<' KOJ1 1,кo лt:т 11а :1ад. Проще l'<шоря, 11 1 1едрt·11ш· лнмбда·11ы ражс1111ii ока жt·т 1vшя1111е 11р акт1J <1ссю1 ш1 всех 11роr·1 м:1-1м11ру­ ющих па J<1\·a . и llO>HO:l.I ); IJIШ Jtei·i< Tlll JТ(.'Jl l>l lO являкнся O'IClll> наЖ/ IЫ\1 llOIIOBll('ДC· 1111ем вJ\\·а .
438 Часть 1. Язык Java Введение в лямбда-выражения Особое значение для ясного п редставления о то м, каким образом лямбда-вы· ражения реал изованы в Java, имеют две я зы ковые конструкции. Первой из них яв· ляется само лямбда-выражение , а второй - функциональный интерфейс . Начнем с п ростого определения каждой из этих конструкци й. Лямбд а-выражение, по существу, является анон имным ( т.е . безымянным) мето­ дом. Н о этот метод не выполняется самостоятельно, а служит для реализации ме­ тода, определяемого в функциональном интерфейсе . Таким образом , лямбда-вы· ражение приводит к некоторой форме анонимного класса. Нередко лямбда-выра· жен ия назы вают также замыкания.ми. Функционалъным называется такой интерфейс, который содержит оди н и тм:ько один абстрактный метод. Как правило, в таком методе определяется п редполагаемое назначение интерфейса. Следовател ьно, функциональн ый и нтерфейс предсгамя­ ет единстве нное де йствие. Например, стандартный интерфейс RunnaЫe является функциональным, пос кольку в нем определяется единственный метод run ( ) , ко­ торый , в свою очередь, определяет действие самого интерфейса RunnaЫ e. Кроме того, в функционал ьном интерфейсе определяется целевой тип лямбда-выражения. В связи с этим необходимо подчеркнуть следующее : лямбда-выражение можно ис· пользовать только в том контексте, в котором определен его целевой тип. И еще одно замечание: функциональный интерфейс иногда еще называют SА М-типом, где SАМ обозначает Single Abstract Method - единственный абстрактный метод. На заметку! В фун кциональном интерфейсе можно определить любой открытый метод, опреде­ ленный в классе Obj ect, например, метод equals ( J, не воздействуя на состоя ние его фун кционального интерфейса. Открытые методы из класса Ob ject счита ются неявными членами фун кционального интерфейса, поскольку они авто матически реал и зуются экзем· пляром фун кционального интерфейса. Рассмотрим подробнее как лямбда- выражения, так и функционал ьные интер­ фейсы. Ос новные п ол ожения о лям бда- выражениях Лямбда-выражение вносит новый элемент в синтаксис и оператор в язы к Java. Этот новый оператор называется лямбда-операт&/юм, или операцией "стрелка" (->) . Он разделяет ля мбда-выражен ие на две части . В левой части указываются любые параметры, требую щиеся в ля мбда-в ыражении. (Если же параметры не требуются , то они указываются пустым списком .) А в правой части находится тело лямбда-f!ы· ражения, где указываются действия, выполняемые лямбда-выражением. Операция -> буквально означает "становиться " или "переходить". В Java определены две разновидности тел лямбда-выражений. Одна из них со­ стоит из единственного выраже ния, а другая - и з блока кода. Рассмотрим снача· ла лямбда-выражения, в теле которых определяется единственное вы ражение. А лямбда-выражения с блочными телами обсуди м далее в это й главе.
Глава 15. Л ямбда-выражен и я 1.39 Прежде чем продолжить дальше, имеет смысл обратиться к некоторым при­ мерам лямбда-выражений. Рассмотрим сначала самое простое лямбда-выражение, какое только можно написать . В приведенном ниже лямбда-выраже нии вычисля­ ется значение ко нстанты . () -> 123.45 Это лямбда-выражение не принимает никаких параметров, а следовательно, список его параметров оказывается пусты м. Оно возвращает значение константы 123 , 45. Следовательно, это выражение аналогично вызову следующего метода: douЫ e myMeth () { return 123 . 45; } Разумеется , метод, определяемый лямбда-выражением, не имеет имени. Ниже приведено более инте ресное лямбда-выражение. () -> Math. random() * 100 В этом лямбда-выражении из метода Маth . random ( ) получается псевдослучай­ ное значение, которое умножается на 100 и затем возвращается результат. И это лямбда-выражение не требует параметров. Если же лямбда-выражению требуются параметры, они указываются списком в левой части лямбда-оператора. Ниже при­ веден простой пример лямбда-выражения с одн им параметром. (n) -> (n % 2)==0 Это выражение возвращает логическое значение true, если числовое значе­ ние параметра n оказывается четным. Тип параметра (в данном случае n) можно указывать явно , но зачастую в этом нет никакой нужды, поскольку его тип в боль­ шинстве случаев выводится. Как и в именованном методе, в лямбда-выражении можно указывать столько параметров, сколько требуется. Функциональные интерфейсы Как пояснялось ранее, функциональным называется такой интерфейс, в кото­ ро м определяется единственн:ый абстрактный метод. Те , у кого имеется предыду· щий опыт программирования нajava , могут возразить, что все методы интерфей­ са неявно считаются абстрактн ыми, но так было до внедрения лямбда-выражений. Как пояснялось в гл аве 9, начиная с версииJDК 8 для метода, объявляемого в ин­ терфейсе, можно определить стандартное поведение по умолчанию, и поэтому он называется .методом по умол'Чанию. Отныне интерфейсный метод считается аб­ страктным лишь в том случае, если у него отсутствует реализация по умолчанию. А поскольку интерфейсные методы , не определяемые по умолчанию, неявно счи­ таются абстрактными, то их не обязательно объявлять с модификатором досrупа abstract, хотя это и можно сделать при желании. Ниже приведен пример объявления функционального интерфейса. interface MyNumЬer { douЫ e getValue (); В дан ном случае метод ge t Va 1 ue ( ) неявно считается абстрактным и единствен­ ным определяемым в интерфейсе MyNumЬer. Следовательно , интерфейс MyNumЬer является функциональным, а его функция определяется методо м getValue ().
''О Часть 1. Язы к Java Как упоми налось ранее, лямбда- вы ражение не вы полняетс я самосто яте льно, а ско­ рее образует реализа цию абстракпюго ме тода, опре де ле нного в фун кциональном ин· терфейсе, где указываетс я его цел ево й тип. Так им образом, лямбда- выраже ние может быть указанотолько в том контексте, в котором определе н его целе войтип. Один из таких контек стов создается в то м случае, когда лямбда-выраже ние присваивается ссьике на функц иональный инте рф ейс. К числу других конте кс тов це ле вого типа от­ нос ятся ини ци ализация пе ре ме нных, опе рато ры return и аргументы мето дов. Рас смотрим приме р, де монстрирующий приме нение лямбда-выраже ния в кон­ текс те при сваи вания. С этой це лью сначала объявляе тся ссылка на фун кциональ­ ный интерфейс MyNurnЬe r, как показано ниже. 11 созда ть ссылку на функциональ ный интерфейс МyNul llЬe r MyNumЬer myNum ; Затем лям бда-вы ражени е присваивается этой сс ылке на функци ональны й ин­ терфейс следую щим обр азом: 11 использовать лямбда-выражение в контексте присваивания myNum = () -> 123 .45; Когда лямбда- выраже ние появляе тся в контек сте свое го це ле вого типа, автома­ тически создается эк зе мпляр класса, ре ализую ще го функциональный инте рфей с, при чем лямб да-выраже ние опре деляет пове де ние аб страктного ме тода, об ъявля­ ем ого в функци ональном инте рфе йсе. А когда этот ме тод вызывае тся че ре з свой адресат, вы полняе тся лямбда-выражение . Таким образом, лямб да-выраже ние по­ зволяет пре образовать сегме нт кода в об ъе кт. В преды дуще м приме ре лямбда-выражение становится ре ализацие й ме тода getValue ().В итоге получае тся значе ние константы 123 ,45, которое выводится на эк ран следующим образом: 11 вызвать ме тод qetValue () , реализуемый 11 присв оенным ранее лямбда-выражением System . out .println ( "myNum . getValue () ); Лям бда-вы раже ние было ранее при свое но пе рем енной myNшn ссы лки на фун к· циональны й интерфе йс MyNurnЬer. Оно возвращает значе ние константы 123 ,45, которое получае тся в ре зультате вы зова ме тода ge tVa lue (). Для того что бы лямбда-выраже ние использовал ось в конте ксте свое го це ле вого типа, абстрактный мето д и лямбда-выражение долж ны быть совмести мыми по типу. Так, ес ли в абстрактном мето де указ ываются два парам етра типа int, то и в лямбда­ вы раж ении долж ны быть указаны два параметр а, типкото рых явно обо значаетс я как int или неявно выво дит ся как int из самого контекста. В об щем, пар аметры лямбда­ вы раж ения должны бы ть совместимы по типу и количеству с параметрами аб стр акт­ ного мето да. Это же относитс я и к возвр ащае мы м типам. А любые исключе ния, гене­ рируемые в лямбда-выраже нии, должны быть прием лемы для аб стр актн ого метода. Не кото рые примеры л ямбда - выраже ний Принимая во внимание все сказанное выше, рассмотрим ряд простых приме· ров, де монстри рующих основные принципы де йствия лямбда-выраже ний. В пер-
Гл ава 15. Лямбда-выражения "' вом примере программы все приведенные ранее фрагменты кода собраны в еди­ ное целое: // Продемон стрировать применение простого лямбда-выраже ния // Фун кциональный интерфейс interface MyNwnЬe r { douЫ e getValue () ; class LamЬda Demo { puЫic static void ma in (St ring args [] ) { MyNwnЬer myNum ; // объявить ссылку на функциональный интерфейс 11 Зде сь лямбда- выражение просто является константным выр аже нием . 11 Когда оно присваивается ссылочной переменной шyNUl ll , получается 11 экземпляр кл асса , в котором лямб да- выражение реализуе т 11 ме тод qetValue () из функциональ ного интерфейса МyNWl lЬe r myNum = () -> 123 .45; 11 выз вать ме тод qetValue () , предоставляемый 11 присвоенным ранее лямбда-выражением Systern . out .println ("Ф иксированное значение : " + myNum .getValue () ); 11 А здесь используется более сложное выражение myNum = () -> Math. raпdorn() * 100; 11 В следующих строках кода вызывается лямбда- выраже ние 11 из предыдущей строки кода Systern. out . println ( "Cлyч aйнoe значение : " + rnyNum .getValue () ); System.out . println ( "Еще одно случайное значение : " + rnyNum. getValue()) ; 11 Лямбда- выражение должно быть совместимо с абстрактным ме тодом , 11 определяемым в фун кци ональ ном интерфейсе. Поэ тому следующа я 11 строка кода ошибочна : 11 myNum = () -> "123 .03"; 11 ОШИБКА! } Ниже приведен результат, выводимый данной программой. Фиксированное значение : 123 ,45 Случайное значение : 88 . 90 663650412304 Еще одно случайное значение : 53 .' 00582701784129 Как упоминалось ранее , лямбда-выражение должно быть совместимо по типу с абстрактн ым методом, для реализации которого оно предназначено. Именно поэтому последняя строка кода в приведенном выше примере закомментирована. Ведь значение типа String несовместимо с типом douЬle, возвращаемым мето­ дом getValue (). В следующем примере программы демонстрируется применение лямбда-выра­ жения с параметром: 11 Продемонстрировать применение лямбда- выраже ния, 11 принимающе го один параме тр 11 Еще один функциональ ный интерфейс inte rface NurnericTest {
442 Часть 1. Язы к Java boolean test (int n) ; class Lambda Demo2 { puЫ ic static void ma in (String args[] ) { // Лямбда- выражение , проверяющее , является ли число четным NumericTe st isEven = (n) - > (n % 21==0; if ( isEven .test (l0) ) System.out . print ln ( "Чиcлo 10 четное") ; if (!isE ven .test (9) ) System.out . println ( "Чиcлo 9 нечетное") ; / / А теперь воспользоваться лямбда -выраже нием , в ко�·ором // проверяется , является ли число неотрицательным NumericTest isNonNeg = (n) -> n >= О; if ( isNonNeg .test (l) ) System.out .println ( "Чиcлo 1 неотрицатель ное ") ; if (!isNonNeg.test (-1) ) System.out .println ( "Чиcлo -1 отрица тельное ") ; Ниже приведе н резул ьтат, выводи мый да нной программой . Число 10 четное Число 9 нечетное Число 1 неотрицательное Число -1 отрицательное В данном примере программы демонстрирустп1 1л ав1 1ая особенность лямбда­ выражений, требующая более подробного рассмотрения. Обр<�ти тс особое внима· ние на лямбда-выражение, выполня ющее проверку на равенсгво: (n) -> (П 2)==0 Обратите внимание на то , •1то тип 11еременной п 11е ука:3ан, но вьшо;tится из контекста. В да шюм случае тип переменной n вы водится из тина int нарамстра метода test (), определяемого в фу11кциошu1ыюм инте рфейп· NumericTest. Впрочем, ни•1то пе мешает явно укааап, ти н параметра в лямб;{а-выражении. Например, сле;1ующее лямбда-в ыражение так же достоверно, как и 11ре;�ьщущее: (int n) -> (n l 2)==0 где параметр n яв1ю указы вается как int. Как 11равюю, явно указывать тип параме­ тро в лн:v1бда-выражений необязttтелыю , хотя в некоторых случаях это может вес же потребоваться. В данном примере программы демонстрируется еще одна важная особен· ность лямбда-выражений. Ссылка на функциональный инте рфейс может быть испол ь:ювана дл я выполнения любого совместимого с ней лямбда-выражения. Обратите внимание на то , что в данной программе определяются два разных лямбда-в ыражения, совместимых с методо м test () и:j фун кционального интер­ фейса NumericTest. В первом лямбда-выражении isNonNeg проверяется , яв­ ляется ли числовое :шачение отрицател ьным, а во втором лямбда-выражении i sNonNeg - явлнето1 ли оно неотрицател ьн ым. Но в любом 01у•ше проверяется значение параметра n. А поскольку каждое и:i ::�т их лямбда-выражений совместимо с методом test (),то оно выполняется 110 ссылке ш1 фун кциональный интерфейс NumericT est.
Гл ава 15. Лямбда -выражения 1.1.З Прежде чем продолжить дальше , следуе т сделать еще одно замечание. Если у лямбда�выражения имеется единственный параметр, его совсем не обязательно заключать в круглые скобки в левой части лямбда-оператора. Например, приве­ де нный ниже способ написания лямбда-выражения также допустим в программах. n->(n%2)==0 Ради согласованности в представленных далее примерах программ списки па­ раметров всех лямбда-выражений заключаются в круглые скобки - даже если они содержат единственный параметр. Разумеется , вы вольны выбрать тот способ ука­ зания параметров лямбда-выражений, который вам бол ьше по душе. В приведенном ниже примере программы демонстрируется лямбда-выраже­ ние, принимающее два параметра. В данном случае в лямбда-выражении проверя­ ется, является ли одно число множителем другого. 11 Продемо нстрировать применение лямбда -выражения , 11 принимающе го два параме тра inter face NшnericTest2 { bool ean test (int n, int d) ; class LamЬda Demo З { puЫ ic static vo id main (String args[] ) { 11 В этом лямбда-выражении проверяе тс я, является ли 11 одно число мн ожи телем друг ого NшnericTest2 isFactor = (n, d) -> (n % d) == О; if (isFactor .test (lO, 2) ) System.out . println ( "Чиcлo 2 является множителем числа 10") ; if (!isFactor .test (lO, 3) ) System. o ut . println ( "Чиcлo З не является множителем числа 10") ; Ниже приведен пример выполнения данной программы. Число 2 является мн ожителем числа 10 Число 3 не является множи телем числа 10 В данном примере программы метод test () определяется в функциональном интерфейсе NumericT est2 следующим образом: bool ean test (int n, int d) ; При объявлении метода test () указываются два параметра. Следовател ьно, в лямбда-выражении, совмести мом с методом test (), следует также указать два параметра. Ниже показан о, как это делается. (n,d)->(n%d)==О Оба параметра, n и d, указываются списком через запятую. Данный пример можно обобщить. Всякий раз , когда в лямбда-в ыражении требуется больше одного параметра, их следует указать списком через запятую, заключив в круглые скобки в левой части лямбда-операто ра.
"' Часть 1. Язы к Java Следует, однако , иметь в виду, что если требуется явно объявить тип одного из параметров лямбда-выражения , то это следует сделать и для всех остальных пара­ метров. Например , следующее лямбда-выражение достоверно: (intn,intd)->(n%d)==О А это лямбда-выражение недостоверно: (intn,d)->(n%d)==О Бл о ч ные л ямбда- выражения Те ло лямбда-в ыражений в предыдущих примерах состояло из единственного выражения. Такая разновидность тел называется rпш�ом въtражения, а лямбда-выра­ жения с телом выраже ния иногда еще называют оди1шчнъLМи. В теле выражения код, указы ваемый в правой части лямбда-оператора, должен состоять из одного выражения. Несмотря на все удобство одиночных лямбда-выражений, иногда в них требуется вычислять не одно выражение. Для подобных случаев вJava пред­ усмотрена вторая разновидность лямбда-выражений, где код , указываемый в пра­ вой части лямбда-оператора, может состоять из нескольких операторов. Та кие лямбда-выражения называются МочнъLМи, а их тело - телом блака. Блочное лямбда-выражение расширяет те виды операций, которые могут вы­ полняться в лямбда-выражении, поскольку оно допускает в своем теле наличие не­ скольких операторов. Например, в блочном лямбда-выражении можно объявлять переменные, организовывать циклы, указывать операторы выбора if и switch, создавать вложенные блоки и т. д. Создать бл очное лямбда-выражение совсем не трудно. Для этого достаточно заключить тело выражения в фигурные скобки та­ ким же образом, как и любой другой блок кода. Кроме наличия в теле выражения нескольких операторов, блочные ля мбда­ выражения применяются точно так же , как и упоминавшиеся ранее одиночные лямбда-выражения. Однако для возврата значения из бл очных лямбда-выражений нужно явно указывать оператор return. Это нужно делать потому, что тело блоч· ного лямбда-выражения не представляет од иночное выражение. Ниже приведен пример программы, в котором блочное лямбда-выражение применяется для вычисления и возврата факториала целочисленного значения. 11 Блочное лямбда-выражение , вычисл яюще е 11 факториал целочисленного значения interface Nume ricFunc int func (int n) ; class BlockLamЬ daDemo { puЬl ic static void ma in (String args[] ) { 11 Это блочное лямбда-выражение вычисляе т 11 факториал целочисленного значения Nume ricFunc factorial = (n) -> { ' int result = 1;
for (int i=l; i <= n; i++) resul t = i * result; return result ; }; Гл ава 15. Лям бда-вы ражения ''5 System. out .println ("Фaктopиaл числа З равен " + factorial . func (З) ); System. o ut . println ( "Фaктopиaл числа 5 равен " + factorial . func (5) ); Ниже приведен результат выполнения данной программы. Фактори ал числа З равен 6 Фа кториал числа 5 равен 120 В данном примере программы обратите внимание на то , что в блочном лямбда­ выражении объявляется переменная result, организуется цикл for и указывает­ ся оператор return. Все эти действия вполне допустимы в теле блочного лямбда­ выраже ния. По существу, тело блока такого выражения аналогично телу метода. Следует также иметь в виду, что когда в лямбда-вы ражении оказывается оператор re turn, он просто вызывает возврат из самого лямбда-выражения , но не из объ­ емл ющего его метода. Ниже приведен еще один пример блочного лямбда-выражения. В дан ном при­ мере программы изменяется на обратный порядок следования символов в строке. 11 Блочное выражение, изме н яюще е на обратный 11 порядок следования символов в строке inter face StringFunc String func (String n) ; class BlockLamЬda Demo2 { puЫ ic static vo id ma in (String args [] ) { 11 Это блочное выражение изменяет на обратный 11 порядок следования символов в строке StringFunc reverse = (str) -> { String result = ""; int i; for(i = str. length()-1; i >= О; i--) result += str . charAt (i) ; re turn result; }; System.out . println ( "Лямбдa обращается на " + reverse . func ("Лямбда ") ) ; System. o ut . println ("B ыp aжeниe обращается на " + reverse . func ("Выраже ние ") ) ; Ниже приведен результат выполнения данной программы. Лямбда обращается на адбмял Выр ажение обращается на еинежарыв
"6 Часть 1. Язы к Java В данном примере программы в функциональном интерфейсе StringFunc объ­ является метод func ( ) , принимающий параметр типа String и возвращающий значение типа String. Следовательно, в лямбда-выражении reverse тип параме­ тра str должен быть выведен как String. Обратите внимание на то , что метод cha rAt () вызывается для параметра str как для объекта. И это вполне допустимо, поскольку этот параметр имеет тип String благодаря выведению типов. Обобщенные функц ионал ьные интерфейсы Ук азывать параметры типа в самом лямбда-выражении нельзя. Следовательно, лямбда-выражение не может быть обобщенным. (Безусловно, все лямбда-выраже­ ния проявляют в той или иной мере свойства, подобные обобщениям , благодаря выведению типов.) А вот функциональный интерфейс, связанный с лямбда-выра­ же нием, может быть обобщенным. В этом случае целевой тип лямбда-выражения отчасти определяется аргументом типа или теми аргументами, которые указыва­ ются при объявлении ссылки на функционал ьный интерфейс. Чтобы понять и оценить значение обобщенных функциональных интерфей­ сов , вернемся к двум примерам из предыдущего раздела. В них применялись два разных функциональных интерфейса: NumericFunc и StringFunc. Но в обоих этих интерфейсах был определен метод func (), возвращавший результат. В пе р­ вом случае этот метод принимал параметр и возвращал значение типа int, а во втором случае - значение типа String. Следовательно, единственное отлич ие обоих вариантов этого метода состояло в типе требовавшихся данных. Вместо того чтобы объявлять два функциональных инте рфейса, методы которых отлича­ ются только типом данных, можно объя вить один обобщенный интерфейс, кото­ рый можно использовать в обоих случаях. Именно такой подход и принят в следу­ ющем примере программы: 11 Приме нить обобщенный функциональ ный интерфейс 11 с разнотипными лямбда- выраже ниями 11 Обобщенный функциональный интерфейс inter face Some Func<T> { Tfunc (T t) ; class Gene ricFunctionalinterfaceDemo { p uЫic static void ma in (String args [] ) { 11 исполь зовать строковый вариант интерфейса SomeFunc Some Func< String> reve rse = (str) -> { String result = ""; int i; for (i = str. length()-1; i >= О; i--) result += str . cha rAt (i) ; return result; };
Гл ава 15. Лямбда -выражения 1+1+7 System.out . println ( "Лямбдa обращается на " + reverse .func ( "Л ямбд a") ); System. o ut . println ("B ыp aжeниe обращается на " + reve rse .func ("Bыpaжeни e") ); 11 а теперь исполь зовать целочисленный вариант интерфейса SomeFunc Some Fu nc<Integer> factorial = (n) -> int result = 1; for (int i=l; i <= n; i++) result = i * result ; return result; }; System. o ut . println ("Ф aктopиaл числа 3 равен "+factorial . func (3) ); System. out . println ("Фaктopиaл числа 5 равен "+factorial . func (5) ); Ниже приведен результат выполнения данной программы. Лямбда обращается на адбмял Выражение обращается на еинежарыв Факториал числа 3 равен 6 Факториал числа 5 равен 120 В дан ном примере программы обобщенный функциональный интерфейс Sorne Func объявляется следующим образом: interface Some Func<T> Т func(T t); гд е Т обозначает как возвращаемый тип, так и ти п параметра метода func (). Это означает, что он совместим с любым лямбда-выражением, принимающим один па­ раметр и возвращающим значение того же самого типа. Обобщенный функциональный инте рфейс Some Func служит для предоставле­ ния ссылки на два разных типа лямбда-выражений. В первом из них используется тип String, а во втором - тип Integer. Таким образом, од ин и тот же инте рфейс может быть использован для обращения к обоим лямбда-выражениям - reverse и factorial. Отличается лишь аргумент типа, передаваемый обобщенному функ­ циональному интерфейсу Sorne Fu nc. Передача лямбда-выражений в качестве аргументов Как пояснялось ранее, лямбда-выражение может быть использовано в любом контексте, предоставляющем его целевой тип. Один из таких контекстов возни­ кает при передаче лямбда-выражения в качестве аргумента. В действительности передача лямбда-выражений в качестве аргументов является весьма распростра­ ненным примером их применения . Более того , это весьма эффективное их при­ менение, поскольку оно дает возможность передать исполняемый код методу в ка-
448 Часть 1. Язык Java чеп·1iе его <1 р1rме1па . Бл<1годаря этому :шачител ьно повы111<1сто1 выразитсл ы�ая сила языка.Jаvа. Лля переда'lи лямбда-выраже11ия в качестве ар1-уме11та 11арамстр, 1юлу•1ающий это иы ражение в качестве аргумента, должен имеп, ти 11 фу11ю1и онаJ1ыюго инте р­ фейса , со11мссти мо1·0 с этим лямбда-выражением. Несмотря на всю нростоту при­ менения лямбщt-выражений в качестве перс;щвасмых ii р1-у ме11то11 , ноле:и ю вес же ноказать, как это 11роисходит на 1 1ракти ке. В следующем 111тмсрс 111ю1·раммы /(С­ монстр ируется вес ь этот пронесс: 11 П е р е д а ть лямбда- выражение в качестве аргумента мет оду iпter face StriпgFuпc Striпg fuпc (Striпg п) ; class LamЬda sAsArgum entsDemo { 11 П е р в ый параме тр этого ме тода имеет тип функционального 1 1 интерфейса . Следователь но, ему можн о передать ссылку на 11 любой экземпл яр этого интерфейса , включая э к з е мпл я р , 11 создаваемый в лямбда-выражении . А второй п а р ам е тр 11 обо значает обрабатыва емую символ ьную с т року static Striпg striпgOp (St riпgFцпc sf, Striпg s) { return sf. func(s) ; puЫic static void maiп ( Striпg args[] ) Striпg iпStr = "Л ямбда - выраже ния повышают эффе кти вность Java "; Striпg outStr; System.out .priпtlп ( "Этo исходная строка : "+iпStr) ; 11 Ниже приведено простое лямбда- выраже ние , преобразующе е 11 в верх ний регистр букв все символы исходной строки , 11 перед аваемо й методу stringOp () ou tStr = stri пgOp ((str) -> str . toUppe rCase () , iпStr) ; System.out . priпtlп ( "Этo стр о к а в верхнем регистре : "+outStr) ; 11 А здесь передается блочное лямбда- выражение , уд а л я юще е 1 1 п робелы из исходной символ ьной строки outStr = striпgOp( (str) -> { Striпg result = ""; iпt i; for(i =О; i < str.leпgth(); i++) if(str .cha rAt (i) != ' ') result += str . cha rAt (i) ; returп result; ), iпStr ); System.out . println ( "Этo с т р о к а с уда ленными пробелами : " + outStr) ; 11 Конечно, можно переда ть и экземпл яр интерфейса StringFunc , 11 созданный в предыдущем лямбда- выражении . Например , после 11 следующе го объявления ссылка reverse делается на экземпл яр 11 интерфейса Str ingFunc StriпgFuпc reve rse = (str) -> {
); String result ""; int i; for(i = str.length()-1; i >= О; i--) result += str . charAt (i) ; return result; Гл ава 15. Лямбда-выражения '49 11 А теперь ссылку reverse можн о передать в качестве первого 11 параме тра ме тоду stringOp () 11 since it refers to а StringFunc obj ect . System. out . println ( "Этo обращенная строка : " + stringOp (reverse, inStr) ); Ниже приведен результат выполнения данной программы. Э то исходна я строка : Лямбда -выражения повышают эффективность Java Это строка в верхнем регистре : ЛЯМБдА-ВЫРАЖЕНИЯ ПОВНШАЮТ ЭФФЕКТИВНОСТЬ JAVA Э то строка с удаленными пробелами : Лямбда -выр аженияповышаютэффективность Jаvа Э то обращенная строка : яинежарыв - адбмяЛ тюашывоп ьтсонвиткеффэ avaJ Прежде всего обратите внимание в данном примере программы на метод stringOp (), у которого имеются два параметра. Первый параметр относится к типу StringFunc, т. е . к функциональному интерфейсу. Следовател ьно, этот па­ раметр может получать ссылку на любой экземпляр функционального интерфейса S tr ingFunc, в том числе и создаваемый в лямбда-выражении. А второй параметр метода, stringOp (), относится к типу String и обозначает обрабатываемую сим­ вольную строку. Затем обратите внимание на первый вызов метода stringOp (): out Str = striпgOp ( (str) -> str . toUpperCase (), inStr) ; гд е в качестве аргумента данному методу передается простое лямбда-выражение. При этом создается экземпляр функционального интерфейса StringFunc и ссыл­ ка на дан ный объект передается первому параметру метода stringOp (). Та ким образом , код лямбда-выражения, встраиваемый в экземпляр класса, передается данному методу. Контекст целевого типа лямбда-выражения определяется типом его параметра. А поскольку ля мбда-выражение совместимо с этим типом, то рас­ сматриваемый здесь вызов достоверен. Встраивать в метод такие простые лямбда­ выражения , как упомянутое выше, нередко оказывается очень уд обно, особенно когда лямбда-выражение предназначается для однократного употребления. Далее в рассматриваемом здесь примере программы методу stringOp ( ) пере­ дается бл очное лямбда-выражение. Оно уд аляет пробелы из исходной символьной строки и еще раз показано ниже. outStr = stri пgOp ( (str) -> { String result ""; int i; for(i = О; i < str.length(); i++) if (str.charAt (i) != ' ' ) result += str . charAt (i) ;
1.50 Часть 1. Язы к Java return result ; ), inStr); И хотя здесь указывается блочное лямбда-выражение, описанный выше про­ цесс передачи лямбда-выражения остается тем же самым и для простого одиноч­ ного лямбда-выражения. Но в данном случае некоторым программистам синтак­ сис может показаться несколько неуклюжим. Если блочное выражение кажется сл ишком дл инным для встраивания в вызов метода, то его можно просто присвоить переменной ссылки на функциональный интерфейс , как это делалось в предыдущих примерах. И тогда остается только передать эту ссылку вызываемому методу. Та кой прием показан в конце рассматри­ ваемого здесь примера программы, где определяется блочное лямбда-выражени е, изменяющее порядок следования символ ов в строке на обратный. Это лямбда-вы­ ражение присваивается переменной reverse, ссылающейся на функциональный интерфейс StringFunc. Следовател ьно , переменную reverse можно передать в качестве аргумента первому параметру метода stringOp (). Именно так и делает­ ся в конце дан ной программы, где методу s tringOp ( ) передаются переменная re ­ ver se и обрабатываемая символьная строка. Экземпляр, получаемый в результате вычисления каждого лямбда-выражения , является реализацией фун кционально го инте рфейса StringFu nc, поэтому каждое из этих выражений может быть переда· но в качестве первого аргумента вызываемому методу stringOp (). И последнее замечание: помимо инициализации переменных, присваивания и передачи аргументов, следующие операции образуют контекст целевого ти па лямбда-выраже ний: приведение типов, те рнарная операция ? , инициализация массивов, операто ры return, а также сами лямбда-выражения. Лямбда-выражения и исключения Лямбда-выражение может ге нерировать исключение. Но если оно генерирует проверяемое исключение, то последнее должно быть совместимо с исключе н и­ ями, перечисленными в выражении throws из объявления абстрактного метода в функциональном интерфейсе. Эта особенность демонстрируется в приведенном ниже примере, где вычисляется среднее числовых значений типа douЫe в масси­ ве. А если лямбда-выражению передается массив нулевой длины, то ге нерируется исключение типа EmptyArrayException. Как следует из данного примера, это ис­ ключение перечислено в выражении throws из объявления метода func ( ) в функ· циональном инте рфейсе DouЬleNumericArrayFunc. 11 Сгенериро вать исключение из лямбда-выраже ния inte rface DouЬleNume ricArrayFunc douЫ e func ( douЬle [] n) throws EmptyArrayException; class EmptyArrayException extends Exception { EmptyArrayException () { super ( "Maccив nу ст" );
class Lamb daException Demo ( Глава 15. Лямбда-вы ражения '51 p u Ыic static v o i d ma in ( String arqs[]) throws EmptyArrayExcept ion { douЬle[J values = { 1.0, 2.0, 3.0, 4.0 ); // В этом лямбда-выражении вычисляется среднее числ о вых // значений типа douЫe в ма ссиве DouЫ eNumericArrayFunc ave rage = (n) -> ( ); douЫe sum = О; if(n. length == 0) throw пеw EmptyArrayException (); for (int i=O ; i < n.length; i++) sum += n[i] ; return sum / n. length ; System .out .println ( "Cpeднee равно " + average .func (values )); // Эта строка кода приводит к ге нерирова нию исключения System.out .println( "Cpeднee равно " • a ve ra ge . f unc ( new douЬle[OJ)); В ре:1ультате первого вы:юва мегода average . fuпc () воавращается среднее значен ие 2,5. А 11ри втором вы:юве этому методу передаегся массив нулевой дл и­ ны, что привою1т к ге нерированию исключения типа EmptyArrayExcept ioп. Напомним, что 11<1л и•1ие вы1мже1шя t . hrows в объя влении метода func () обя:1а­ тел ыю. Ьез этого программа не бу;(ет <: к омнилирована, 1юскол 1.ку лямб;1а-11 ыраже­ ние переспшет быт�, совместимым с метол.о м func (). Дан ный 11ример демонстрирует еще одну важную особенность лямбда-вы ра­ же ний. Обратите внимание 11а то, что 11араметр, указы ваем ый 11ри объявлении метода func () н фун кционалыюм 11 11п·рфейсе DouЬleNumericArrayFunc , обо­ знач<1.ет ма<·пш , тог.да ю1к пара метр лямбда- в ыражения просто укаЗ<tн как л, а не n []. Напомним, что тин 11араметра лямбдсt-вы ражения в ы водит<: я 1п цt·л сво1·0 контекстсt . В /\аtнюм случае 1\t' Л<�вым контекстом яиляется масс и и пша douЫe [], поэтому и п а рам етр n 0·1·1юсип·я к тину douЫ e [].Следо вател ыю, укааывать ;i тот парам(�тр как n [] совсем не обя:�ател ы10 и даже недопусти мо. И хотя его можно было бы явно ука:1ат1, как douЬ le (] л, это не дало бы в да нном случае 1111как11х п реимуществ. Лямбда-выражения и захват переменных Переменные, 011рсделяемые в объемлющей обла<"ТИ действия ля мб;\а-выраже- 1н1 я, допун ны н этом выражении. Н;шример, в лямбда-вы ражс 1 нш можно ис11оль­ зова1ъ переменную экасмнляра ил и п·.�.тиче<�кую нсремснную, определяемую в объ­ емл ющем его классе. В лям6;1а-выражении 11опу1 1е11 также по ссыл ке thi s (явно или неюшо) вы:1ывающиj.i :� к:1е :\111ляр объемлющего его класса. Та ким обра:юм , в ля:\1б;1а-
'52 Часть 1. Язык Java выражении можно получить или установить значение переменной экземпляра или статической переменной и вызвать метод из объемлющего его класса. Но есл и в лямбда-выражении используется локальная переменная из объемлю­ щей его области действия, то возникает особый случай , называемый захватом пере­ менний. В этом случае в лямбда-выражении можно использовать только те локаль· ные перемен ные, которые действителъно являются завершенными. Действительно завершенной считается такая переменная , значение которой не изменяется после ее первого присваивания. Такую переменную совсем не обязательно объявлять как final, хотя это и не считается ошибкой. (Параметр this в объемл ющей обла­ сти действия автоматически оказывается действительно завершенным, а у лямбда­ выраже ний собственный параметр this отсутствует. ) Следует, однако , иметь в виду, что локальная переменная из объемлющей об­ ласти действия не может быть видоизменена в лямбда-выражении. Ведь это нару­ шило бы ее действительно завершенное состоя ние, а следовательно, привело бы к недопустимому ее захвату. В следующем примере программы демонстрируется отличие действительно ко­ нечных переменных от изменяемых локальных переменных: 11 Пример захвата локальной переменной из объемлющей области действия inte rface MyFunc { int func (int п) ; class VarCapture { puЫ ic static vo id ma in (String args[]) ( 11 Локальная переме нна я, которая может быт ь захвачена int num = 10; MyFunc myLamЬda = (n) -> { 11 Такое приме нение переме нной num допустимо , посколь ку 11 она не видоизменяется intv=num+n; 11 Но следующа я строка кода недопустима , посколь ку в ней 11 предпринимается попытка видоизменить значение переменной num 11 nшn++; return v; ); 11 И следующая строка кода приведет к ошибке , пос коль ку в ней 11 нарушается действительно завершенное состояние переменной num 11nшn=9; } Как следует из комментариев к данному примеру программы, переменная nurn является действительно завершенной, и поэтому ее можно использовать в ля мб­ да-выражении myLamЬda . Но если попытаться видоизменить переменную num как в самом лямбда-выражении, так и за его пределами , то она утратит свое действи· тельно заве ршенное состояние. Это привело бы к ошибке, а программа не подле­ жала бы компиляции.
Гл ава 1 5 . Лямбда -вь1ражения 1.53 Следует особо подче ркнуть, что в лямбда-выражении можно использовать и видоизменять переменную экземпляра из вызывающего его класса. Но нельзя использовать локальную переменную из объемлющей его области действия , если только эта переменная не является действительно завершенной. Ссыл ки на методы С лямбда-выражениями связано еще одно очень важное средство, называемое ссы.лкай на метод. Такая ссылка позволяется обращаться к методу, не вызывая его. Она связана с лямбда-выражениями потому, что ей также требуется контекст целе­ вого типа , состоящий из совместимого функционального и нтерфейса. Имеются разные виды ссылок на методы. Рассмотрим сначала ссылки на статические методы. Ссылки на статические методы Для создания ссылки на статический метод служит следующая общая форма: имя_к.пасса : : юся_метода Обратите вн имание на то , что имя класса в этой форме отделяется от имени метода двумя двоеточиями (: :). Этот новый разделитель внедрен в версии JDК 8 специально для данной цели. Та кой ссылкой на метод можно пользоваться везде, где она совместима со своим целевым типом. В следую щем примере программы демонстрируется применен ие ссылки на ста­ тический метод: 11 Продемонстрировать ссылку на статический метод 11 Функциональный интерфейс для операций с символьными строками inter face StringFunc { String func {String n) ; 11 в этом интерфейсе определяется статический метод strReverse () class MyStringOp s { 11 Статический ме тод, изме н яющий порядок 11 следования символов в строке static String strReverse ( String str) { String result = ""; int i; for(i = str. length()-1; i >= О; i--) result += str . cha rAt (i) ; return result; class MethodRe f Demo { 11 В этом ме тоде фун кциональный интерфейс указывается в качестве 11 типа первого его параметра . Следователь но, ему может быт ь передан 11 любой экземпляр этого интерфейса , включая и ссылку на ме тод static String stringOp ( StringFunc sf, String s) {
'5' Часть 1. Язык Java return sf. func(s) ; puЫic static void main ( String args[]) { String inStr = "Лямбда- выраже ни я повышают эффективность Java "; String out Str; // Здесь ссылка на ме тод strRaverse () передается методу stringOp () out Str = stringOp (MyStringOps ::strReverse , inStr); System. out .println ( "Иcxoднaя строка : "+inStr) ; System.out . println ("Oбpaщeннaя строка : "+outStr); Ниже приведен пример выполнения данной программы. Исходная строка : Лямбда- выражения повышают эффектив нос ть Java Обращенная строка : яин ежарыв -адбмял тюашывоп ьтсонвиткеффэ ava J В данной программе особое внимание обратите на следующую строку кода: outStr = stringOp (MyStringOps ::strReverse , inStr ); В этой строке кода ссылка на стати ческий метод strReve rse (), объявляе­ мый в классе MyStringOps, передается первому аргументу метода stringOp (). И это вполне допустимо, поскольку метод strRe ve rse () совместим с функцио· нальным интерфейсом StringFunc. Следовател ьно. в выражении MyStringOps : : strReverse вычисляется ссылка на объект того класса, в котором метод strReve rse () предоставляет реализацию метода func () из функционального ин· терфейса StringFunc. Ссыпки на методы экэемппяра Для передачи ссылки на метод экземпляра для конкретного объекта служит следующая общая форма: сС1Ш.1 1: а_яа_ объе.1 1: �: : __.,�ода Как видите, синтаксис этой формы ссылки на метод экземпляра похож на тот, что используется для ссьшки на статический метод, за исключением того, что вме­ сто имени класса в данном случае используется ссьшка на объект. Ниже приведен переделанный вариант программы из предыдущего примера, чтобы продемон· стрировать применение ссьшки на метод экземпляра. // Продемонстрировать приме нение ссЫJiки на ме тод экземпляра 11 Фун кциональный интерфейс дл я операций с символьными строками interface StringFunc ( String func (String n) ; 11 Теперь в этом кл ассе определяется метод экземпл яра strReverse () class MyStringOps { String strReverse ( String str) { String result = "" ; int i;
for (i = str. length(}-1; i >= О; i--} result += str . charAt{i} ; return result; class MethodRe f Demo 2 { Гл ава 15. Лям бда -выражения 455 11 В этом ме тоде функциональный интерфейс указывается в качестве 11 типа первого его параметра . Следователь но, ему може т быт ь передан 11 люб ой экземпляр этого интерфейса , включая и ссылку на ме тод static String stringOp { StringFunc s f, String s) { return sf . func{s} ; puЫ ic static void ma in {String args [] } { String inStr = "Лямбда -выр аже ния по вышают эффе ктивность Java"; String ou tStr; 11 создать объект типа МyStringOps MyS tringOp s strOp s = new MyStringOps {} ; 11 А теперь ссылка на ме тод экземпл яра strReverse () 11 передается ме тоду stringOp () out Str = stringOp (strOp s : :strReverse, inStr }; System.out . println ( "Иcxoднa я строка : "+inStr} ; System.out . println { "Oбpaщeннaя строка : "+outStr }; Эта версия программы выводит такой же'результат, как и предыдущая ее версия. В данном примере программы обратите вн имание на то, что метод strReve rse () те перь объявляется в классе MyS tringOps как метод экземпля ра. А в теле метода ma i n () создается экземпляр strOps класса MyStringOps. Этот экземпляр служит для создания ссылки на свой метод strReverse () при вызове метода stringOp () , как еще раз показано ниже. В данном примере метод экземпляра strReverse () вызывается для объекта strOp s. ou tStr = stringOp {strOp s ::s t rReverse, inStr} ; Возможны и такие случа и, когда требуется указать метод экземпляра, который будет использоваться вместе с любым объектом данного класса, а н е только с ука­ занным объектом. В подобных случаях можно создать ссылку на метод экземпляра в следующей общей форме: В этой форме имя класса указывается вместо имени конкретного объекта , не­ смотря на то , что в ней указывается и метод экземпляра. В соответствии с этой формой первый параметр метода из функционального и нте рфейса совпадает с вызывающим объектом, а второй параметр - с параметром, указан ным в методе экземпляра. Рассмотрим пример программы, в которой определяется метод coun- ter (), подсчитывающий количество объектов в массиве, удо влетворяющих уело-
'56 Часть 1. Язык Java вию, определяемому в методе func () из функционального интерфейса MyFunc. В данном случае подсчитываются экзем1U1яры класса HighT emp. 11 Использовать ссЬ1Лку на метод экземпляра вме сте с разными объе ктами 11 Фун кциональный интерфейс с ме тодом , принимающим два ссЬ1Лочных 11 аргуме нта и возвращающим логическое значение interface MyFunc<T> { boolean func(T vl, Т v2); 11 Класс дл я хранения ма ксимальной температуры за день class HighTemp { private int hTemp ; HighTemp (int ht) { hTemp = ht; } 11 возвратить логическое значение true , если вызывающий объе кт 11 типа Hi9hTe11p име ет такую же температуру , как и у объе кта ht2 boolean same T emp (Hi ghTemp ht2 ) { return hTemp == ht2 . hTemp; 11 возвратить логическое значение true, если вызывающий объе кт 11 типа Hi9hTe1 1p име ет температуру ниже , чем у объекта ht2 boolean lessThanтemp (HighTemp ht2 ) { return hTemp < ht2 . hTemp ; class InstanceMe thWithObj ectRe fDemo 11 Метод, возвращающий колич ество экземпл яров объе кта, 11 найденных по критериям , задаваемым параме тром 11 фун кционального интерфейс Nyl'Unc static <Т> int counter (T [] vals, MyFunc<T> f, Т v) int count = О; for(int i=O; i < vals. length; i++) if(f.func (vals [i] , v) ) count++; return count ; puЫic static void ma in (String args[]) { int count ; 11 создать ма ссив объе ктов типа Hi9hT81 11P Hi ghTemp [] wee kDayHighs = { new HighTemp (89) , new HighTemp (82) , new HighTemp (90) , new HighTemp(89) , new HighTemp (89) , new HighTemp(91) , new HighTemp(84) , new HighTemp (83) } ; 11 Использовать ме тод counter () вместе с ма ссивами объе ктов 11 типа Hi9hT8J8P . Обратите внимание на то , что ссЬ1Лка на ме тод 11 экземпляра B88 8 T8Jl lP () передается в качестве второго параметра count = counter (weekDayHighз , HighTemp: : sameT emp , new HighTemp(89) ); System. o ut . println ( "Дней , когда ма ксимал ьная температура бЬ1Ла 89: "+count );
Гл ава 15. Лямбда-выражения '57 11 А теперь создать и исполь зовать вместе с данным 11 ме тодом еще один массив объе ктов типа HiqhT8J1 1P Hi ghTemp [J we ekDayHighs2 = { new HighTemp ( 32 ) , new HighTemp(12) , new HighT emp (24) , new HighTemp(19) , new HighTemp (18) , new HighTemp(12) , new HighTemp (-1) , new HighTemp (lЗ) } ; count = counter (wee kDayHighs2 , HighTemp ::same T emp , new HighTemp(l2) ); System.out . println ( "Дней , когда максимальная температура была 12 : "+count) ; 11 А теперь восполь зоваться ме т одом lessТhanT81 11P () , чтобы 11 выяснить , сколь ко дней температура была ме ньше заданной count = counter (wee kDayHighs, HighTemp: :lessThanTemp , new HighTemp(89) ); System. out . println ( "Дней , когда максимал ьная температура была ме ньше 89 : " + count ); count = counter (wee kDayHighs2 , HighTemp ::lessThanTemp , new HighTemp(19) ); Sys tem. out . println ( "Дней , когда максимал ьная температура была ме ньше 19: "+count) ; Ниже приведен результат выполнения данной программы. дней , когда максимал ьная температура был а 89: З дней , когда максимал ьная температура была 12 : 2 дней , когда ма ксимал ьная температура была ме ньше 89 : 3 дней , когда максимальная температура был а ме ньше 19: 5 В данном примере программы обратите внимание на то, что в классе HighTemp объявлены два метода экземпляра: same T emp () и lessThanTemp (). Первый метод возвращает логическое значение true, если оба объекта типа HighTemp содержат одинаковую те мпературу. А второй метод возвращает логическое значение true, если температура в вызывающем объекте меньше , чем в передаваемом. Каждый из этих методов принимает параметр типа HighTemp и возвращает логическое значе­ ние. Следовательно, каждый из них совместим с функциональным интерфейсом MyFun c, поскольку тип вызывающего объекта может быть приведен к типу перво­ го параметра метода func (), а тип его аргумента - к типу второго параметра этого метода. Таким образом, когда следующее выражение: HighTemp ::same т emp передается методу counter (), то создается экземпляр функционального интер­ фейса MyFunc, где тип первого параметра метода func ( ) соответствует типу объ­ екта, вызывающего метод экземпляра, т. е . типу HighTemp . А тип второго параме­ тра метода func () также соответствует типу HighTemp, поскольку это тип параме­ тра метода экземпляра sameTemp (). Это же справедливо и для метода экземпляра les sThanTemp ().
458 Часть 1. Яэык Java И последнее замечан ие: используя оператор supe r, можно обращаться к вари­ анту метода из суперкласса, как показано ниже , где имя обозначает имя вызывае­ мого метода. super: :aнir Ссыпки на обобщенные методы Ссьшками на методы можно также пользоваться для обращения к обобщенным классам и/или методам. В качестве примера рассмотрим следующую программу: 11 Продемонстрировать приме нение ссылки на обобщенный ме тод, 11 объявленный в необобщенном классе 11 Функциональ ный интерфейс для обработки ма ссива значений 11 и возврата целочисленного результата interface MyFunc<T> { int func (T(] vals, Т v) ; 11 в этом кл ассе определяется ме тод countМatching () , возвращающий 11 количество элементов в массиве , равных указанному значению . 11 Обратите вниыание на то, что метод countМatchinq () являе тся 11 обобщенным, тогда как класс МyArrayOps - необобщенным class MyArrayOps { static <Т> int countMatching (T[] val s, Т v) { int count = О; for(int i=O; i < vals. length; i++) if (vals [i] == v) count ++ ; return count; class GenericMe thodRe fDemo { 11 В качестве первого параме тра этого метода указывается 11 функциональный интерфейс МyFunc , а в качестве двух других 11 параметров - ма ссив и значение , причем оба типа Т static <Т> int myOp (MyFunc<T> f, Т[] va ls, Т v) { return f.func (va ls, v) ; puЬl ic static void ma in (String args[] ) { Integer[]vals={1,2,З,4,2,З,4,4,5}; String[] strs = { "Один" , "Два" , "Три", "Два" }; int count ; count = myOp (MyA rrayOps ::< Integer>countMatching , vals, 4) ; System.out .println ( "Maccив vals содержит " + count +"чи сла 4") ; count = myOp (MyAr rayOps ::< String>countMatching, strs , "Two") ; System. out . println ( "Maccив strs содержит "+count + " числа два " ); Ниже приведен результат выполнения данной программы. Массив va ls содержит З числа 4 Массив strs содержит 2 числа два
Гл ава 15. Лямбда-выражения '59 В данном нримере 111ю1·раммы необобщенный класс MyArrayOp s содержит обобщенный метод coun tMa tcr1ing ().Этот метод во:шращает кол ичество элемен­ тов в массиве, сов1 1адающих с ука:ш 1111ым :шачснием. Обратите внимание на норя­ док ука:1ания аргумента обоGщенного тиш1. 1 lанример, при нервом вы:юве и:з ме­ тода ma .i n ( ) :'!тому Ml"l'OJ()' н е ре;(ается <1ргуме11т типа I n teger следующим обра:юм: coun t = myOp (MyAr rayOps ::<Integer>coun tMatching , va l s, 4) ; Обратите также внимание на то , что это 11роисходит после ра:1дел ителя : : . Этот <:юпакс ис можно обобщип•. Когда обоб щенный мепщ ука:1ы ваето1 как метод :i к:�ем­ пляра , его ар1умент тина ука:1ьшастся 1юслс ра:щел ителя : : и пере;( именем :'!того метода. Сле,'(уе-1� однако, :1а метить, что явно указывать ар1умент тина в да нном слу­ чае (и во м1ю1·их i(ру1·их) совсем нс 1н.:обя:1атсл ьно, поскол ьку тин :н01·0 аргумента выводится автоматически. Л в тех случаях, ко1·да указывается обобще1111ыii класс , а р гуме нт тина СЛСJ(уст llOCJIC 11ме1 1и ЭТОl'О класса и 11сред ра3ДСЛИТСЛ(�М : : . Несмотря на то •по в 11реJ(I•щущих примерах был продемонстрирован мсха­ ни:Jм 11римс11е11ия сс ы;ю l< на методы, эти нримсры все же не раскрывают в 1юююй мере их преи муществ. Ссылки на методы мо1-ут, в частности , ока:1ап.ся очень 110- ле:шымн в со•1ет<11 1ии с карка('(>М коллеющй Collccl io11s 1'' ra шewo1·k, 011и<ъшаемым в главе JН. И ра;щ 1юл11оты 1пл оже 11ня ниже нривсден краткий, но юн ля;щ ый 11ри­ мср н1шмс11е11ия ссыл l<и на метод , чтоGы опредсл ип. наибольший :iл емент в 1<ол­ лскщн1 . (Если вы еще не:шакомы с каркасом колле кций Cullcctiu11s I•' 1«1шewoгk, nер­ нитссь к :·по му 11римсру 11осле то п>, юн< 11роработастс материал �лавы 1 Н.) Обнаруж rпъ в l<ол лсю1ии наиболыний :-мсме11т можно, в •1астности . вызвав метод ma x ( ), 01 1 рс;(елс1шый в кл ассе Col lections. При вы:ювс варианта метода ma x ( ), 11римс11ясмого 11 р<tсс матривасмом :щссь нримерс, нужно нсрсдать ссылку на коллсю1ию и :�1с1е м11ляр объе кта , реал изующего интерфейс Comparator<T>. В этом инте рфейсе 011рс;(еляется поря;(ок сравнения двух объектов. В нем объ­ является сдш 1ствеш1 ый абстрактн ый метод compare (), принимающий два аргу­ мента, имеющих типы ср<t 1шиваемых объектов. Этот метод должен во:шратнть числ овое :ш<t •1е1н1с бол ыпе нуля, сел и 11ервый ар1умент бол ыне вто рого; нулевое значение, если оба ар1·умснта равны; и числовое :ш ачение меныпс нуля, есл и пер­ вый объект мены11е второго. Прежде J(ЛЯ вы:юва мсто;(а max { ) с ;щу мя определяемыми поль:ювателем объек­ тами эк:1емпл яр интерфейса Compara tor<T> приходилоо. получать , реал и зовав сначала :�тот интерфейс явным оGра:ю м в отдел ьном классе, а :�атем со:ща в эк:3ем­ пляр да нного кл асса. Лаж.·с этот эк:1смпляр передавался в качестве компаратора методу max ( ) . В версии .JHK Н 1юя вилась во::1можнос1ъ просто передать методу max ( ) ссылку на срав11е1шс, 11оскоm.ку в это м случае комнаратор ре<uш :1устся ав­ то матически. Этот 11ро11ес« J\е!\ю11п·1 н1 рустся ниже на простом 11римерс со:ща� шя колле ю�ни ти па ArrayList объ<.· ктов типа MyClass и поиска в ней наибол ыпсго :JШ\ЧСНИЯ, oпpe/(('JIЯCMOl'O в методе сравнения. 11 Использовать ссылку на ме тод , чтобы найти 11 ма ксимал ьное значение в колл екции impo rt jav a.ut il .*; class MyCJ.зss private iпt va l;
'60 Часть 1. Язык Java MyClass(int v) ( val = v; 1 int getVal () ( return val ; 1 class UseMethodRe f ( 11 Метод сошраrе () , совме с тимый с аналогичным ме тодом, 11 определенным в интерфейсе Coшparator<T> static int compareMC (MyClass а, MyClass Ь) ( return a.getVal () - b.ge tVal () ; puЫ ic static void main (String args[] ) { ArrayList<MyClass> al = new ArrayList<MyClass>() ; al . add (new MyClass (l)); al .add (new MyClass (4) ); al .add ( new MyClass (2) ); al . add (new MyClass (9) ); al . add (new MyClass (ЗJ ); al . add (new MyClass (7) ); 11 найти ма ксимальное значение , исполь зуя ме тод сошраr&МС () MyCl ass rnaxValObj = Collections .rnax (al, UseMe thodRef: :compareMC) ; System. out . println ( "Maкcимaльнoe значение равно : "+maxVa lObj . getVal () ); Ниже приведен результат выполнения данной программы. Максимальное значение равно : 9 В дан ном примере программы обратите внимание на то , что в самом клас· се MyClass не определяется метод сравнения и не реализуется интерфейс Comp a rator. Те м не менее максимальное значение в списке объектов типа MyClass может быть получено в результате вызова метода ma x (), поскольку в клас· се UseMethodRe f определяется статический метод compareMC ( ), совместимый с методом compare (), определенным в интерфейсе Comparator. Та ким образом, отпадает необходимость явным образом реализовывать и создавать экземпляр ин· терфейса Compa rator. Ссыл ки на ко н структор ы Ссылки на конструкторы можно создавать таким же образом, как и ссылки на методы. Ниже приведена общая форма синтаксиса, которую можно употре· блять для создания ссылок на конструкторы. 11U U1 жласса : : new Эта ссьтка может быть присвоена любой ссьтке на функциональный интер­ фейс , в котором определяется метод, совместимый с конструкторо м. Ниже при­ веден простой пример применения ссьтки на конструктор.
Гл ава 15. Лямбда -выражен ия 461 11 Продемон стрировать приме нение ссылки на конструктор 11 В фун кциональном интерфейсе МyFunc определяется ме тод , 11 в озвращающий ссыл ку на класс МyClas s in terface MyFunc { MyCl ass func (int n) ; class MyClass private iпt val; 11 Этот конструктор принима ет один аргумент MyClass(iпt v) ( val = v; } 11 А это конструктор по умолч анию MyClass() { val = О; ) 11 ... int getVal() { return val; ); clas s ConstructorRe fDemo { puЫic static void main ( String args []) ( 11 Создать ссылку на конс труктор класса МyClass . 11 Метод func () из интерфейса МyFunc принима ет аргумент , 11 поэтому оператор new обращается к параме тризированному 11 конструктору кл асса МyClass , а не к его конструктору по умо лчанию MyFunc myClassCons = MyClass ::n ew; 11 создать экземпляр кл асса МyClass по ссылке на его конс труктор MyCl ass mc = myClassCons .func(l00 ) ; 11 исполь зовать толь ко что созданный экземпляр кл асса МyClas s System. out .println ("Знaчeниe val в объе кте mc равно "+mc . getVal () ); Ниже приведен результат, выводимый данной программой. Значение val в объе кте mc равно 100 В дан ном примере программы обратите внимание на то , что метод func () из интерфейса My Func возвращает ссылку на тип My Class и принимает параметр типа int. Обратите также внимание на то, что в классе M yClass определяются два конструктора. В первом конструкторе указывается параметр типа int, а второй является конструктором по умолчанию и поэтому не имеет параметров. А те перь проанализируем следующую строку кода: MyFunc myCl assCoпs = MyClas s: :new; В этой строке кода создается ссылка на конструктор класса MyClass в выраже­ нии MyClass : : new. В дан ном случае ссылка делается на конструктор MyClass ( int v) , поскольку метод func ( ) из интерфейса My Func принимает параметр типа int, а с ним совпадает именно этот конструктор. Обратите также внимание на то , что ссылка на этот конструктор присваивается переменной myClassCons ссылки на функциональный интерфейс MyFunc. После выполнения данной строки кода
1.62 Часть 1. Яэы к Java переменную rny ClassCons можно использовать для создания экземWiяра класс а MyC lass, как показано ниже. По существу, переменная rn yClassCons предоставля­ ет еще один способ вызвать конструктор MyClass ( int v) . MyClass mc = myClassCons .func(lOO) ; Аналогичным образом создаются ссылки на конструкторы обобщенных клас­ сов. Единственное отличие состоит в то м, что в данном случае может быть уха­ зан аргумент ти па. И делается это после имени класса, как и при создании ссылки на обобщенный метод. Создание и применение ссьmки на конструктор обобщен­ ного класса демонстрируется на приведенном ниже примере, где функциональ­ ный интерфейс My Func и класс MyClass объявляются как обобщенные. 11 Продемонстрировать nрименение ссылки на 11 конструктор обобщенного кл асса 11 Теперь функциональный интерфейс МyFunc обобще нный interface MyFunc<T> { MyClass<T> func (T n) ; class MyClass<T> private Т val ; 11 Этот конструктор принимает один аргуме нт MyClass(T v) { val = v; } 11 А это конструктор по умолч анию MyClass( ) { val = null; ) 11- . т getVal () } return val ; }; class ConstructorRe fDemo2 { puЫ ic static void ma in (String args [] ) { 11 создать ссылку на конструктор обобщенного класса МyClass<T> MyFunc<Integer> myClassCons = MyClass<Integer> ::new; 11 создать экземпляр кл асса МyClass<T> 11 по данной ссылке на конструктор MyCl ass<Integer> mc = myClassCons .func(lOO) ; 11 в оспользов аться только что созданным 11 экземпляром класса МyClas s<T> System.out . println { " Значение val в объе кте mc равно "+mc . getVal ( )); Эта версия программы выводит такой же результат, как и предыдущая ее вер­ сия, только те перь функциональный интерфейс My Func и класс MyClass являют­ ся обобщенными. Следовательно, в последовательность кода, создающего ссЬV1ку на конструктор, можно включить аргумент типа, как показано ниже, хотя это тре­ буется далеко не всегда. MyFunc< Integer> myClassCons = MyClass<Intege r> : :new;
Гл ава 15. Лямбда-выра же ния '63 Аргумент типа I n t ege r уже ухазан при создании переменной myCl а s sCons, по­ этому его можно использовать для создания объекта типа My Class<Integer>, как показано в следующей строке кода: MyC l ass<Integer> mc = myClassCons .fun c(lOO) ; В представленных выше примерах бьm продемонстрирован механизм примене­ ния ссьmки на конструкто р, но на практике они подобным образом не используют­ ся, поскольку это не приносит никаких выгод. Более того, наличие двух обозначений од ного и того же конструктора приводит, по меньшей мере , к конфликтной ситуа· ции. Поэтому с целью продемонстрировать более практический пример применения ссьmок на конструкторы в приведенной ниже программе применяется статический метод my ClassFactory (), который является фабричным для объектов класса любо­ го типа, реализующего интерфейс My Func. С помощью этого метода можно создать объект любого типа, имеющего конструктор, совместимый с его первым параметром. 11 Реализовать простую фабрику классов , используя ссыл ку на конс труктор interface MyFunc<R, Т> R func(Tn); 11 Пр остой обобщенный кл асс class MyCl ass<T> 1 private Т val ; 11 Конструктор , принимающий один параме тр MyClass(T v) { val = v; } 11 Конструктор по умолчанию . Этот конс труктор в 11 данной программе НЕ исполь зуется MyClass() { val = null; } 11 ... Т getVal () { return val ; }; 11 Простой необобщенный класс class MyClass2 { String str; 11 Конструктор , принимающий один аргуме н т MyClass2 (String s) { str = s; } 11 Конструктор по умолчанию . Этот конс труктор в 11 данной программе НЕ исполь зуется MyClass2() { str = ""; 1 11 ... String ge tVal () { return str; } ; class Constructo rRe fDemo З { 11 Ф абричный метод для объе ктов разных кл ассов . 11 У каждого класса долже н быт ь свой конс труктор , 11 принимающий один параме тр типа Т. А параметр R 1 1 обозначает тип создаваемого объе кта
464 Часть 1. Язык Java static <R, T> R myCl assFactory ( MyFuпc<R, Т> сопs, Т v) ( return coпs . fuпc (v) ; puЬlic static vo id ma in (St riпg args[] ) { 11 Создать ссылку на конс труктор класса МyClass . 11 В данном случае оператор new обращается к конс труктору , 11 принимающему аргумент MyFunc<MyClass<DouЬle>, DouЬle> myClas sCons = MyClass<DouЫ e> : :пew; 11 создать экземпляр типа кл асса МyClass , используя фабричный ме тод MyClass<DouЬle> mc = myClassFactory (myClassCoпs, 100.1) ; 11 исполь зовать только что созданный экземпляр кл асса МyClass System.out . priпtlп ( "Значение val в объе кте mc равно " + mc . getVal ()); 11 А теперь создать экземпл яр другого кла сса , 11 исполь зуя ме тод JDYClassFactory () MyFuпc<MyClass2, String> myClassCoпs2 = MyClass2: :пew; 11 создать экземпляр кл асса МyClass2 , исполь зуя фабричный ме тод MyCl ass2 mc2 = myClassFactory (myClas sCoпs 2, "Лямбда" ) ; 11 исполь зовать толь ко что созданный экземпляр кл асса МyClass System.out . priпtlп ( "Значение str в объекте mc2 равно " + mc2 . getVal ()); Ниже приведен результат выполнения дан ной программы. Значение val в объе кте mc равно 100.1 Значение str в объе кте mc 2 равно Лямбда Как видите, метод myClassFactory () используется для создания объектов типа MyClass<DouЫe> и MyClass2. Несмотря на отличия в обоих классах, в частности , класс MyClass является обобщенным, а класс MyClass2 - необобщенным, объек· ты обоих классов могут быть созданы с помощью фабричного метода myClass Fa ctory (), поскольку оба они содержат конструкторы, совместимые с методом func () из функционального интерфейса My Func, а методу my ClassFactory () пере­ дается конструктор того класса, объект которого требуется создать. Можете поэкспе­ риментировать немного с данной программой, попробовав создать объекты разных классов, а также экземпляры разнотипных объектов класса MyClass. При этом вы непременно обнаружите, что с помощью метода myClassFactory () можно создать объект любого типа, в классе которого имеется конструктор, совместимый с методом func () из функционального интерфейса My Func. Несмотря на всю простоту данного примера, он все же раскрывает истинный потенциал ссылок на конструкторы в Java. Прежде чем продолжить дальше, следует упомянугь о второй форме синтакс иса ссылок на конструкторы, в которой применяются массивы. В частности , для соз· дания ссылки на конструктор массива служит следующая форма: !l'•n [] : :new где тип обозначает создаваемый объект. Та к, если обратиться к форме класса MyClass, представленной в первом примере применения ссылки на конструктор
Глава 15. Лямбда-выражен и я 465 (ConstructorRefDemo), а также.· оfiъя ни1ъ интерфейс MyArrayCreator следую­ щим оfiра:юм: inter face MyAr rayCreat or<T> т func (int 11 ); то в приведенном ниже фрагменте кода создастся двухэлементный масс ив объек­ тов типа MyClass и каждому из 1шх присваивается нача;1ы1ос значение. MyAr rayCreator<MyClass []> mcArrayCons = MyClass[] ::n ew; MyClass [] а=mcArrayCons .func(2) ; а[О] = new MyClass (l) ; a(l] = new MyC lass (2) В :п ом фрагменте ко;(а вы:юн мсто;(а func ( 2) 11риво;1,ит к со:щанию дnухэлс­ ме11 тно1·0 масси1�а . Как правило, фу1 1книоналы1ый интерфейс должен содержать метод, нринимающий сди11ствс.� 1111ый 11араметр тина int, есл и он служит il,Л Я об­ раще ния к конструктору масоны . Предопределенные функциональные интерфейсы В 11ривсдс1н1ых ;1,0 сих нор нримсрах 011ре;1,елялись собственные фун кциошu1 1,­ н ые интерфейсы для 11,ел ей ш1 1:ш1;1,1 юй ;1,емонстрации основных при1щи11ов ;1,ей­ ствия лямбда-ныражений и фуню1,ион<v1 ы1ых инте рфейсов. l lo аачастую 011 рс;1,слять собстве1 111ый фун ю1,иошu1 ы1ый инте рфейс 11е нужно, 1юскол ьку в нерсииJJ)К 8 вне­ дрен ноный пакет java . util . funct ion, нредоставляющий нескол �.ко нрсдu п реде­ ленных фупю1,иошu1ы1ых интерфейсов. l)олее 1юдробно они будут рассматриваться в части 11 ;(ашюй книги, а в ·1·<1бл. 15.1 нриведены некоторые избрюшыс и:� них. Табл ица 15.1 . Предо пределенные фун кциональные интерфейсы Функциональный интерфейс Назн ачение UnaryOperator<T> В ы нолняст унарную 011 ера11ию над объе ктом тина Т и воз- 111 м1щ1ст рс:1у11 1тп то1·0 же типа. Соде ржит мсто;\ ap - ply () BinaryOpe rator<T> Вы 1юл1111ст логи ческую опсращ1ю над двумя объекта ми т1111а Т 11 rю:ш ра щает pc:1y;lliraт того же п1 11а. С1щержит метод apply () Consumer<T> Вы1юш1 яст 01 1еранию над обЪ<� ктом п111а т . Соде ржит мсн1;1 accept () Supplier<T> Во:шращает объе1<т типа Т. Соде ржит мt·то;\ get () Function<T , R> Вы11ол11нет 011срацию над объе ктом т �1 1 1а Т и 110:111ра щает в ре:1у11 1тпе оt'iъскт типа R. Соде ржит метод apply () Predicate<T> Онредсляет. у;1о влстворяст ли об·1,ект пша Т некоторому 111· р ан111Ш'l'('Л ы1ому условию, Во:шращае1· логи •1е1,кос :iнa­ 'l(�HИt'. обо:щача ющсе резу11 1,тат. Содt• р ж1п мсто;1 te st ()
'66 Часть 1. Язык Java В приведенном ниже переделанном примере представленной ранее проrрам· мы BlockLamЬda Demo демонстрируется применение преопределенного функцио­ нального инте рфейса Func tion. Если в предыдущей версии данной программы для демонстрации блочных лямбда-выражений на примере вычисления факто­ риала заданного числа бьш создан собственный функциональный интерфейс NumericFunc, то в новой ее версии для этой цели применяется встроенный функ· циональный интерфейс Func tion, как показано ниже. 11 Исполь зовать встроенный фун кциональный интерфейс Function 11 имп ортировать функциональ ный интерфейс Function import java.util . function . Fun ction; clas s UseFunctioninterfaceDemo { puЫic static void main (String args[] ) { 11 Это блочное лямбда- выражение вычисляет факториал // целочисленного значения . Для этой цели на сей раз 11 исполь зуется фун кциональ ный интерфейс F'unction Function< Integer, Integer> factorial = (n) -> { }; int result = 1; for(int i=l; i <= n; i++) result = i * result; return result; System.out . println ( "Фaктopиaл числа 3 равен " + factorial . apply (З)); System.out . println ( "Фaктopиaл числа 5 равен " + factorial . apply (5) ); Эта версия программы выводит такой же результат, как и предыдущая ее версия.
ЧАСТЬ 11ГЛАВА16 Обработка симв ольн ых строк ГЛАВА 17 Пакет java . lang ГЛАВА 18 Па кет java .util, част ь 1. Collections Fra mework ГЛАВА 19 Пакет java . util, часть 11. Прочие служебные кл ассы ГЛАВА 20 Пакет java . io для ввода-вывода ГЛАВА 21 Система ввода - вывода NIO ГЛАВА 22 Работа в сети ГЛ АВА 23 Насл едов ание ГЛАВА 24 Пакеты и и н терфейсы ГЛАВА 25 Обработка искл ючен ий ГЛ АВА 26 М н ого п оточное программирование ГЛАВА 27 Перечисл ения, автоупаковка и а ннота ции [метада нные] ГЛАВА 28 Ввод-вывод, аплеты и прочие вопросы ГЛАВА 29 Обобщени я ГЛАВА 30 Лямбда-выражения .. ._ _. Библиотека Java
1 6 Обра ботка сим вольных строк Краткий об:юр обработки сим1юл ы1 ых строк в Java был сдел ан в глав<� 7. Авэтой1:ш ш с ;�а1111ая тема рассматривается шщробнее. Как и в других я:Jы ках 111ю­ r раммнровашш, вjava гuАumл ы1 ш1 гтjюкп является 11оследо вательностыо символов. Но в отличие от 11с1<оторых друп1х языков, где сим1юл ьн ые строки реа11и:юван ы в в иде массивов симвшю в, вJ<1Уа 011и являются объектами класса String. Реали:ы1�ия символ ы1ых строк в виде встроенных объектов даст воз�южност1, 11рсдоста ви·1ъ в Jt\'a полныii набор срсл.ств для удо бной обработки символ ы1ых строк. 1 Iапример, в Jaya нредоста вл яются методы для сравнения и объеюшсния д11;'Х символ ы1 ых строк, поиска в 11их 1юдсгрок и изменения регистра символов. Кроме TOI'<> , объе кты кл асса Str ir1g могут быть со:щаны самыми рttзными способа­ ми , •по по:шолнст легко нолучап. символ ы1ыс строки по мере ш�.добности в них. Как ни страшю, в ре:1ул 1.татс со:щанин объе кта типа String 11олучается сим­ вольш1я строка , которан не может быть и:1мене11а. Иными словами, 1юслс то го как объект ти 11а S t r ing бу;�ст со:ща 11, и:�мснип, символ ы, сост<1вляющис новую строку, уж е нел ь:�я . На нервыii в:и:шщ это может покаэаться ссрье:шым ограничением, но на самом 11еле 0110 не Ttt K и важ 110. 1 lад СИМIЮЛ ЫIЫМИ строками можно ВЫ\ЮЛНЯТЬ любые опера1{ии, 110 всяки�i pa:i, ко г;щ требуется изменен ная версия существую­ щей символыюй строки, со:щается новый объект типа String, соде ржащий вес внесенные и:1мс11е11ия. Л исхо;щ ая символы�ая строка остается без изменения. Та кой JJO/{X< !i\ нриннт нотому, что ф11ксирова1111ая неизменяемая символ ы1 ая стро­ ка может быть рс;uш:ювана бол ее �� ффсктиnно, 'lем изменяемая. Лее ли требуются видо и:�мсннсмые символы1ые строки , то в JaYa нредлагаются нtt выбор два кл асса: StringBuffer и StriпgBui lder. Объекты обоих классов содержат символ ьные стро ки , которые '\IO IJ'T быть и:1мс11е11ы 1юсле их создания. Классы String, StringBufter и StringBui lder определены в ш1 кете jav a. lang и ноэто му допуш1 ы во всех нрограммах автомати чески. Вес эти кл ассы объ­ явлены с модифи като ром i\Остуна f ina 1, а следовател ьно, ни от одного и:1 них 11ел 1.:1я щюи:тести 1ю;щлассы. :-:) то ;щ нус юtет 11<.�которую 011тимиза1щю, новышаю­ щую 11ро1пво;1итсл ыюпъ общих онераний с символ ьным строкttмн. Все три клttс­ са реал и:1уют интерфейс Ch arSel1ueпce. И 1юслсднес за мечание: нси:J!\Н.'нясмосп, символьных строк в объектах типа St ring 0:11 1ачаст. что со;1ерж имос :� к:1е м11ляра класса St.ring нс может быть изме­ нено носле е1·0 со:1;1а11ин. 1 lo llCJH'Me1111aя , объя вленная как ссылка на объект ти па String, может быть в люfiой мо;1,1е1 п и:1мс11ена та ким обра:юм , чтобы \1ка:1ыват1, н а друго й объект тина Sr ring.
'70 Часть 11. Библиоте ка Java Конструкторы символьных строк В классе String подцерживается несколько конструкторов. Для создания пусто­ го объекта типа s t r ing вызывается стандартный конструктор. Например, в следую­ щей строке кода создается экземпляр класса String, не содержащий символы: String s = new String() ; Зачастую символьные строки требуется создавать с начальными значениями. Для этой цели в классе String предоставляются разнообразные конструкторы. В частности , для создания символьной строки , инициализируемой масс ивом сим· волов, служит следующий конструктор: String (char ClrNaOЛl l []) Ниже приведен пример применения этого конструктора. Он инициализирует строку s символами "аЬс ". char chars[]=( 'а', 'Ь', 'с' ); String s = new String ( chars ); Используя следующий конструктор, можно задать подциапазон, т.е. определен· ную часть массива символов для инициализации ними строки : Strinq(char CWНPOl ll l [], int н.avaлwl ll li _ J1НДei:c , int ЖQШIVec!l'ao_cюaю.l l oa) где параметр на чаль ный _ индекс обозначает начало подциапазона, а параметр количество_ символов - те символы, которые нужно использовать для иници· ализации строки. В следующем примере строка s инициализируется символами "cde ": charchars[]={ 'а', 'Ь', 'с', 'd', 'е', 'f'}; String s = new String (chars, 2, 3) ; Используя следующий конструктор, можно создать объект типа String, содер­ жа щий ту же последовательность символов, что и другой объект типа String: String (String С!!'рОЖОашi_о65е.J:!1') где параметр строковый _ о бъект обозначает объект типа S tring. Рассмотрим сле­ дующий пример программы: 11 Создать один объе кт типа String из другого class Ma keString { puЬlic static void main (String args []) charс[1={'J', 'а', 'v', 'а'1; String sl = new String (c) ; String s2 = new String (sl) ; System.out . println (sl) ; System.out . println (s2) ; Ниже приведен результат, выводи мый данной программой. Как видите, сим· вольные строки s 1 и s2 содержат одинаковые значен ия . Java Java
Гл ава 16. О бработка символьных стр ок &71 Несмотря на то что в примитивном типе char языка Java для представления основного набора символов в Юникоде используется 16 бит, в типичном формате символьных строк, пересылаемых через Интернет, испол ьзуются массивы 8-раз­ рядных байтов, создаваемых из набора символов в коде ASCII. Чаще всего употре­ бляются 8·разрядные строки в коде ASCII, и поэтому в классе String предоставля· ются конструкторы, инициализирующие символьную строку массивом типа byte. Ниже приведена общая форма этих конструкто ров. String (Ьуtе CJr.NВ OJШ[] ) String (Ьyte CJrюtOJШ [ ], int наvальн.шi_х ндехс , int xOJU1vec!1'.вo_CJ1NВOJ J 011) Здесь параметр симв олы обозначает массив байтов. Вторая форма конструк­ тора позволяет указать требуемый поддиапазон. В каждом из этих конструкторов преобразование байтов в символы выполняется в соответствии с кодировкой, вы­ бираемой на конкретной платформе по умолчан ию. Применение этих конструк­ торов демонстрируется в следующем примере программы: 11 Создать симв оль ную строку из подмн ожества ма ссива симв олов class SubStringCons { puЫic static void ma in (String args[] ) { byte ascii[] = {65, 66, 67, 68, 69, 70 }; String sl = new St ring {ascii ); System. out . println (sl) ; String s2 = new String (ascii , 2, 3); System. out . println (s2) ; Ниже приведен результат, выводимый данной программой. ABC DEF CDE Имеются также расширенные версии конструкторов, где байты преобразуются в символьные строки и можно указать коди ровку символов, определяющую поря· док преобразования байтов в символы. Но, как правило, требуется кодировка, вы­ бираемая на конкретной платформе по умолчанию. На заметку! Содержимое массива копируется всякий раз, когда объект ти па String создается из массива. Даже есл и содержимое массива изменится после созда ния символ ьной строки в виде объекта ти па String, последний оста нется без изменения. Объект типа String можно создать из объекта типа StringBuffer, испол ьзуя следующий конструктор: Strinq (StringВuffer о65еж!1'_буфера_с!rр0ж) А создать символьную строку из объекта типа StringBu ilder можно с помо­ щью такого конструктора: S tring (StringBuilder oбъex!l'_npo c!rpoeнJl.Jl_ c!l'pOXJl l ) В следующем конструкторе поддерживается расширенный набор символов вЮникоде : String (int жодо.&1 1 е_ !1'оvюr[] , int нava.ILWODi_яндeжc , int жатrvес!l'ао_схнаоло.в)
1+72 Часть 11. Библиотека Java где пар аметр кодовые _ то чки обо:шi!част массив, соде ржащий симrюлы 11 К)никоде. Результи рующая строка со:щается и:1 110;1.диа11а:Jош1 сим1ю;юв, 11а•1;uю которого обоз11а•1аст на чальный_ индекс, а дл ина - коли чt!ств о _ симв ол ов. Имеюп: я также ко н структоры, 11о:зволяющие ука:1ывать набор символов. На заметку! Обсужде ние основных понятий и составляющих Юникода , в том числе кодо вых точек, а та кже обращение с ними в Java приведено в гл аве 17. Дл ина символьной строки Кол ичество символ ов, из кото рых состо ит строка , 011ре;tсл яет ее ;�л и ну. Чтобы получить это :111аченис, достаточно вы:ш<lть мсто;t lengt.h (); int length () В следующем фрагменте кода выводится сим1юл ьшш строка ":З " , поскольку именно три <· им1юла содержит исходная строка s: char chars[] = { 'а', 'Ь', 'с'}; String s = new String (chars) ; System. out .println (s.length () ); Специальные строковые операции Символ ьные строки являются очень важной составляющей нроrраммирова· ния, и 11о:�то му н .Java обеспечивастся снециалы�ая 110;исржка некоторых строко­ вых операци й в рам ках синт<lксиса это 1·0 н:1ыка. Эти оп<.�рации вю1 ю•�ают автома· тичсское со:щш ше новых эк:.1с мшн�ров КJtac ca String и:1 строковых литерал ов, сцепление многих объектов тиш1 String с 110мощыо 011срации +, а та кже 11рсобра· зованис других ти1юв да нных в их строковое нредг!'а влснис. Длн рс.uш :i<lЦИИ всех этих 01н.·раций имеются с11с цшu1ы1ыс явно указываемые методы, 1ю дл я удобства и бол ьшей ясности 11рщ·раммирования нa.Java они вы1юлш1ютсs1 автоматически. Строковые литералы В предыдущих примерах было 1юказа 110, как нн11ым обра:ю м <:0:11\<ша·1ъ объекты класса String из массива символов с номо�щ,ю оператора new. llo спъ и более простой способ сдслат1, то же самое с 1юмощыо строковых литер.uюв. Лля каждо- 1·0 строl(ового литерала в прикладной программе 11а .J ava автомати 'lсски со;щает· ся объект тина String . Та ким обра:юм, строконый лите рал можно ипющ,зовать дл я ю1ицюuш:1ации объекта ти на String. 1 lанример, н слс;(ующсм фра1·мснтс ко;щ со:ща ютсн две од инаковые символы1ые строки: char chars[J = { 'а', '_Ь', 'с' }; String sl new String (chars ); String s2 " аЬс"; // использовать строковый литерал
Гл ава 16. Об работка символ ь ных стр ок '73 Объект типа String создается для каждого строкового литерала, и поэтому строковый литерал можно использовать в любом месте , где допускается примене­ ние объекта типа String. Например, методы можно вызывать непосредственно для символьных строк в кавычках как по ссылкам на объекты. Та к, в приведенном ниже примере метод length () вызывается для символьной строки "аЬс". Как и следовало ожидать, на экран выводится строка "3 " . System. out .println ("abc" . length () ); Сцепление строк Как правило, в Java не разрешается выполнять арифметические операции над объектами типа String. Но из этого правила имеется исключение: с помощью операции + можно сцеплять, т.е . соединять две символьные строки, порождая в ито­ ге объект типа String. Операции сцепления символьных строк можно объединять в цепоч ку. Например, в приведенном ниже фрагменте кода выполняется сцепление трех символьных строк. В итоге выводится символьная строка "Ему 9 лет " . String age = "9"; Strings= "Ему"+age+"лет."; System. o ut . println (s) ; Сцепление символьных строк находит практическое применение , в частности , п ри создан ии очень дл инных строк. Вместо того чтобы вводить длинные символь­ ные строки в исходном коде неразрывно и допускать их автоматический перенос на новую строку, такие строки можно разбить на мелкие части , сцепляемые с по­ мощью операции +, как показано ниже. // Исполь зовать сцепление во избежание длинных строк class ConCat { puЫic static void ma iп (Striпg args[J ) { String loпgS tr = "Это мо жет быт ь очень длинная строка , " + "которую следовало бы перенести на " + "новую строку . Но благодаря сцеплению " + "этого удается избежа ть ."; System. out . println ( longS tr); } Сце пл е н ие СИМВОЛЬНЬIХ строк с другими типами данных Символьные строки можно сцеплять с данными других типов. Рассмотрим в каче­ стве примера следующую немного измененную версию приведенного ранее примера: intage=9; String s =" Ему"+ age + " лет."; System. out . println (s) ; В дан ном примере переменная age относится к типу int, а не String, но ре­ зультат получается прежним. Так происходит потому, что значение типа int авто­ матически преобразуется в свое строковое представление в объекте типа String.
'7' Часть 11. Библиоте ка Java После этого символьные строки сцепляются , как и прежде . Компилятор преоб­ разует операнд age в его строковый эквивалент, тогда как остальные операнды рассматриваемой здесь операци и + являются экземплярами класса String. Сцепляя операнды других типов данных с символьн ыми строками в операциях сцепления, следует быть внимательным, иначе можно получить совершенно нео­ жиданные результаты. Рассмотрим в качестве примера следующий фрагмент кода : Strings= "четыре:"+2+2; System.out . println (s) ; В данном примере выводится следующий результат сцеплен ия: четыре : 22 вместо ожидаемого: четыре : 4 Дело в том, что благодаря предшествованию операций сначала выполняется сцепление символьной строки "четыре " со строковым представлением первого числа 2, а полученный результат сцепляется затем со строковым представлением второго числа 2. Для того чтобы выполнить сначала целочисленное сложение , следует заключить эту операцию в круглые скобки, как показано ниже. В ито ге строка s будет содержать символы " четыре : 4 " . Strings= " четыре:"+(2+2); П реоб разование с имвольных строк и метод toS tring () При сцеплении символьных строк в Java данные других типов преобразуются в их строковое представление путем вызова одного из перегружаемых вариан­ тов метода преобразования va lueOf ( ), определенного в классе String. Метод valueOf ( ) перегружается для всех примитивных типов данных, а также для типа Obj ect. В частности , для эл ементар ных типов данных метод va lueOf ( ) возвра­ щает символьную строку, содержащую удо бочитаемый эквивалент того значе­ ния , с которым он был вызван . А для объектов метод va lueOf ( ) вызывает метод toString ( ) вызывающего объекта. Подробнее о методе va lueOf () речь пойдет далее в этой гл аве , а до тех пор рассмотрим метод toString ( ) как уд обное сред­ ство для строкового представления объектов создаваемых классов. Метод toString () реализуется в каждом классе, поскольку он определен в классе Ob j ect. Но реализация метода toString () по умолчанию редко оказы­ вается полезной. Поэтому во всех наиболее важных из создаваемых классов метод toString (), скорее всего , придется переопределить, чтобы обеспечить в каждом из них свое строковое представление. Правда, сделать это совсем не трудно, ис­ пользуя следующую общую форму метода toString (): Strinq toStrinq () Чтобы реализовать этот метод в своем классе, достаточно возвратить объект типа String, содержащий удо бочитаемую символьную строку, надлежащим обра­ зом описывающую объект данного класса.
Гл ава 16. Об работка символьных стр ок '75 Переопределение метода toString (} в создаваемых классах позволяет полно­ стью интегрировать их в среду программирования нa java. Например, переопре­ деленные варианты метода toString (} можно применять в операторах print (} и println (}, а также в операциях сцепления символьных строк с данными дру­ гих ти пов. В следующей программе эта особенность де монстрируется на примере переопределения метода toString (} в классе Вох: // Переопредели ть ме тод toString () в кл ассе Вох class Вох { } douЫ e width; douЫ e height ; douЫ e depth; Box (douЫe w, douЫe h, douЫe d) { width = w; height = h; depth = d; } puЫic String toString () return"Размеры"+width+"на"+ depth+"на"+height+"."; clas s toStringDemo { puЫ ic static vo id ma in (String args [] ) Вох Ь = new Box(lO, 12, 14); String s = "Объе кт Ь типа Вох : " + Ь; // выполнить сцепление // симв оль ной строки с объектом типа Вох System. o ut . println (b) ; // преобразовать объе кт типа ВОх // в симв оль ную строку при выводе System. out . println (s) ; Эта программа выводит следующий результат: Размеры 10.0 на 14.0 на 12.0 Объект Вох типа Ь: Размеры 10.0 на 14.0 на 12.О Извлечение символов В классе String предоставляется немало способов извлечь символы из объек· та типа String. Рассмотрим некоторые из них. И хотя символы, составляющие строку, нельзя индекс ировать та ким же образом, как и в символьных массивах, тем не менее , для выполнения операций во многих методах из класса String приме­ няется индекс ( или смещение) в символьной строке . Как и массивы, символ ьные строки индекси руются начиная с нуля. М етод charAt () Чтобы извлечь из строки единственный символ , достаточно обратиться к нему непосредственно, вызвав метод charAt (}. Ниже приведена общая форма этого метода. char charAt (int rде)
476 Часть 11. Библиотека Java Здесь 11араметр где обозна<�ает индск(· п1 м вот1 , который требуется нолу<J ить. �i начение 11араметра где дол ж н о быть неотри щпсл ьн ым и ука:1ы 1ш·1ъ 1ю1юже11ис и:шл ею1емо1·0 символа в строке. Метод charAt () во:шра щает символ , находя щий­ ся в указан ном ноложен ии. В следующем нримсре строковое ;11ш•1ение " Ь " 1 1 рис ва­ ивается переменной ch. char с!1; ch = "abc" . charAt (l); М етод ge tChars () Если требуется извл е'lь несколько символов сразу, то длн :·ной цел и можно вы­ звап, метод getChars (). 1 Iижс приведе на общая ф орма :�то1·0 метода . void getChars (int на чало источника , int конец источника , char адресат [) , int на чало_ адреёа та ) Здес 1, 11арамстр на чало_ исто чника обо:ш<t•�ает шщекс IOPl<t.lla 110;\строки, а параметр конец_ исто чника - индекс символа, слс;�ующе1·0 1юсле 1< 011щ1 и:Jвле­ кае мой 11одстроки . Та ким обра:юм, иэвлекастся 11одстрока, со;�ержащая символ ы в нре;\ел а от на чало_ исто чника до конец _ источника - 1 . Массив, 11риним<1ющий извлекае мые символ ы, аад<1ется в качестве 11ар<1метра адреса т, а индекс , начиная с кото роп1 извлекаемая подсчюка будет копироватьс.н в уювшшый мап�ив адреса т, - в 1<а чссп1е 11араметра на чало_ адреса та . Сл едует, однако , щн1 1ш·1ъ меры, <Jтобы указанный массив адре са т оказ<щся достаточного ра:1мсра и в нем ра:�местил ись все символы из зада нной подстроки. В следующем примере программы демонстр и руетс я 11риме11с11ие метода getChars ( ) : c1ass getC:ha rs Demo { puЬlic static void ma in (St ring args [] 1 { String s = "Это демонстрация ме тода getChars () ."; iпt start = 4; iпtепd=8; char buf{] = new char{ end - start]; s.getChars (start , end, bu f, 0) ; System. out . println (bu f) ; Ниже 11риведе 11 резул ьтат, выводимый данной 111ю1·1ыммой. демо Метод getBytes () В юt •1сст11е а.1 11 ;rернати вы методу g etChars () 11рсдоп·,шл ястся метод getBytes (), сохраняющи й символ ы в массиве ба йтов . В этом методе 11ы1юл няется преобразова­ ние символ ов в байты , выбираемое на конкретной платформе 110 умоJ1 11а11ию. Ниже при ве;(е на щюстсйшая форма это 1·0 м етода . byte [] ge tВytes ()
Гл ава 16. Обраlотк• смМWОМ.мых строк 1.77 Имеются и другие формы метода getBy tes (). Этот метод применяется, · rла:в­ ньm образом, в тех случаях, когда значения типа Strin9 эщ:ворmруются в те сре­ ды, где не поддерживаются 16-разрядные символы в ЮниКоде. Например, в бОJПr шинстве сетевых протоколов Интернета и форматов те�с с товых файлов применя· ется 8-разрядный код ASCII для всех операций обмена текстовыми данными. Метод toCharArray () Если требуется преобразовать все символы из объекта типа String в символь­ ный массив, то сделать это проще всего , вызвав метоД toCha tArray ().Этот метод возвращает массив символов из всей строки. Ниже приведена его общая форма. ahar [ ] toCharArray () Этот метод предоставляется в качесТве доri:оЛнеh:ИЯ ; Поскольку тот Же саЬlый результат можно получить, вызвав метод ge tChars (). Сравнение символьных строк · В классе String имеется немало методов, u���.@-� A1fЯ,cp�� qht вольных строк или подстрок в них. Рассмотрим некоторые из этих методов. - 1,J' Мtтодь1 equa1s о и equalsigric)i�case .( ) · ·' Для сравнения двух символьных строк на равенство достаточно вызватt меrо,ц equa ls (), который имеет следующую общую форму: Ьo o lean equa ls (Obj ect С!l'р()Жа) где параметр строка. обозначает объект типа Str:fl'!-tl! �Nй фasimsaeтcЯ'c .�Ыi зывающим объектом типа String. Этот м«':ТРД ,�� логическое значение true , если сравниваемые строки содержат те же: самые символы и в том же по­ р'Щr.ке; а иначе - логическое значение fal se. СраВнен:Ие выrtЬ.itняеТся с учетом регистра. ' ' ': 'Для сравнения символьных строк без учета регйстра tимВo:i i ci� достаточно вьr­ �вaть мeтoд equal s I gn oreCase ().При сравненИн дВ'ух симВо.i i ьнь'rх сфок наборы сь�олов А- z и а - z в этом методе считаются одинаковымИ.: On имеет следующую обЩую форму: . ·: · ·· · · �l•an equalsignoreCase (OЬject C!l'pOXa ) ,.: где параметр строка обозначает объект типа S tring, который сравнивается с ВNo­ зывающим объектом типа String. Этот метод возвращает логическое значение true , если сравниваемые стрщ щ щдержат QДиuак�J>I�·��ол� :в том •е �М порядке, а иначе - логическое значение false. . , Ниже приведен пример программы, в которои демtJнстрируется применение методов equ als () и equ alslgnoreCase (). 1 1 Продемонстрировать приме нение ме тодов equaliJ ( ) 1 1 и equalsiqnoreCase ()
'78 Часть 11. Библ иотека Java class equalsDemo { puЫ ic static void ma in ( String args[] ) { St ring sl "Приве т"; String s2 = "Привет"; String sЗ = "Прощай "; String s4 = "ПРИВЕТ "; System.out.println(sl + " равно " + s2 + " -> " + sl . equals (s2) ); System.out.println(sl + " равно " + sЗ + " -> " + sl . equals (sЗ) ); System.out.println(sl + " равно " + s4 + " -> " + sl . equals (s4) ); System. out . println (sl + " равно беэ уче та регистра " + s4 +" - > " + sl . equalsignoreCase (s4) ); Эта программа вывод ит следующий результат: При ве т равно Привет -> true Привет равно Прощай -> false Привет равно ПРИВЕТ -> false Привет равно беэ уче та регистра ПРИВЕТ -> true М етод reqionМatche s () Этот метод сравнивает одну заданную часть символьной строки с другой ее ча­ стью. Имеется также перегружаемая форма метода regionMatches ( ) для сравне­ ния частей символьной строки без учета регистра. Ниже приведены общие фор­ мы этого метода. boolean reqionМatches (int нa v&1Uo.16D i жндежс , Strinq С'l'р0жа2 , int яндежс_наЧала_C'l'pODl2 , int жотrvес'.1'.во_Cl lOaIOJ J' oa) boolean reqionмatches (boolean иrнор.1 11р о.вата__реrжс!rр, int начал:ъ�виl_жндежс , Strinq С'l'р0жа2 , int яндежс начала стро 1С1 1J 2, int жотrvество_ синволов) - - В обеих формах данн ого метода параметр на чальный_ индекс обозначает ин­ декс начала той части символьной строки из вызывающего объекта типа String, с которой сравнивается символ ьная строка, задаваемая в качестве параметра строка 2. Индекс символа, начиная с которого должна сравниваться заданная строка 2, задается в качестве параметра индекс_ на чала_ строки2, а длина сравни­ ваемой подстроки - в качестве параметра количе ство_ симв олов. Если во второй форме дан ного метода параметр игнорирова ть_регистр принимает логическо е значение true , то сравнение подстрок выполняется без учета регистра. В против­ ном случае регистр учитывается. Методы startsWi th () и endsWi th () В классе String определены два метода, являющиеся в большей или меньшей степени специализированными формами метода regionMatche s ().Так, в методе startWith () определяется, начинается ли задан ный объект типа String с ука-
Глава 16. Обработка символьных строк 479 занной символьной строки, а в методе endsWith( ) - завершается ли объект типа String задан ной п одс трокой . Ниже приведены общие формы :п их методо в. Ьoolean startsWi th (String c!l'poxa) Ьoolean endsWith ( String строха ) Здесь параметр строка обо:ша•щет подстроку, наличие которой про ве ряетс я в начале или в ко1111е вы:!ывающего о бъекта типа Stri ng соответственно. Есл и эта 11одстрока п р исутствует в да нном м ес те вызыва ющего объекта типа St ring, то возвра щ ается ло1·ическое значение true , а иначе - логическое значение fa lse. Например , в р езульт;�те сл едую щ и х вызовов: "Fooba r ".endsWith ("ba r" ) и "Fooba r" . startsWlth ("Foo ") возвращается 11 01·и ческое :н�ач ение t rue. Во второй , при ве11еююй ниже форме метода s tartsWi th ( ) можно задать на­ чальную точку /\ЛЯ 1юиска :�а,11а11ной 11одстроки . boolean startsWith (String строха , int на vальныli _ индехс) те на ча льный _ индекс обо:шачает индекс с и мв ола , с к ото рого начинае1тя поиск за­ данной rю;1счюки в исходной строке. Например, в результате следующего вызова: "Fooba r" . startsWith ( "bar ", 3 ) возврюцается логическое :ш ачение t rue. Метод equals () в сравнении с операцией -- -- Следует иметь в виду, что метод equals ( ) и операция == выrюлш1ют разные де йствия. Как 1юяснял0<ъ ранее, метод equa ls ( ) сравнивает символ ы и:J объекта типа String, тогда как операция = - две ссылки на объекты и опре,11еляет, ссыла­ ются ли они на один и тот же экаем11ляр. В следующем примере программы пока­ зано, что два разных объекта типа Stri ng могут содержать одинаковые символы , но ссылки на :i ти объекты 11ри сравнении не будут равнозначными: 11 Метод equals () в сравнении с операцией == class EqualsNotEqualTo { puЬl ic static vo id ma i n(St r ing args[] ) String sl "Привет" ; String s2 = new Strin g(sl) ; System.out.println(sl + " равно " + s2 + " -> " + sl .eчua ls (s2) 1; System.out.println(sl + " == " + s2 + " -> " + (sl s2) ); Переменная s 1 ссылается на экземпляр класса String, созданный присваива­ нием ей стро1«щого литерала "Привет". А объект, на который ссылаетсн 11еремен­ ная s2, (:о:щаето1 с ипюлh:юванием 11еремс1 1 11ой sl в качестве и11ш1ш1ли затора. Таки м образо м , содержимое обоих о бъектов тина String <щ инаково, 110 :·но pa:i-
480 Часть 11. Библ иотека Java ные объекты. Следовательно, переменные s 1 и s 2 ссылаются не на один и тот же объект, и поэтому они не равны (при сравнении в операции =), как доказывает приведенный ниже результат, выводимый данной программой. Привет равно Привет -> true Привет == Привет -> false М етод compareTo () Зачастую недостаточно знать, что символьные строки одинаковы. В приклад­ ных программах , выполняющих сортировку, обычно требуется выяснить, оказы­ вается ли текущая символьная строка меньше, бол ьше или равнай следующей строке. Одна символьная строка меньше другой, если она следует перед ней в лексикогра­ фическом порядке , и больше другой, если она следует после нее. Для этой цел и служит метод compa reTo ( ) , определенный в интерфейсе ComparaЫe<T>, реал и­ зуемом в классе String. Этот метод имеет следующую общую форму: int coшpareTo (String С!l'р()Жа ) Здесь параметр строка обозначает объект типа String, сравниваемый с вызы­ вающим объектом типа String. В озвращаемый результат сравнения символьных строк интерпретируется так, как показано в табл. 16.1 . Та бл ица 16.1. Результат сравнения символьных строк в методе compareтo () Значение Меньше нуля Больше нуля Нуль Описание Вызывающая символьная строка меньше строки str Вызывающая символьная строка больш е строки str Символ ьные строки равны В приведенном ниже примере программы сортируется массив символьных строк. Для определения порядка пузырьковой сортировки в этой программе ис­ пользуется метод compareTo (). 11 Пузырьковая сортировка объектов типа String class SortString { static String arr [J = { }; "Now" , "is", "th e", "time", "for ", "all", "good" , "men" , "to" , "соте", "to" , "the", "aid" , "of", "the ir" , "count ry" puЫic static voi d ma in (St ring args []) { for(int j = О; j < arr.length; j++) { for{inti = j + 1;i < arr.length; i++) if {arr[i] .compareTo (ar r[j] } < 0) { String t = arr[j]; arr[j) arr[i) ; arr[i] = t; } System.out . println (arr[j) );
Гл ава 16. Обработка символ ьных строк 481 :-)та 11рограмма выво;щт отсорт11рова1111ыс слова в слс;�.ующсм 1юрн:lкс : Now aid all coin e COLIП t ry for good is 1n e11 о[ thP t. he their time to to Как сле;tvст 11:1 рс:1ую>1·ата 111>11юл11с1111я Jtaшюi·i 111ю1·ра!1-!МЫ, сра111 1е1111с п1,1 11оm,- 11ых стро к в мсто;tе compa r· ето () 11ршкхщ11п с учетом ре гистра. В •1а<т1 ю<т11 , слово "NOlv" сл с;1уст 11рсж;1с всех ост;u1 ы1ых <·лов. 11оп<ол 1.ку 0110 11;1 '11111астп1 с 11po 1111п 1oii буквы. а 11р<н111п1ан Gуква 11."1<.тт мс111>111<.т :11 1 a'lc1111c в наборе п1 мволо11 11 1\о; (с ;\SCII. Есл11 пн1 11ол ы1ыс прою1 трсflустсн срав11111 1 а·1ъ fic:1 учета рсп1стра . то .' t:НI :-iтo ii нсл11 ('JI \ 'Ж IП '\Н :TOJt compa r '·�TPJ уп<;геСd;;е (). 1lн жс 11р1111с;1е11а сп1 о flщан фор,1а. int compareTolgnoreCase (String строка ) :-)тот мето;\ 1ю :111ращаст та коii же рс:1ул 1;гат, как и метод compareTo (;, 110 тол 1,­ ко fic:1 учета рсп1стра Пl'\tволов. Пo11pofiyiiтc ввести :-пот мето;t 11 111н1 11<'. 'tc1111ыii 111>1 111(' 11р11мср llJ IO l'P <IM\lbl. в llTOI'(' слово "Now" \'ЖС 11(' llOHBllТПI llCJ >llЫ\I в ПillCKC отс орп1рова1111ых < ·;юн. Поис к в с имвольн ых строках В кл ассе St� 1.' i пq 11рс/tщ·та 11л н ютс н itBa метщ1а /\Л Н 1ю11 сю1 в с11'\111олы1оii строке 011рс;1сл с 111101·0 п1мвола 11л11 1ю;tстрою1. • Мет< >.'\ iп;ie xOf ( ) - нахо,:щт 11ср11ос 11хожi(С 111н· < · имвола или 1ю.к трок11. • Метщ\ last.IпdexC f () - 11ахою п 1юcJ1<.'J(llcc 11хож/1с 1111е п1'\111ол <1 1с·1 11 11о;t­ строю1. Ofia :�т11 \K'TOJ\a 11е1н·1ру)1с1юл·н 11сп\ол ыо1м11 пюсоfiа �1 11 11 1ю-ра:11ючу. 1 !о в лю­ ()ом слу•1ае 01111 во:шращ;нот 1ю:111 111110 11 <Т\ЮКС (ш1:tекс ), 1лt· 11aii:tc11 п1\1 110;1 11л11 llO/(Гl'\HH<a . а 11р11 11ey;\;t 'lllO\I l(( 'XO)(t' IJOllП<a - :11 1а•1<.·1111е -1 . Лл н 1ю11п«1 11ср1101·0 11хож;tс1111н п1�1 11ол;1 в строке служ ит слсJ()'IО Щан форча: int indexOf (char символ) л ) (J I JI llO IH' IOI IIOCЛ('j (I I<:l'O llXOЖi\C lllHI ПI M llOJi a 11 ( трокс служит та кая фор\1а: int lastlndexOf (char символ) IЛС ш1ра мстр CИMB OJi ofio:111a•1acт Jl('l\( )\JJ.1ii ПIМIЮЛ 11 СТ) ЮК(' .
1.82 Ч асть 11. Библиотека Java И наконец, для поиска первого или последнего вхождения подстроки служит приведенная ниже форма, где параметр строка обозначает искомую подстроку. int indexOf (Strinq C!l'po.xa) int las tindexOf (String C!l'pO.xa) Воспользовавшись следующими формами рассматриваемых здесь методов, можно указать начальную позицию для поиска символа или подстроки в исходной строке: int indeXOf (int CJU UJ OЛ, int нava.nwa.Di JrНДе.хс) int lastindexOf (int С108ОЛ , int нa vaлWwi i _ JtНДeжc) int indexOf (String С!l'рожа , int наvалънШt инде.хс) int lastindexOf (String С!l'рОжа , int наvа.1i i: :н1Di _ хндежс) где параметр на чальный_ индекс задает начальную позицию для поиска в строке. В методе inde xOf () поиск начинается от позиции на ча льный_ индекс и до конца строки , а в методе lastindexOf () - от позиции на чаль ный_ индекс и до нуля. В следующем примере программы демонстрируется применение различных форм методов индекс ирования для поиска символов и подстрок в исходной строке : 11 Продемонс трировать приме нение разных форм 11 ме тодов indeXOf () и lastindexOf () class indexOfDemo { puЫ ic static void ma in (String args [] ) { String s = 11Now is the time for all good men 11 + 11 to come to the aid of their country . 11 ; System. out . println (s) ; Systern. out . println (11 indexOf (t) = 11 + · s.indexOf ( ' t ' )); System. o ut . println ( 11lastindexOf (t) = 11 + s.lastlndexOf ( ' t' )); System. out .println ( 11 indexOf (the ) = 11 + s.indexOf ( 11 the ") ); System. out .println ("lastlndexOf (the ) = " + s.lastindexOf (11 the ") ); Systern.out . println ( 11indexOf (t, 10) = " + s.indexOf ( 't' , 10) ) ; System. out .println ("lastindexOf (t, 60) = " + s.lastindexOf ( ' t ' , 60) ); System. out . println ( 11 indexOf (the , 10) = " + s.indexOf ("the", 10) ); System. out . println ( 11 lastindexOf (the , 60 ) = 11 + s.lastindexOf ( 11th e", 60 ) ); Ниже приведен результат, выводимый данной программой. Now is the time for all good men to соте to the aid of their country. indexOf (t) = 7 last indexOf (t) = 6 5 indexOf (the ) = 7 lastindexOf (the ) = 55 indexOf (t, 10) = 11 lastindexOf (t, 60 ) = 55 indexOf (the , 10) = 44 lastindexOf (the , 60) = 55 Видоизм енение си мвол ьных строк Объекты ти па String неизменяемы, и поэтому всякий раз, когда требуется их видоизменить, их содержимое следует скопировать в объект типа StringBuffe r
Глава 16. Обработка символьных строк L.83 ил н StriogBt1ilc1e r или же воп юл 1.:ю вап.ся од11 им и:1 мснщов и:! юыс<·а St.riog, п>э;1а ющих новые ко 1ши п1мвол 1>1 1ых гrрок с 11 11ессн11ыми и:1ме11ен иями. В :iтом раэделе рассматриваются 111юстсй111 ис и:1 этих методов. Метод suЬstring () 1lтобы 11 :шлеч1, 11одстроку 11 :1 символ ыюй строки, достато•1 1ю 11 ы:111ап. мето;\ subst. гiпg (),укоторо 1 · 0 имеютс я юн.· формы. Первая его форма та кова: S tring suЬs tring (int на чальный _ ннд екс) где нара мстр на чаль ный__ индекс оfiо:ша•1аст 11о:шцию, с которой должна начинать­ ся 1ю,11п·1юка. Эта форма 110:111ра щает к01 111ю нодстроки , которая 11ачи11астс я с 11о:н1- ции на чаль ный__ иид�'КС и 111ю;1олжасто1 до :ывсршсния вы:1ывающсй строки. Вторая форма метода stJ l)striпg () но:шо;шст ука:1<tть как 11а•1;u1ы1ый, так и ко- 11еч11ый 1111/\С КС llO)\<'ТJIOIOI CJI C/\)' I OЩllM оfiра:юм: S tring suЬs tring (int на чалыnIЙ_нндекс , int конечный_ннд екс) ще 11арамt:тр на чалы1ый_. и ндекс оGо:щ ачает шх1ицию, с котороj.i 11олж11а 11а•ш­ ш1·1ъся и:шлекаемая 110/\СТJН>ка. а коне чный_ индекс - 1ю:ш11ию, на J<oп>poii /\ОЛЖ· 11а оканчив<tп"·я и :шлскаt· мая 11011<т1 юка. Во:1вр<tщ<tемая нодсгрока содержит все Пl l\1\IOJ\ bl , ОТ \ll'J >IIOii ll O:HJl\ИИ И /1,0 llOCJI C/\ l l C Й , НО И<"КJIIO'l<iЯ l\OCJIC/\llIOIO. В следующем 111тмерс 11рограммы метод substriпg () ипюл ь:\уется i\Л Н :�а ме­ н ы 11 иcxo;1 110ii <·11м1юл ыюii СТJЮК<' всех эк:-�емнляров ою юit 1 10,r1,стро к и ,т1,ругоii 110д­ п·рокой. // Замена подс троки class StringRcplace pLit.ilic static void ma.i. 11 (ё,t. riпg args []) String org " "This is iJ t. est. . Tl1is is, too. " ; Strir1g search � '' is''; String sub = '' was''; String resLilt = " " ; int i; do { / / эамени1ъ все совпадающ и е rюдстроки System.01Jt. . priпt.lп (<Нg) ; i с' oгq.ir 1ciexC!f (sea rct1) ; if(i 1= -1) { [?SlJH OГCJ .Sl!b sU1пg (O, i) ; result = resLil t + sub; result result � org . subst ring li + search .length l) ); OLCJ '0 resul t; wt1ile(i '' -1); 1Iижс 11ри вс;1.<�11 ре:1ул 1;1-; 1т, 11ыво;1,111\1ыii ;1а11110й 11рограм111ой. This is а test . This is, too. Thwas is а test . т1·1 is i�;, too. Thwas 1вs а test. Tl1is is, too. Tl1was was а test. Thwas is, t.oo . Thwas was а test. Thwas was, too.
484 Часть 11. Библиоте ка Java М етод conca t () Чтобы соединить две подстроки, достаточно вызвать метод concat ( ) . Ниже приведена его общая форма: Strinq concat (Strinq c!l'J)Oxa) Этот метод создает новый строковый объект, соде ржащий вызываемую стро­ ку, в ко нце которой добавляется содержимое параметра строка . Метод concat () выполняет то же действие, что и операция +, т. е. сцепление символьных строк. Например, в следующем фрагменте кода соединенная строка "one two " присваи· вается переменной s2: String sl = "one"; String s2 = sl. concat("two" ); Этот фрагмент кода дает такой же результат, как и приведенный ниже фраг­ мент кода. String sl "on e"; String s2 = sl + "two"; М етод replace () У этого метода имеются две формы. В первой форме все вхождения одноrо символа в исходн ой строке заменяются другим символом: Strinq replace (char ясходнмif , char эаненаеНRЖ) где параметр исходный обозначает заменяемый символ , а параметр заменяемый ­ заме няющий. В результате замены возвращается видоизмененная символьная строка. Например, в следующей строке кода: String s= "Hello".replace('1', 'w'); переменной s присваивается символьная строка "Hewwo ", получившее в результате замены символа ' l' на 'w' висходной строке. Аво второй форме метода rерlасе () одна последовательность символов заменяется другой следующим образом: Strinq replace ( CharSequence ясходиая , CharSequence .заненаенАR) Метод trim () Этот метод возвращает копию вызывающей символьной строки, из которой удал ены все начальные и конечные пробелы. Он имеет следующую общую форму: Strinq trim () Ниже приведен пример применения метода t r im( ) . В итоге переменной s присваивается символьная строка "Здр авствуй , мир ". Strings= " Здравствуй , мир ".trim ( ); Метод trim() очень уд обно вызывать для обработки ко манд, вводимых поль· зователем. Та к, в приведенном ниже примере программы пользователю сначала предлагается ввести название штата, а затем выводится название города - стол п·
Гл ава 16. Обработка символьных строк 485 цы штата. Метод t r im ( ) используется в этой программе для удаления всех началь­ ных и конечных пробелов, которые могут быть непреднамеренно введены поль­ зователем. // Использовать ме тод tri.JI () для обработки команд, // вводимых поль зователем import jav a.io.*; class UseTrim { puЫ ic static void ma in (String args[]) throws IOException { // создать буферизированный поток чтения типа BufferedR.eader , // исполь зуя стандатный п оток ввода Systea .in BufferedRe ade r br = new Bu ffe redRe ader (new InputStreamRe ader ( System. in) ); Striпg str; System. o ut . println ("B вeдитe ' стоп ' для завершения ."); System. o ut . println ("B вeдитe наз вание штата : ") ; do{ str = br. readLine(); str = str . trim() ; // удалить пробелы if(str. equals( "Иллинойс") ) System. out .println ( "Cтoлицa - Спрингфилд .") ; else if (str . equals ( "Миccypи") ) Systern.out . println ("C тoлицa - Джефферсон-сити ."); else if (str .equals ("Kaлифopния ") ) System. o ut .println ( "Cтoлицa- Сакраменто .") ; else if (str . equa ls ("Baшин гтoн" )) System. out .println ("Cтoлицa - Олимпия .") ; 11 ... while ( ! str. equals ("стоп") ); П реобразование данных методом valueOf () Метод valueOf () преобразует данные из внутреннего представления в уд обо­ читаемую форму. Этот статический метод перегружается в классе String для всех встроенных в Java типов данных таким образом, чтобы каждый тип был правиль­ но преобразован в символьную строку. Метод valueOf () перегружается и для типа Obj ect, поэтому объект типа любого создаваемого класса также может ис­ пользоваться в качестве аргумента. (Напомним, что класс Ob j ect является супер­ классом для всех остальных классов.) Ниже приведены некоторые формы метода va lueOf (). static Strinq valueOf (douЫe v•сло) static Strinq valueOf (lonq v•сло) static Strinq valueo1: (0bj ect объех�) static Strinq valueOf (char сянво.IПI[] ) Как упоминалось ранее, метод va lueOf ( ) вызывается в том случае, если тре­ буется строковое представление некоторого другого типа данных, например, в операциях сцепления символьных строк. Этот метод можно вызывать и непо­ средственно с любым типом да нных, чтобы получить подходящее строковое пред­ ставление этого типа дан ных. Все примитивные типы данных преобразуются
'86 Часть 11. Библиотека .Jаvа в их общее счюковое представление. Для любого объекта, передаваемого методу va lueOf (), возвращается результат вызова метода toString ().На самом деле тот же самый результат можно получить, просто вызвав метод toString (). Для бол ьшинства массивов метод va lueOf ( ) возвращает заш ифрованную символьную строку, которая обозначает, что это массив определенного типа. Но для масс ивов ти па char создается объект типа String, содержащий все символы из массива ти па char. Для этой цели служит следующая форма метода valueOf (): static Strinq valueOf (cha.r СJКНВОIА r [] , int нav&l lЬIВl li жндежс , int 1Co.l llr '1 1' 8C�SO_ CJINВOJlOB ) - где параметр символы обозначает массив, содержащий символы, параметр на ча ль ный_ индекс - позицию в массиве , с которой начинается подстрока, а па­ раметр количество_ симв олов - длину подстроки . Изменение регистра символов в строке Метод toLowe rCase ( ) преобразует все символы строки из верхнего регистра в нижний, а метод toUppe rCa se () - из нижнего регистра в верхн ий. Небуквенные символы, например десятичные цифры, остаются без изменения. Ниже приведе­ ны простейшие формы этих методо в. Strin9 toLo-rCase () Strinq toUp pe rCase () Оба метода возвращают объект типа String, содержащий эквивалент вызываю­ щей строки в нижнем или верхнем регистре символов соответственно. В обоих слу­ чаях преобразование выполняется с учетом региональных настроек по умолчанию. Ниже приведен пример программы, демонстрирующий применение методо в toLowe rCase () и toUpperCase (). 11 Продемонстрировать применение ме тодов toUp pe rCase () и toLowerCase () class ChangeCase { puЫ ic static void main (St ring args[] ) { String з = "Это тест."; System.out . println ( "Иcxoднa я строка : "+з) ; String upper = s.toUpperCase (); String lowe r = s .toLowe rCase () ; System.out . println ("Bepxний регистр : "+upper ); System.out . println ( "Hижний регистр : "+lower ); Эта программа выводит следующий результат: Исходная строка : Это тест Верхний регистр : ЭТО ТЕСТ Нижний регистр : это тест Следует также иметь в виду, что имеются и перегружаемые варианты методов toLowe rCase ( ) и toUppe rCase ( ) , позволяющие определять объект типа Locale для управления преобразованием. В некоторых случаях определение региональ-
Гл ава 16. Обработка символьн ы х стр ок 487 ных настроек может иметь особое значение и способствовать интернационализа­ ции прикладной программы. Соеди нение символ ьных строк В версию JDK 8 в класс String был внедрен новый метод join (), предназна­ ченный для соединения двух и более символьных строк, разграничиваемых ука­ з анным разделителем, например, пробелом или запятой. У этого метода имеются две формы. Ниже приведена первая из них. static Strinq join ( CharSequence разде.1 11 1 �елъ , CharSequence . . . с�роюr) Здесь параметр ра зделитель обозначает знак, используемый для разделе­ ния последовательностей символов, задаваемых в качестве параметра строки. Благодаря тому что в классе String реализуется интерфейс CharSequence, в каче­ стве параметра строки может быть указан список символьных строк. (Подробнее об интерфейсе CharSequence речь пойдет в гл аве 17.) . В следующем примере про­ граммы демонстрируется применение первой формы метода j oin ( ) : 11 Продемонс трировать применение метода join() , 11 определенного в кл ассе Strinq class StriпgJoinDemo { puЫic static vo id ma in ( String args [] ) { String result = String.join (" ", "Alpha ", "Beta" , "Gamma" ); System.out . println (result) ; result = String.joi n(", ", "John" , "ID# : 569" , "E-mail : John@HerbSchildt . сот ") ; System.out . println (result ); Эта программа выводит следующий результат: Alpha Beta Gamma John , ID#: 569, E-mai l: John@HerbSchildt . com При первом вызове метода join () вводится пробел между каждой из символьных строк, а при втором его вызове - запятая с пробелом. Данный пример наглядно по­ казьшает, что разделитель совсем не обязательно должен быть одиночным знаком. Вторая форма метода j oin () позволяет соединить список символьных строк, получаемых из объекта класса, реализующего интерфейс IteraЫe. Среди проче­ го, инте рфейс Ite raЫe реализуется в классах из каркаса колл екций Collections Framework, рассматриваемого в гл аве 18. Подробнее об интерфейсе IteraЫe речь пойдет в гл аве 17. Допол нител ьные методы из кл а сса String Помимо представленных выше методов, в классе String имеется также цел ый ряд других методо в, включая и перечисленные в табл . 16.2 .
'88 Ч асть 11. Библиотека Java Табл ица 16.2. Дополнительные методы иэ класса Strinq Метод int codePointAt (int i) int codePointвefore (int i) int codePointCount (int начало, int конец) Ьoolean contains ( CharSequence строка) Ьoolean contentEqual s(CharSequence строка ) boolean contentEqual s(S tringBuffer строка > static String forиa a t(String форматирующая_строка , Ob ject ... арrу.менты) static Strinq forшat (Locale регшт , Strinq форматирующая_ строка , Ob ject ... ареументы) boolean isEl llp ty () Ьoolean шatches (strinq регу.л л рное_выражение) int offsetвyCodePoints (int начало, int число) String replaceFirst (String регу.л л рное_выражение , S trinq новая_строка ) String replaceAll ( String регу.л л рное_выражение , String новая_строка > Strinq [] split (String регу.л л рное_выражение) Описание Возвращает кодовую точку в Юникоде на позиции i Возвращает кодовую точку в Юникоде на позиции, предш ествующей i Возвращает количество кодовых точек в части вызы· вающей символ ьной строки от позиции начало и до позиции кошщ-1 Возвращает логическое значение true, если вызыва· ющий объект содержит указанную стртсу, а иначе - логическое значение false Возвращает логическое значение true , если вызыва­ ющий объект содержит указанную стртсу, а иначе - логическое значение false Возвращает логическое значение true, если вызыва­ ющий объект содержит указанную стртсу, а иначе - логическое значение false Возвращает символьную строку, отформатированную так, как определяет заданная форматирующая_строка. (Подробнее о форматировании -в гл аве 19.) Возвращает символ ьную строку, отформатированную так, как определяет заданная форматирующая_строка. (Подробнее о форматировании см . в гл аве 19.) Возвращает логическое значение true, если вызыва­ ющая строка не содержит символы и имеет нулевую дл ину Возвращает логическое значение true, если вы­ зывающая строка совпадает с заданным регу.л л рным_ выражением, а иначе - логическое значение fal se Возвращает индекс позиции в вызывающей строке, которая отстоит на заданное ЧUCJIO кодовых точек от начальной позиции, задаваемой по индексу начало Возвращает символ ьную строку, в которой первая подстрока, совпадающая с заданным регу.л л рным_ выражен ием, заменяется новой_строкой Возвращает символ ьную строку, в которой все п одстроки , совпадающие с заданным регу.л л рным_ выражением, заменяются новой_строкой Разбивает вызывающую строку на части и возвращает массив, содержащий результат. Каждая часть разделя­ ется заданным регулярным_выражением
Метод Strinq [] split (Strinq реzу.лярное_выраженш, int мта:имум) CharSequence suЬSequence (int начальный_индекс , int конечный_ индекс) Глава 16. О б работка символьных стр ок 489 Окончание таШ. 16 .2 Описание Разбивает вызывающую строку на части и возвращает массив, содержащий резул ьтат. Каждая часть разделя­ ется задан ным fJеZJА А рным_выраженшм. Количество частей определяется параметром � Если па­ раметр ма� принимает отрицательное значение, вызывающая строка разбивается полностью. Если же параметр максимум принимает неотрицател ьное значение, то последний элемент возвращаемого мас­ сива содержит остаток вызывающей строки . А если значение параметра ма� равно нулю, то и в этом случае вызывающая строка разбивается полностью Возвращает подстроку из вызывающе й строки, на­ чиная с позиции нач41 1 ьный_индекс и кончая позицией �wнечный_индекс. Этот метод требуется для интерфей­ са CharSequence, реализуемого в классе Strinq Обратите вн имание на то , что некоторые из перечисленных выше методов оперируют регулярными выражениями, кото рые мы рассмотрим в гл аве 30. Кл а сс StringBuffer Этот класс подобен классу String, в котором предоставляется бальшая часть фун кциональных возможностей для обработки символьных строк. Как вам должно быть уже известно, класс String представляет неизменяемые последовательности символов постоянной длины, тогда как класс StringBuffer - рас ширяемые и до­ ступные для изменений последовательности символов. Он позволяет вставлять сим­ волы и подстроки в середину исходной строки или добавлять их в се конце. Объект типа StringBuffer автоматически наращивается, чтобы предоставить место для по­ добных расширений, и зачастую для возможности такого наращивания он содержит больше предварительно определенных символов, чем требуется на самом деле. Ко н структо р ы кл а сса StringBuffer В классе StringBu ffer определены следующие четыре конструкто ра: StrinqBuffer () StringBuffer (int раэнер) StringBuff er (String стро ка) StringBuffe r(CharSequence синsо.1 11.r ) Первый конструктор по умолчанию ( без параметров) резервирует место для 1 б символ ов, не перераспределяя память. Второй конструктор принимает цело­ численный аргумент, явно задающий размер буфера. Тр етий конструктор при­ нимает аргумент типа String, задающий начальное содержимое объекта типа StringBu ffer и резервирующи й место для 16 символов, не перераспределяя па­ мять. Класс StringBuffer выдел яет место для lбдополнител ьных символов, если
L.90 Часть 11. Библ иоте ка Java не указы вается конкретный размер буфера, чтобы сэкономить время , затрачивае­ мое на перерас11ределение памяти . Кроме того , частое перераспределение памяти может привести к ее фрагментации. Выделяя место под несколько дополнитель­ ных символов, кл асс StringBuffer снижает количество требующихся повторных перераспределений памяти. Четвертый конструктор создает объект, содержащий последовател ьность символов, задаваемых в качестве параметра символы, а также резервирует место для 16 дополнительных символов. М етоды length ( ) и capacity () Те кущую дл ину объе кта типа StringBu ffer можно получ ить, вызвав метод length (),атекущ ий объем выделенной памяти - вызвав метод capaci ty ().Эти методы имеют следующие общие формы: int length () int capacity () В приведенном ниже примере демонстрируется применение методов length () и capacity ( ). // Сравнить ме тоды lenqth () и capaci ty () из кл асса StringBuf fer class StringBufferDemo { puЫ ic static vo id ma in (String args[] ) { StringBuffer sb = new StringBuffer ("He llo" ); System.out . println ( "бyфep ="+ sb) ; System.out . println ( "дл и нa ="+ sb . length (•)); System.out . println ("e мкocть = "+sb . capacity () ); Ниже приведен результат, выводимый этой программой. Он показывает, каки м образом класс StringBuffer резервирует свободное пространство для дополни­ тельных манипуля ций с символ ьными строками. буфер = Hello длина = 5 емкость = 21 Переменная sb инициализируется строковым значением "Hello" при ее соз­ дан ии, поэтому дл ина буфера для хранения этого значения равна 5. А объем вы­ деляемой памяти (емкость буфера) составляет 21, поскольку 16 дополнительных символов добавляются автоматически. М етод ensureCapacity () Если требуется предварительно выделить место для определенного количества символов после создания объекта типа StringBuffer, то можно воспользоваться методом ensureCapacity ( ),чтобы установить емкость буфера. Это удобно, если заранее известно, что к объекту типа StringBuffer предполагается присоеди­ нить большое количество мелких символьных строк. Метод ensureCapacity () имеет следующую общую форму: vo id ensureCapacity (int Nl lIOIOl &UЬN&lf_eнxoc�ъ)
Гл ава 16. Об работка символьны х строк '91 где параметр минима льная_ емкость обозначает минимал ьный размер буфера. (Буфера, размер которых превышает заданную минима ль ную_ емко сть, также мо­ гут быть выделены из соображений эффективности .) М етод setLength () Для задания дл ины символьной строки в объекте типа StringBu ffer служит метод setLength (), общая форма которого выглядит следующим образом: void setLenqth (int дmr.на) где параметр длина обозначает кон кретную дл ину символьной строки. Ее значе­ ние должно быть неотрицательным. Когда увеличивается дл ина символьной строки , в конце существующей строки добавляются пустые символы. Если метод setLength ( ) вызывается со значением ме ньше текущего значения, возвращаемого методом length (), то символы, ока­ завшиеся за пределами вновь задан ной дл ины строки , будут уд алены. В примере программы , представленном в следующем разделе, метод setLength ( ) применя­ ется для сокращения объекта типа StringBuffer. М етоды charAt () и setCharAt () Значение отдельного символа можно извлечь из объекта типа StringBuffer, вызвав метод charAt ().Азначение символа в объекте ти па StringBu ffer м ож н о ус тановить с помощью метода setCharAt ().Ниже приведены общие формы этих методов . char charAt (int rде) void setCharAt (int rде , char СJ1 1 юаол) В форме метода cha rAt () параметр где обозначает индекс извлекаемого сим­ вола, а в форме метода setCharAt () - индекс задаваемого символ а, тогда как па­ рам етр симв ол - значение этого символа. Значение параметра гд е для обоих ме­ тодов должно быть неотрицательным и не должно указывать место за пределами символьной строки. В сл едующем примере программы демо нстрируется применение методов cha rAt () и setCharAt (): 11 Продемонстрировать приме нение ме тодов charAt () и setCharAt () class setCharAt Demo { puЬlic static vo id ma in (String arg s[] ) { StringBu ffer sb = new StringBuffer ( "Hello" ); System.out . println ("Cyфep до ="+sb) ; System.out . println ( "дo вызова charAt (l) ="+ sb. charAt (l) ); sb .se tCharAt (1, 'i'); sb . setLength (2) ; System.out . println ("Cyфep после = "+sb) ; System.out . println ("п ocлe вызова cha rAt (l) Эта программа выводит следующий результат: "+sb . charA t(l) );
'92 Часть 11. Библиотека Java буфер до = Hello до вызова cha rAt (ll = е буфер после = Hi после выз ова cha rAt (li = i Метод ge tChars () Для кuпиров<tн ия подстроки из объе кта тина StringBuffer в массив служит метщ� ge tCha rs (),имеющий следующую общую форму: void getChars (int на чало источника , int конец источника , char адреса т[] , int на чало _ адресата) где 11арамстр на чало_ исто чника обозначает индекс начала подстроки , а пара· метр конец_ источника - индекс символ а, следующего после конца требуемой подстро ки. Это означает, что подстрок;� соде ржит символ ы от позиции на ча ло _ исто чника до но:нн щи конец _ источника -1 . Массив, приним<tющий символы, 11ередается в к<t•1сстве параметра адреса т, <t индекс массива, куда копируется под· строка , - в качестве нараметра на чало _ адре са та . Следует принять меры к то му, чтобы массив адреса т имел достаточный размер, позволяющий вместить кол и чс· ст1.ю символ ов из ука:�ашюй нодстроки. М етод append () Метод append ( ' присоединяет строковое представление любого другого тшы данных в конце вызывающего объекта типа StringBuffer. У него имеется не­ скол ько перегружаемых вариантов. Ниже приведены некоторые из них. StringBuffer append (String строка ) StringBuffer append (int число) StringBuffer append (Object объект) Строковое представление каждого параметра зачастую получаетс я в резул ьтате вы:юва метода String . va lueOf (). Получе нный резулнгат присоеди няется к те ку· щсму объе кту типа StringBuffer. Сам буфер возвращается каждым вариантом ме· тода append ().Это позволяет соединить в цепочку несколько последовате11 ы1ых вы:ювов, как показано в следующем примере программы: 11 Продемонстрировать приме нение ме тода append () class appe ndDemo { puЫic static 'тoid main( String args[]) { String s; intа=42; StringBuffer sb = new StringBu ffer (40) ; s = sb . append ("a = ") .append (a) .appe n d("!") .toString () ; System.out . println (s) ; Ниже приведен резул ьтат, выводимый дан ной программой. а=42;
М етод insert () Гл ава 16. Обработка символьн ы х строк '93 Этот метод вставляет одну символьную строку в другую. Он перегружается та­ ким образом, чтобы принимать в качестве параметра значения всех примитив­ ных типов плюс объекты типа String, Ob ject и CharSequence. Подобно методу append (), метод insert () получает строковое представление значения , с кото­ рым он вызывается. Эта строка затем вставляется в вызывающий объект типа StringBuffer. Ниже приведе ны некоторые общие формы метода insert ( ) . StringBuffer insert (int :яндежс , Strinq С!!'рОЖа) StringBuffer insert (int 1/Ufде жс , char сянаол) StringBuffer insert (int 11Ндежс , Ob ject объеж�) Здесь параметр индекс обозначает индекс позиции, на которой символьная стр ока будет вставлена в вызывающий объект типа StringBuffer. В следующем примере программы демонстрируется вставка слова "нравится" между словами "Мне " и "Java": 11 Продемонстрир овать приме нение ме тода insert () cl ass iпsertDemo ( puЫic static void maiп (String args [] ) ( StringBuffer sb = new St ringBuffer ( "Mнe Java!"); sb . insert (4, "нравится ") ; System. out . println (sb) ; Эта программа выводит следующий результат: Мне нра вится Java ! М ет од reverse () Изменить порядок следования символов в объекте типа StringBuffer на обрат­ ный можно с помощью метода rever se ( ) . Общая форма которого приведена ниже. StringBuffer rever se () Этот метод возвращает объект с обратным порядком следования символов по сравнению с вызывающим объектом. В следующем примере программы демон­ стрируется применение метода reverse (): 11 Изменить порядок следования симв олов в объе кте 11 типа Str ingВUffer с помощь ю ме тода reverse () cla ss ReverseDemo { puЫ ic static void ma in (String args [] ) { StringBuffer s = new StringBuffer ( "abcdef") ; System. o ut . println (s) ; s.reverse () ; System.out . println (s) ; Ниже приведен результат, выводимый данной программо й. abcde f fedcba
494 Часть 11. Библиоте ка Java М етоды de lete () и de leteCharAt () Уд алить символ ы из объекта типа StringBu ffer можно с помощью методов de lete ( ) и deleteCharAt ().Ниже приведены их общие формы. StrinqBuffer de lete (int на чалЬIUJЙ индеж с, int жонечJВdi индежс) StrinqBuffer deleteCharAt (int по�Ици�) - Метод de l е te ( ) уд аляет последовательность символов из вызывающего объек­ та . F.го параметр на чаль ный_индекс обозначает индекс первого символ а, который требуется уд алить, а параметр коне чный_ индекс - индекс символа , следующего за последним из удал яемых символ ов. Та ким образом , удал яемая подстрока начинает­ ся с позиции на ча ль ный_ индекс и оканчивается на позиции коне чный_ индекс -1. Из этого метода возвращается результирующий объект типа StringBuffer. Метод de leteCharAt () удаляет символ на указанной по зиции. Из этого метода возвращается результирующий объект типа StringBuffer. В следующем примере программы демонстрируется применение методов de lete () и de leteCharAt (): 11 Продемонстрировать приме нение методов de lete () и deleteCharAt () class de leteDemo { puЫ ic static void maiп (Striпg args[] ) { StringBu ffer sb = new StringBuffer ( "Этo пр остой тест.") ; sb .delete (З, 11) ; System. out .println ( "Пocлe выз ова delete () : "+sb) ; sb . de leteCharAt (O) ; System. out .println ( "Пocлe вызова deleteCharAt () : "+sb ) ; Эта программа выводит следующий результат: После вызова delete () : Это тест . После вызова de leteCharAt () : то тест . М етод replace () Вызвав метод replace (), можно заменить один набор символов другим в объ­ екте типа StringBuffer. Ниже приведена общая форма этого метода. StrinqBuffer replace (int нача.пъ.нuЯ индежс , int жонеЧНJХЙ индежс , Strinq стреха) - Подстрока, которую требуется заменить, задается параметрами на чальный_ индекс и коне чный_индекс. Та ким образом, заменяется подстрока от символ а на позиции на чальный_индекс до символа на позиции конечный_ инде кс -1. А за­ меняющая строка передается в качестве параметра строка. Из этого метода воз­ вращается результирующий объект типа StringBuffer. В следующем примере программы демонстрируется применение метода rep lace (): 11 Продемонстрировать приме нения метода replace () class replaceDemo {
Глава 16. Обработка символ ьных строк '95 puЫic static void main (String args [] ) { StringBu ffer sb = new StringBuffer ( "Этo пр остой тест.") ; sb . replace (4, 8, "был" ); System. out . println ( "Пocлe замены : " + sb ) ; Ниже приведен результат, выводимый данной программой. После замены : Это был тест . Метод sUьstrinq () Вызвав метод substring (),можно получ ить часть содержимого объекта типа StringBuffer. У этого метода имеются две следующие формы: String suЬstring (int на vал.ышЯ J1U1Дexc) String suЬs tring (int на vал.ышЯ=яндехс , int xaнev.IWJi _ JUlдexc) В первой форме этот метод возвращает подстроку, которая начинается с по­ зиции на ча льный_ индекс и продолжается до конца вызывающего объекта типа StringBuffer. Аво второй форме он возвращает подстроку от позиции на чаль ный_ инд екс и до позиции коне чный_индекс- 1. Эти формы метода substring () дейст­ вуют таким же образом, как и рассмотренные ранее их аналоги из класса S t r ing. Доп ол н ительн ые м етоды из кла сса StrinqBuffer Помимо описанных выше методов, класс StringBuffer содержит ряд других методо в, включая и перечисленные в табл . 16.3 . Табл ица 16.Э. Дополнительные методы иэ класса StringBuffer Метод StringBuffer appendCodePoint (int CJIUdИOЛ) int codePointAt (int i) int codePointвefore (int i) Описан и е Присоединяет кодовую точку в Юникоде в конце вызывающего объекта. Возвращает ссылку на объект Возвращает кодовую точку в Юникодс на позиции i Возвращает кодовую точку в IО никоде на позиции, пред шеетвующей i int codePo intCount (int Возвращает количество кодовых точек в части яа vа.по , int .конец) int indexOf (String C!l'p0Jt4 ) int indexOf (St ring C!l'poxa , int на vапънтi Xl l,lf SJtC) вызывающей строки от позиции на чало и до позиции хонец- 1 Выполняет поиск в вы:Jывающем объекте типа StringBuffer первого вхождения C!l'pOIUI. Возвращает индекс позиции при совпадении, а иначе - значение -1 Выполняет поиск в вызывающем объекте типа StringBuffer первого вхождения C!l'pOЖll, начиная с позиции нavaльll ll li _ 11 11Д ежс. Возвращает индекс позиции п ри совпадении, а иначе - значение -1
'96 Часть 11. Б иблиотека Java М етод int lastindexOf (String C!l'p0.1 1: 4) int lastindexOf (String с!l'рОжа , int .вav&IJWl lJli Ю1Дежс) int offsetвyCodePoints (int .ва vало , int v•сло) CharSequence suЬSequence (int .вav&1aoНl lli Ю1Де.1 1: с, int жoвeVIAl li _ �eжc) vo id tri.mTo Size () Окон'Чание таtИ. 16.3 Описание Выполняет поиск в вызывающем объекте типа StringBuffer последнего вхождения C!rpOпr. Возвращает индекс позиции при совпадении, а иначе - эначен ие -1 Выполняет поиск в вызывающем объекте типа StringBuffer последнего вхождения C!l'pOпr, начиная с позиции .ва vа.ш.нui_Ю1Дежс. Возвращает индекс позиции при совпадении, а иначе - значение -1 Возвращает индекс символа в вызывающей строке, который отстоит на заданное v•сло кодовых точек от начального индекса, определяемого параметром .ва vало Возвращает подстроку из вызывающей строки, начиная с позиции .вavaлъia.Di Ю1Дежс и оканчивая позицией жoвeVIAl li _ Ю1Де.а:с. эТот метод требуется для интерфейса CharSequence, реализуемого в классе StringBuffer Требует, чтобы размер символ ьного буфера вызывающего объекта был ум еньшен для большего соответствия текущему содержимому В следующем примере программы демонстрируется применение методов in­ de xOf () и lastindexOf (). class IndexOfDemo { puЫ ic static vo id ma in (String args[] ) { StringBuffer sb = new StringBuf fer ( "one two one " ); int i; i = sb. indexOf ("one" ); System. out . println ( "Индe кc первого вхождения: "+i) ; i = sb. lastindexOf ("one" ); System . out .println ( "Индe кc последнего вхождения : "+i) ; Эта программа выводит следующий результат: Индекс первого вхожде ния : О Инде кс последнего вхождения : 8 Клacc stringBuilder Класс StringBuilder, появившийся в версии JDK 5, - относительно не­ давнее дополнение функциональных возможностей, существовавших до него вJava для обработки строк. Класс StringBuilder ничем не отличается от класса StringBuffer, за исключением то го , что он не синхронизирован , а следователь­ но, не является потокобезопасным. Применение класса StringBuilder дает вы­ игрыш в производител ьности. Но в тех случаях, когда обращение к изменяемой строке происходит из нескольких потоков исполнения без внешней синхрониза­ ци и, следует применять класс StringBu ffer, а не StringBuilde r.
17 Пакет java . lang Эrа гл ава посвящена классам и интерфейсам, определенным в пакете j а v a . lang. Как вам должно быть уже известно, пакет j ava . lang автоматически импортируется во все программы. Он содержит классы и интерфейсы, которые сосrавляют основу всех программ на Java. Пакет java . lang наиболее широко ис пользуется вJava и включает в себя следующие классы: Вoolean EnUJ1 1 Proce ss Strinq Byte Float Proce ssBuilder StrinqBuffer Character InheritaЫeТhreadLocal Proce ssBuilder . Str inqBuilder Redirect Character . SuЬset Inteqer RuntilDe System Character . Lonq Run tiшePerш.ission Thread UnicodeВlock Clas s мath SecurityМanaqer ThreadGroup Clas sLoader NWDЬer Short ThreadLocal Clas sValue Ob ject StackTraceE leшent ThrowaЫe Compiler Packaqe Strictмath Void DouЫe В данном пакете опр еделены также два вложенных класса тиш1 Cha r act er: Cha racter . Sub Set и Character . UnicodeBlock. Ниже перечислены интерфейсы, определенные в пакете j ava . l a ng. AppendaЫe CloneaЫe ReadaЫe AutoCloseaЬle ComparaЬle RunnaЬle CharSequence IteraЬle Тhread . UncauqhtExcep tionHandler Некоторые кл ассы, входя щие в пакет j ava . lang, содержат устаревшие ме­ тоды, большинство из которых относится еще к версии jаvа 1.0 . Эти методы вес еще предосrавляются в Java для поддержки постепенно выводимого из экс плуа­ та ции унаследованного кода и не рекомендуются для употребления в новом коде . Поэтому не рекомендованные к употреблению методы в этой r:1 1 авс нс рассматри­ ваются.
498 Часть 11. Библиотека Java Оболочки примитивных типов Как упоми11ало<ъ в части 1, примитивные тины данных наподобие int и char н рим еняются в .J;н·а и:� соо б ражений нроизводительности и нс являются частью объектной иерархии. Они 11ерсдаются методам по :тачению и не могут быть пере­ щшы и м по ссылке. Кроме того , из двух методов нельзя ссылаться на оди'Н и тот же эк.зе.м пл.яр ти па i n t. Но рано или поздно возникает потребность в объектном пред­ ставлении 0;(1юго из примитивных типов данных. Например, существуют классы коллекци й, 11 ред11а:шаченные дл я обращения тол ько с объектами ( они обсужда­ ются в IJi aвe 18). Чтобы сохранить п римитивный тип дан ных в одном из таких классов, нужно заключить в него этот примитивный тип. Для того чтобы удовлет­ ворить rютребносп, в этом для каждого примитивного типа дан ных, в Java предо­ ставляется отдел ьный класс , обы•1но называемый оболтtкой типа. Оболочки типов были предста влены в гл аве 12, а здесь они рассматриваются более подробно . Класс NwnЬer Абстрактн ый класс N umb e r является супсрклассом, который реализуется в кл ас­ сах оболочек числовых типов byte, short, int, long, float и douЫe. В классе Numb e r имеются абстрактн ые методы , возвращающие значение объекта в разных числовых форматах. Например, метод douЬleVa lue () возвращает :шачение как тип douЫ e, а метод floatValue ( ) - как тип float и т. д . Все эти м етоды пере­ числены ниже. byte byteValue () dou Ыe douЬleValue () float floatValue () int intValue () long longValue () short shortValue () Значения , возвращаемые эти ми методами , могут быть округлены, усечены ил и собраны в "мус ор" вследствие сужающего преобразования. У класса NumЬer и меются кон кретн ые подкласс ы, содержащие явные значения каждого числового типа: DouЫe, Float, Byte, Short, Integer и Long. Кла ссы DouЬle и Float Эти классы служат оболочками для числовых значений с плавающей точкой типа douЫe и float соответственно. Ниже приведены конструкто ры класса Float. Float (douЫe число) Float (float число) Float (String С!rрожа ) throws NuшЬerFor11 1 atException Как видите, объекты типа Float должны быть созданы со значениями типа float или douЫe. Они могут также быть созданы из строкового представления числа с плавающей точкой. Ниже приведены конструкторы класса DouЫ e.
DouЫe (douЫe '\Ul'CЛO) Гл ава 17. Пакет java.Lang д99 DouЬle (Strinq С!FрОЖа) throws NWDЬerFormatException Объекты типа DouЫe могуг быть созданы из значения типа douЫ e или сим­ вольной строки , содержащей значение с плавающей точкой . Методы, определенные в классе Float, перечислены в табл. 17.1, а методы, определенные в классе DouЫe, - в табл . 17.2. В классах Float и DouЫe определя­ ются следующие ко нстанты. ВУТЕS МАХПРОNЕNТ МАХVAL'OE МIN ПРОNЕNТ МIN NOIO OU. МIN VAL'OE NaN Дл ина типа float или douЫ e в байтах (внедрена в версииJDК 8) Максимальный показатель степени Максимальное положительное значение Минимальный показатель степени Минимальное положительное нормальное значение Минимальное положител ьное значение Не число POSITIVE INFINITY Положительная бесконечность NEGATIVE_INFINIТY Отрицательная бес конечность SIZE ТУРЕ Размер закл юченного в оболочку значения в битах Объект типа Class для типов float и douЫe Табл ица 17.1 . Методы из класса Float Метод by te byteValue () static int COJl lPa re (float чUC1 10 l, float 11исло2) int coшpareTo (Float j) douЬle douЬleValue () Ьoolean equals (OЬject FIOatOЬJ) static int floatTolntвi ts (float число) Описание Возвращает зна•1ение вызывающего объекта как тип Ьуtе Сравнивает значения ttш:лol и ttUCJW2. Возвращает нулевое значение, если сравни ваемые числовые значения равны; отри цательное значение, если чUCJWI меньше, чем число2; или положительное зна­ чение, есл и ЧUCJWI бол ьше, чем чUCJW2 Сравн ивает числовое значение f\ЫЗывающего объ­ екта со значением f Возвращает нулевое значение, если сравниваемые числовые значения равн ы; отри­ цател ьное значе11 ие, есл и вызывающий объект име­ ет меньшее значе11ие; ил и положительное значение, есл и вызывающий объект имеет бол ьшее значение Возвращает значение вызывающего объекта как тип douЬle Возвращает логи ческое значение true , есл и вызы­ вающий объект типа Float равен объекту FIOatOЬj, а иначе - логи ческое значение false Возвращает совместимую со стандартом IEEE ком­ бинацию двои чных разрядов од инарной точ ности, соответствующую зада нному 11ш-.лу
500 Часть 11. Библиотека Java М етод static int floatToRawi ntвits (float число) float floatValue () int hashCode () static int hashCode (float число) static float intвitsToFloat (int число) int intValue () Ьoolean isinfinite () static boolean isinfinite (float ЧUC!IO) Ьoolean isNaN () static Ьoolean isNaN (float число) long longValue ( ) static float max(float val, float val2) static float IDin (float val, float val2) static float parseFloat (String строка) throws NwaЬerForшatException short shortValue () static float sum ( float val, float val2) static String toHexS tring (float ЧUC!IO) Пр одолжение табл. 17.1 Описан ие Возвращает совместимую со стандартом IEEE ком­ бина11ию двоичных разрядов од инарной точности , соответствующую задан ному числу. Значение NaN не до пускается Возвращ<1ет значение вызывающего объекта как тип float Возвращает хеш-код вызывающего объекта Возвращает хеш-код зада нного числа ( внедрен в вер­ сииJDК 8) Возвращает эквивалент типа float совместимой со стандартом IEEE комбинации двои чных разрядов оди­ нарной точности, определяемой параметром число Возвращает значение вызывающего объекта как тип int Возвращает логическое значен ие true , если вы­ зывающий объект содержит бесконечное значение, а иначе - логическое значение false Возвращает логическое значение true, если число определяет бес конечное значение, а иначе - логи­ ческое значение false Возвращает логическое значение true, если вы­ зывающий объект содержит нечисловое значение, а иначе - логическое значение false Возвращает логическое значение true, если число определяет нечисловое значение, а иначе - логиче­ ское значение false Возвращает значение вызывающего объекта как тип long Возвращает наибольшее из двух значений val и val2 ( внедрен в версииJDК 8) Возвращает наименьшее из двух значений val и val2 ( внедрен в версииJDК 8) Возвращает эквивалент числа типа float, содержа­ щегося в строке, по основанию 10 Возвращает значение вызывающего объекта как тип short Возвращает результат сложения значений val+val2 ( внедрен в версииJDК 8) Возвращает символьную строку, содержащую чш:.w в шестнадцатеричном формате
Метод String toString () Описание Гл ава 17. Пакет java.Lang 501 Ок01t11ание табл. 17.1 Возвращает строковый эквивалент вызывающего объекта etatic String toString (float чш:ло) Возвращает строковый эквивалент значения, опре­ деляемого параметром чш:ло atatic Float valueOf (float чш:.w) Возвращает объект типа Float, содержащий значе­ ние, передаваемое в качестве параметра число atatic Float valueOf (String строка) throws NurnЬer Form at Except ion Возвращает объект типа Float, содержащий значе­ ние, указанное в строке Табл ица 17.2. Методы из класса DouЬle Метод Ьуtе byteValue () atatic int coшpare (douЫe чш:.лоl , douЬle чш:ло2) int сошраrеТо (DouЫe d) static long douЬleToLongBita (douЬle чш:ло) Описание Возвращает значение вызывающего объекта как Ьуtе Сравнивает значения 'ШCJIOl и число2. Возвращает нулевое значение, если сравниваемые числовые значения ра вны; отрицательное значение, есл и чUGWJ меньше, чем чUGW2; или положительное значение, если числоl бол ьше, чем число2 Сравнивает числовое значение вызывающего объекта со значением d. Возвращает нулевое значение, если сравн ива­ емые числ овые значения равн ы; отрицательное значение, если вызывающий объект имеет меньшее значение; ил и положительное значение, если вызы вающий объект имеет большее значение Возвращает совместимую со стандартом IEEE комбинацию двоичных разрядов двойной точности, соответствующую заданному чш:пу s tatic long Возвращает совместимую со стандартом IEEE комби нацию douЬleToRawLongBits (douЬle двоичных разрядов двойной точносги. е<ютветствующую чш:ло) заданному чш:пу. Значение NaN не донускается douЬle douЬleValue () Возвращает значение вызывающего объект<1 как тип douЬle Ьo o lean equal s (OЬject DоиЬ/еОЬ j) floa.t floatValue () int ha.shcode О Возвращает логическое значен ие true , если вызывающий объект типа DouЬle равен объекту DоиЬlеОЬj, а и наче - ло­ гическое значение false Возвращает значение вызывающего объекта как тип float Возвращает хеш-код вызывающего объекта static int hashCode (douЬle Возвращает хеш-код зада нного числа (внедрен в версии чш:ло) JDK 8) int intValue () Ьo o lean isinfinite ( ) Возвращает значение в ыз ывающего объекта как тип int Возвращает логическое значение true , есл и вызьшающий объект содержит бесконечное :шачение, а иначе -логичt.� ское значение fal se
502 Часть 11. Библиоте ка Java Метод sta.tic Ьoolean isinfini te (douЫe чш:ло) Ьo o lean isNaN () sta.tic Ьo o lean isNaN (douЫe число) sta.tic douЫe lonqBitsToDouЬle (lonq чш:ло) lonq lonqValue () sta.tic douЫe шax(douЬle vaJ, douЬle val2) sta.tic douЬle ll lin (douЬle vaJ,douЬleval2) sta.tic douЬle parseDouЬle (Strinq строка> throws NШl lЬe rFoDD&tExoeption short shortValue () sta.tic douЬle sWD (douЬle val, douЬle val2) sta.tic Strinq toBexStrinq (douЬle ЧUCtIO) Strinq toStrinq () sta.tic Strinq t0Strin9 (douЬle чш:ло) sta.tic DouЬle valueOf (douЬle чш:ло) sta.tic DouЬle valueOf (Strinq строкn) throws NШDЬerFoJ:1 11a t.Exc8ption Окtтчанщ• табл. 17. 2 Описание Возвращает логическое значение true, если чш:ло оп реде­ ляет бесконечное значение, а иначе - логическое значение false Возвращает логическое з11а•1ение true, если вызывающий объекr содержит нечисловое значение, а иначе -логи че­ ское значение false Возвращает логическое значение true, если чш:ло опреде­ ляет нечисловое значение, а иначе - логическое зна•1ение false Возвращает эквивалент типа douЬle совместимой со стан­ дартом IEEE комбинации двоичных разрядов двойной точ­ ности, определяемой параметром чш:ло Возвращает значение вызывающего объекrа как тип lonq Возвращает наибольшее из двух значений vaJ и vaJ2 (вне­ дрен в версииJDK 8) Возвращает наименьшее из двух значений vaJ и val2 (вне­ дрен в версииJDК 8) Возвращает эквивалент числа типа douЬle, соде ржащегося в строке, по основанию 10 Возвращает значение вызывающего объекrа как тип short Возвращает результат сложения значений val+val2 (внедрен в версии JDK 8) Возвращает символьную строку, содержащую чш:ло в шест­ надцатеричном формате Возвращает строковый эквивалент вызывающего объекrа Возвращает строковый эквивалент значения, определяемо­ го параметром чш:ло Возвращает объекr типа DouЬle, содержащий значение, передаваемое в качестве параметра чш:ло Возвращает объекr типа DouЬle, содержащий значение, указанноевстроке В приведенном ниже примере программы создаются два объекта типа DouЫe: один - с помощью значения типа douЫe, другой - с помощью символьной стро­ ки , которая может быть интерпретирована как тип douЫ e. class DouЬleDerno { puЫ ic static void ma in (St ring args[]) DouЫ e dl = new DоuЫе (З.14159) ;
DouЫ e d2 = new DouЬle ("314159E-5" ); Гл ава 17. Пакет java.Lang 503 System . out .println (dl +"=" + d2 +"-> "+dl . equal s(d2) ); Как следует из приведенного ниже результата выполнения данной программы, оба конструктора создают одинаковые экземпляры класса Doub l е. Об этом свидетель­ ствует также вызов метода equals ( ) , возвращающего логическое значение true. 3. 14159 = 3 .14159 -> true М етоды isinfinite () и isNaN () В классах Float и DouЫe предоставляются методы isinfinite ( ) и isNaN (), помогающие манипулировать двумя специальными значениями типа douЫe и float. Эти методы выполняют проверку на равенство двум однозначным зна­ чениям, определенным по стандарту IEEE для чисел с плавающей точкой: беско­ нечности и NaN ( не число) . Метод isinfini te ( ) возвращает логическое значение true , если проверяемое число бесконечно велико или бесконечно мало по вели­ чине. А метод isNaN ( ) возвращает логическое значение true, если проверяемое значение является нечисловым. В следующем примере программы создаются два объекта типа DouЫ e: один из них содержит бесконечное , другой - нечисловое значение: // Продемонстрировать приме нение методов isinfinite () и isNaN () class InfNaN { puЫ ic static void ma in (String args[] ) DouЫ e dl = new DouЬle (l/ 0.); DouЫ e d2 = new DouЬle (0/ 0.); System.out.println(dl + ": " + dl.isinfinite() + " " + dl .isNaN() ); System.out.println(d2 + ": " + d2.isinfinite() + " " + d2 . isNaN() ); Эта програм ма выводит следую щий результат: Infinity : true , false NaN : false , true Кла сс ы Byte , Short, Integer и Long Классы Byte, Short, Integer и Long служат оболочками для целочисленных ти­ пов byte , short, int и long соответстве нно. Ниже приведены их конструкторы. Вуtе (byte число) Вyte (Strinq строка) throws NuшЬe rFormatException Short (short число) Short ( Strinq строжа) throws NumЬerFormatExcep tion Inteqer (int число) Inteqer ( Strinq стро ка ) throws NumЬerFormatException
504 Часть 11. Библиотека Java Long (long число) Long (String строха ) throw s NWl lЬe rFormatException Как видите, объекты этих классов могут быть созданы из числовых значений или символьных строк, со;1ержащих допусти мые представления числовых значений. Методы , определенные в этих классах, перечислены в табл. 17.3-17.б. Как ви­ дите , в них определя ются методы для синт-аксического анализа целых чисел из символ ьных строк и преобразования символьных строк обратно в цел ые числа. Варшшты этих методо в позволяют указывать основание системы счисления для пре­ обр<tзования чисел. Чаще всего применяется основание 2 для двоичных чисел, 8 - для вос ьмеричных, 10 - для десятичных и 16 - для шестнадцатеричных чисел. В этих классах определены следующие константы: MIN VALUE МАХ VALUE SIZE Минимальное значение Максимальное значение ТУРЕ Длина заключенного в оболочку значения в битах Объект типа Class для типов byte, short, int или lonq Та бл ица 17.З. Метод ы иэ класса Byte Метод Ьуtе ЬyteVa lue () Описание Возвращает значение вызывающего объекта как тип Ьуtе static int CO!l lpa re (byte чUCl lO l, Ьуtе Сравнивает значения чUCl lO lиЧUCl l0 2. чUС1 1О 2) Возвращает нулевое значение, если сравнивае- int сошраrеТо(Вуtе Ь) static Byte decode (Strinq строка) throws NшaЬerJ!'orшatException douЬle douЬleValue () Ьoolean equals (OЬject Вyt.eOly) float floatValue () int hashCode О static int hashCode (Ьуtе ЧUCl lO ) мые числовые значения равны; отри цательное значение, если ЧUCl lO l меньше, чем число2; по­ ложительное значение, если ЧUC.1 10 l больше, чем ЧUCl l0 2 Сравнивает числовое значение вызывающего объекта со значением Ь. Возвращает нулевое значение, если сравниваемые числовые значе­ ния равны; отрицательное значение, если вы­ зывающий объект имеет меньшее значение; ЮIИ положительное значение, если вызывающий объект имеет большее значение Возвращает объект типа Byte, содержащий зна­ чение, указанное в строке Возвращает значение вызывающего объекта как тип dоuЬlе Возвращает логическое значение true, если вы ­ зывающий объект типа DouЬle равен объекту Вуrе()Ьj. а иначе - логическое значение false Возвращает значение вызывающего объекта как значение типа float Возвращает хеш-код вызывающего объекта Возвращает хеш-код заданного чш:ла (внедрен в версииJDК 8)
Метод int intValue () lonq lonqValue () static byte parseВyte (String строкп> throws Nwl lЬe rForшatException atatic Ьуtе parseВyte (String строкп,intосновани е ) throws Nul llЬe rForшatException ahort ahortValue () Strinq toString () static Strinq toStrinq (byte ЧUС/Ю) atatic int toUnsignedint (Ьyte val) Описание Гл ава 17. Пакет javaJang 505 Окон'Шнш таШ. 17.З Возвращает значение вызывающего объекта как тип int Возвращает значение вызывающего объекта как тип lоnq Возвращает эквивалент числа типа Ьуtе, содер­ жащегося в апроке, по основанию 10 Возвращает эквивалент числа типа Ьуtе, со­ держащегося в апроке, по указанному основанию системы счисления Возвращает значение вызывающего объекта как short Возвращает символьную строку, содержащую де­ сятичный эквивалент вызывающего объекта Возвращает символьную строку, содержащую десятичный эквивалент Чш:l lй Возвращает значение valв виде целого значения без знака (внедрен в версииJDК 8) static long toUnsignedLong (Ьуtе val) Возвращает значение valв виде длинного цело­ го значения без знака (внедрен в версии JDK 8) static Вуtе valueOf (byte ЧUС/Ю) static Вуtе valueOf (Strinq строка) throws NwaЬerForшatException atatic Вуtе valueOf (String строкп, int основание) throws NwlЬerForшatException Возвращает объект типа Вуtе, содержащий зна­ чение, передаваемое в качестве параметра ЧUС1Ю Возвращает объект типа Вуtе, содержащий зна­ чение, указанное в строке Возвращает объект типа Вуtе, содержа щий значение, указанное в строке с учетом основани я системы счисления Табл ица 17., . Методы из класса Short М етод Описание byte byteValue ( ) Возвращает значение вызывающего объекта как тип Ьуtе atatic int сошраrе (short число} , Сравнивает значения число} и число2. Возвращает ahort число2) нулевое значение, если сравниваемые числовые значения равны; отрицател ьное значение, еuш число} меньше, чем число2; положител ьное значе­ ние. если число} больше, чем число2 int сошраrеТо (Short s) Сравнивает числ овое значение вызывающего объекта со значением s. Возвращает нулевое зна­ чение , если сравниваемые числовые значения равн ы; от рицател ьное значение, есл и вызы ваю­ щий объект имеет меньшее значение; или поло­ жител ьное значен ие, если вызывающий объект имеет бол ьшее значение
506 Часть 11. Библиотека Java М етод static Short decode (Strinq строка ) throws NumЬe rFormatException douЫe douЬleValue () boolean equals (Object ShortOЬJ) float floatValue () int hashCode () static int hashCode (short чUС1 1 0) int intValue () lonq lonqValue () static short parseShort (Strinq строка> throws NWl lЬe rFormatException static short parseShort (Strinq строка , int основание) throwa NuшЬe rFormatException static short reveraeBytes (ahort ЧUС/10) short shortValue () Strinq toStrinq () static Strinq toS trinq (short ЧUCl l Q) static int toUnsiqnedint (ahort val) static lonq toUn siqnedLong (short van static Short valueOf (short ЧUС/10) static Short valueOf (Strinq строка) throws NumЬerFormatException static Short valueOf (String строха , int основание) throws NuшЬerFormatException Окончание табл. 17. 4 Описание Возвращает объект типа Short, содержащий зна­ чение, указан ное в строхе Возвращает значение вызывающего объекта как тип dоuЫе Возвращает логическое значение true, если вызывающий объект типа Short равен объекту ShortOЬj, а иначе - логичес кое значение false Возвращает значение вызывающего объекта как тип float Возвращает хеш-код вызывающего объекта Возвращает хеш-код заданного чuС1 1 а (внедрен в версииJDК 8) Возвращает значение вызывающего объекта как тип int Возвращает значение вызывающего объекта как тип long Возвращает эквивалент числа типа short, содер­ жащегося в строке, по основанию 10 Возвращает эквивалент числа типа short, со­ держащегося в строке, по указан ному основанию системы счисления Меняет местами старший и младший байты за· данного ЧUC/la и возвращает результат Возвращает значение вызывающего объекта как тип short Возвращает символьную строку, содержащую де­ сятичный эквивалент вызывающего объекта Возвращает символьную строку, содержащую де­ сятичный эквивалент ЧUC/la Возвращает значение val в виде целого значения без знака (внедрен в версииJDК 8) Возвращает значение val в виде длинного целого значения без знака (внедрен в версииJDК 8) Возвращает объект типа Short, содержащий зн а­ чение, передаваемое в качестве параметра чuС1 1 0 Возвращает объект типа Short, содержащий зна­ чение, указанное в строке Возвращает объект типа Short, содержащий значение, указанное в строке с учетом основанш� системы счисления
Гл а ва 17. Пакет java.Lang 507 Табл ица 17.5. Методы из кnасса Inteqer Метод static int ЬitCount {int чш:ло) Ьуtе ЬyteValue {) statia int сошраrе {int чш:лоl , int чш:ло2) int coaipareTo {Int:eqer i) static int coшparetJns igned {int 'IUCl lO l,intWCl l0 2) static Int:eqer decode{String строка) throws Nu8ЬerForшatException clouЬla douЬlaValue {) static int divideUn signed {int ilелимое, int дмитмъ) Ьo o lean equals {<Ьject lnli!gerOl!]) float floatValue {) Описание Возвращает кол ичество битов в заданном Чlro'U! Возвращает значение вызывающего объекта как тип Ьуtе Сравнивает значения чш:лоl и чш:ло2. Возвращает нулевое значение, если сравниваемые числовые зна­ чения равны; отрицательное значение, если чш:.лоl меньше, чем чш:ло2; положительное значение, если чш:.лоl больше, чем чш:.ло2 Сравнивает числовое значение вызывающего объ­ екта со значением i. Возвращает нулевое значение, если сравниваемые числовые значения равны; отри­ цательное значение, есл и вызывающий объект име­ ет меньшее значение; или положительное значение, если вызывающий объект имеет большее значение Сравнивает значения чш:лоl и чш:.ло2 без учета знака. Возвращает нулевое значение, если сравниваемые числовые значения равны; отрицательное значе­ ние, если чш:лоl меньше, чем чш:ло2; положительное значение, если чш:лоl больше, чем чш:ло2 (внедрен в версииJDК 8) Возвращает объект типа Integer, содержащий зна­ чение, указанное в строке Возвращает значение вызывающего объекта как тип douЫe Возвращает результат деления делимого на делитмъ без знака (внедрен в версииJD К 8) Возвращает логическое значение true, если вы­ зывающий объект типа Integer равен объекту lnli!gerOf!i, а иначе - логическое значение false Возвращает значение вызывающего объекта как тип float static Int:eqer getinteger {String Возвращает значение, связанное со свойством oкpy- IL!Ul_ aюйcmsa) женил, определяемым параметром WIUl_oвoйcmвa . При неудачном исходе возвращается пустое значе­ ние null static Inteqar gatinteger {Strinq Возвращает значение, связанное со свойством oкpy- IL!Ul_aюйcmsa , int по__у.молчанию) жения, определяемым параметром UМRс. .. аюйства. static Int:eqer getinteger { String и.мя_своi iства , Integar RО.. ..JIМОЛ" ШШ Ю) При неудачном исходе возвращается значение, за­ данное по_ум мчанию Возвращает значен ие, связанное со свойством окру­ жения, определяемым параметром и.мя_свойства. При неудачном исходе возвращается значен ие, за­ данное 11О..JIМ М ЧФШЮ
508 Часть 11. Библиотека Java Метод int hashCocle () static int hashCode (int чисw) static int hiqhestOneВi t(int чuсло) int intValue () lonq lonqValue () static int lowestOneВit (int чuсло) static int max(int val, int val2) static int min (int val, int val2) static int nwaЬerOfLeadi n qZeros (int чисw) static int nwaЬerO:fТrailinqZeros (int чuсло) static int parseint (Strinq строка> throws NwaЬerForшatException static int parseint (Strinq строка,intосновани е ) throws NwaЬerForшatException static int parseUnsiqnedint (Strinq строка) throws NwaЬerForшatException static int parseUnsiqnedint (Strinq строка,intосновани е ) throws NwaЬerFormatException Про до.лжение табл. 17.5 Описание Возвращает хеш-кnд вызывающего объекта Возвращает хе ш-код заданного чш:ла (внедрен в вер­ сииJDК 8) Определяет позицию самого старшего бита в за­ данном чш:ле. Возвращает значение, в котором уста­ новлен тол ько этот бит. Если ни один из бито в не установлен, возвращается нулевое значение Возвращает значение вызывающего объекта как тип int Возвращает значение вызывающего объекта как тип lоnq Определяет позицию самого младшего бита в за­ данном чш:ле. Возвращает значение, в котором уста­ новлен тол ько этот бит. Если ни один из битов не установлен, возвращается нулевое значение Возвращает наибольшее из двух значений val и val2 (внедрен в версииJDК 8) Возвращает наименьшее из двух значений val и val2 (внедрен в версииJDК 8) Возвращает количество старш их битов, установлен­ ных в нуль и предшествующих первому старшему биту, установленному в задан ном числе. Если чисw равно нулю, возвращается числовое значение 32 Возвращает количество младших битов, установлен­ ных в нуль и предшествующих первому младшему биту, установленному в заданном чuсле. Если чисw равно нулю, возвращается числовое значение 32 Возвращает целочисленный эквивалент числа, со­ держащегося в строке, по основанию 10 Возвращает целочисленный эквивалент числа, со­ держащегося в строке, по указанному основани ю си­ стемы счисления Возвращает целочисленный эквивалент числа без знака, содержащегося в строке, по основанию 10 (внедрен в версииJDК 8) Возвращает целочисленный эквивалент числа без знака, содержащегося в строке, по указанному основтшю системы счисления (внедрен в версии JDK 8) static int reшainderUnsiqned (int Возвращает остаток от деления дмимого на д«иитмъ дели.мое , int д«иитмь) без знака (внедрен в версииJDК 8)
Метод static int reverse (int чш:.w) static int reverseВytes (int ЧUCl lO ) static int rotateLeft (int ЧUCI IO , int n) static int rotateRiqht (int Чlre/IO, int n) short shortValue () static int siqnua (int чш:ло) static int sum (int val, int val2) Описание Гла ва 17. Пакет java.tang 509 Окончание табл. 17. 5 Изменяет на противоположный порядок следова­ ния битов в заданном чш:ае и возвращает результат Изменяет на противоположный порядок следова­ ния байтов в заданном чш:ае и возвращает результат Возвращает результат смещения заданного чш:ла на п позиций влево Возвращает результат смещения заданного чш:ла на п позиций вправо Возвращает значение вызывающего объекта как тип short Возвращает значение -1, если заданное ЧUС1 1О отри­ цательное; нулевое значение, если ЧUCl lO равно нулю; и значение 1, если ЧUCl lO положительное Возвращает результат сложения значений vaJ+val2 ( внедрен в версииJDК 8) static Strinq toBinaryStrinq (int Возвращает символьную строку. содержащую двоич- ЧUС!W) ный эквивалент заданного чш:ла static Strinq toRexStrinq (int чш:ло) static Strinq toOctalString (int чш:ло) Strinq toStrinq () static Strinq toString (int Чlre/W) static Strinq toStrinq (int ЧUCIW, int основание) static lonq toUn siqnedLonq (int val) static Strinq toUnsiqnedStrinq (int vaJ) static Strinq toUnsiqnedStrinq (int val, int основание) static Inteqer valueOf (int ЧUCl lO ) static Inteqer valueOf(Strinq строка) throws NuDIЬerForшatException static Inteqer valueOf (Strinq строка,intосновани е ) throws NUl llЬe rForшatException Возвращает строку, содержащую шестнадцатерич­ ный эквивалент заданного чш:ла Возвращает строку, содержащую восьмеричный эк­ вивалент заданного ЧUС/Ю Возвращает символьную строку. содержащую деся­ тичный эквивалент вызывающего объекта Возвращает символьную строку. содержащую деся­ тичный эквивалент заданного ЧUС/Ю Возвращает символьную строку. содержащую деся­ тичный эквивалент заданного ЧUС/Ю с учетом указан­ ного основания Возвращает значение valв виде длинного целого значения без знака (внедрен в версииJDК 8) Возвращает символьную строку, содержащую деся­ тичное целочисленное значение vаlбез знака (вне­ дрен в версииJDК 8) Возвращает символьную строку. содержащую цело­ численное значение vaJ без знака с учетом основm1ия системы счисления (внедрен в версииJDК 8) Возвращает объект типа Inteqer, содержащий зна­ чение, передаваемое в качестве параметра чш:.ло Возвращает объект типа Inteqer, содержащий зна­ чение, указанное в строке Возвращает объект типа Inteqer, содержащий зна­ чение, указанное в строке с учетом основm1ия систе­ мы счисления
510 Часть 11. Библиоте ка Java Та блица 17.6 . Методы из класса Lonq Метод static int Ьi tCount (lonq число) byte ЬyteValue () static int сошраrе (lonq ЧUC/IQl , lonq чш:ло2) int compareTo (Lonq n static int compareUnsigned ( long чш:.лоl , lonq число2) static Lonq decode (Strinq строка) throws NwaЬerForJDatException static lonq divideUnsigned ( lonq дмимое, lonq дмитмь) douЫe douЬleValue () Ьoolean equal s (Object LungOЬj) float floatValue () static Lonq qetLonq (Strinq имя_свойства) static Lonq 9etLon9 (Strin9 имя_свойства , lon9 по_умолчанию) static Lonq 9etLonq (Strin9 имя_свойства, Longпо_умолчан и ю) Описание Возвращает количесrво битов в заданном ЧUC/le Возвращает значение вызывающего объекта как тип Ьуtе Сравнивает значения чш:лоl и чUC/IQ2. Возвращает нулевое значение, если сравниваемые числовые значения равн ы; отрицательное значение, если ЧUС1 102 меньше, чем чиС1 1О 2; положительное значе­ ние, если числоl больше, чем число2 Сравнивает числовое значение вызывающего объекта со значением L Возвращает нулевое зна­ чение, если сравниваемые числовые значения равны; отрицательное значение, если вызываю­ щий объект имеет меньшее значение; или поло­ жительное значение, если вызывающий объект имеет большее значение Сравнивает значения ЧUC/IQl и чUС1 1О 2безучета знака. Возвращает нулевое значение, если сравни­ ваемые числовые значения равны; отрицательное значение, если число] меньше, чем чUС1 1О 2; положи­ тельное значение, есл и чUC1 1 ol больше, чем число2 (внедрен в версииJDК 8) Возвращает объект типа Lonq, содержащий значе­ ние, указанное в cmpmre Возвращает результат деления делимое на дмител6 без знака (внедрен в версииJDК 8) Возвращает значение вызывающего объекта как тип dоuЫе Возвращает логическое значение true, есл и вызывающий объект типа Lonq равен объекту LongOЬj, а иначе - логическое значение false Возвращает значение вызывающего объекта как тип f'loat Возвращает значение, связанное со свойсrвом окружения, определяемым параметром wся_ свойсmва. При неудачном исходе возвращается пусrое значение null Возвращает значение, связанное со свойсrвом окружения, определяемым параметром wся_ свойства . При неудачном исходе возвращается значение, заданное по_умолчанию Возвращает значение, связанное со свойсrвом окружения, определяемым параметром wся_ свойства. При неудачном исходе возвращается значение, заданное по_умолчан и ю
Метод int hashCode () static int hashCode (long чш:.w) static int hiqhestOneВit ( lonq чш:м) int intValue () lonq lonqValue () static int lowestOneВi t(lonq чш:ао) static lonq JD&X(lonq val, lonq va/2) static long min ( long val, long valZ) static int nUJDЬe�fLeadingZeros (long чuc.w) static int nWl lЬe �fТrailingZeros (lonq чш:ло ) static long parseLonq (String строка> throws NUl llЬe rFoz:matException static long parseint (String строка, int основание) throws NWl lЬe rForшatException static long parseUnsignedint (String строка) throws NUJDЬerForшatException static long parseUnsignedint ( Strinq строка, int основание) throws NWl lЬe rFo ria a tException Описание Гл ава 17. Пакет java.Lang 511 Пр одолжение та6л. 17.6 Во3вращает хеш-код вы3ывающеrо объекта Во3вращает хеш-код 3аданного чш:ш (внедрен в версииJDК 8) Определяет по3ицию самого старшего бита в 3а­ данном ЧUC/le. Во3вращает 3начение, в котором установлен только этот бит. Если ни один и3 битов не установлен, во3вращается нулевое зна­ чение Во3вращает 3Начение вы3ывающего объекта как тип int Во3вращает 3начение вы3ывающеrо объекта как тип lоng Оп ределяет п о3ицию самого младшего бита в 3а­ данном ЧUC/le. Во3вращает 3начение, в котором установлен только этот бит. Есл и ни один из битов не установлен, возвращается нулевое зна­ чение Во3вращает наибольшее из двух 3начений val и valZ (внедрен в версииJDК 8) Во3вращает н аименьшее и3 двух значений val и valZ (внедрен в версииJDК 8) Возвращает количество старших битов, уста­ новленных в нуль и предшествующих первому старшему биту, установленному в заданном ЧUC/le. Если чш:ло равно нулю, возвращается числовое 3начение 64 Во3вращает кол ичество младших битов, уста­ новленных в нуль и предшествующих первому младшему биту, установленному в 3адан ном чuС1 1е . Если чш:ло равно нулю, возвраща<. . "ГСЯ •шсловое 3начение 64 Возвращает эквивалент типа long числа, содержа· щеrося в стрике, по основан ию 10 Возвращает эквивалент типа long числа , содержа­ щегося в строке, по указанному ОС1ЮваНUЮ системы счисления Возвращает цело численный эквивалент числа без 3нака, содержащегося в строке, rю основанию 10 (внедрен в версииJIЖ 8) Возвращает целочисленный эквива.т1е 1Т Т числа без 3Нака, содержащегося в строке, по указанному оаювтшю системы счисления (внедрен в версии JDK 8)
512 Часть 11. Библ иоте ка Java Метод static lon9 r&1 11A inderUnsigned (int делимое , int делитмь) static lon9 reverse ( lon9 ЧUСIЮ) static lon9 reverseВytes(lon9 чш:ло) static lon9 rotat8Left ( lon9 чш:.4Q, int n) static lon9 rotateRi9ht ( lon9 чш:ло, int n) short shortValue () static int siqnum (int чш:.w) static int sum ( lon9 val, int val2) static Strin9 toBinaryStrin9 (lonq ЧUСIЮ) static Strin9 toBexStrinq (lonq чш:ло) static Strinq toOctalStrin9 (long чш:ло) Strin9 toString () Продол жениR табл. 17.6 Описание Возвращает остаток от деления указанного делимого на делитмь без знака (внедрен в версии JDK 8) Изменяет на противоположный порядок с.л едова· ния битов в заданном числе и возвращает резуль­ тат Изменяет на противоположный порядок с.л е­ дования байтов в заданном числе и возвращает результат Возвращает результат смещения заданного чш:ла на п позиций влево Возвращает результат смещения заданного чисм� на п позиций вправо Возвращает значение вызывающего объекта как тип short Возвращает значение -1, если заданное ЧUCI IO OT· рицательное; нулевое значение, если ЧUС1Ю равно нул ю; и значение 1, если чш:ло положительное Возвращает результат сложения значений val+val2 (внедрен в версииJDК 8) Возвращает символьную строку, содержащую дво­ ичный эквивалент заданного ЧUCl/l l Возвращает строку, содержащую шестнадцатерич· ный эквивалент заданного числа Возвращает строку, содержащую восьмеричный эквивалент заданного числа Возвращает символьную строку, содержащую де­ сятичный эквивалент вызывающего объекта static String toStrinq (long ЧUСIЮ) Возвращает символьную строку, содержащую де­ сятичный эквивалент заданного числа static Strinq toString (long ЧUCl lO , int основани е ) static String toUn siqnedS trinq (lon9 val) static String toUn siqnedS tring ( long val, int основани е ) static Long valueOf (long ЧUCl lO ) Возвращает символьную строку, содержащую десятичный эквиваленr заданного числа с учетом указанного основания системы счисления Возвращает символьную строку. содержащую де­ сятичное целочисленное значение vаlбез знака (внедрен в версииJDК 8) Возвращает символьную строку, содержащую целочисленное значение val без знака с учетом указанного основания системы счисления (внедрен в версииJDК 8) Возвращает объект типа Long, содержащий значе­ ние, передаваемое в качестве параметра чш:ло
Гл ава 1 7 . Пакет java .Lang 513 Окончание табя. 17. 6 Метод Описание static Long valueOf (Strinq cmpoкtJ) throws NwDЬerForшatExoeption Возвращает ООьект типа Long, содержащий значе­ ние, указанное в строке atatic Long valueOf (Strinq Возвращает ООьект типа Lon9, содержащий зна­ чение, указанное в строке с учетом указанного основани я системы счисления cmpmw, int осиоаание) throws NwlЬerForшatExoeption Взаимное преобразование чисел и символьных строк Одной из наиболее часто выполняемых рути нных операций в программиро­ вании является преобразование строкового представления чисел во внутренний двоичный формат. Правда, сделать это в Java совсем не трудно. В классах Byte, Short, Integer и Long для этой цели предоставляются методы parseByte (), par se Short (),parseint () и parseLong ( ) соответственно. Эти методы возвра­ щают значения типа byte, short, int или long, эквивалентные числовой стро­ ке , с которой они были вызваны (аналогичные методы предус мотрены в классах Float и DouЫe) . В приведенном ниже примере программы демонстрируется применение мето­ да par seint ().Вэтой программе сум мируется ряд целочисленных значений, вво­ димых пользовател ем. С этой целью целочисленные значения считываются мето­ до м readLine () в виде числовых строк, которые затем преобразуются методом pa rse int ()вэквивалентные им числовые значения ти па int. /* Эта программа суммируе т ряд целых чисел , вводимых поль зователем . */ Она преобразуе т строковое представление каждого числа в целое значение ме тодом parseint ( ) import java.io.*; class ParseDerno { puЫ ic static void rna in (String args[] ) th rows IOException // создать буфери зированный поток чтения типа ВufferedReader , // исполь зуя стандатный лоток ввода Sys teш .in Buf f eredReader br = new Bu ffe redReader (new InputStrearnRe ader (Systern.in) ); String str; int i; int surn=O; System.out . println ("B вeдитe число , О - дл я выхода.") ; do{ str = br . readLine (); try { i = Integer .parseint (str) ; catch ( NumЬerFormatException е) { System. out .println ("Heвepный формат ") ; i=О; surn += i; Sys tem . out .println ("T eкyщa я сумма : " + surn) ; while(i != 0);
514 Часть 11. Библ и оте ка Java Для преобразования целого •шсла в десятичную строку служат варианты ме­ тода toStri ng (), определенные в классе Byte, Short, Integer или Long. В клас­ сах Integer и Long предоставляются также методы toBinaryString (), toHex Stri ng ( ) и toOctalString (), преобразующие числовое значение в двоичную, шестнадцатеричную и вос ьмеричную строки соответственно. В следующем примере программы демонстрируетс я преобразование целого числа в двоичную , шестнадцатеричную и восьмеричную строковую форму: /* Преобразова ть ц е лое число в двоичную, шестнадца теричную и вос ь меричную строко вую форму */ class StringConve rsions { puЫ ic static void ma in (String args [] ) { int num = 19648; System. out .println (Чиcлo num + " в двоичной форме : " + Integer . toBinaryString ( num) ); System. out . println (Чиcлo num + " в вос ьмеричной форме: " + Integer . toOct alString (num ) ); System.out .println (Чиcлo num + " в шестнадцатеричной форме : " + Integer . toHexString (num ) ); Ниже приведен резул ьтат, выводимый дан ной программой . Число 1 9 648 в двоичной форме : 100110011000000 Число 19648 в восьмеричной форме : 46300 Число 19648 в шестнадцат еричной форме : 4сс0 Кл а сс Character Класс Character служит простой оболочкой для типа char. Конструктор этого класса выглядит следующим образом: Character ( char сянвол) где параметр симв ол обозначает тот символ, который заключается в оболочку соз­ даваемого объекта типа Character. Чтобы получить значение типа cha r, содер· жащееся в объекте типа Character, достаточно вызвать метод charValue (),как показано ниже. Этот метод возвратит символ. char charValue () В классе Character о пределен ряд констант, включая следующие. ВУТЕS МАХ RADIX МIN RADIX МАХVALUE МIN VALUE ТУРЕ Длина типа char в байтах (внедрена в версииJDК 8) Максимальное основание системы счисления Минимальное основание системы счисления Максимальное значение Минимальное значение Объект типа Cl ass для типа char В состав класса Character входит ряд статических методов, рас пределяющих символы на катего рии и изменяющих их регистр . Они перечислены далее в табл . 17.7 . В следующем примере программы демонстрируется применение некоторых из этих методов:
Гn ава 17. П акет java.Lang 515 11 Продемонстрировать применение некоторых ме тодов типа Iв class IsDemo { puЫ ic static void ma in (String args [] ) char а[] = {'а', 'Ь', '5', '?', 'А', ' '); for(int i=O; i<a. length; i++) { if (Character .is Digit (a(i] )) System.out . println (a[i] +" цифра .") ; if (Character . isLetter (a[i] )) System. out . println (a[i] +"- буква.") ; if (Character .is Wh itespace (a[i] )) System.out . println (a[i] + " - пробельный символ.") ; if (Character . isUpperCase (a [i] )) System.out . println (a[i] + - прописная буква.") ; if (Characte r.isLowe rCase (a[i] )) System.out . println (a[i) +"- строчная букв а.") ; Эта программа выводит следующий результат: а - буква. а - строчная буква . Ь - буква. Ь - строчная буква . 5 - цифра. А- буква. А - прописная буква . - пр обель ный симв ол . В классе Character определены еще два метода, forDigit ( ) и digit (),пред­ н азначенные для взаи много преобразования целочисленных значений и цифр, которые их представляют. Ниже приведены их общие формы. static char forDi9i t(int vиCJ J o, int основан.в) static int diqit (char ц�rфра , int основаюrе) Метод forDigit ( ) возвращает цифровое значения , передаваемого в качестве параметра число. Основание системы счисления для преобразования числа опре­ деляется параметром основание. А метод digit ( ) возвращает целочисленное значение, связанное с заданным символом (предположительно цифрой) и с уче­ том указанного основания системы счисления. (Имеется еще одна форма метода digit () для получения кодовой точки в Юникоде. Более подробно кодовые точ­ ки обсуждаются в следующем разделе.) В классе Character определен также метод compareTo ( ) , имеющий следую­ щую общую форму: int coшpareTo (Character с) Этот метод возвращает нулевое значение , если вызывающи й объект и символ с и меют одинаковое значение; отрицател ьное значение, если значение вызываю­ щего объекта меньше; а иначе - положительное значение. Класс Character содержит метод ge tDirectionality (), позволяющи й опре­ делить напраменность символа. Для описания напраменности символов в этот класс добамено несколько констант, хотя в большинстве программ определять на­ правленность символов не требуется . Кроме того , в классе Character переопре­ деляются методы equals () и hashCode ().
516 Часть 11. Библиотека Java Таблица 17.7. Различные методы из класса Cha racter М етод static boolean isDefined (char символ ) static boolean isDigit (char символ) static boolean islden tifierlgnoraЫe (char символ) static boolean islSOControl (char символ ) static boolean isJavalden tifierPart (char символ) static boolean i sJavaldentifierSta rt (char симвм) static boolean isLetter (char символ ) static boolean isLetterOrDigi t (char симвм ) static boolean isLowerCase (char символ) static boolean isМi.rrored ( char символ) static boolean isSpaceChar (char символ) static boolean isTitleCase ( char символ) static boolean isUnicodeidentifierPart(char символ) Описани е Возвращает логическое значение true, если символ оп ределен в Юникоде, а иначе - логичс· ское значение false Возвращает логическое значение true, если символ является цифрой, а иначе -логическое зна•1ение false Возвращает логическое значение true , сели символ должен быть проигнорирован в идснти· фикаторе, а иначе - логическое значение false Возвращает логическое значение true, если символ является управляющим символ ом по стан· дарту ISO, а иначе - логи ческое значение false Возвращает логическое значение true, если символ может быть частью идентифи като раjаvа , а иначе -логическое значение false Возвращает логическое зн<�чение true, если символ может быть п ервым символ ом иденти· фикатора jаvа, а иначе -логическое значение fal se Возвращает логи ческое значение true, если символ является буквой, а иначе -логическое значение false Возвращает логи ческое значение true, если симвм является буквой или цифрой , а иначе - логическое значение false Возвращает логическое значение true, если символ является строчной буквой, а иначе - ло­ гическое значение fal se Возвращает логическое значение true, если симвм является зеркально отображае мым симво­ лом в Юникоде , а иначе - логическое значение fal se. Зеркалъно отображаемым называется сим· вол , предн азначенный для текстов, отображае· мых справа налево Возвращает логическое значение true, если символ является пробельным символом в Юникоде, а иначе - логическое значение false Возвращает логическое значение true, если символ является прописной буквой , а иначе - ло­ гическое значение fal se Возвращает логическое значение true, есл и С1LМ8ОЛ может быть частью идентификатора в Юникоде (кроме первого символа) , а иначе - логическое значение false
Метод static boolean isUnicodeidentifierStart(char СUМ8М) вtatic boolean isUpperCase (char сuмвм) Описание Гл ава 17. Пакет java. ia ng 517 Oicmt'ЧUHUJ? табл. 17. 7 Возвращает логическое значение true, если CUМiIOl l может быть первым символом иде нтифи­ катора в Юникоде. а иначе - логическое значе­ ние false Возвращает логическое значение true , есл и символ является прописной буквой, а иначе - ло­ гическое значение false вtatic boo lean isWhite space (char Возвращает логическое значение true , если СUМ8М) симвм является пробельн ым символом, а ина­ че - логическое значение falвe static char toLowerCase (char сuмвм) вtatic char toTi tleCase (char сuмвм) static char toUp pe rCase (char сuмвм) Возвращает эквивалентную символу строчную букву Возвращает эквивалентную символу прописную букву Возвращает эквивалентную символу прописную букву Для обращения с символами имеются еще два кл асса. В частности, класс Cha racter . Subset служит для описания подмножества символов в Юникоде , а кл асс Cha racter.UnicodeBlock соде ржит блоки символов в К)никоде . Дополнения класса Character для поддержки кодовых точек в Юникоде За последнее время в класс Cha racter бьши внесены существенные до полнения. Начиная с версии JПК 5 класс Character обеспечивает поддержку 32-разрядных символов в К)никоде. В прошлом все символы в К) никоде составляли 16 двоичных раз рядов, что равно длине типа char (и значения, заключаемого в оболочку класса Character) , поскольку коды этих символов находятся в пределах от О до FFFF. Но набор символов в Юникоде бьш расширен, для чего потребовалось еще 16 двоичных разрядов. Те перь коды символов находятся в пределах от О до lOFFFF. В связи с этим появились три важных те рмина. Кодова.я rnol/.кa - это символ в пределах от О до lOFFFF. А символ ы, имеющие код свыше FFFF , называются да­ полниrпелъны.ми. Осиовну ю многоязыковую плоскогrпъ составлнют символы в 11 редеш 1х от О до FFFF. В связи с расширением набора символов в Юникоде возникло серьезное затруд­ нение для проrраммированин нa java. Значение дополнител ьных символов оказы­ вается больше, чем умещается в типе char, поэтому для их поддержки требуютсн дополнительные средства . В Java :по затруднение ра:Jрешается двумн способами. Во-первых, для представления до полнительных символ ов в Java используется сурро­ гатная пара типа char. Первая половина этой пары называется старш.и.м суррогатом, а вторая - .младщи.м, сур р огаrпом. Для взаимного преобразования кодо вых точек и л:о-
518 Часть 11. Библ иоте ка Java 1юл11ителы1ых симвшюв н редусмотрены НОВЫt' методы в роде code Po intAt ().иво­ вто р ых, в Ja\•a 11с рсгружаются некото р ые методы, существовавшие ранее в классе Cha racter. В нерtт ружаемых фор мах этих методов исполь:Jуются да нные тина int вместо cha r. А 1юс кол 1. ку тип int достаточно велик, чтоб ы вместить любо й сим­ вол как <щ1 ю :11 1 ;р1е11ие, то его можно ипюл1.:ювать для х р анения любого символа. 1 Iапример, у всех методов из табл. 17.7 имеются перегружаемые формы, онсрирую­ щис J{аtшыми тишt int. Н иже 11риведены примеры общих форм некоторых и:J :пих методов. static boolean isDigit (int ходовая !l'очжа) static boolean isLetter (int жодоваЯ !l'очжа ) static int toLowerC ase (int жодовая_ точжа) П омимо метщ{ов, пер егружаемых для о бращения с кодовыми то чками, в класс C haract er ВВl'дсны методы, п редоставляющие донолнител ьную 1юддс ржку кодо­ вых точек. И збранные методы это й категор ии 11р иведены в табл. 17.8. Табл ица 17.8. Избранные метод ы из класса Character для поддержки 32-разрядн ых кодо вых точек в Юникоде Метод static int charCount (int кодовоя_точка) static int codePointAt ( CharSequence CUМl lQJI Ьl1 int 1WЗЩ1ия) static int codePointAt (char CILМlIOJ IЬl [], int позиция) static int codePointвefore (CharSequence симвwzы, int позиция) Описание Возвращает значен ие 1. есл и кодовая_точкл может быть нредстамена одной переменной тина char. А если требуются две такие переменные, то возвра­ щается значение 2 Возвращает кодовую точку, нахоl\Я щуюся на зада нной позиции Возвращает кодовую точку, находящуюся на заданной позиции Возвращает кодовую точку, нахоl\Ящуюся на позиции , п редшесrвующей заданной позиции static int codePointвefore (char Возвращает кодовую точку. находящуюся на позиции, СUМ8n!Ш[] , int позиция) предшесrвующей заданной позиции static Ьo o lean is&apeodePoint (int кvдо оа.я _ mo onca ) static Ьo o lean isRighSurrogate (char Cl lМiIOl l ) static Ьo o lean isLowSurroqate (char CWCO Ol l ) static Ьo o lean isSup p l81 118D taryCodePoint (int �) static Ьo o lean isSur rog atePair (char старший_ сур рогат ,charМl lDОш шl _ сур рогат ) Возвращает логическое значе ние true, если кодовая_ точка относится к основной многоязыковой плоско­ сrи, а иначе -логическое значение false Возвращает логическое значение true, есл и СUМО О.1 1 содержит достоверный символ старшего суррогата Возвращает логическое значение true, если СUМО О.1 1 содержит достоверный символ младшего суррогата Возвращает логическое значение true, если СUМО О.1 1 содержит дополнительный символ Возвращает логическое значение true, если аnаршuй_сур роют им.лоiJши й _сурJюгат образуют до­ стоверную суррогатную пару
Метод static Ьo o lean iaValidCodePoint (int кодовая_ точка) •tatic char [] toChara (int кодовая_точка) atatic int toChars (int кодовая_ точка, charaOperom(], int позиция) static int toCodePoint (char старший_сур ро гат, char младший_ сур ро ют) Класс Boolean Описание Гл ава 1 7 . Пакет java.Lang 519 Окончание maWi. 17.8 Возвращает логическое значение true, если кодовая_ mot«a содержит достоверную кодовую точку Преобразует кодовую_ТfW'l lСJ в ее эквивалент типа char , для чего мoryr потребоваться две переменные типа char. Возвращает массив, содержащий результат Преобразует кодовую_точну в ее эквивалент типа char, сохраняя результат в массиве адреrот, начиная с заданной по оиц ии. Возвращает значение 1, если кодовая_тоt«а может быть представлена одной пере­ менной типа char, а иначе - значение 2 Преобразует старший_сур рою тимладший_сур ро гат в эквивалентную кодовую точку Класс Boo lean служит очень тонкой оболочкой для логических значений типа boo lean, что удо бно в тех случаях, когда логические значения требуется пере­ давать по ссылке. Этот класс содержит константы TRUE и FALSE, определяющие объекты типа Boolean, которые соответствуют истинному и ложному значени­ ям. В классе Boolean определяется также поле ТУРЕ, являющееся объектом ти па Clas s для типа boolean. В классе Boolean определены следующие конструкторы: Вool ean (boolean лor•vecxoe знаvеюrе) Вoolean (String лоГJ1 1 vесхая_с !l'рОха ) В первом ко н структоре параметр логическое _ зна чение должен быть равным true ил и false. А во втором конструкторе новый объект класса Bool ean будет со­ де ржать логическое значение true , есл и параметр логиче ская_ строка содержит с имвол ьную строку "true" (в верхнем или нижнем регистре). В противном случ ае новый объект будет содержать логическое значение fa lse. В классе Boo lean определены методы , перечисленные в табл . 17.9 . Та бл ица 17. 9. Методы из класса Bool ean Метод Ьoolean ЬooleanValue О atatic int сошраrе (Ьoolean Ы , Ьo o lean Ы) int сошраrеТо (Вoolean Ь) Описа ние Возвращает эквивалент типа boolean Возвращает нулевое значение, если значения Ы и Ы одинаковы; положительное значение, если значение Ы равно true, а значение Ы равно f'al se; и отрица­ тельное значение прот ивном случае Возвращает нулевое значение, если значения вызы­ вающего объекта и Ь одинаковы; положительное зна­ чение, если значение вызывающего объекта равно true, а значение Ь равно false; и отрицательное значение противном случ;;�е
520 Часть 11. Библиотека Java М етод Ьoolean equal s (OЬject BoolOЬJ) static boolean getвoolean (String и.мя_свойсmва) int hashCode О static int hashCode (Ьoolean логи'U!СКОе_:1 1шчен ие) static boolean loqicalAnd (Ьo o lean opl, Ьoolean ор2) static boolean logicalOR (Ьoolean opl , Ьoolean ор2) static boolean logicalXOR (Ьoolean opl , Ьoolean ор2 ) static Ьo o lean parseВoolean (Зtring строка) String toStrinq () String toStrinq (Ьoolean логwrеск ое _значен ие) static Ьo o lean valueOf (Ьo o lean логwrеское_значение) static boolean valueOf (Strinq логическ ая _строка) Клacc vo id Окrтчани� табл. 17. 9 Описание Возвращает логическое значение true, если вызыва­ ющий объект равнозначен объекту Во о 101!j,аиначе- логическое значение false Возвращает логическое значение true, если си· стемное свойство, определяемое параметром имя_ свойсmва, равно true, а иначе - логическое значение fal se Возвращает хеш-код вызывающего объекта Возвращает хеш-код указанного лоzическоzо_ЗНQtU!НU Я (добамен в версииJDК 8) Выполняет логическую операцию И над операндами opl и ор2 и возвращает результат (добавлен в версии JDK 8) Выполняет логическую операцию ИЛ И над операн­ дами opl и ор2 и возвращает результат (добамен в версииJDK 8) Выполняет логическую операцию исключающее ИЛ И над операндами opl и ор2 и возвращает резуль­ тат (добамен в версииJDК 8) Возвращает логическое значение true, если строка содержит символьную строку "true " без учета реrи· стра, а иначе - логическое значение false Возвращает строковый эквивалент вызывающего объекта Возвращает строковый эквивалент указанного логическ о zо_значения Возвращает логический эквивалент указанного ЛОZWU!СКDlО_ЗНЛ'lеНUЯ Возвращает логическое значение true, если указан­ ная лоzичесJ«l Я _ строка содержит символьную строку "true" без учета регистра, а иначе - логическое значение f'alse Этот класс содержит еди нственное поле ТУРЕ, в котором хранится ссылка на объект типа Class для типа vo id. Экземпляры этого класса не создаются. Класс Process Абстрактный класс Process инкапсулирует пр оцесс, т. е. выполняющуюся про· грамму. Он используется в основном в качестве суперкласса для типа объектов,
Гл ава 17. Пакет javaJang 521 создаваемых методом ехес () из класса Runt ime или методом start () из класса ProcessBui lder. Класс Process содержит абстрактные методы , перечисленные в табл. 17.10. Табл ица 17.10. Методы иэ класса Process Метод void destroy () Process destroyForciЫy () int extValue () InputStreaa qetErrorS treaш. () InputStreaш qetOutpu tStreaш () OUtputS treaш. qetOutputS treaш. () Ьoolean isAlive () Int waitFor () throws InterruptedException Int waitFor ( lonq время_ ожидания , TiJDВUnit единица_времени) throws InterruptedException Класс Runtime Описание Прерывает процесс Принудител ьно завершает вызывающий про­ цесс. Возвращает ссылку на процесс (добавлен в версииJDК 8) Возвращает код завершения процесса Возвращает поток ввода для чтения дан ных из потока вывода ошибок err вызывающего про­ цесса Возвращает поток ввода для чтения данных из потока вывода out вызывающего процесса Возвращает поток вывода для записи данных в поток ввода in вызывающего процесса Возвращает логическое значение true , есл и вызывающий процесс по-прежнему действует, а иначе - ло гическое значение false (добавлен в версииJDК 8) Возвращает код завершения процесса. Не воз­ вращает управление до тех пор, пока нроцесс, для которого он вызван, не завершится Ожидает завершения вызывающего процесса. Период ожидания определяется параметром время_ожидания в еди ницах времени, обознача­ емых параметром единица_времени. Возвращает логическое значение true , если процесс за­ вершился , а по истечении заданного времени_ ожидания - логическое значение false (добав­ лен в версииJDК 8) Этот класс инкапсулирует исполняющую среду. С оздать о бъект типа Ru nt ime нельзя , но можно получ ить ссыл ку на те кущий объект типа Ru ntime , вызвав ста­ тичес кий метод Runtime . getRuntime (). Получив ссьш ку на те кущий объект типа Ru ntime , можно вызвать нескол ько методо в, уп р авля ющих состоя нием и пове­ де нием виртуал ьной машины JVМ. Аплеты и друго й не заслуживающий довер ия код не мо гут вызывать методы из класса Runtime , не генер и руя исключение типа SecurityException. Н аиболее употр еб ительные мето;\ы из класса Run t ime пере­ числены в табл. 17.11.
522 Часть 11. Библиоте ка Java Та бл ица 17.1 1. Избранные методы иэ кл асса Runtime М етод void addShutdownНook (Тhread поток) Process exec (Strinq имя_ програм мь 1) throws IOException Proce ss exec ( Strinq имя_ програм м ы, Strinq окруженш[ ]) throws IOException Proce ss exec (Strinq мас сив _ камандной_строки []) throws IOException Process exec (String мас сив _ ко.мандн ой _строки [] , Strinq окружение[] ) throws IOException void exit (int код_ завершmил) lonq freeМemory () void gc () static Run t.:il ll le qetRuntilae () void halt (int код) void load (Strinq имя_фай.ла_ библштrеки) void loadLiЬrary (String имя_ библшпnеки) Ьoolean reшoveShutdownВook (Тhread штwк) Описание Регистрирует поток, который должен быть запу­ щен на исполнение при остановке виртуальной машиныJVМ Выполняет программу, оп ределяемую параме­ тром имя_прогрй.м мhl , как отдел ьн ый процесс. Воавращает объект типа Prooess, описывающий новый процесс Выполняет программу, определ яемую параметром имя_програм м ы, как отдел ьный процесс в окру­ жении, обозначаемом параметром окружение. Возвращает объект типа Process, описывающий новый процесс Выполняет командную строку, передаваемую в качестве параметра мас сив _камандно й _стртщ как отдел ьный процесс. Возвращает объект типа Proce ss, описывающий новый процесс Выполняет командную строку, передаваемую в ка­ честве параметра мас сив _командной_строки, как отдел ьный процесс в окружении, обозначаемом параметром окруженш. Возвращает объект типа Process, описывающий новый процесс Прерывает выполнение и возвращает код_ завершен ия родительскому процессу. Ус ловно ну­ левой код означает нормальное завершение, а все другие коды завершения обозначают раз.личные виды ошибок Возвращает приблизительное количество байтов свободной памяти, доступной исполняющей систе­ ме Jаvа Инициирует сборку "мусора" Возвращает текущий объект типа Runti.Dle Немедленно прерывает работу виртуальной маши­ ны ]УМ. Никакие потоки или методы завершения не выполняются. Вызывающему процессу возвра­ щается заданный код Загружает динамическую библиотеку, файл которой обозначается параметром имя_файл.а_ библшпnеки, включая и полный пуrь к нему Загружает динамическую библиотеку, имя которой связывается с параметром имя_библштrеки Уд аляет ооmок из списка потоков, запускаемых на исполнение при останове виртуальной машины JVМ. Возвращает логическое значение true при удачном ис.ходе, т.е . в том случае , если поток удален
Метод void runFinalization () long totalМulory () void traceinstructions (boolean Вк.лючить_трас си. ровку) void traoeМethodCalls (Ьoolean Вк.лючит ь _траrеировку) Описание Гл ава 17. Пакет java.Lang 523 0кt»fЧaHU�та/И. J7.JJ Инициирует вызовы метода finalize ( ) для неис· пользованных, но еще не возвращенных объектов Возвращает общее количество байтов оперативной памяти, доступной программе Включает или отключает трассировку инструкций в зависимости от значения параметра Включить_ трш:сиро вrсу . Если этот параметр принимает логи· ческое значение true, то трассировка включается, а если он принимает значение fal se - трассиров· ка отключается Включает и отключает трассировку вызовов методов в зависимости от значения параметра Включить_mрас сuротсу . Если этот параметр при· нимает логическое значение true, то трассировка включается, а если он принимает значение false - трассировка отключается Рассмотрим два наиболее распространенных примера применения класса Runt irne : управление памятью и выполнение дополнительных процессов. Управление памятью Несмотря на то что в Java орган изуется авто матичес кая сборка "мусора" , и но­ гда требуетс я знать , какая часть выделяемой оперативной памяти занята объекта· ми и какая ее часть еще свободна. Эти сведения можно, например, использовать, чтобы проверить эффективность прикладного кода или выяснить, сколько еще объектов определенного типа может быть инициализировано. Для получения этих сведений служат методы totalMernory () и freeMerno ry (). Как упоминалось в части 1, систе ма сборки "мусора" в Java запускается перио· дич ески для утилизации неиспользуе мых объектов. Но иногда может возникнуть потребность собрать отвергнутые объекты до того , как система сборки "мусора" будет запущена в очередной раз. Ее можно запускать по требованию, вызывая ме· тод gc ( ) . Можно также попробовать вызвать сначала метод gc ( ) , а после него - метод freeMemo ry (), чтобы получ ить основные сведения об использовании памя· ти . Выполняя далее прикладной код, можно снова вызвать метод freeMerno ry (), что бы выяснить, сколько памяти еще с вободно. Та кой подход к управлению па· мятью демонстрируется в следующем примере программы: // Продемонс трировать приме нение ме тодов totalМE1 11 1 ory(), // fre&Мeшory() и qc() class Memo ryDemo { puЫic static void main ( String args []) { Runt ime r = Runtime .getRuntime (); long meml , mem2 ; Integer some ints [] = new Integer [lOOOJ ; System. o ut . println ("Bceгo памяти : "+r.totalMemo ry () ); meml = r.fr eeMemo ry () ;
524 Часть 11. Библиотека Java System.out . println ( "Cвoбoднoй памяти исходно : "+meml ); r.gc () ; meml = r.fr eeMemory (); System.out . println ("C вoбoднoй памяти после очистки : " + meml); for (int i=O ; i<lOOO; i++J som eints [i] = пеw Integer (i) ; // выделит ь памя ть для // объе ктов типа Inteqer mem2 = r . freeMemo ry (); System. out . priпtln ( "Cвoбoднoй памя ти после выделения : " + mem2} ; System.out . priпtln ( "Иcпoльзoвaн o памя ти для выд еления : " + (meml-mem2J ); 11 отбросить Intege rs for (iпt i=O ; i<lOOO; i++) some int s[i] null; r.gc() ; // запустить сборку "мусора " mem2 = r .fr eeMemory (); System.out . priпtlп ("C вoбoднoй памяти после очистки " + "отвергнутых объе ктов типа Integer : "+mem2) ; Ниже при веден примерный результат, выводи мый данной программой (у вас он может оказаться иным в зависимости от конкретной исполняющей среды ). Всего памяти : 1048568 Свободной памя ти исходно : 751392 Свободной памяти после очис тки : 841424 Свободной памяти после выделения : 824000 Исполь зовано памяти дл я выделе ния : 17424 Свободной памяти после очистки отброшенных объе ктов типа Iпteger : 842640 Выполнениедругих программ В безопасных средах рассматриваемые здесь языковые средства Java можно использовать для выполнения других тяжеловесных процессов (т.е . программ) в многозадачной операционной системе. Некоторые формы метода ехес () по­ зволяют указывать программу, которую требуется выполнить, а также передать ей входные параметры. Метод ехес ( ) возвращает объект типа Process, который затем может быть использован для управления взаимодействием прикладной про­ граммы нaJava с эти м вновь запущенным процессом. Но поскол ьку языковые сред­ стваJаvа могут функционировать на разных платформах и в среде различных опе­ рационных систем, то метод ехес ( ) сильно зависит от ко нкретной среды . В приведенном ниже примере метод ехес () используется для запуска приложе­ ния Notepad - простого текстового редактора в Windows. ОчевиднQ, что код этого примера должен выполняться в среде операционной системы Windows. // Продемонс трировать применение ме тода ехес () class ExecDemo { puЫ ic static void ma in (St riпg args[] ) Runt ime r = Ruпtime .getRuntirne ();
Proce ss р = null; try { р = r .exec ( "Notepad" ); catch (Exception е) { Гл ава 17. Пакет java .Lang 525 System. out .println ("Oшибкa запуска Notepad. ") ; Существует несколько альтернативных форм метода ехес ( ) , но форма, пока­ занная в данном примере, используется чаще всего. Объектом типа Process, воз­ вращаемым методом ехес (), можно манипул ировать, используя другие методы из класса Proce ss после запуска программы на выполнение. Та к, вызвав метод de­ stroy (), можно уд алить процесс, а с помощью метода wai tFor ( ) - заставить при­ кладную программу ожидать завершения процесса. Метод exitValue ( ) возвратит значен ие, кото рое возвращается процессом по его завершении. Обычно это нуле­ вое значение, если не возникает никаких осложнений. Ниже приведен предыду­ щий пример, демонстрирующий применение метода ехес ( ) , но видоизмененный таким образом , чтобы ожидать завершения запущенного процесса. 11 Ожида ть завершения работы текстового редактора Notepad class ExecDemoFini { puЬlic static void ma in (String args[] ) Run time r Runt ime .getRunt ime () ; Process р = null; try { р = r.exec ( "Notepad" ); p.w aitFor () ; catch (Exception е) { System.out . println ( "Oшибкa запус ка Motepad. ") ; System. out . println ( "Notepad возвратил " + p.exitValue () ); Во время выполнения процесса можно выполнять операции стандартного ввода и вывода . Методы getOutputStream () и ge tinputStream () возвращают дескрипто ры стандартных потоков ввода in и вывода out для данного процесса. (Операции ввода-вы вода подробно рассматри ваются в гл аве 20.) Кла сс ProcessBuilder Класс ProcessBui lder обеспечивает друго й способ запус ка процессов (т.с . про­ грамм) и управления ими. Как пояснялось ранее , все процессы представлены кл<�с­ сом Process, и каждый процесс может быть запущен методом Ru ntime . ехес (). А в классе Proce ssBuilder предоставляются более развитые средства управления проце ссами , с помощ1,ю которых можно, например, установить текущий рабочий каталог и изменить параметры окружения . В классе ProcessBui lder определены следующие конструкторы:
526 Ч асть 11. Библ иоте ка Java ProcessBui lder (List<String> аргунен ты) ProccessВuilder (String .. . аргунен ты) где параметр аргументы обозначает список аргументов, указывающих имя програм­ мы, которую требуется запустить на выполнение со всеми необходимыми аргумента­ ми ко мандной строки. В первом конструкторе аргументы передаются в списке типа L i s t, а во втором конструкторе они указываются в качестве параметра переменной длины. В табл . 17.12 перечислены методы, определенные в классе ProcessBuilder. Табл ица 17. 1 2. Методы из класса ProcessBuilder Метод List<String> COIDl llan d() ProcessBuilcler COl ll ll l&nd (List<String> аргументы) ProcessBuilcler cOl ll lDand (Strinq .. . арrументы) File directory () ProoessBuilcler directory (File l«l1l ll lJIO г) Мap<String , Strinq> envi ronшent () ProcessBuilcler inheritIO () ProcessBuilcler .�rect redirectError () ProoessBuilcler redi.rectError (File fl ProcessВUilcler redirectError (ProcessBuilcler . �rect ailpemm) Ьoolean redirectErrorStreaш () Описание Возвращает ссьuпсу на список типа List, который со­ держит имя п рограммы и ее аргументы. Изменения в этом списке относятся к вызывающему объекту Задает имя программы и ее аргументы в ка честве параметра ар гументы. И зменения в этом списке от­ носятся к вызывающему объекту. Возвращает ссылку на вызывающий объект Задает имя п рограммы и ее аргументы в качестве параметра ар гументы. Возвращает ссылку на вызыва­ ющий объект Возвращает текущий рабочий каталог для вызыва­ ющего объекта. Если каталог тот же самый, что и у п рограммы нaJava, которая запустила процесс, то возвращается пустое значение null Уст анавливает текущий каталог для вызывающего объекта. Возвращает ссЬUiку на вызывающий объект Возвращает переменные окружения, связанные с вы­ зывающим объектом, в виде пар "ключ-значение" Вынуждает вызываемый процесс использовать те же самые источник и адресат для стандартных потоков ввода-вывода, что и вызывающий процесс Возвращает адресат для стандартног о потока выво­ да ошибок в виде объекта типа ProcessВUilder . �rect Задает адресат для стандартного потока вывода оши­ бок в указанный файл. Возвращает ссьmку на вызыва­ ющий объект Задает аОреmт для стандартного потока вывода оши­ бок. Возвращает ссьmку на вызывающий объект Возвращает ло гическое значение true, если стан­ дартный поток вывода ошибок направляется в стан­ дартный поток вывода данных. А если эти потоки вы вода используются раздельно, то возвращается логическое значение false
Метод ProcessBuilder redirectErrorStream (Ьoolean слштие) Proce sвBuilder . Redirect redirectinput () ProcessBuilder redirectinput (File j) Proces sBuilder redirectinput (Proce ssBuilder . Redirect источник) ProcessBuilder . Redirect redirectOutput () ProcessBuilder redirectOutput (File j) Proce ssBuilder redirectOutput ( ProcessBuilder . Redirect адреmт) Process start () throws IOException Описание Гл ава 17. Пакет java.Lang 527 Окон'tанш табл. 17.12 Если параметр C/IUЯШle принимает логическое зна­ чение true, то сrандартный поток вывода ошибок направляется в стандартный поток вывода дан ных. А если параметр СЛW1Ние принимает логическое зна­ чение false, то эти потоки вы вода используются раздел ьно, что делается по умолчанию. Возвра щает ссьшку на вызывающий объект Во:�вращает источник для стандартного потока ввода в виде объекта типа ProcessBuilder . Redirect Задает источник для стандартного 11отока ввода в ука­ за нный файл. Возвращает ссылку на вызывающий объект Задает источник для ст андартного потока ввода. Во:шращает ссьш ку на вызывающий объе кт Возвращает адресат для ста ндартного потока вывода в виде объекта тип а ProcessBuilder . Redirect Задает адресат для стандартно го потока вы вода в ука­ занный файл. Возвращает ссылку на вызывающий объект Ус танавл ивает адреmт для станда ртного потока выво­ да. Возвращает сtъшку на выз ыва ющи й объект Запускает п1юцссс, укааьшас мый вызывающим объек­ том. Иными слова ми, запускает заданную п1юграмму В табл . 17.12 обратите внимание на те методы , в которых используется класс Proces sBui lder . Redirect. Этот абстрактн ый класс инкапсул и руст исто ч н и к или ад ресат ввода-вывода , связанный с процессом. Кроме того , эти методы по:ша­ ляют переадресовать источник или адресат операций ввода-в ывода . 1 lапри мер , выэвав метод to () , можно переадресовать вывод в файл, вы:1вав метод f r o rn () - переадресовать ввод иэ файла, а выэвав метод appendTo () - присоеди нить вывод к файлу. Объект типа Fi le, свяэанный с файлом, может быть п олуче н при выэовс метода file ().Ниже приведены общие формы этих методо в. static Proce ssBu ilder . Redi rect to (File f) static Proce ssBuilder . Redirect from (File f) static Proces sBuilder .Redirec t appendTo (File f) File file () В классе ProcessBui lder . Redirect поддерживается также метод type (), воэвращающий значение перечислимого типа ProcessBuilder . R e dir e ct . Туре . В этом перечислении описываютс я виды переадресации ввода-вы во;�а , определяе­ мые константамиАРРЕND, INHERIT, Р1РЕ, READ и WR ITE. В кл ассе ProcessBuild er . Redi rect та кже определены константы INHERIT и PI РЕ.
528 Часть 11. Библ иотека Java Чтобы создать процесс , испол ьзуя класс ProcessBuild er, достато чно создать экземпляр этого класса, указав и мя программы и вес необходимые аргументы, а чтобы начать выполнение программы - вызвать метод start () для этого экзем­ пл яра. В приведенном ниже примере демонстрируется запуск текстового редакто­ ра Notepad в Wi11dows. Обратите внимание на то , что в качестве аргумента пере­ дается имя текстового файла, который требуется отредакти ровать. class PBDemo { puЫ ic static void ma in (String args []) { try { Proce ssBu ilder proc = new ProcessBuilde r ( "notepad . ехе", "testf ile") ; proc .start () ; catch (Exception е) { System.out .println { "Oшибкa запуска Not epad. ") ; Класс System Этот класс содержит коJVIекцию статических методо в и переменных. Стандартн ые пото ки ввода, вывода данных и ошибок в исполняющей систе ме Java хранятся в переменных in, out и err соответстве нно. Метод ы, определен­ ные в классе System, перечислены в табл . 17.13. Многие из них ге нерируют и с­ ключение типа SecurityException, если операция не допускается диспетчером защиты . Табл ица 17.13. Метод ы иэ кл асса Sys teш Метод Описание Копирует массив. Копируемый массив перс- static void arraycopy (C.Ьject источник, int начоло_исmочник а , адреmт,intНЛ'ID.l lQ _aдpem mn , int размер) С.Ь jесt дается в качестве параметра uсточних, а индекс позиции, с которой начинается копирова- static Strinq clearProperty ( Strinq которая> static Console console () ние из uсточнuка, обозначается параметром НЛЧD.!IQ_ш:точника. Массив, принимающий ко­ пию, передается в качестве параметра aiJpeo om , а индекс позиции, с которой следует начинать копирование, обозначается параметром НЛЧD.!IQ_адресата . И наконец, параметр размер задает кол ичество копируемых элементов Уд аляет переменную окружения, обозначаемую параметром ко оюрая . Возвращает предыдущее значение, связанное с переменной окружения, обозначаемой параметром ко оюрая Возвращает консоль, связанную с виртуальной машинойJVМ. В отсутствие текущей консоли у виртуальной машиныJVМ возвращается пу­ стое значение null
Метод static lonq currentTi.JaeNillis () static void exit (int код_завершен ш� ) static void qc () static мap<Strin9 , Strin4iJ> qetenv () static Strinq getenv (Strinq которая) static Properties getproperties () static Strinq qetProperty (Stri.ng /«JIМ/ЮЯ) atatic Strinq qetproperty (Strinq /«JIМ/ЮЯ, Strinq nо_умо.лчт t ию) static Securitytfanaqer qetsecurityМanaqer () static int id8ntityВashCoc:l8 (0Ьject об6екm) atatic Chan ne l inheri tedChan n el () throws IOException static Strinq lineSeparator О atatic void load (Strin9 имя_ фаi i.ла _ бuблиоте кu ) static void loadLiЬrary (Strinq имя_ биМиоmеки) Описание Глава 17. Пакет java.tang 529 Прод мженш табл. 17.13 Возвращает текущее время в миллисекундах, прошедших с полуночи 1 января 1970 года Прерывает выполнение и возвращает код_ завершен ия родител ьскому процессу (обычно операционной системе). Усл овно нулевой код означает нормальное завершение, а все другие коды завершения обозначаюr различные виды ошибок Инициирует сборку "мусора" Возвращает объект типа нар, содержащий теку­ щие переменные окружения и их значен ия Возвращает значение, связанное с переменной окружения, передаваемой в качестве параметра которая Возвращает свойства, связанные с исполняю­ щей системой jаvа (класс Properties описыва­ ется вглаве 18) Возвращает свойство, связанное с переменной окружения, обозначаемой параметром которая. Если нужное свойство не найдено, возвращает­ ся пустое значение null Возвращает свойство, связанное с переменной окружения, обозначаемой параметром копюрая. Если нужное свойство не найдено, возвраща­ ется пустое значение, задаваемое параметром 71О.. .JIМ МЧСZНUЮ Возвращает текущий диспетчер защиты или пустое значение null, если диспетчер защиты не установлен Возвращает хеш-код идентичности обr.екта Возвращает канал , наследуемый виртуальной машиной JVМ, ил и пустое значение nu ll, если канал не наследуется Возвращает строку. содержащую символ ы раз­ делителей строк Загружает динамическую библиотеку. файл которой задается параметром имя_файла_ биtiлштrеки, включая полный пугь доступа к нему Загружает динамическую библиотеку, имя кото­ рой связывается с параметром имя_биtiли отеки
530 Часть 11. Библ и оте ка Java Метод static void 11 1a pLiЬraryName (Strinq библиотека) static long nanoTi.шe () static void runFinalization () ОкrтчанШ? табл. 17.13 Опмсанме Возвращает зависящее от платформы имя ука· занной библumпеки Получает наиболее точный таймер системы и возвращает его значение о наносекундах, прошедших от некоторого произвольно вы· бранного момента времени. То чность таймера неизвестна Инициирует вызовы метода finalize () для неиспользуемых, но еще не уrилизирован· ных объектов static void setErr (PrintStre- Задает 1W1 1WК _вьиюда_ошибок в качестве стан- rwmок_вьиюда_ошибок) дартного потока вывода ошибок err static void setin (InputStream поток_ Задает поток_ввода в качестве стандартного по- ввода) тока ввода in static void setOut ( PrintStream nоток_вьиюда) Задает поток_ вывода в качестве стандартного потока вывода ou t static void setProperties (Properties Устанавливает текущие системные свойства, сиаnисны е _ своШ:тва) определяемые параметром сш:те.мн ые _свойапва static Strinq setProperty (Strinq которое, Strinq v) Присваивает заданное значение vсвойству, определяемому параметром IWmQfIOe static void setSecurityМanaqer (Secu Ус танавливает зада нный дuсп ппчер _защиты ri tyМanaqer дuсп ппчер _ защиты) Рассмотрим некоторые типичные примеры применения класса System. Измерение времени выполнения программы методом currentTimeМills () Одним из особенно любопытных примеров применения класса System явля· ется измерение времени выполнения различных частей прикладной программы с помощью метода currentTime Mi lls (), возвращающего текущее время в милли· секундах , прошедшее с полун очи 1 января 1970 года. Чтобы хронометрировать отдельную часть проверяемой программы, следует сохранить это значение непо­ средственно перед началом выполнения этой части и сразу же после ее выпол не­ ния вызвать метод currentTime Mi lls () еще раз. Время выполнения определяет· ся как разность между конечным и начальным моментами времени. В следующем примере программы показано, каким образом такое измерение времени выполне· ния осуществляется на практике: 11 Измерение времени выполнения программы class Elapsed { puЫic static void main ( String args[]) long start , end; System. out .pr intln (
Гл ава 17. П акет java.Lang 531 " И змерение времени перебора от О до 100000000") ; // измерить время перебора от О до 100000000 start = System . curreпtTimeMillis (); // получить начальный // моме нт времени for ( long i=O; i < 1000000001; i++) ; end = System. currentTimeMillis () ; // получить конечный 11 моме нт времени System.out . println ( "Bp eмя выполне ния : "+ (end-start) ); Ниже приведен примерный результат, выводимый данной программой (у вас он может оказаться иным в зависимости от конкретной исполняющей среды). Измерение времени перебора от О до 100000000 Время выполнения : 10 Если таймер вашей системы работает с точностью до наносекунд, можете пе­ реписать эту программу, воспользовавшись методом nanoTime () вместо метода currentTime Mills ().Вкачестве примера ниже показана гл авная часть этой про­ граммы, переписанная с целью воспользоваться методом nanoTime (). start = System . nanoTime () ; // получить началь ный моме нт в ремени for (long i=O; i < 1000000001; i++) ; end = System . nanoTime (); // получить конечный моме нт времени П ри менени е метода arraycopy О С помощью метода arraycopy ( ) можно быстро скопировать массив любого типа из одного места в другое. Это намного быстрее , чем выполнить эквивалент­ ный цикл, написанный вручную нajava. В качестве примера ниже приведена про­ грам ма, где два масс ива копируются методом arraycopy (). Сначала массив а ко­ пируется в массив Ь, а затем все элементы массива а сдвигаются на одну позицию в начало массива. После этого весь массив Ь смещается на одну позицию в конец массива. // Исполь зовать ме тод arraycopy ( ) class ACDemo { static byte а[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 }; staticbyteЬ[]={ 77, 77, 77,77, 77, 77, 77, 77, 77, 77}; puЫic static void ma in (String args []) { System.out . println ("a =" + new String(a) ); System.out . println ("b ="+ new String(Ь) ); System.arraycopy (a, О, Ь, О, a.length) ; System.out . println ("a ="+ new String(a) ); System.out .println ("b ="+ new String \ b) ); System.arraycopy (a, О, а, 1, a.length - 1) ; System.arraycopy (b, 1, Ь, О, b.length - 1) ; System.out . println (� a "+new String(a) ); System.out .println ("b = " + new String(Ь) ); Как следует из приведенного ниже результата выполнения данной программы, копировать можно один и тот же источник и получатель в обоих направлениях.
532 Часть 11. Библиотека Java а ABCDEFGH IJ ь мммммммммм а ABCDEFGH IJ ь ABC DEFGH IJ а AABCDEFGНI ь BCDEFGН IJJ Свойства окружения В любом случае ;1осту1 111ы следующие свойства окруж ения: file . separator java.class . path java. class . ver sion java . compi ler java .ext . di rs java . home java.io.tmpdir java.library . path java . speci fication .name java . specification . vendor java . speci fication .version java . vendor java . vendor .url java . version java.vm.version line . separator os . arch os.name java.vm. name os . version java.vm. specificati on .name path . separator java.vm. specification . vendor user .dir java.vm. specification .version user . home java.vm.vendor user . name З начения р<tзличных переменных окружения можно получить, вы:Jвав метод System. g e tPrope r ty ().Например, в следующей программе выводится путь к те· кущему пол ьзовател ьс кому каталогу: class ShowUserDir { puЫic static void ma in (String args[] ) ( System.out . println (System. ge tPrope rty ("us er .dir") ); Класс Object Как упоминалось в части 1, класс Ob ject служит суперклассом для всех осталь· ных классов. В кл ассе Ob j ect определены методы , перечисленные в табл . 17.14 и применяемые к любому объекту. Та бл ица 17.1,. Методы иэ кл асса ОЬ jесt Метод OЬject clone () throws CloneNotSupportedException Ьoolean equals (Object объект) Описание Создает новый объект, кото рый оказывается таким же, как и вызывающий объект Возвращает логическое значение true, есл и заданный объект равнозначен вызывающему объекту void finalize () throws ThrowaЫe Вызывается по умолчанию перед удал ением не· используемого объекта
Метод final Clas s <?> ge tClass () int hashCode О final void notify () final void notifyAl l () String toS tring () final void wa it () throws InterruptedException final void wai t ( long мW1Лисекунд) throws InterruptedException final void wai t ( long мW1Лuсекунд , int наносекунд) throws InterruptedException Описание Гл ава 17. Пакет java.Lang 533 Окон:чанштаМ. 1 7. 14 Получает объект типа Clas s, который описыва­ ет вызывающий объект Возвращает хеш-код, связанный с вызывающим объектом Прерывает исполнение потока, ожидающего вы­ зывающий объект Прерывает исполнение всех потоков, ожидаю­ щих вызывающий объект Возвращает символьную строку, описывающую объект Ожидает завершения другого потока исполне­ ния Ожидает заве ршения другого потока испол­ нения до истечения указанного кол ичества MWl.ilUceк.yнд Ожидает завершения другого потока и с пол­ нения до истечения указа нного кол ичества мW1Лuсекунд плюс наносекунд Применение метода clone () и интерфейса CloneaЬle Бол ьшинство методов, определенных в классе Ob ject, описываются в дру­ гих гл авах дан ной книги. Но од ин из них, метод clone (), требует особого рас­ смотрения. Этот метод создает дубл икат того объекта , который его вызывает. Клонировать можно объе кты тол ько тех классов, которые реализуют инте рфейс CloneaЫ e. В интерфейсе CloneaЫe никакие члены не объявляются. Он служит лишь для указания на то , что в реализующем его классе допускает поразрядное копи­ рование (т.е. клонирование) объектов. Если попытаться вызвать метод clone () для объекта класса, не реализующего интерфейс CloneaЫe, будет сгенерировано исключение типа CloneNot SupportedException. При клонировании конструк­ то р клонируемого объекта не вызывается. Получаемый в итоге клон является то ч­ ной ко пией своего оригинала. Кл онирование считается потенциально опасным действием, поскол ьку оно мо­ жет вызвать нежелательные побочные эффекты . Та к, если клонируемый объект содержит ссылочную перемен ную objRef, то в клоне она будет ссылаться на тот же с амый объект, что и в оригинале. Если клон вносит изменения в содержимое объекта, на который ссылается переменная obj Re f, то изменится и ориг инал. Обратимся к другому примеру. Если объект открывает поток ввода-в ывода , а затем клонируется , то оба объекта будуг оперировать одним и тем же потоком ввоюt-вы-
53' Часть 11. Библиотека Java вода. Более того, если один из этих объектов закроет поток ввода-вывода, то вто­ рой может по-прежнему пытаться выводить в него дан ные, что приведет к ошиб­ ке. Чтобы разрешить подобные затруднения, иногда требуется переопределить м етод clone ( ) , определенный в классе Obj ect. В связи с осложнениями, которыми чревато клонирование объектов, ме­ тод clone ( ) объявляется в классе Ob ject как protected. Это означает, что он может быть вызван из метода, определенного в классе, реализующем интер­ фейс CloneaЬle, или же должен быть явно переопределен в классе как puЫ ic. Рассмотрим каждый из этих подходов на конкретных примерах. В следующем примере программы реализуется интерфейс CloneaЫe и опреде­ ляется метод cloneTe st (), вызывающий метод clone ( ) из класса Ob ject: // Продемонстрировать приме нение ме тода clone () class Te stClone implements CloneaЫe { int а; douЫ e Ь; // В этом ме тоде вызывается ме тод clone () из класса OЬj ect TestClone cloneTest () { try { 11 вызвать метод clone () из класса Ob ject return (TestClone ) super . clone (); catch (CloneNotSupportedException е) { System.o ut . println ("Клoниpoв aниe невозможн о ."); return this; class Clone Demo { puЫ ic static void main (St ring args []) TestClone xl = new TestClone () ; TestClone х2 ; xl.a = 10; xl.b = 20.98; х2 = xl . cloneTest () ; // клонировать объе кт xl System.out . println ( " xl : "+xl .a +""+xl .b) ; System.out . println ("x2 : "+х2 .а +""+ х2 .Ь) ; В данном примере метод cloneTe st ( ) вызывает метод clone ( ) из класса Ob ject и возвращает получаемый результат. Обратите внимание на то , что объ­ ект, возвращаемый методом clone (), должен быть приведен к соответствующему типу (в данном случае - TestClone). В следующем примере программы метод clone ( ) перегружается таким обра­ зом, чтобы вызываться за пределами своего класса. Для этого его следует объявить с модификатором доступа puЬlic, как показано ниже. 11 Переопределить ме тод clone () class TestClone implements CloneaЫe { int а; douЫ e Ь; 11 метод clone () переопределяется теперь как puЫic puЫic Obj ect clone () { try { // вызвать ме тод clone () из класса OЬject return super.clone () ;
Гл ава 17. Пакет java.tang 535 catch (CloneNotSuppo rtedExcept ion е) System.out .println ( "Клoниpoвaниe невозможно .") ; return this; class Clone Demo2 { puЬlic static vo id main ( String args [] ) TestClone xl = new TestClone () ; TestClone х2 ; xl.a = 10; xl.b = 20.98; 11 зде сь ме тод clone () вызыв а ется непосредс твенно х2 = (TestClone ) xl . clone() ; System. out . println ("xl : "+xl .a +" "+xl .b) ; System. out .println ("x2 : "+х2 .а +" "+х2 .Ь) ; Побочные эффекты от кл онирования иногда трудно обнаружить поначалу. Ведь очень легко решить, что класс безопасен для клонирова ния, когда на самом деле это не так. Вообще говоря , реализовывать интерфейс CloneaЫe в любом классе без серьезной на то причины не стоит. Класс Class Этот класс инкапсулирует состояние времени выполнения класса или интер­ фейса. Объекты типа Class создаются автоматически при загрузке кл асса, а объ­ я влять объект класса Class явно нельзя . В общем, дл я получения объекта типа Class вызы вается метод getClass (), определенный в классе Ob j ect. Класс Class представляет обобщенный ти п, объя вляемый следую щим образом: Class Clas s<T> где Т обозначает тип представляемого класса или интерфейса. Наиболее часто употребляемые методы, определенные в классе Class, приведены в табл . 17.15 . Та бл ица 17.15. Избранные метод ы из класса Class Метод static Class<?> forName (Strinq имя) throws ClassNotFoundException static Class<?> forNaшe (Strinq имя, Ьoolean способ, ClassLoacler загрузчик) throws ClassNotFoundException <А extends Annotation> А getAn n otation (Class<A> тuп_аннотации) Описание Воз враща ет объект типа Class 1ю его п олному имени Возв ращает объект типа Class по его полному имени. Объект загружается с помощью указанного загрузчut«1- Е.сли параметр способ принимает логическое значение true, то объект инициализируется, а и на че - не ини­ циализируется Возвращает объект интерфейса An n otation, содер­ жащий аннотацию, связан ную с указанн ым типом_ аннотации для Rызыва ющего объект<�
536 Часть 11. Библиоте ка Java Метод An n otation [] getAn n otations () <А extends An n otation> А[] getAn n otationsВyТype (Class<A> тип_а1 11ютац ии) Class<?> [] getClasses () Clas sLoader ge tClassLoader () Cons tructor<T> getConstructor (Class типы_параметров) throws NoSuchМethodException , SecurityException Constructor<?> [] getConstructor s() throws SecurityException An n otation [] getDeclaredAnno­ tations () <А extends An n otation> А[] qet DeclaredAnnotationsByТype (Clas s<A> тuп_аннотtЩии) Constructor<?> [] getDe­ claredCon structors () throws SecurityException Field [] getDeclaredFields () throws SecurityZxception Мethod[] getDeclaredМ&thods () throws SecurityZxception Пр одолженш 111аол. 17.15 Описание Получает ан нотацию, связанную с вызывающим объ­ ектом, и сохраняет ее в массиве объектов интерфейса Annotation. Возвращает ссылку на массив Возвращает массив аннотаци й, в том числе и по­ вторяющихся аннотаци й, имеющих указанный тип_аннотаци и , связан ный с вызывающим объектом (добавлен в версииJDК 8) Возвращает объекты типа Class для каждого откры­ того класса и интерфейса, являющегося членом клас· са, представленного вызывающим объектом Возвращает объект типа ClassLoader, каrорый за­ грузил класс или интерфейс Возвращает объект типа Constructor, обознача­ ющий конструктор класса, предоставленного вы­ зывающим объектом, имеющим указанные типы_ параметров Получает объекты типа Constructor для каждого из открытых конструкторов класса, представленного вызывающим объектом, и сохраняет их в массиве. Возвращает ссылку на этот массив Получает объекты интерфейса An n otation для всех аннотаций, объя вленных в вызывающем объекте, и сохраняет их в массиве. Возвращает ссылку на этот массив (унаследованные аннотации игнорируются) Возвращает массив ненаследуемых аннотаций, в том числе и повторяющихся аннотаций, имеющих указа н· ный тип_ан н отации, связанный с вызывающим объ­ ектом (добавлен в версииJDК 8) Получает объекты типа Constructor для каждого из объявленных конструкторов класса, представленных вызывающим объектом, и сохраняет их в массиве. Возвращает ссылку на этот массив (конструкторы су­ перклассов игнорируются) Получает объекты типа Field для каждого из полей, объявленных в классе или интерфейсе, представлен­ ном вызывающим объектом, и сохраняет их в масси­ ве. Возвращает ссылку на массив. (Унаследованные поля игнорируются.) Получает объекты типа Иеthоd для каждого из ме­ тодов, объявленных в классе или интерфейсе, пред­ ставленном вызывающим объектом, и сохраняет их в массиве. Возвращает ссылку на этот массив. (Унаследованные методы игнорируются.)
Метод Field getField (Strinq имя_паля) throws NoSuchМethodExcep tion , securityExcep tion Field [ ] qetFields () throws Securi tyException Class<?>[] qetinterfaces () Нethod qetмethod (String и.мя_метода, Class<?> ... тиrш_параметров) throws NoSuchМethodException , SecurityException Мethod[] getмethods () throws SecurityException Strinq getName () ProtectionDoшain qetProtectionDoшain () Class<? super Т> qetSuperclass () Ьo o lean isinterface () Т newinstance () throws IlleqalAcce ssException , InstantiationException Strinq to String () Описание Гл ава 17. Пакет java.Lang 537 0кtт'ШНШ таШ. 17.15 Возвращает объект типа Field, который представ­ ляет открьrгое поле, заданное в качестве параметра имя_nаля для класса или интерфейса, предстааленно­ rо вызывающим объектом Получает объекты типа Field для каждого откры­ того поля , класса или интерфейса, предстааленного вызывающим объектом, и сохраняет их в массиве. Возвращает ссылку на этот массив При вызове для объекта, предстааляющего класс или интерфейс, этот метод возвращает массив интерфей­ сов, реализуемых данным классом. Когда этот метод вызывается для объекта, представленного интерфей­ сом, он возвращает массив интерфейсов, расширяе­ мых да нным интерфейсом Возвращает объект типа Мethod, который предСI·ав­ ляст открытый метод, обозначаемый как имя_метода и имеющий заданные тwш_параметр ов в классе или интерфейсе, представленном вызывающим объектом Получает объекты типа Мethod для каждого открыто­ го метода из класса или интерфейса, представленного вызывающим объектом, и сохраняет их в массиве. Возвращает ссылку на этот массив Возвращает полное имя класса или интерфейса типа, представленного вызывающим объектом Возвращает домен защиты , связан ный с вызывающи м объектом Возвращает суперкласс типа, нредстаменного вызы­ вающим объектом. Возвращает пустое значение null, если представленный тип относится к классу OЬj ect ил и вообще не относится к классу Возвращает логичес кое значение true , если тип, представлен ный вызывающим объе кто м, ямяется интерфейсом, а иначе - логи ческое :тачение false Создает новый экземпляр (т.е . новый объект), имеющий такой же тип, как и представлен ный вы­ зывающим объектом. Это равнозначно 11 рименению 011ератора new со стандартным конструктором класс<1 . Возвращает новый объект. Этот метод завершится не­ уда чно, если предс1·авленный тип окажется абстракт­ ным, не относя щимся к классу или не имеющим стан­ да рт ный конструктор Возвращает строковое представление типа, представ­ ленного вызы ва ющим объектом или интерфейсом
538 Часть 11. Библиотека Java Методы, определенные в кл ассе Class, ч асто применяются в тех случая х, когда требуются сведения о типе о бъекта во время выполнения. Как следует из табл . 1 7 .15, в этом кл ассе предоставляются методы , нозволяющие нолучать допол­ нител ьные сведения об определенном классе, в том числе его открытых конструк· торах , полях и методах . Это имеет значение и дл я обеспечения функциональных возможностей комп оненто в J аvа Веапs, как поясняется далее. В следующем примере программы демонстрируется применение метода getClass (), н а следуемо го из кл асса Ob j ect, а та кже метода getSuperclass ( ) , наследуе мого из клас с а Class. // Продемонс трировать получение сведений о типе 11 объе кта во время выполнения class Х { int а; float Ь; class У extends Х { douЫ e с; class RТ TI { puЫ ic static void ma in (String args[] ) { Хх=newХ(); У у=new У() ; Class<?> clObj ; clOb j = x.getCla ss () ; // получить ссылку н а объе кт типа Class System.out . println ("x - объе кт типа : " + clObj . getName () ); clOb j = y.getClas s () ; // получить ссылку на объе кт типа Class System.out . println ("y - объе кт типа : " + clOb j . getNam e() ); clObj = clObj . getSupe rclass (); System.out . println ("C yпepклa cc объе кта у: " + clObj . getName () ); Ниже приведен результат, выводи мый данной программой. х - объект типа: Х у - объект типа: У Суперкласс объе кта у: Х Клacc classLoade r Абстрактный класс ClassLoade r определяет порядок загрузки классов. В прикладной программе можно создавать подкласс ы, расширяющие класс ClassLoader, реализуя его методы. Это позволяет загружать классы не таким спо­ собом, которым выполняется обычная загрузка в исполняющей системе Java. Но делать этого обычно не нужн о.
Кпасс маtь Гл а ва 17. Пакет java.Lang 539 Этот класс обеспечивает все математические фун кции в формате с плавающей точ ко й , п р именяемые в геометрии и тригонометрии, а также некоторые методы общего назначения. В классе Ma th определены две константы типа douЫ e: Е ( рав­ ная приблизительно 2 , 72) и PI (равная примерно 3 , 14). Тригонометрические функции Методы , перечисленные в табл . 17.16, принимают параметр типа douЫ e, вы­ ражающи й угол в радианах и во звраща ю щи й результат соответствующей тригоно­ метр ической функции. Табл ица 17.16. Методы из класса мath, представляющие прямые тригонометр ические фун кции Метод Описание static douЫe sin (douЬle аргумент) Возвращ ает си нус угла, определяемого параме- тром аргумент в радианах static douЬle cos (douЬle аргумент) Возвращает коси нус угла, определяемого п<1ра- мет ром аргумент в радианах static douЬle tan (douЬle аргумент) Возвращает тангенс угла, определяем ого пара- мет ром аргумент в радианах Методы, перечисленные в табл . 17.17, принимают в качестве параметра резуль­ тат вы полнения тригонометрической функции и возвращают в качестве результа­ rа угол в радианах. Они представляют тригонометрические фун кции, обратные приведенным в табл . 17.16. Табл ица 17.17. Метод ы из класса маth, представляющие обратн ые тр игонометрические функции Метод s tatic douЬle asin (douЬle аргумент) static douЬle асов (douЫe аргумент ) static douЬle atan (douЬle аргумент) Описание Возвращает угол , синус котороп1 он рсдел нет указанный ар гумент Возвращает угол , кос инус которою оп реде­ ляет ука:�анный аргумент Возвращает угол , т<111ге11с ксn· о ро го rшреде­ ляет укаа<111ный аргумент static douЬle atan2 (douЬle х, douЬle у) Возвращаег у r·ол , т<1нгснс которого равен х/у Методы, переч исле нные в табл . 17.18, вычисляют гивер боличе<·кий синус , ко­ сину<: и та нгенс угл<�.
5'0 Часть 11. Библиоте ка Java Таблица 17.18. Методы из кл асса Маth , представля ющие ги пербол ические фун кции М етод Описание static douЫe sinh (douЫe аргумент) Возвращает гипербол ический синус угла, опре­ деляемого нараметром аргумент в радиан ах static douЫe cosh (douЫe ар гумент) Возвращает гиперболический коси нус угла, определяемого параметром аргумент в радианах static douЫe tanh (douЬle ар гумент) Возвращает гиперболический тангенс угл а, определяемого параметром ар гумент в радианах Экспоненциальные функции В классе Ма th определен ряд методов, представляющих экспоненциал ьные функции (табл . 17.19 ). Та блица 17.19. Методы из класса Ма th, предста вл яющие экспоненциальные фун кции М етод static douЬle cЬrt (douЬle ар гумент) static douЬle ехр ( douЬle ар гумент) static douЬle expml (douЬle ар гумент) static douЬle log (douЬle аргумент> static douЬle loglO (douЬle ар гумент) static douЬle loglp (douЬle аргумент) static douЬle pow (douЬle у , douЬle х) static douЬle scalЬ (douЬle аргумент , int показатель) static float scalЬ (float аргумент , int показатель) static douЬle sqrt (douЬle аргумент) Функции округлен ия Описание Возвращает кубический коре ш. из указанного аргумента Возвращает пока затель сте пени указа нного аргумента Возвращает показатель степени укааанного аргумента- 1 Возвращает натуральн ый алгоритм указанно­ го аргумента Возвращает логарифм по основанию 10 от указанного аргумента Возвращает натуральный логарифм ука а анно­ го аргумента+ 1 Возвращает у в сгепен и х, например, в резуль­ тате вызова pow (2 . О, 3.0) воавращается числовое аначение 8.О Возвращает результат аргумент х 2 .. .. ..,. ,. .,. . Возвращает результат аргумент х 2 "" "" "" "" " Возвращает квадратный корень из указанно­ го аргумента В классе Ma th определены также методы, предназначенные для выполнения различных операций округления (табл . 17.20). Обратите внимание на два метода
Гл а в а 17. Пакет java.Lang 5'1 ulp () в конце табл ицы. В данном контексте имя метода ulp означает коли-чество единиц в последнем знлке, т. е . расстояние между текущим значением и ближайшим бальшим значением. Эту особенность можно использовать для достижения нуж­ ной точности результата. Табn ица 17.20. М етоды округл е ния иэ класса ма th Метод static int аЬs (int аргумент> static long аЬs (long арrумент> static float aЬs (float ар гумент) static clouЬle aЬs (clouЬle tфг)Jмент) static clouЬle oeil (clouЬle аргумент) static clouЬle floor (clouЬle аргумент) static int floorDiv (int де.лuмое , int делите.ль) static int floorDiv (long де.лuмое, long делите.ль) static int floorМod (int делимое , int делите.ль) static int floo:rНod ( long делимое, long делите.ль) static int 11 1&Х (int х, int у) static long 11 1&Х (long х, long у) static float max(float х, float у) static douЬle max(douЬle х, douЫe у) static int Dlin (int х, int у) atatic long Dlin (long х, long у) atatic float Dlin (float х, float у) static clouЬle Dlin (douЬle х, douЫe у) static clouЬle nextA.fter (clouЬle аргумент , clouЬle направление) Описание Возвращает абсолютное значение указанного аргумента Возвращает абсолютное значение указанного аргумента Возвращает абсолютное значение указанного аргумента Возвращает абсолютное значение указанного арrумента Возвращает наименьшее целое число, которое больше или равно указанному аргументу Возвращает наибольшее целое число , которое меньше ил и равно указанному аргументу Возвращает результат целочисленного деления де.лuмое/ де.лите.ль (добавлен в версииJDК 8) Возвращает результат целочисленного деле ния делимое/ де.лите.ль (добавлен в версииJDК 8) Возвращает наименьший цел ый ост<1ток от деления делимое/де.лите.ль (добавлен в версии JIЖ 8) Возвращает наименьший цел ый остаток от деления делимое/ де.лите.ль (добавлен в версииJDК 8) Возвращает большее из двух чисел х и у Возвращает большее из двух чисел х и у Возвращает большее из двух чисел х и у Возвращает большее из двух чисел х и у Возвращает меньшее из двух чисел х и у Возвращает меньшее из двух чисел х и у Возвращает мен ьшее иэ двух чи<"ел х и у Возвращает меньшее из двух ЧИ<'СЛ х и у Начиная со значения указанного аргумента, воэвра­ щает следующее значение в заданном нлrrрОШ1еНuu. Если аргу.мен11F=направление, то возвр;нцается за­ данное направление
542 Часть 11. Библ иоте ка Java Метод static float nextAfter (float аргумент , douЬle 11апрашzение) О1аrнчан11е 11 1 абл. 17.20 Описан ие Начиная со значения указанного аргумента, возвра· щает следующее значение в заданном шmрав.ле нuи. Если аргументс=наnр&1 1J1 ение, то возвращается заданное напраsле н ш static douЬle nextDown (douЬle val) Возвращаеr сл едующее зн ачение, меньшее, чем val (добавлен в версииJDК 8) static float nextDown (float va/) Возвращает следующее значение, меньшее, чем val (добавлен в версииJDК 8) static douЬle nextUp (douЬle аргумеит) static float nextUp (float ар гумеит) static douЬle rint (douЬle аргумеит) Возвращае1· сл едующее значение в положительном направле нии от указанного ар гумента Возвращает сл едующее значение в положительном направлении от указанного аргумента Возвращает ближайшее к указанному аргументу целое значение static int round (float аргумеит) Возвращает указанный аргумент, округленный до ближайшего значения типа i.nt static lon9 round (douЬle аргумеит) Возвращает аргумент, округленный до ближайшего значения типа lonq static float ulp ( float аргумент) Возвращает количество еди ниц в последнем знаке для указанного аргумента static douЬle ulp (douЬle аргумент) Возвращает количество еди ниц в последнем знаке для указанного аргумента Прочие методы из класса Мath Помимо методов, перечисленных в приведенных выше табл ицах, в классе Math определен ряд других методов, представленных в табл . 17. 21. Обратите внимание на суффикс Exact в именах некоторых из этих методов. Эти методы были вне· дрены в версииJDК 8. Они генерируют исключение типа Ar i thme t i cExcepti on, если происходит переполнение. Следовательно , эти методы предоставляют про­ стой способ следить за переполнением при выполнении различных операций. Табл ица 17. 21 . Прочие метод ы из класса маth Метод static i.nt addExact (int аргумент] , i.nt аргум.ент2) static lonq addExact ( lonq аргументl , lonq аргумент2) static douЬle copySi.qn (douЬle аргум.ент, douЫe знак_арzумента> Описание Возвращает результат сложения аргу.мен тl+аргумент2. Ге нерирует исключение типа Ari thmeticException, ее.ли происходит переполнение (добавлен в версииJDК 8) Возвращает резул ьтат сложения аргументl+аргумент2. Ге нерирует исключение типа Ari thJaeticException, если происходит переполнение (добавлен в версииJDK 8) Возвращает указанный аргумент с таким же знаком, как и у параметра знак_ аргумента
Метод static float copySiqn(float арzумент , float ЗНQI(_ аргумента> static int decr81 118D tzxact (int аргумент) static long decr&1 11& ntExact ( lonq аргумент> Описание Глава 17. Пакет javaJang 543 Пр одолженш табл. 17. 21 Возвращает аргумент с таким же знаком, как и у параметра знтс_�та Возвращает результат агрицательного приращения �m- 1 . Ге нерирует исключение типа ArithmeticException, если происходит переполнение (добавлен в версииJDK 8) Возвращает результат агрицательного приращения �m- 1 . Ге нерирует исключение типа ArithmeticException, ec.Jiи происходит переполнение (добавлен в версииJDК 8) static int Возвращает показатель сте пени 2, используемый для двo- get.Exponent (douЬle аргумент) ичного п редставления указанного аргумента static int qetExponent (float Возвращает показатель степени 2, используемый для дво- арzуменm) ичного представления указанного ар гументл static hypot (douЬle Возвращает длину гипагенузы прямо}тольного треугол ь- тюроноl , douЬle сторона2) ника по длине двух противоположных сторон static douЬle Возвращает остаток ar деления делимое/ делитель IUEreшaincler (douЬle делимое, clouЬle делитмь) static int Возвращает результат приращения аргумент+l. Гe нe­ increшentExact (int ар гумент) рирует исключение типа ArithmeticException , ec.Jiи происходит переполнение (добавлен в версии JDK 8) static lonq Возвращает резул ьтат приращения ар гумент+ l. Гe нe- incr81 11e ntExact (lonq рирует исключение типа Arithl lle ticException, если арzумент> происходит переполнение (добавлен в версииJDК 8) static int DJ.UltiplyExact (int Возвращает результат ум ножения аргу.ментl*аргу.мент2. арzументl , int аргумент2) Iенерирует исключение типa Ari thmeticException , есл и static lonq ll lUl tiplyExact ( lonq арzументl , lonq аргу.мент2) static int neqateExact (int аргумент> происходит переполнение (добамен в версии JDК 8) Возвращает результат умножения аргу.ме1 1т l•аргумент2. Ге нерирует исключение типа Ari thmeticException, есл и происходит переполнение (добавлен в версииJDК 8) Возвращает результат агри цания - аргуметп . lенерирует исключение типа Ari thl lle ticException, ec.Jiи происхо­ дит переполнение (добамен в версииJDК 8) static lonq арtумент> neqateExact (lonq Возвращает результат от ри цан ия - аргумент. Ге нерирует исключение типa Ari.thmeticException, есл и п ро исхо­ дит переполнение (добавлен в версииJDК 8) static douЫe random () static float siqnum. (douЬle аргумент> Возвращает псевдослучайное число в пределах от О до 1 Определяет знак значения. Возвращает нулевое зна чение. если указанный аргу.ме1 1 т равен нулю; .з начение 1, если аргумент больше нуля; и значение -1 , если аргумент мень­ ше нуля
5'' Часть 11. Библ иотека Java Метод static float signwa (float аргумент) Окон'ЧаНШI табл. 17. 21 Описание Определяет знак значения. Возвращает нулевое значение, если указанный аргумент равен нулю; значение 1 , если аргумент больше нуля; и значение -1, если аргумент мень- • ше нуля sta tic int suЬtractExact (int Возвращает результат вычитания аргумент1-аргумент2. аргументl , int аргумент2) Ге нерирует исключение типа Ari thl lle ticException, если static lonq suЬtractExact (int аргументl , lonq аргумент 2 ) static douЬle toDeqre e s (douЬle угм) static int tointExact ( lonq аргумент) static douЬle toRadians (douЫe угм) происходит переполнение (добавлен в версии JDK 8) Возвращает результат вычитания аргумент1-аргумент2. Ге нерирует исключение типа Ari thl lle ticException, если происходит переполнение (добавлен в версииJDК 8) Преобразует радианы в градусы, причем заданный угм должен быть указан в радианах. Возвращает полученный результат в градусах Возвращает указанный аргумент в виде значения типа int. Ге нерирует исключение типа Ari tJuaeticException, если п роисходит переполнение (добавлен в версииJDК 8) Преобразует градусы в радианы, причем заданный угм должен быть указан в градусах. Возвращает полученный результат в радианах В следующем примере программы демонстрируется применение методов toRa dians () и toDegrees (): // Продемонстрировать приме нение методов toDeqrees () и toRadians () class Angles { puЬl ic static void ma in (String args[]) douЫ e theta = 120 .0; System. out . println ( theta + " градусов равно " + Math .to Radians ( theta) +"радиан.") ; theta = 1.312 ; System.out . println (theta + " радиан равно " + Math . toDegrees (theta ) +"градусов ."); Ниже приведен результат, выводимый данной программой. 120 .0 градусов равно 2.0943951023931953 радиан . 1.312 радиан равно 75 . 17206272116401 граду сов . Кл а сс Strictмath В этом классе определяется полный ряд математических методов, аналогич­ ных те м, что имеются в классе Маt h. Но методы из класса StrictMa th отличаются те м, что они гарантируют практически одинаковую точность во всех реализациях Java, в то время как методы из класса Ма th обеспечивают меньшую точность ради повышения производительности.
Кл асс Compiler Гл ава 17. Пакет java .lang 545 в KJ! <l('( 'C Сотр j ] е [ 1ю;щср ж1111а<"I ПI ('О:!)(а1111с Та l(ИХ cpc;1 .J a\·a . IЛС {i;1 iiт- к o ;t K0\1- llllJlllJ>Vl'TOI 11 llCllOJlllЖ'Ml.1ii 1( 0,' \ ll:Vl('l "l 'O llllTC)Hl) >C'I H ) >)'CMOl'O . в o li 1.1•1110\1 11ро 1 · р ;1:v1- м 11 рова111111 11а f а\·а :� тот кла<"<' нс 111111щ·11щтс н. Клaccы Thread, ThreadGroup и и нтерфейс RunnaЬ le В 1111·1·< · pфc iicc l\lн ;11 al> 1с·11 кл а<"сах тt·1 u�dci 11 Thl'eacJCrощ" 1ю1 1, , ·1срж1шастс н \11 10- 1 ·0 1 ю·1 ·0• 111о l' 11 р о1·1 ><1 \1 !\111ро11а1111с. ( )1111 расс:чатрнваютсн 11 11жс 110 о·ц<·;1 ы1осл1 . На заметку! Краткий обзор методик, применяемых для управления потока ми исполнения, ре ализа­ ции инте рфейса J\ 1ш 11 ar;: е и создания многопоточ ных программ. предста влен в гл аве 11. Инте рфейс RunnaЬ le :-)тот 1111·1·( ·pфc i i c /tолжс11 1i 1.1л. р с;1m1:ю11;111 в тоГ�ом кл ассе, 111111н11111 1\ 10 1 нl· ч oт­ ·'tl'Лl•llЫii llOTOI( llПIOJlllCllllH. в 11/ПС рфс iiс с Rшн1atJl е OllJ>l'/\CJ!H('T('H ТО.'1 1 >1\0 IЦllll а ()ггр;1кт11ыi i \l('TO. 't п1п i). кото р 1.1 ii служ11т то •1коii 11хо;1а 11 11ото к 11пю.· 111с11ш1. !111жс IIOI0\:1a 110, K<l lO l\I оГ�ра:Ю\1 011/H')(('JIН( "l ('H :-пот \1 СТО/\. 011 ,' \OJI ЖCll ii 1.1л. р са;1 11- :ю11а 11 в co:1;\<lll< ll' J\H.I X 1ютоках 11п1oл11l'llllН. void run () Кл асс Thread Класс Тl1гс>д<J со: 1;1<1<0т 11011ыi i 11оток 11ы1ют1с1111н. В нем рсал11:1\·<·тсн 111 1 п· рфсii с RuпГlдf) l Е; 11 01 lf>C/\('JIH IOTl'H ('Jl(','\\"IO Щll(' 11;111\i1))]('(' у1 ютрсГ�1IТ('JIl>ll l·ll' (\( 11 !(. 1 1 )\ 1\ 1 ( 1р1,1: Thread () Thread (RunnaЫe объект потока ) Thread(RunnaЫe объект-п отока , String имя_потока ) Thread (String имя потока ) Thread ( ThreadGroup группа_п отоков , Runna Ыe объект потока ) Thread (ThreadGroup группа_потоков , RunnaЬle объект-п отока , String имя_п отока ) Thread (Thre adGroup группа_п отоков , Str ing имя_п отока) 1 �'\(' 1 1 а рамстр <0 б1- t"К'7' . ПO'J 'c'l<'c) orio:111;1•1;1cт :·l lC \ ('Ml lJlllP l( Jl<l('C<t. J>C< IJl ll:\\'IOll(('! '( ) llfП('p ­ фcik R11 r11·1" 1t: 1 с 11 011р с;1<·;1 11 10 1щ·1·0 �ll'l 'To, 1'.' \С 1�;1•11111астп1 11п10 л11с1111с 11оток<1 . 1 l чн 1 ютою1 11п 1ол11<·1 1ш1 0 () 0:11 1а•1астс н 11ара мстроч имя llC"l'<J Ka . E('Jl ll :�то 11чн 11п1011- 11t·11ш1 11с УКа :1<1110. 0110 со:ц;1сто1 1111р туал 1>1 1оii '1 a 1111111oii .f\'M . fl ;1p a\l<."l ) J . , , _ ·:: ·::.'! rю т._'К< »' oiio:111a•1acт 1·р ,·1 111\' 1юто l \011, кото р о ii ri y;\c т 11р1111а,'\J1 сжал. 1н1111.1 ii 11оток 11пюл1н· 1111н . Е сл 11 Т< 1кан 1·1 "·1 111<1 1н· , ·1с1:1;111а. 110111o1ii 1юл11< 11п1ш111с 11ш1 0·1 11ос 11101 к тoii i(\ (' c1 �10 ii 1·pyi 111c, 'IТО 11 f'O,' \l l 'f'( ';1 1.cк11ii llOTO K. В KJl<I('( '( ' Тf1 1"('<1<'1 OllJ><'.'t<'Jll' lll•I CJI C,'L\'IOll\l fl' KOllC"l 'a flTJ.I :
546 Часть 11. Библиотека Java МАХ PRIORI TY MIN PRIORI TY NORМ PRIORITY Как и следов;uю ожидать, эти ко нстанты определяют максимальный, мини­ мальный и нормальный уровни приоритета потоков исполнения. Методы, опре­ деленные в кл ассе Thread, перечислены в табл . 17.22 . В ранних версиях Jаvа в со­ став кл асса Thread входили также методы stop (), suspend () и resume ().Но как пояснялось в �:лаве 11, эти методы стал и не рекомендованными к употреблению, нос кольку они де й с т ву ют неустойчиво. Не рекомендованным к употреблению является и мепщ count StackFrame s (), потому что в нем вызываются методы suspend ( ) и de stroy (),что может привести к взаимной блокировке. Табл ица 17.22 . Методы иэ класса Thread Метод static int activeCount () void checkAcce ss () static Тhread currentТhread () static void dwapStack () Описан ие Возвращает примерное количество активных потоков исполнения в той груп пе, которой принадлежит да нный поток Вынуждает диспетчер защиты проверять, спо­ собен ли текущий поток исполнения получать доступ и/или изменить тот поток исполнения , в котором вызывается метод checkAcoe ss () Возвращает объект типа Тhread, который ин­ капсулирует поток исполнения, вызывающий ЭТОТ Мt.'ТОД Выводит стек вызовов потока исполнения static int enumera te (Тhread nотоки [] ) Размещает копию объектов типа Тhread из текущей группы потоков исполнения в указан­ ный массив потокu. Возвращает количество потоков исполнения static М&p<Thread , St4ckTraceEl81 118 nt []> ge tAllStAckTraces () ClassLoader getContextClas sLoader () static Тhread . UncauqhtExceptionНandler getDefaul tOncauqh tExceptionНandler () Возвращает объект отображе ния типа на.р, который содержит трассировку стека для всех активных потоков исполнения в группе. Каждая запись в данном отображении со­ стоит из ключа в виде объекта типа Тhread и его значения в виде массива элементов типа StAckTraceElement Возвращает загрузчик контекста классов, ис· пользуе мый для загрузки классов и ресурсов, предназначенных для текущего потока испол­ нения Возвращает обработчик необрабатываемых исключений по умолчанию
Метод lonq getID () final StriDg get:Naм() final int getpriority () StacltТraodl81 18D t[J getStacltTraoe() '1'Ьжеаd. State qetState () final ТhжeadGroup qet'threadGroup () static Ьo o lean holdsLoclt (Cl>ject обr,екпа) void interrupt () static Ьo o lean interrupted () final Вo o lean isAlive () final Вoolean isDaerDOn () Ьo o lean isinterrupted О final void join О throws InterruptedException final void join (lonq �) throws InterruptedExoeption final void join ( long мwrлисек унд , int �) throws InterruptedException void run() void setcontextClassLoader (ClasвLoader d) Гл ава 17. Пакет java Jang 5'7 Продаяженштабя. 17.22 Описание Возвращает иде1Пификатор вызывающего по­ тока исполнения Возвращает имя потока исполнения Возвращает установленный приоритет потока исполнения Возвращает массив, содержащий трассировку стека вызывающего потока исполнения Возвращает состояние вызывающего потока исполнения Возвращает объект типа ТhжeadGroup, членом которого является текущий поток исполнения Возвращает логическое значение true, если вызывающий поток владеет блокировкой на заданный о615ект, а иначе - логическое зна­ чение f'alse Прерывает поток исполнения Возвращает логическое значение true, если текущий поток исполнения прерван, а иначе - логическое значение f'alse Возвращает логическое значение true, если вызывающий поток исполнения еще действу­ ет, а иначе -логическое значение f'alse Возвращает логическое значение true, если вызывающий поток исполнения является по­ токовым демоном (одним из потоков испол­ няющей системы Java ). а иначе - логическое значение f'alse Возвращает логическое значение true. если вызывающий поток исполнения прерван. а иначе - логическое значение f'alse Ожидает завершения потока ис полнения Ожидает завершения потока исполнения. в котором вызван этот метод. до истечения за­ данного количества � Ожидает завершения потока исполнения, в ко­ тором вызван этот метод, до исгечения задан­ ного количества мuл лисекунд плюс нtmoct!ICJl f д Начинает исполнение потока Устанавливает d в качестве загрузчика кон­ текста классов. используемого вызывающим объектом
5'8 Часть 11. Библиоте ка Java Метод final void setDa&l llO n(Ьoolean СОС1 11й1 11Ше ) sta.tic void setDefaul tUncau ghtExceptionИandler (Тhread . UncaughtExceptionИandler е) Окончан1и 11 1а м. 17. 22 Описание Помечает поток исполнения как потоковый демон Уст анавливает ев качестве обработчика необ­ рабатываемых исключений по умолчанию final void setNaшe (String u.мя_ потока) Усr анавливает заданное и.мя_потока для пото- final void setpriority (int npuopumem) ка исполнения Ус танавливает заданный nриоритет для пото­ ка исполнения void setUncauqhtExceptionИandler (Тhr Уст анавливает е в качестве обработчика не- еаd . Uncaugh tExceptionИandler е) обрабатываемых исключений по умолчанию для вызывающего потока исполнения sta.tic void sleep (long мил л исекунд) throws InterruptedException sta.tic void sleep ( long мил л исекунд, int �) throws InterruptedException void sta.rt () String to S tring ( ) sta.tic void yield () Кnacc ThreadGroup Прерывает выполнение потока на заданное количество мил л исекунд Прерывает выполнение потока на заданное количество мил л исекундплюс наносеку нд Запускает поток на исполнение Возвращает строковое представление потока исполнения Вызывающий поток исполнения, предлагая уступить ресурс ЦП другому потоку испол· нения В этом классе создается группа потоков исполнения. В нем определены два кон­ структора: ТhreadGroup (String мня группы) ТhreadGroup (Тhre adGroup родя!l'ельс.юtй_объе.1 1: !1', String мня_группы) В обеих формах конструктора параметр имя_ гр уппы обозначает имя группы потоков исполнения. В первой форме создается новая группа, родителем кото­ рой будет текущий поток исполнения. А во второй форме родитель группы опре­ деляется параметром роди тель ский_ объект. Методы, определенные в классе ThreadGroup и рекомендованные к употреблению, перечислены в табл. 17.23. Та бл ица 17.23. Методы из класса ТhreadGroup Метод int activeCount () Описание Возвращает прибл изительное кол ичество актив­ ных потоков исполнения в вызывающей группе, включая и потоки в подгруппах
Метод int activeGroupCount () final void checkAccess () final void de stroy () int enUDl8rate ( Thread гpyn na []) int enUDl8rate (Тhread груп па [], Ьoolean все) int enwnerate ( ThreadGroup zpyn na []) int enwnerate ( ThreadGroup zpyn na [] , boolean все) final int getмaxPriori ty () final String getName () final ThreadGroup getParent () final void interrupt () final boolean isDaemon () Ьoolean isDestroyed () Описание Гл ава 17. Пакет javaJa ng 5'9 Продолжение табл. 17. 23 Возвращает прибл изительное количество актив­ ных груп п, включая и подгруп пы, для кото рых вызывающий поток исполнения является роди­ телем Вынуждает диспетчер защиты проверять, может ли вызывающий поток исполнения получить до­ ступ к груп пе, для которой вызван метод check­ Access () , и/или изменить ее Ун ичтожает группу потоков исполнения (и лю­ бые ее подгруп пы), для которой вызывается этот метод Размещает в массиве гpyn na активные потоки ис­ полнения, входя щие в груп пу вызывающего пото­ ка, а также в ее подгруппы Размещает в массиве группа активные потоки исполнения, входя щие в груп пу вызывающего потока. Есл и параметр все принимает логическое значение true, то в массиве группа размещаются также все потоки исполнения из подгруп п дан но­ го потока Размещает в массиве груп па активные подгруп пы (включая подгруп пы подгруп п и т.л . ) из группы вызывающего потока Размещает в массиве груп па а ктивны е подгруп пы из груп пы вызывающего потока. Есл и параметр все принимает логическое значение true , то в массиве груп па размещаются также все акти в­ ные подгруп пы всех 11одгру1111 Возвращает максималы1ый приоритет, устан о в­ ленный для груп пы Возвращает имя группы Возвращает пустое значение nul l, есл и у выэы­ вающего объекта типа ТhreadGroup отсутствует роди тел ь. В противном случае возвращается ро­ дитель вызывающего объе кта Вызывает метод interrupt () для всех пот о ков исполнения в груп пе и любых ее шщгру1 111ах Возвращает л оги •1еское значение true, есл и теку­ щая группа является демоном . а иначе - логи че­ ское значение fal se Возвращает логи ческое :m ачение true , е сл и те­ кущая груп па ун ичтоже на, а иначе - ло1·ичеrкое :тачение f al s e
550 Часть 11. Библиотека Java Метод void list () 0кОН"Ulние табя. 17.23 Описание Выводит сведения о груп пе final Ьoolean parentOf ( ТhreadGroup гpyntra) Возвращает логическое значение true, если вы­ зывающий поток исполнения является родителем груп пы (или самой груп пой ), а иначе -логическое значение false final void setDa&l llO n(Вo o lean демон) final void setмaxPriority (int приоритет) String toString () void uncaught.Exception (Тhread 11D1 1WX , ТhrovaЫe е) Если параметр демон принимает логическое зна­ чение true, то вызывающая груп па помечается как демон Уст анавливает максимальный приоритет для вы­ зывающей группы Возвращает строковое представление группы Этот метод вызывается, когда исключение стано­ вится необрабатываемым Груп пы потоков испол нения позволяют управлять ими как еди ным целым. Это оказывается особенно важно в тех случаях, когда требуется п риостановить или про­ должить вы полнен ие многих взаимосвязанн ых потоков ис полнения. Допустим , имеется программа, в которой один ряд потоков исполнения ис пользуется для пе­ чати документов , другой - для отображения документа на экране , а трети й - для со­ хранения документа в файле на диске. Есл и печать прерывается , то придется пре­ рвать все потоки исполнения, имеющие отношение к печати . Такое применение групп потоков исполнения демонстрируется в следующем примере программы , где создаются две группы потоков ис полнения - по два потока в каждой . // Продемонстрировать приме не ник групп потоков исполнения class NewThread extends Thread { boolean suspendFlag; NewТhread (String threadname , ThreadGroup tgOb) { super ( tgOb, threadname ); System.out . println ( "Hoвый поток : "+this ); suspendFlag = fal se; start() ; // запустить поток исполнения 11 Точка входа в поток исполнения puЫic void run() { try { for(inti 5;i>О;i--){ System.out . println ( getName () + "· "+i) ; Thread .sleep (lOOO) ; synchroni zed (th is) { while (suspendFlag) wait () ; catch (Exception е) { System.out .pr intln ("Иcключ eниe в"+getName());
} Гл ава 17. П акет java.Lang 551 Systern. out . println ( getName (J +"завершается . " ); synchronized void rnysuspend () suspendFlag = true ; synchroni zed void myresurne () suspendFlag false; notify(J; class ThreadGroupDemo { puЫic static vo id rna in (String args [] ) ( ThreadGroup groupA new ThreadGroup ("Гpyплa А" ); ThreadGroup groupB = new ThreadGroup ("Гpyллa В" ); NewTh read оЫ NewThread оЬ2 NewThread оЬЗ NewThread оЬ4 new NewTh read ("Oдин" , groupA) ; new NewThread ("Двa", groupAJ ; new NewThread ("Tpи", groupB) ; new NewThread ("Чeтыpe ", groupB ); System. out . priпtln ("\nBывoд из мет ода list () :"); groupA . list () ; groupB. list(); System. out . printlп () ; System. out .println ( "Пpepывaeтcя Группа А" ); Thread tga[] = new Thread [ groupA. activeCouпt ()]; groupA .enumerate (tga ) ; // получить потоки исполнения из группы for(int i =О; i < tga.leпgth; i++) { ((N ewThread) tga[i] ) .rnysuspeпd (); // приос тановить каждый 11 поток исполнения try { Thread .sleep (4000) ; catch (InterruptedException е) { System. out . priпtln ( "Глaвный лоток исполнения прерван.") ; System. o ut . println ("B oзoбнoвлeниe Группы А" ); for(int i = О; i < tga.length; i++J { ((N ewThread) tga[i) J .rnyresurne(); // возобновить все потоки 11 исполн ения в группе // ожидать завершения потоков исполнения try { System.out . println ( "Oжидaниe завершения потоков исполнения ."); оЫ . join(); ob2 .join() ; obЗ .join() ; оЬ4 . join (); catch (Exceptioп е) { System.out . println ( "Иcключeниe в главном потоке исполн ения ") ; System.out . printlп ( "Глaвный поток испол нения завершен .") ;
552 Часть 11. Библиоте ка Java Ниже приведен нримерный результат, выводимый данной программой (у вас он может оказаться иным в зависи мости от кон кретной исполняющей сред ы). Новый поток : Тhrеаd [Один, 5, Группа А] Новый п оток : Тhrеаd [Дв а, 5,Группа А] Новый п оток : Тhrеаd [Тр и, 5, Группа В] Новый п оток : Тhrеаd [ Четыре ,5, Группа В] Вывод из list() : java .laпg . Thre adGroup [ пame=Гpyппa A, ma xpri=lOJ Тhrеаd [Один, 5, Группа А] Тhrеаd [Дв а, 5,Группа А] java .laпg . Thre adGroup [ пame=Гpyппa B,maxpri= lO] Тhrеаd [Три, 5,Группа В] Тhrеаd [Четыр е ,5, Группа В] Прерывается Группа А Три: 5 Четыре : 5 Три: 4 Четыре : 4 Три: 3 Четыре : 3 Три: 2 Четыре : 2 Возобновление Группы А Ожидание за вершени я потоков исполнения . Один: 5 Два: 5 Три: 1 Четыре : 1 Один: 4 Два: 4 Три завершается . Четыре завершается . Один: З Два: З Один: 2 Два: 2 Один: 1 Два: 1 Один завершается . Два завершается . Гла вный поток исполнения з а вершен . В этой программе обратите внимание на то , что выполнение группы А приоста· навливается на четыре секунды . Как подтверждает результат, выводимый данной программой, приостанавливается выполнение потоков "Один" и "Два" , но потоки "Три" и "Четыре" продолжают выполняться . По истечении четырех секунд выпол· нение потоков "Один" и "Два" возобновляется. Обратите также внимание на то, как останавл и вается и возобновляется выполнение группы А. Сначала потоки ис· полнения из группы А извлекаются пуrем вызова метода enume rate () для этой группы. Затем каждый поток исполнения приостанавливается в процессе обхода результирующего массива. Чтобы продолжить исполнение потоков в группе А, снова делается обход потоков по списку, и каждый поток запускается для продол· жения своего исполнения. И последнее замечание: в данном примере применя· ется рекомендованный подход для приостановки и возобновления потоков ис· полнения. Это не касается методов suspend () и resume (),не рекомендованн ых к употреблению.
Клacc ы тhreadLocal Иinh eritaЬleThreadLocal Гл ава 17. Пакет java.Lang 553 В пакете jav a.lang определены еще два класса, имеющих отношение к пото­ кам исполнения. • Класс ThreadLocal служит для создания локальных переменных потоков исполнения. У каждого потока исполнения будет своя копия локальной пе­ ременной. • Класс Inh eritaЫeThreadLocal служит для создания локальных перемен­ ных потоков исполнения, кото рые могут наследоваться. Класс Package Этот класс инкапсулирует данные о ве рсии пакета. Сведения о версии пакета п ри обретают особую ценность в связи с широким распространением пакетов, а также потому, что в программах нa java могут потребоваться сведения в доступ­ ной ве рсии пакета. Методы , определенные в классе Package, перечислены в табл . 17.24. А в следующем примере программы демонстрируется применение класса Pac kage для вы вода списка пакетов, сведения о которых требуются программе в данный момент: 11 Продемонс трировать применение кл асса Packaqe clas s PkgTest ( puЫic static void rnain (String arg s[]) Package pkgs [J ; pkgs = Package .getPackages () ; for(int i=O; i < pkgs . length; i++) Systern. out .println ( ); pkgs[i].getNarne() + " " + pkgs [i] .getirnpl ernen tationTitle () + " " + pkgs [ i] . get irnplernentationVendo r () + " " + pkgs [i] .getirnplernentationVersion () Табл ица 17.21.. Метод ы иэ класса Package Метод <А extends An n otation> А getAn n otation (Class<A> тип_аннотации) An n otation [] qetAnno tations () Описание Возвращает объект типa An n otation, содержа­ щий аннотацию, связанную с указанным типом_ аннотации, для вызывающего объекта Возвращает все аннотации, связанные с вы­ зывающим объектом, в массиве объекrов типа An n otation. Возвращает ссылку на этот массив
554 Часть 11. Библиоте ка Java Метод <А extends An n otation> А[] qetAnnotationsByТype (Cla ss<A> тип_ аннотации) <А extends An n otation> А[] getDeclaredAn n otation (Class<A> тип_аннотации) Annota tion [ ] getDeclaredAnnotations () <А extends Annotation> А[] getDeclaredAn n otationsВyТype (Class <А> тип_ттотации) String getiшplementationTitle () String getiшplementationVendor () String getiшplementationVersion () String getName () static Package 9etPacka9e (Strin9 имя_пакета ) static Package [] getpackages () String getЗpeci ficationTitle () String getSpecificationVendor () String getSpecificationVerвion () int hashCocle () Ьo o lean isAn n otationPresent (Class<? extends An n otation> ан но тация) Ьoolean isCoшpatiЬleWith ( Strinq оомер_версии > throws NwDЬerFormatException Ьoolean isSealed () Пр одолженu!! табл. 17.24 Описание Возвращает массив аннотаций, в том числе и по­ вторяющихся ан нотаций, имеющих указанный '1'ИП_ан н отаци и и связанных с вызы вающим объектом (добавлен в верс ииJDK 8) Возвращасr объект типа An n otation, содержа­ щий ненаследуемые анно1.tции, связан ные сука­ занным типам_аннотации (добамен в версии JDK 8) Возвращасr объект типа Annotation для всех аннотаций, объявленных в вызывающем объек­ те (наследуемые ан нотации игнорируются) Возвращасr массив ненаследуемых аннотаций , в том числе и повторяющихся аннотаций, име­ ющих указанный тип_аю ю тации и связанных с вызывающим объектом (добамен в версии JDK S) Возвращасr заголовок вызывающего пакета Возвращает наи менован ие реализатора вызыва­ ющего паксrа Возвращасr номер версии вызывающего пакета Возвращасr имя вызывающего паксrа Возвращает объект типа Package по указанному имени_пакета Возвращасr все пакеты, о которых осведом­ ляется программа, выполняющаяся в данный момент Возвращасr заглавие спецификации вызываю­ щего паке-га Возвращасr имя мадельца вызывающего пакета из его спецификации Возвращает номер версии спецификации вызы­ вающего паксrа Возвращасr хеш-код вызывающего паксrа Возвращасr логическое значение true, если описываемая аннотация связана с вызывающим объектом, а иначе -логическое значение false Возвращает логическое значение true, если ука­ занный оомер_верси и меньше ил и равен номеру версии вызывающего паксrа Возвращасr логическое значение true, если вызывающий паксr гермсrизирован, а иначе - логическое значение fal se
Метод Ьo o lean isSealed (URL url) String toString () Описание Гл ава 17. Пакет java.Lang 555 Окончание табл. 17 .24 Возвращает логическое значение true, если вы­ зывающий пакет герметизирован относительно заданного url, а иначе -логическое значение false Возвращает строковый эквивалент вызывающе­ го пакета Клacc Ru ntimePermi ssion Класс RuntimePe rmi ssion относится к механизму защиты Java и подробно здесь не рассматривается. Класс ThrowaЫe В классе ThrowaЫe поддержи вается система обработки исключений в Java. От него происходят все классы исключений. Этот класс подробно рассматривался в главе 10. Клacc securityмa nager От этого класса можно наследовать подклассы для создания диспетчера за­ щиты. Ссылку на текущий диспетчер защиты можно получ ить, вызвав метод get SecurityManager (), определенный в классе System. Клacc s tackTraceE lement В классе StackTraceEleme nt описывается единственный стековъtй фрейм - от­ дельный элемент трассировки стека при возникновении исключения . Каждый стековый фрейм представляет тачку выполнения, которая включает имя класса, метода и файла, а также номер строки исходного кода. Массив элементов типа StackTraceEleme nt возвращается при вызове метода getStackTrace () из клас­ са ThrowaЫ e. У класса StackTraceEleme nt и'м еется следующий единственный конструктор: StackTraceElement ( String ЯНR жласса , String ЯНR не�ода , String ЯНR=файла , int С!l'рОЖа) - где параметр имя_ кла сса обозначает имя конкретного класса; параметр имя_ ме тода - имя конкретного метода; параметр имя_ файла - имя конкретного файла; параметр строка - номер строки кода. В отсутствие достоверного номера строки кода в качестве параметра строка следует указать отрицател ьное значение. Более
556 Часть 11. Библ и отека Java того , з н а ч е ние -2 11ар амстра строка о;шачаст, что данный стековый фрейм ссыла­ ется на 11латфо р ме1 1 но-о р иентированный метод. Методы , 1юдд срживасмыс в кл а с с е StackTraceElement, п е р е числены в табл . 17.25. Они предоставляют про грамме доступ к трасс и р о вке се стека. Та бл ица 17.25. Методы из кл асса StackTraceElement М етод boolean equals (Object обьект) Strinq qetClasзName () Strinq qetFileName () int qetLineNwaЬer () Описан и е Возвращает логическое значение true , есл и вызывающий объект типа StackTraceElement оказы вается та ким же, как и зманный объект, а иначе - логичес кое значение false В озвращает имя кл асса , в котором то чка вы1юл­ нения описывается вызывающим объектом типа StackTraceEleшent Возвращает имя файла, где исходный код точки вьшолне­ ния о п исывается хранимым вызывающим объектом типа StackTraceEleшent Возвращает номер строки исходного код<1 , где точка выполнения описывается вызывающим объектом типа StackTraceEleшent. Иногда номер строки кода недосту­ пен, и тогда возвращается отри цател ьное значение Strinq qetмethodName () Возвращает имя метода , в котором точка выпол­ нения описывается вызывающим объе кто м типа StackTraceEleшent int hashCode () Возвращает хеш-код вызывающего объекта типа StackTraceElement boolean isNativeМe thod () Возвращает логическое значение true , если точка вы­ полнения описывается вызывающим объектом типа StackTraceEleшent, оказывающимся в п латформенно­ ориентированном методе, а иначе - логическое значение falзe Strinq toS trinq () Возвращает строковое представление вызывающей после- довател ьности Клacc Enum Как пояснялось в гл аве 12, перечисл ение - это список именованных констант. (Напомним, что перечисление создается с помощью кл ючевого слова enum. ) Все неречисления автомати чески наследуются от класса Enum. Класс Enum я вляется обобщенным и объявляется следующим образо м: clas s Enuш<E extends Enuш<E>> где Е обозначает перечислимый тип. У класса Enum отсугствуют открытые кон­ структоры.
Глава 1 7. Пакет java.Lang 557 В классе Enum определен ряд методов, доступных для использования во всех п еречислениях (табл . 17.26). Табл ица 17.26. Методы из класса Enum Метод protected final OЬject clone () throws CloneNotSUpportedException final int co11 1p areTo (E е) Описание Вызов этого метода иницииру- ет генерирован ие исключения типа CloneNotSupportedException. Этим предот- вращается клонирование перечислений Сравнивает порядковое значение двух констант одного того же перечисления. Возвращает отрицательное значение, есл и по­ рядковое значение вызывающей константы меньше е; нулевое значение, есл и порядковые значения обеих констант совпадают; и положи­ тельное значение, если порядковое значение вызы вающей константы больше е final boolean equal s (OЬject объект) Возвращает логическое значение true , если зада нный объект и вызывающий объект ссыла- ются на одну и ту же константу final Class<E> getDeclaringClass () Возвращает тип перечисления, членом которо­ го является вызывающая константа final int hashCode () final Strinq name ( ) final int ordinal () String toStrinq () atatic <Т extends EnWD<T>> Т valueOf (Class<T> тип_ перечuС1 1 енuя, String Ш!.Я) Класс ClassValue Возвращает хеш-код вызывающего объекта Возвращает неизменяемое имя вызывающей константы Возвращает значение, обозначающее позицию константы перечислимого типа в списке констант Возвращает имя вызывающей константы. Это имя может отличаться от испольаовавшегося при объявлении перечисления Возвращает константу, свяаанную с указан ным и.мене.мвзаданном типе_тфечUС1 1 енuя Применяется для связи значения с типом. Это класс обобщенного типа, опре­ деляемый следующим образом: Class ClassValue<T> Он предназначен дл я узкоспециализированного применения, а н с для обычно­ го програм мирования.
558 Часть 11. Библ иоте ка Java И нтepфe й ccharSequence В инте рфейсе CharSequence определяются методы, предоставля ющие досrуп тол ько для чтения к последовател ьности символов. Все его методы перечисле­ ны в табл . 17.27. Этот интерфейс реализуется в классах S t rin g , StringBu ffe r , StringBuilde r и пр. Табл ица 17.27 . Метод ы из инте рфейса CharSequence Метод char charAt (int индекс) default IntStream ch ars () default IntStream CodePoi nts () int len9th () CharSequence suЬSequence (int начальный_ индекс , int КQНечный_ индекс) Strinq toS trinq () Описание Возвращает символ, находя щийся на пози­ ции, обозначаемой задан ным индексом Возвращает ноток ввода-вывода (в форме ин­ терфейса IntS tream) для символ ов в вызыва· ющем объекте (добавлен в версииJDК 8) Возвращает поток ввода-вывода (в форме ин­ терфейса IntStream) для кодо вых точек в вы­ зывающем объекте (добавлен в версииJDK 8) Возвращает количество символов в вызываю­ щей последовател ьности Возвращает подмножество вызывающей последовател ьности от позиции наЧО.1 1Ь ный_ индекс и до позиции конечный_индекс- 1 Возвращает строковое представление вызыва· ющей последовател ьности Интерфейс соmраrаЫе Объекты классов, реализующих инте рфейс Compa raЫe, могут быть упорядо­ чены. Иными словами, классы, реализующие интерфейс ComparaЫe , содержат объекты, которые можно сравнивать некоторым целенаправленным образом . Интерфейс ComparaЫe является обобщенным и объявляется следующим образом : interface ComparaЫe<T> где Т обозначает тип сравниваемых объекто в. В интерфейсе Comp a raЫe объя вля· ется оди н метод , который ис пользуется для определения то го , что в языке Java на· зы вается естественнъtм упарядачением экземпляров класса. Ниже приведена общая форма этого метода . int compareTo (T объех�) Этот метод сравнивает вызывающий объект с заданным объектом. Он возвра· щает нулевое значение, если значения обоих объектов равны; отрицательное зна· чение, если вызывающий объект имеет меньшее значение, а иначе - положитель­ ное значение.
Гл ава 17. Пакет java.Lang 559 Этот интерфейс реализуется в нескольких рассмотренных ранее классах. В частности , метод соmраrеТо () определяется вклассах Вуtе, Character, DouЫ e, Float, Long, Short, String, Integer и Enum. Кроме того, объекты классов, реали­ зующих этот интерфейс , могут быть использованы в разных коллекциях, как по­ ясняется в следующей гл аве . Интерфейс АрреndаЬlе Объекты классов, реализующих интерфейс AppendaЫe, позволяют добавлять к ним символы или символ ьные последовательности. В интерфейсе Ap pendaЫe определяются три формы методов: AppendaЫe append (char CJl l Нl l OЛ) throws IOException Ap p endaЫe append (CharSequence CJl l .NВOJПl) throws IOException AppendaЬle append (CharSequence СJ1 1 нвоJП1 , int начало , int жонец) throws IOException В первой форме заданный симв ол добавляется к вызывающему объекту. Во вто­ рой форме к вызывающему объекту добавляется последовательность символов, обозначаемая параметром символы. А третья форма позволяет указать часть по­ следовательности символов (от позиции на чало до позиции конец- 1 ) , обознача­ емой параметром симв олы. Но в любом случае возвращается ссылка на вызываю­ щий объект. Инте рфе й с IteraЬle Интерфейс IteraЫe должен быть реализован всеми классами, объекты кото­ рых предполагается использовать в цикле for в стиле for each. Иными словами, чтобы использовать объект в цикле f or в стиле f or е ach, его класс должен реализо­ вывать интерфейс I teraЫe. Этот интерфейс является обобщенным и объявляется следующим образом: interface IteraЫe<T> где Т обозначает тип объектов, итерируе мых (т.е . перебираемых) в цикле. В ин­ терфейсе IteraЫe определяется метод i terator (),который возвращает итера­ тор элементов, содержащихся в вызывающем объекте : Iterator<T> iterator () Начиная с версииJDК 8 в интерф_е йсе IteraЫe определяются также два мето­ да по умолчанию. Первый из них - forEach () -объя вл яется следующим образом: default vo id forEach (ConsWl l er<? super Т> де�iс�аяе) Для каждого перебираемого в цикле элемента метод forEach ( ) выполняет код, определяемый параметром действие. Здесь Consume r обозначает функцио­ нальный интерфейс , внедренный в версии JDК 8 и определяемый в пакете j ava . ut il . function (он будет рассмотрен в гл аве 19).
560 Часть 11. Библиоте ка Java Второй метод 110 умолчанию - splitera t o r () - объявляется следую щим об­ разом: default Sp literator<T> spliterator () Этот метод во:шращает объект типа Spl i t: e r а t о r в виде итерато ра-разделите­ ля для перебираемой в цикле посл едовател ьности . (Подробнее об итераторах-раз· делител ях речь пойдет в гл авах 18 и 29.) На заметку! Итераторы рассматри ваютс я в гл аве 18. Интерфейс ReadaЫe Интерфейс ReadaЫe указывает на то , что объект может быть использован в качестве источника для чтения символов. В этом интерфейсе определен един· ственный метод read (), как показано ниже. int read ( CharВuffer буфер) throws IOException Этот метод читает символы в задан ный буфер. Он возвращает количество про­ читанных символов или значение -1, если достигнут знак конца файла (EOF). И нтерфейс Au toCloseaЬle Интерфейс Au toC l oseaЫe обеспечивает поддержку оператора try с ресурса· ми, реал изующего механизм, который иногда называют автоматuч,еrким управленшм ресурса.ми (ARM). Оператор try с ресурсами автоматизирует процесс ос вобожден ия ресурса вроде потока ввода-вывода , когда он больше не нужен. (Подробнее о этом с м. в �лаве 13.) То лько объекты классов , реализующих интерфейс Au t осl о s е а Ы е , мoryr применяться вместе с оператором try с ресурсами. В интерфейсе Au toCloseaЫe оп ределяется единственный метод close (),как показано ниже. void close () throws Exception Этот метод закрывает вызыва ющий объект, освобождая любые ресурсы, ко­ то рые он мог уде ржи вать. Он автоматически вызывается в конце оператора try с ресурсами, избавляя таким образом от необходимости явно вызывать метод close ( ). Интерфейс Aut oCloseaЫe реализуется несколькими классами , в клю­ чая все классы, открывающие поток ввода-вывода , который может быть закрыт. Интe pфeй cThread . UncaughtExcep tionНandler Статический интерфейс Thread . UncaughtExceptionHandl er реализуется классами, в которых требуется обрабатывать необрабатываемые исключения. Он реализуется, в частности , в классе ThreadGroup. В этом инте рфейсе объя влен еле· дующий единственный метод: void uncauqhtExcep tion (Тhread по�ож , ТhrowaЫe мсхлюvеюwе)
Гл ава 1 7 . Пакет java .Lang 561 где параметр по ток обозначает ссылку на поток исполнения , сгенерировавший ис ключение, а исключение - ссылку на это исключение. Подпакеты из пакета в j ava . lang ВJava определен ряд следующих подпакетов, входящих в состав пакета j а vа . 1 ang: • java .lang . annotation • jav a.lang . instrument • java .lang . invoke • jav a.lang .management • java.lang.ref • java.lang . reflect Далее каждый из этих подпакетов описывается вкратце . Пaкeт java .lang . annotation Средства аннотирования в Java поддержи ваются с помощью пакета j а va . l ang .annotation. В этом пакете определяются интерфейс Annotation, а также перечислен ия Eleme ntTyp e и Re tent ionPol icy. (Интерфейс Anno tation описы­ вался в главе 12.) Пaкeт java .lang . instrument В этом пакете предоставляются средства , которые могут быть использо­ ваны для дополнения необходимым инструментарием различных аспектов выполнения программ. В нем определяются инте рфейсы Instrumentation и ClassFileTrans former, а также клас с ClassDefinition. Пaкeт java .lang . invoke В этом пакете подде ржи ваются динамические .н:Jы ки. Он содержит та кие клас­ сы , как Ca llSite, Me thodHandle и MethodType. Пaкeт java .lang .managemen t Обес печ и вает подде ржку управления виртуал ьной машиной JVM и исполняю­ щей сред ой. Используя средства из пакета java.lang .management, м ожно про­ сматривать различные ас пекты выполнения программы и унравлять ими.
562 Часть 11. Библиоте ка Java П a кeт java .lang .ref Как уп оминалось ранее , средства сборки "мусора" в Java автоматически опре­ дел яют момент, когда нс остается ссылок на объект. И тогда предполагается , что этот объект бол ьше нс требуется, а занятую им память можно ос вободить. Классы из пакета j ava . lang . re f предоставляют возможности более гибкого управления процессом сборки "мусора". Пa кeт java .lang . reflect Рефлексия - это способность программы анали:шровать код во время выполне· ния. Пакет java . lang . reflect позволяет получать сведения о полях, конструк­ торах, методах и модифи каторах класса. Помимо прочего, эти сведения могуr понадобиться для созда ния программных инструментов, которые позволяют ра­ боrать с компонентами JavaBeans. В таких инструментах рефлексия использует­ ся для динамического определения характеристик компонента. Рефлексия была представлена в гл аве 12 и также рассматривается в гл аве 30. В пакете java . lang . reflect определяется несколько классов, в том чис­ ле Me thod, Field и Constructor, а также несколько интерфейсов, включая AnnotatedElemen t, Member и Туре . Помимо этого , в состав пакета java . lang . reflect входит класс Array, позволя ющий динамически создавать массивы и опе­ рировать ими.
18 П акет java . util, ч аст ь 1. CoLLections Fra mewo rk С этой гл авы начинается рассмотрение классов и интерфейсов, определен­ н ых в пакете java.util. Этот важн ый пакет предлагает большой выбор классов и интерфейсов, поддерживающих обширный ряд фун кциональных возможно­ сте й. В частности, в пакет j а v а . и t i 1 входят классы, создающие псевдослучайные ч исла, управля ющие датой и временем, просмотром событий, манипулирующие наборами битов, выполняющие синтаксический анализ символьных строк и об­ раб атывающие форматированные данные. В пакет j ava . util входит также одна из самых эффективных подс истемjаvа - каркас коллекций Collectioпs Fгamewoгk. Этот каркас представляет собой сложную иерархию интерфейсов и классов, реа­ л изующих современную технологию управления группами объектов. Он заслужи­ вает пристального внимания всех программирующих нajava. Пакет j а va . и t i 1 предоставляет обширный ряд функциональных возможностей, поэтому он достаточно объемный. Ниже приведен перечень его основных классов. AЬstractCollection Fora a ttaЬleFlaga Properties AЬstractList Fora a tter PropertyPeraission AЬstractмap GreqorianCalendar PropertyResourceВundle AЬstractQueue BashМap RandOID. AЬstractSequentialList BashSet ResourceВundle AЬstractSet BashtaЬle Scan n er ArrayDeque Identi tyBashМap ServiceLoader ArrayLi st IntSUJ1 11 1.a ryStatistics Sill lp leTiJD.eZone (добавлен в версии.JDК 8) Arrays LinkedВashМap Spliterators (добавлен в версииJDК 8) Ваsе64 LinkedВashSet Spl itaЬleR.andoJD. (добавлен в версии.JDК 8) (добавлен в версииJDК 8) BitSet LinkedLi st Stack Calendar List:ReaourceВundle StringJoiner (добавлен в версии IDK 8) Collections Locale StringТokeni zer CUrrency LongSU81 11a ryStatistics T.iJl le r (добавлен в веосии fDK 8) Date OЬjects TiJD.erTask Dictionary OЬservaЬle TiJD.eZone
56' Часть 11. Б иблиоте ка Java DouЬleSUJ1 11 11A ryS tatistics Optional (добавлен в версии_JОК 8) (добавлен в версии.JОК 8) EnumМap OptionalDouЬle (добавлен в версии JDК 8) EnumSet Optionalint (добавлен 11 версии JDK 8) Even tLi s tenerProxy OptionalLonq (добавлен в версииJDК 8) EventObject PriorityQueue ТrееМар TreeSet UUID Ve ctor WeakHashКAp В пакете jav a.util определены следующие интерфейсы: Collection Мap . Entry Set CoJDPa rator NaviqaЬleМap SortedМap Deque NavigaЬleSet SortedSet Enumeration Observer Spliterator (добавлен в версииJDК 8) Even tLi s tener Pril lli. tive iterator Spliterator . OfDouЬle (добавлен в версии.JDК 8) (добавлен в версии JDK 8) FormattaЬle Prill li tiveiterator . OfDouЬle Spl iterator .Ofint (добавлен в ве рсии JDK 8) (добавлен в версии JDK 8) Iterator Pril lli. tiveiterator .Ofint Spliterator . OfLong (добавлен в ве рсии JDK 8) (добавлен в версии JDK 8) List Pril lli. tiveiterator . OfLonq Spliterator .OfPrill li tive (добавлен в версииJDК 8) (добавлен в версииJDК 8) Listiterator Queue Мар RandoDIAccess Из-за большого размера пакета j а v а . u t i l его описание разделено на две rла· вы. Эта гл ава посвящена средствам из каркаса коллекций Collectioпs Fraшework, входящего в пакет j а vа . u t i 1. А в гл аве 19 рассматриваются остальные классы и инте рфейсы из этого пакета. Краткий обзор коллекций Каркас коллекций Collectioпs Framework вJava стандартизирует способы управ· ления группами объектов в прикладных программах. Коллекции не были частью исходной ве рсии языка Jаvа , но были внедрены в версии J2SЕ 1.2. До появления каркаса коллекций для хранения групп объектов и манипулирования ими в Java предоставлялись такие специальные классы, как Dict ionary, Vector, Sta ck и Р rope rti е s. И хотя эти классы были достаточно уд обны, им недоставало общей, объединя ющей основы. Так, класс Ve ctor отличался способом своего применения от класса Properties. Такой первоначальный специализированный подход не был рассчитан на дальнейшее расширение и адаптацию. Для разрешения этого и ряда других затруднений и были внедрены коллекции. Каркас коллекций был разработан для достижения нескольких целей. Во­ первых, он должен был обеспечивать высокую производительность. Реализация основных коллекций (динамических массивов, связных списков, деревьев и хеш·
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Framework 565 таблиц) отличается высокой эффективностью. Программировать один из таких "механ измов доступа к данным" вручную приходится крайне редко. Во-вторых, каркас должен был обеспечивать единообразное функционирование коллекций с высокой степенью взаи модействия. В -третьих, коллекции должны были до­ пус кать простое расширение и/или адаптацию. В этом отношении весь каркас коллекций построен на едином наборе стандартных интерфейсов. Некоторые стандартн ые реализации этих интерфейсов (например, в классах L i nkedList, H a shSet и Tree Set) можно использовать в исходном виде. Но при желании мож­ но реализовать и свои коллекции. Для уд обства программистов предус мотрены разл ичные реализаци и специального назначения, а также частичные реализации, которые облегчают создание собственных коллекций. И наконец, в каркас коллек­ ций были внедрены механизмы интеграции стандартных массивов. Алгоритмьt составляют другую важную часть каркаса коллекций. Алгоритмы оперируют коллекциями и определены в виде статических методов в классе Collections. Та ким образом, они доступны всем коллекциям и не требуют реали­ зации их собственной версии в каждом классе коллекции. Алгоритмы предостав­ ля ют стандартные средства для манипул ирования коллекциями. Другим элементом, тес но связанным с каркасом коллекций, является интерфейс Iterator, определяющий итератор, который обеспечивает общий, стандартизиро­ ванн ый способ поочередного доступа к элементам коллекций. Иными словами, ите­ ратор предоставляет способ перебора содержимого кол лек ций. А поскольку каждая кол­ лекция предоставляет свой итератор, то элементы любого класса коллекций могут быть доступ ны с помощью методов, определенных в интерфейсе Iterator. Та ким образо м, код, перебирающий в цикле элементы множества, можно с минимальны­ ми изм енениями применить, например, для перебора элементов списка. В версии JDK 8 внедрена другая разновидность итератора, называемая ите· раторомразде.лителем. Если говорить коротко, то итераторы-разделители обеспе­ чи в ают параллельную итерацию. Итераторы-разделители поддерживаются в ин­ терфейсе Spli terator и ряде вложенных в него интерфейсов, которые, в свою очередь, поддерживают примитивные типы данных. В версииJDК 8 внедрены так­ же интерфейсы итераторов, предназначенные для применения вместе с прими­ тивными типами данных. К их числу относятся интерфейсы Primi ti ve lterator и Primitiveiterator . OfDouЫe. Помимо коллекций, в каркасе Collections Framework определен ряд интерфей­ сов и классов отображений, в кото рых хранятся пары "ключ-значение". Несмотря на то что отображения входят в состав каркаса коллекций, строго говоря, они не я вля ются коллекциями. Те м не менее для отображения можно получить представле­ нш кол лек ции. Та кое представление содержит эл ементы отображения , хранящиеся в коллекци и. Та ким образом, содержимое отображения можно при желании об­ раба ты вать как колле кцию. Механизм коллекций был ус овершенствован для некоторых классов, изначаль­ но определенных в пакете j ava . util таким образом, чтобы интегри ровать их в новую систему. Но несмотря на то что внедрение коллекций изменило архитек­ туру многих первоначальных служебных классов, они не стали от этого нереко­ мендо ван ными к употреблению. Коллекции просто предлагают луч шее решение некоторых задач.
566 Часть 11. Библиотека Java На заметку! Есл и у вас имеется некоторый опыт программирования на С++, то вам может по­ мочь сходство коллекций в Java со ста ндартной библ иотекой шаблонов (STL), определенной в С++. То , что в С++ назы вается контейнером, в Java именуется коллекцией. Но у ка ркаса колл екций Collectioпs Fra mework и библиотеки STL имеются существенные отличия. Поэтому не делайте поспешных выводов. Изменения каркаса коллекций в версии JDK5 С выходом верс ии JDK 5 в каркасе коллекций Collections Framework произо­ шел ряд существенный изменений, значительно повысивших его эффективность и упростивших его применение. К числу этих изменений относится внедрен ие обобщений, автоматическая упаковка и рас паковка , а также орган изация цикла for в стиле for each. И хотя JDК 8 является уже третьей основной версией jаvа, появившейся после выпус ка JDK 5, последствия изменений , внесенных в каркас коллекций в версии JDK 5, настолько значительны , что они до сих пор заслужи· вают особого внимания. Основная причина состоит в то м, что по-прежнему су· ществует и экс плуатируется большой объем кода, написанного до версии JDK 5. Понимать последствия и причины этих изменений очень важно на тот случай, если придется сопровождать или обновлять устаревший код. Обобщения коренным образом изменил и каркас коппе кций Внедрение обобщений коренным образом изменило каркас коллекций, по­ скол ьку он был полностью переделан в связи этим нововведением. Все коллекции те перь являКУГся обобщенными, и многие методы, оперирующие коллекциями, также принимают обобщенные параметры. Проще говоря , внедрение обобщений коснулось всех частей каркаса коллекций. Обобщения внесли в коллекции именно то, чего им явно недоставало: типовую безопасность. Раньше во всех коллекциях хранились ссылки на класс Ob j ect, а это означало , что в любой коллекции могли храниться объекты любого типа. Та ким образом, в одной коллекции можно было непреднамеренно сохранить несовме­ стимые ти пы данных. А это могло привести к ошибкам несовместимости типов во время выполнения. Благодаря обобщениям теперь можно явным образом указать тип сохраняемых да нных и тем самым избежать подобных ошибок во время вы­ полнения. Несмотря на то что внедрение обобщений изменило объявления большинства классов и интерфейсов, а также некоторых их методов, в целом каркас коллекций действует таким же образом, как и до появления обобщений. Безусловно, чтобы извлечь выгоду, которую обобщения приносят коллекциям, придется переписать устаревший код. Это важно сделать еще и потому, что при компилирован ии кода, написанного до появления обобщений, современный компилятор будет выдавать предупреждающие сообщения. Во избежание подобных сообщений в код всех соз· даваемых коллекций придется ввести сведения об их типе.
Гл а ва 18. Пакет java.utiL, часть 1. CoLLections Framework 567 В средствах автоматической упаковки используются при м итивные ти п ы данных Автоматическая упаковка и распаковка упрощают сохранение данных примитив­ н ых типов в коллекциях. Как будет показано далее , в коллекциях можно сохранять только ссьшки , но не значения примитивных типов. Если раньше требовалось со­ хранить в коллекции значение примитивного типа вроде in t, то его приходилось вруч ную упаковывать в оболочку данного типа. А когда значение извлекалось из обо­ лочки, нужно бьшо его вручную распаковывать, приводя его явным образом к сооl'­ ветствующему примитивному типу. Уп аковка и распаковка осуществляются теперь в Java автоматически, когда требуется сохранять или извлекать дан ные примитив­ ных типов. Та ким образом, выполнять эти операции вручную больше не нужно. Цикп for в стиле for each Все классы в каркасе коллекций усовершенствованы та к им образом, чтобы реализовы вать интерфейс IteraЫe. Это означает, что содержимое коллекции можно перебрать, организовав цикл for в стиле for each. Раньше для перебора соде ржимого коллекции нужно было использовать итератор, рассматриваемый далее в этой гл аве , организуя цикл вручную. Несмотря на то что итераторы по­ п режнему применяются для некото рых целей, во многих случаях циклы на основе итерато р ов могут быть заменены циклами for в стиле for each. Интерфейсы коллекций В каркасе коллекций определяется несколько интерфейсов. В этом разделе де­ лается кратки й обзор каждого из них. Начать рассмотрение коллекций с их интер­ фейсов следует потому, что они определяют саму сущность классов коллекций. А ко нкретные классы лишь предоставляют различные реализации стандартных ин­ терфейсов. Интерфейсы, поддерживающие коллекции, перечислены в табл . 18.1. Табл ица 18.1. Инте рфейсы, подде ржи вающие колл екции Интерфейс Описание Collection Позволяет рабагать с груп пами объектов. Находится на вершине иерархии коллекций Deque Расширяет интерфейс Queue для организации двусторонних очередей List Расширяет интерфейс Collection для управления последовательностями (списками объектов) NaviqaЬleSet Расширяет интерфейс SortedSet для извлечения элементов по результатам поиска ближайшего совпадения Queue Расширяет интерфейс Collection для управления специальными типами списков, где элементы уд аляются только из начала списка Set Расширяет интерфейс Collection для управления множествами, которые должны содержать однозначные элементы SortadSet Расширяет интерфейс Set для управления агсортированными множествами
568 Часть 11. Библиотека Java Помимо перечисленных выше интерфейсов, для составле ния коллекций ис· пользуются интерфейсы Compa rator, Ra ndomAccess, Iterator и Listiterator, которые подробнее рассматриваются далее в этой гл аве. А начиная с версииJDК 8 к их числу принадлежит также интерфейс Spliterator. Есл и говорить кратко , то инте рфейс Comparator определяет два сравниваемых объекта, а интерфейсы Iterator, Listiterator и Spl i terator перечисляют объекты в коллекции. Если же список реал изует инте рфейс RandomAc ce ss, то тем самым он поддерживает эффективный произвольный доступ к своим элементам . Ради обеспечения максимальных удобств применения интерфейсов коллекци й некоторые методы в них м01уг бьпъ необязательными. Необязательные методы позволяют видоизменять содержимое коллекций. Коллею�ии, поддерживающие такие методы, называются измен.яе м ЪIМ·и, а кол л екции, не позволяющие изменять свое содерж имое, - неизменяемъtми. Если предпринимается попытка вызвать оди н из этих методов для неизменяемой коллекции, то генерируется исключение типа UnsupportedOperat ionException. Все встроенные коллекции являются изменяе­ мыми. В последующих разделах подробно рассматриваются интерфейсы коллекций . И нтерфейс соllесtiоn Этот интерфейс служит основанием, на котором построен вес ь каркас коллек· ций, поскольку он должен быть реализован всеми классами коллекций. Интерфейс Collection является обобщенным и объявляется следующим образом: interface Collection<E> где Е обозначает тип объектов, кото рые будет содержать коллекция. Интерфейс Co llection рас ширяет интерфейс IteraЫe. Это означает, что все коллекции можно перебирать, организовав цикл for в стиле for each. (Напомним, что тол ь· ко те клас сы, кото рые реализуют интерфейс I t e r аЫе, позволяют перебирать их эл ементы в цикле for.) В инте рфейсе Collection определяются основные методы , кото рые должн ы иметь все коллекции. Эти методы перечислены в табл. 18.2 . В связи с тем что все коллекции реализуют интерфейс Collection, знакомство с его методами требует· ся для ясного понимания каркаса коллекций. Некоторые из этих методов могут ге­ нерировать исключение типа Un s upportedOpe rationExcept ion. Как пояснялось ранее, это исключение возникает в том случае , если коллекция не может быть из· менена. Исключение типа Clas sCas tException генерируется в том случае, если объекты несовместимы, например, при поп ытке ввести несовместимый объект в коллекцию. Исключение типа NullPointerException генерируется при попыт· ке ввести пустое значение nul 1 в коллекцию, не допускающую наличие пустых эле­ ментов . Исключение типа IllegalArgumentException генерируется в том слу· чае , есл и указан неверный аргумент. А исключение типа IllegalStateExcepti on ге нерируется при попытке ввести новый элемент в заполненную коллекцию фик· сированной дл ины.
Гл ава 18. П акет java.utiL, часть 1. CoHections Framework 569 Табл ица 18.2. Методы иэ инте рфейса Collection Метод Ьoolean add (Е отекm) Ьoolean addAll ( Collection< ? exten ds Е> с) void clear () boolean contains (OЬject о&екm) Ьoolean containsAll (Collection<?> с) Ьoolean equals (Object объект) int hashCode () Ьoolean isEJl lp ty () Описание Вводит заданный объект в вызывающую коллекцию. Возвращает логическое значение true, если объект успешно введен в коллекцию. А если объект уже присуrствует в коллекции, кото рая не допускает ду­ бл ирование объектов, то возвращается логическое значение false Вводит все элементы заданной коллекции с в вызы­ вающую коллекцию. Возвращает логическое значе­ ние true, если коллекция изменена (т.е. все элемен­ ты введены), а иначе - логическое значение false Уд аляет все элементы из вызывающей коллекции Возвращает логическое значение true, есл и задан­ ный объект явл яется элементом вызывающей кол­ лекции, а иначе - логическое значение fal se Возвращает логическое значение true, если вызы­ вающая колл екция соде ржит все эл ементы заданной коллекции с, а иначе - логическое значение false Возвращает логическое значение true, есл и вызы­ вающая коллекция и заданный объект равнозначны, а иначе - логическое значение false Возвращает хеш-код вызывающей коллекции Возвращает логическое значение true, если вы­ зы вающая колл екция пуста , а иначе - логическое значение false Itera tor<E> i terator ( ) Возвращает итератор дл я вызывающей коллекции default Stream<E> Возвращает поток, использующий вызывающую parallelStreaш. ( ) коллекцию в качестве источ ника для ввода-вывода эл ементов. В этом потоке поддерживаются парал­ лельные операции ввода-вы вода , сел и это вообще возможно (добавлен в версииJDК 8) Ьoolean remove (Object объект) Уд ал яет од ин экземпляр объектл из вызывающей коллекции. Возвращает логи ческое значение true , если элемент удален, а иначе - логическое значение false Ьoolean ruoveAll (Collection<?> с) default boolean r81 110 ve if ( Predi cate<? super Е> преUикат) Уд аляет все элементы заданной колл екции сиз вызывающей коллекции. Возвра щает логи ческое значение true, есл и в ко нечном итоге коллекция из­ меняется (т.е . элементы из нее удале ны), а иначе - логическое значение fal se Уд аляет из вызывающей коллекции элементы, удов­ летворя ющие условию, которое задает предикат (до­ бавлен в версии .JDК 8)
570 Часть 11. Библиоте ка Java Метод boolean retainAll (Collection<?> с) int size () de fault Spliterator<E> spliterator () default StreaJD<E> streaш () Ob ject [] toArray () <Т> Т[] toArray (T массив[] ) Оканчанuе таlМ. 18.2 Описание Уд аляет из вызы вающей коллекции вес элементы, кроме эл ементов заданной коллекции с. Возвращает логическое значение true, есл и в ко нечном итоге коллекция изменяется (т.е. элементы из нее уд але­ ны), а иначе -логическое значение fal se Возвращает количество эл ементов, содержащихся в коллекции Возвращает итератор-раздел итель для вызывающей коллекции (добавлен в версииJDК 8) Возвращает поток, использующий вызывающую коллекцию в качестве источника для ввода-вывода элементов. В этом потоке поддерживаются последо­ вательные олсрации ввода-вывода (добавлен в вер­ сии JDК 8) Возвращает массив, содержа щий все элементы вы­ зывающей коллекции. Элементы массива являются копиями элементов коллекции Возвращает массив, содержащий элементы вызыва­ ющей коллекции. Элементы массива являются ко пи­ ями эл ементов коллекции. Если размер заданного мас сива равен количеству элементов в коллекции, они возвращаются в этом мас сив е. Ее.ли же размер мас сива меньше количества элементов в коллекции , то создается и возвращается новый массив нужного размера. А если размер мас сива больше количества элементов в коллекции, то во всех элементах, сле­ дую щих за последним из коллекции, устанавл ива­ ется пустое значение nul l. И если любой элемент коллекции относится к типу, не являющемуся под­ тилом .мас сив а, то генерируется исключение тила ArrayStoreException Объекты вводятся в коJШекции методом add ( ) . Следует, однако , иметь в виду, что метод add ( ) принимает аргумент типа Е. Следовательно , добавляемые в коJШекцию объекты должны быть совместимы с предполагаемым типом данных в коллекции. Вызвав метод addA l 1 ( ) , можно ввести все содержимое одной коллекции в другую. Вызвав метод remo ve (), можно уд алить из коллекции отдел ьный объект. Для того чтобы из коллекции уд алить группу объектов, достато чно вызвать метод r e ­ mo veAll (). А для того чтобы уд алить из коллекции все элементы , кроме указан­ ных, следует вызвать метод retainAll () . В версииJDК 8 появилась возможность удал ить элемент из коллекции, если он удовлетворяет условию, кото рое задается в качестве параметра пр едика т при вызове метода remo ve I f ( ) . Этот параметр 01" носится к ти пу функционального интерфейса Predicate, внедренного в версии JDK 8, как поясняется в гл аве 19. И наконец, для очистки коллекции достаточно вызвать метод clear ().
Гла в а 18. Пакет java.utiL, часть /. CoLLections Fra m ework 571 Имеется также возможность определить, содержит ли коллекция определен­ ный объект, вызвав метод contains () . Чтобы определить, содержит ли одна кол­ лекция все члены другой, следует вызвать метод containsAl l ( ) . А определить, пуста ли коллекция, можно с помощью метода isEmp ty (). Количество элементов, содержащихся в данный момент в коллекции, возвращает метод size (). Оба метода toAr ray () возвращают массив, который содержит элементы , хра­ нящиеся в коллекции. Первый из них возвращает массив класса Ob j ect, а вто­ рой - масс ив элементов того же ти па, что и массив, указанный в качестве пара­ метра этого метода. Обычно второй метод более предпочтителен , поскольку он возвращает массив элементов нужного типа. Эти методы оказываются важнее , чем может показаться на первый взгляд. Ведь обрабатывать содержимое комек­ ции с использованием синтаксиса массивов нередко оказывается очень выгодно. Обеспечив связь коллекции с масс ивом, можно выгодно воспол ьзоваться преиму­ ществами обоих языковых средств Java. Две коллекции можно сравнить на равенство , вызвав метод equa ls (). То чный смысл равенства может зависеть от конкретной коллекции. Например, метод equals () можно реализовать таким образом, чтобы он сравнивал значения эле­ ментов, хран имых в комекции. В качестве альтернативы методу equal s ( ) можно сравнивать ссылки на эти элементы . Еще один очень важный метод i terator () возвращает итератор коллекции. Новый метод s р 1 i t е r а t о r ( ) возвращает итератор-разделитель для коллекци и. Итераторы очень часто испол ьзуются для обращения с коллекциями. И наконец, методы stream () и paral lelStream () возвращают поток в виде объекта интер­ фейса Stream, использующий комекцию для ввода-вывода элементов (подробнее новый интерфейс Stream рассматривается в гл аве 29). Интерфейс List Этот интерфейс расширяет инте рфейс Collection и определяет такое поведе­ ние комекций, которое сохраняет последовательность элементов. Элементы мо­ гут быть введены или извлечены по индексу их позиции в списке, начиная с нуля. Список может содержать повторяющиеся элементы . Интерфейс List является обобщенным и объявляется приведенным ниже образом , где Е обозначает тип объектов, которые должен содержать список. interface Li st<E> Помимо методов, объявленных в интерфейсе Co llection, в интерфейсе List определяется ряд своих методов, перечисленных в табл. 18.3 . Однако некоторые из этихметодов генерируют исключение типаUn suрро rtеdОре rаtiоnЕхсерtiоn, если комекция не может быть видоизменена, а исключение типа ClassCastException генерируется, если объекты неоовместимы, например, при попытке ввести в спи­ сок элемент несовместимого типа. Кроме того, некоторые методы генерируют ис­ ключение типа IndexOu tOfBounsException, если указан неверный индекс. А ис­ ключение типа Nul lPointerException генерируется при попытке ввести в список пустой объект со значением null, когда пустые элементы в данном списке не до пу­ скаются. И наконец, исключение типа IllegalArgumen tException генерируется при указании неверного аргумента.
572 Ч асть 11. Библиоте ка Java Табл ица 18.З. Методы иэ инте рфейса List Метод void ac:ld(int индекс, Е обl;екm) Ьoolean addAll (int индекс , Collection<? extends Е> с) Е qet(int индекс) int indexOf (OЬject обl;екm) int lastindexOf (OЬject o&elan) Описание Вводит заданный оlЛ;ект на позиции вызывающего списка по указанному индексу. Любые введенные ранее элементы смещаются, начиная с указанной позиции и далее к началу списка. Это означает, что элементы в списке не перезаписываются Вводит все элементы из коллекции с в вызывающий список, начиная с позиции по указанному индексу. Введенные ранее элементы смещаются, начиная с указанной позиции и далее к началу списка. Это означает, что элементы в списке не перезаписывают­ ся. Возвращает логическое значение true, если вы­ зывающий список изменяется, а иначе - логическое значение false Возвращает объект, хранящийся в вызывающем спи­ ске на позиции по указанному индексу Возвращает индекс первого экземпляра заданного обr.екта в вызывающем списке. Если заданный обr.ект отсугсгвует в списке, возвращается значение -1 Возвращает индекс последнего экземпляра заданного обr.екта в вызывающем списке. Если заданный обr.ект отсугсгвует в списке, возвращается значение - 1 Listi terator<E> listiterator () Возвращает итератор дляобхода элементов с начала Listi terator<E> listi terator (int индекс) Е remove (int индекс) default void replaceAll (UnaryOperator<E> opToAp p ly) Е set(int индекс, Е o&elan) default void sort (Coшparator<? super Е> iroмnapamop) List<E> suЬList (int 1ШЧ41Ю, int конец) вызывающего списка Возвращает итератор для обхода элементов вызы­ вающего списка, начиная с позиции по указанному инiJексу Уд аляет элемент из вызывающего списка на позиции по указанному инiJексу и возвращает удаленный эле­ мент. Результирующий список уплотняется, т.е . эле­ менты, следующие за уд аленным, смещаются на одну позицию назад Обновляет каждый элемент списка значением, по­ лучаемым из функции , определяемой параметром opToAp p "ly (добавлен в версииJDК 8) Присваивает заданный о&еюп элементу, находя­ щемуся в списке на позиции по указанному инiJексу. Возвращает прежнее значение Сортирует список, используя заданный компаратор (добавлен в версииJDК 8) Возвращает список, включающий элементы от пози­ ции 1ШЧ41 1О до позиции конец- 1 из вызывающего спи­ ска. Ссылки на элементы из возвращаемого списка сохраняются и в вызывающем списке
Гл а ва 18. Пакет java.utit, часть 1. CoLLections Fra m ewo rk 573 Варианты методов add () и addAl l (), определенные в интерфейсе Collection, дополняются винтерфейсе List методами аdd (int , Е) иaddAll (int , Collection) . Эти методы вводят элементы на позиции по указанному индексу. Кроме того, семанти­ ка методов add ( Е) и addAll ( Collection ) , определенная в интерфейсе Collection, изменяется в интерфейсе List таким образом, что они вводят элементы в конце спи­ ска. Изменить каждый элемент в коллекции можно с помощью метода replaceAll (). (Для этой цели в версииJDK 8 внедрен функциональный интерфейс Una ryOpe r а tor, как поясняется в гл аве 19.) Из данного списка можно получить подсписок, вызвав метод subList () и ука­ зав начальный и ко нечный индексы подсписка. Нетрудно догадаться, что благо­ даря методу subList () обращаться со списками намного уд обнее. Отсортировать список можно, в частности , с помощью метода sort () , определенного в интер­ фейсе List. Интерфейс Set В интерфейсе Set определяется множество. Он расширяет интерфейс Collection и определяет поведение коллекций, не допускающих дублирования элементов. Та ким образом, метод add () возвращает логическое значение fal se при попытке ввести в множество дубл ирующий элемент. В этом интерфейсе не определяется никаких дополнительных методов. Интерфейс Set является обоб­ щен ным и объявляется приведенным ниже образом, где Е обозначает тип объек­ тов, которые должно содержать множество . interface Set<E> И нтepфeй csortedSet Интерфейс SortedSet расширяет интерфейс Set и определяет поведение множеств , отсорти рованных в порядке возрастания. Инте рфейс SortedSe t яв­ ляется обобщенным и объявляется приведенным ниже образом, где Е обозначает тип объектов, которые должно содержать множество . interface SortedSet<E> Помимо методов ,предоставляемых интерфейсом Sеt, в интерфейсе SоrtеdSеt объявляются методы, перечисленные в табл . 18.4 . Некоторые из них генерируют исключение типа NoSuchElementException, если в вызывающем множестве от­ сутствуют какие-нибудь элементы. Исключение типа С 1 а s sCas t Ехсерt i on генери­ руется, если заданный объект нt:совместим с элементами множества . Исключение типа Nu llPointerExcept ion генерируется при попытке использовать пустой объект, когда пустое значение nu ll в множестве недопусти мо. А при указан ии не­ верного аргумента генерируется исключение типа IllegalArgumentExcept ion. В интерфейсе SortedSet определен ряд методов, упрощающих обработку эле­ ментов множеств . Чтобы получить первый элемент в отсортированном множе­ стве , достаточно вызвать метод first (), а чтобы получить последний эл емент - метод last (). И з отсортированного множества можно получить подмножество , вызвав метод subSet () и указав первый и последний элементы множества. Если
57' Часть 11. Библиоте ка Java требуется получить подмножество , которое начинается с первого элемента суще­ ствующего множества, следует вызвать метод headSet ().А если требуется полу­ чить подмножество , которое начинается с последнего элемента существующего множества, следует вызвать метод tailSet (). Табл ица 18.,. Методы иэ инте рфейса sortedset Метод Comparator<? super Е> comparator () Е first() SortedSet<E> headSet (Е конец ) Е last () Описание Возвращает компаратор отсортированного множества. Если для множества выбирается естественный порядок со­ ртировки , то возвращается пустое значение null Возвращает первый элемент вызывающего отсортирован- ного множества Возвращает объект типа SortedSet, содержащий элемен­ ты из вызывающего отсортированного множества, кото­ рые предшествуют элементу, определяемому параметром конец. Ссылки на элементы из возвращаемого отсортиро­ ванного множества сохраняются и в вызывающем отсо­ ртированном множестве Возвращается последний элемент из вызывающего отсо­ ртированного множества SortedSet<E> Возвращает объект типа SortedSet, котор ый включает suЬSet (Е начало , Е конец) в себя элементы, начиная с позиции начало и до позиции конец-1. Ссылки на элементы из возвращаемого отсорти- SortedSet<E> tailSet (Е начало) рованного множества сохраняются и в вызывающем отсо­ ртированном множестве Возв ращает объект типа SortedSet, содержащий элемен­ ты из вызывающего множества, которые следуют после элемента на заданной позиции начало. Ссылки на эл емен­ ты из возвращаемого отсортированного множества сохра­ няются и в вызывающем отсортированном множестве И нтepфeй cNavigaЬleSet Этот интерфейс расширяет интерфейс SortedSet и определяет поведение кол­ лекции, извлечение элементов из которой осуществляется на основании наиболее точного совпадения с заданным значением или несколькими значениями. Интерфейс NavigaЬleSet является обобщенным и объявляется следующим образом: interface NavigaЬleSet<E> где Е обозначает тип объектов, содержащихся в множестве . Помимо методов, наследуе мых из интерфейса SortedSet, в интерфейсе NavigaЬleSet опре­ деляются методы, перечисленные в табл. 18.5 . Они генерируют исключение ти па ClassCastException, если заданный объект несовместим с элементами множества. Исключение типа Nul lPointerException генерируется при по­ пытке ввести пустой объект, когда в множестве не допускаются пустые значе­ ния nu ll. А при указании неверного аргумента передается исключение типа IllegalArgumentException.
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra mewo rk 575 Та бл ица 18.5. Методы из инте рфейса NavigaЬleSet Метод Е ce iling (Е обr.ею11) Iterator<E> descending! terator () Navi gaЬleSet<E> descendingSet () Е floor (Е 1кп.ек т) Описание Вы1 1ол11ж·т 1101кк 11 "'1 1 южс.:ствс наимс1 11>111с.:1'0 ::JJI <"\ICI Пa е1ю кp1 1- л.:pl flo е >= 0Гте1т1, Есл и такоii :,J лсме1 1т 11aii;1c.:1 1. 011 во: шращ<1- с:то1 , 11 11рол 1111 10\1 сл У•1ас.: - 11устос.: :11 1ачс.:111 1<' nu ll Во:шращас.: г 11тс.:ратор. в1>1 1 юл ш1 1011 щй ofixo11 <n fio;1 1.111<тo :�_·н· · \1 t•1 1та множеП'В<I к мс1 1ы11ему. т, с.;_ ofipaтныii 11тератор Во:шра щас 1' ofiъc 1cl' т1 111а NavigaЬleSet. ofi paтныii 110 от1 10111<" 111110 1с вы:11.1вающс.:\tУ \1 1 /ОЖС.:ству. Рс: 1ул 1т1рующсс \t llожс.:ство l IOil,i\< '1 JiIO·I B<ll'TOI ll l•J:ll•I B< II0\1\11\1 �1 1 IОЖ('П 'ВI 1 \1 В1.111оmшс.:т 1101кк в м11ожест11е 11аиfiолы11е1·0 :-J лс.:че1 1та е110 1\ р1 1- тер11ю е <= ofk.eю n. Есл и такоii ::1л с.:ме1 1т 11aiiдc1 1. 01 1 во: шраща· <·тп1 . в 11роп 1111 ю\1 случае - 11устос :11 1ачс1 111с null NavigaЬleSet<E> Во:шра щ;1<'Т оfiъскт л 11 1 а NavigaЬleSet, ПJJtepжaщнii вп· headSet (Е 111'j1:л1 11U1_ .7к1 11 11ца , :-JЖ'\!П tп.1 111.1:1ывающе1·0 множест ва . мс.:11 ы1111t· :1;щa1 111oii Ьoolean нк.-1ючuте. 1ыю) нерхиеu_<>/кт ицы. Ес.:111 же.: 11а р;н1егр вк.;1ю•11т�елыю 111н 11111\1аст Е higher (Е 1к1ьек11 1 ) Е lower (Е 1кn>1: !К11I) Е pollFirst () Е pollLas t () NavigaЫeSet<E> suЬSet (Е 11 11.жшш_.фа иица , Ьoolean liK:l/OЧ{IЯ_/IU .Ж1llOIO_ фа1tUЦ)' , Е 11ер:л1u1Я_ .1ю 1 1ица, Ьoolean uк.;1ю•1ш1_t11'j1х1 11т''­ lfк1 11 ицу) ;1oп 11l('< 'KOt' :111ачt·111 1с.: true. то 11 11о:ш ращас\1ос \1 1 1ожсп 110 111<лю11<1tтсн 11 :->лсмснт. ра вныН :1aлa11110ii 11tфл11еli_<>раиице. \'с:1у:1ы llJJ)' Klll\('C �11 \0Ж(Tl'JI< ) ll< ЩitC p ЖllВ<ll'ТC H 111.1:1ы11;\l()\l\l l\I \1 1\( )A(("("J'I\( 1\1 В1.111ш1 1 шст 1ю11 ск в \ШОЖС.:(Т\\е 11а11fiолы11с1·0 :мt·\1с1па е110 кр11- п·1н1ю 1• > 1ктеюп. Есл 11 та ко ii :�ло1с1п 11aiiлc1 1. 011 110:111ращ. 1l 'Т· сн. а 1111<11к· - 1 1усп 1с :11 1а•1с1 111е null B1.1110JIJIH(-I' ll<HICK 11 \1/IОЖСГПIС 11<1 11\l('l ll •lll("l 'O :i.' l('\lt'l lT<I 1' 110 K J Jll· те1 н110 е < 1К17.еК1 1 1. Ес1 11 та коi·i :-JJ1C\tc.:1 1т 11aiiл< ·1 1. 011 во: тращ;кт­ ся , <1 1111ачс - 11уп ос :111a•1i:1 111c nul l Во:111ращ;1ст 11срвыli :-JЛC'\-1CllТ, llOll\Tl lO Y/\< l.'IHH <·н 1 11:1 ч11ожt·п ·11;1 . :-)то :-IЛC.:Ml'\\T с \\<l ll\I CllЫllИM :11 1а•1е1 11н·ч. 1юп;1 1:1 1 .к\' \ll lOЖl'CТ\IO о-1л 1рп 1ро11а110. Есл11 же \t 1 10жсгпю оl(а: 11.1 11;н·тсн 11\'ПЫ \1. то во :тра щ;н:тс н нустос :11 1ачс1 1 1н: nul l Во:траща< ·т 1юсл с111 111ii :iл смс1 1т. 11011}TI 10 \Л<IЛНЯ t·1 -. 1 11:1 \11\ОЖl' CTB<I. ;-)то :1;1смс1 1т (' ll<НIUOJ IJ.111 1·1 \1 :11 1a•1ct 1lll' \I , 1\( )( ' 1(0:1 1.К\' \l l lOЖ('­ CT \I( ) от сорп1ров;11 10. Ес 111 же чножсп ·во ок;�:11.11ыt·1 п1 llY< ·1ы\1. то во:шра 111;1<-то1 11У1 тос :11 1а•1с1 111с null Во: тр;1щаст оfiъскт тина NavigaЬleSet. 111c1 1011<11011111ii нп · :-1 .' l('\l('I \'IЪI 111>1:11.1в;� ющсго MI IOiKCCT ll<I, которые r,, 1;1 1.111с :1<1;1;1111111ii 1111.ж ·ие1i_ , • Jх1 11ицы 11 \1<·1 11.111с :1aл ;11 11 1oii 1wJдщ1i_фm1ицы. Ес1 11 же 11ap<\\K"l'P (IК., , /Нll/(IJ/_ l/llJl('///(//(J_,•pa11uцy 11/Нll l\Ш;\('1' )11 l/'J 11/('( 'KI )(' :н �;1•1с1 111с.: true. то н рt-:1\'11 1;п 1 р\· ю111<т �1 11ожlтт110 111'-'11< >'1<1<-тсн :-1:н ·\1с1 1т , 1 ><1 111 11.1ii :1a;p 111 юii 1111.ж·иеu_ фтшце. . \ ссл11 11ap;l \ll'TP 11к.;1111чш1_ие/1л1 1 1то_. 1юи ицу 1111111111м;1ст .: 1< 1пl' tс< ·ю lt' :н 1;111< ·1111с true. то 11 pc: l\'.' / l iПip\IOЩC<.: \11 10)1\('('Т/Ю lllCl \0 11;\("IЛI 11 :1_' 1( '\ll'l lT. 11aш 11.1 ii :1;ща111юii 111фх1 1сi�_<>/ю 111ще. 1'с:1ул 1;п 1р\ющt·t· \1 11< )Ж<'СТ111 1 ll( Jj�'Il' J IЖl·l ll< l('TПI ВJ,1:1ы вa1<Jil\l l\<I Ml l< JЖ('Г\'11( )\1
576 Часть 11. Библиоте ка Java Метод NavigaЬleSet<E> tailSet (E ни:нснял_ грт т ца, Ьoolean включительно) Интерфейс Queue Ок1жчан1v табл. 18.5 Описание Возвращает объект типа NavigaЬleSet, включающий все элементы из вызывающего множества, которые больше за­ дан ной ниж:ней_границы. Если же параметр включите.льно принимает логическое значение true , то в результирующее множество включается эл емент, равный заданной нижней_ границе. Результирующее множество поддерживается вызы­ вающим множеством Этот инте рфейс расширяет интерфейс Col lection и определяет поведение очереди , которая действует как список по принципу "первым вошел - первым об· служен". 1см не менее имеются разные виды очередей, порядок организации в ко· то рых основывается на некотором критерии. Интерфейс Qu eue является обоб­ щенным и объявляется следующим образом: interface Queue<E> где Е обозначает тип объектов, которые будут храниться в очереди. Методы, опре­ дел енные в интерфейсе Queue , перечислены в табл . 18.6. Та бл ица 18.6. Методы из инте рфейса Queue М етод Описани е Е element () Возвращает элемент из головы очереди . Возвращаемый элемент не удал яется. Есл и очередь пуста, ге нерируется ис· ключение типа NoSuchEleшentException boolean offer (Е обыкm) Пытается ввести заданный обЪект в очередь . Возвращает логическое значение true , если обl.ект введен, а иначе - ло ­ гическое значение f'alse Еpe e k() Возвращает эл емент из головы очереди . Если очередь пуста , возвращает пустое значение nul l. Возвращаемый элемент не удаля ется из очереди Е poll() Возвращает элемент из головы очереди и удаляет его. Есл и очередь пуста, возвращает пустое значение null. Е remove() Уд аляет элемент из головы очереди , возвращая его. Ге нерирует исключение типа NoSuchElementException, если очередь пуста Некоторые методы из данного интерфейса генерируют исключение типа ClsssCastException, если заданный объект несовместим с эл ементами очереди . Исключение типа NullPointerException генерируется, когда предпринимается попытка сохранить пустой объект, а пустые элементы в оче реди не разрешен ы. Исключение типа IllegalArgime ntException генерируется при указании невер­ ного аргумента. Исключение типа IllegalStateException генерируется при по­ пытке ввести объект в заполненную очередь фиксированной дл ины. И наконец,
Гл ава 18. Пакет java.utiL, часть 1. CoHections Fram ewo rk 577 исключение типа NoSuchElerne n tException ге нерируется при попытке уд алить :элемент из пустой очереди. Несмотря на всю свою простоту, инте рфейс Qu eue представляет интерес с не­ скольких точек зрения. Во-первых, элементы могут удаляться только из начала очереди. Во-вторых, имеются два метода, poll () и rerno ve (). с помощью кото­ рых можно получать и уд алять элементы из очереди . Отличаются они тем , что метод po ll () возвращает пустое значение nul l, если очередь пуста , тогда как метод rernove ( ) ге нерирует исключение. И в-третьих, имеются еще два метода , e lement () и peek (), которые получают элемент из головы очереди , но не уд аля­ ют е го. Отличаются они те м, что при пустой очереди метод elerne nt () генериру­ ет исключение, тогда как метод peek () возвращает пустое зн ачение null. И на­ коне ц, следует иметь в виду. что метод offer () только пытается ввести эл емент в очередь. А пос кольку некото рые очереди имеют фиксирован ную дл ину и могут быть заполнены. то вызов метода offer () может заве ршиться неудачно. Инте рфейс Dequeue Интерфейс Dequeue расширяет интерфейс Queue и определяет по веде ние дву­ сторонней очереди , которая может фун кциони ровать как стандартная очередь по принципу "первым вошел - первым обслужен" или как стек по принципу "по­ следним вошел - первым обслужен". Интерфейс Dequeue является обобщенным и объявляется приведенным ниже образом , где Е обозначает тип объектов, кото­ рые будет содержать двусторонняя очередь. interface Deque<E> Помимо методов, наследуемых из интерфейса Qu eue, в интерфейсе Dequeue определяются методы, перечисленные в табл. 18 .7. Некоторые и;1 этих методов гене­ рируютисключение типа Сlаs sСаstExcept ion, если заданный объект несовместим с :элементами двусторонней очереди. Исключение типа NullPo interException генерируется , когда предпринимается попытка сохран ить пустой объект, а пустые элеме нты двусторонней очереди не допускаются. При указании неверного аргумен­ та генерируется исключение тина I1legalArgirnentException. Исключение типа IllegalStateExcept ion ге нерируется при попытке ввести объект в запол нен­ ную двустороннюю очередь фиксированной дл ины. И наконец, исключение типа NoSuchElernentException генерируется при попытке удал ить элемент из пустой очереди. Табл ица 18.7. Методы из инте рфейса Dequeue Метод Описание void addFirst (Е объект) Вводит заданный объект в гол о ву двусторо нней очереди. Ге нерирует исключение типа IllegalStateException, если в очереди фиксирован ной дл ины нет свободного места void addLa st (Е объект) Вводит заданный объект в хвост двусторонней очеред и. Ге нерирует исключение типа IllegalStateException, есл и в очереди фиксированной дл ины нет с вободн о го места
578 Часть 11. Библиотека Java М етод Iterator<E> descendingiterator () Е getFi rst () Е getLast () boolean offerFirst (E об5екm) Ьo olean offerLast (Е об5екm) Е peekFi rst () Е peekLa st () Е pollFirst () Е po llLast () Е рор() void push (E об&екm) Е reшoveFi rst () Продолжение табл. 18.7 Описан и е Возвращает итератор для обхода эл еме нтов от хвоста к го­ лове двусторонней очереди. Иными сл овам и, возвращает обратный итератор Возвращает первый элемен т двусторон ней очереди. Возвращаемый элемент из очереди не улал яется. Ге нерирует исклю чение типа NoSuchElementException, есл и двусто­ ронняя очередь пуста Возвращает последний элемент двусто ронней очереди. Возвращаемый эл емент из очереди не уд аляется . Ге нерирует искл ю чение типа NoSuchE lemen tException, есл и двусто­ ронняя очередь пуста Пытается ввести заданный обr.ект в голову двусто ронней очереди. Возвращает логичес кое значение true , есл и объект введен, а иначе - логи ческое значение false. Таким обра­ зом , этот метод возвращает лог ическое значение fal se п ри попытке ввести заданный объект в з ап ол ненную двусторон­ нюю очередь фиксированной дл ины Пытается ввести заданный объект в хвост двусторонней очереди. Возвращает логическое значение true , есл и объект введен, а ина че - логическое зна чение fal se Возвращает эле мент, н аходящийся в голове двусто ронней очереди. Есл и очередь пуста. возвращает пустое значение nu ll. Возвращаемый эл емент из очереди не уд аляется Возвращает эл емент, находящ ийся в хвосте двусторонней очереди . Если очередь пуста, воз вращает пустое значение nu ll. Возвра щаемый элемент из очереди не уд аляется Возвращает элемент, н аходящийся в гол ове двусторонней очереди, одн овременно удал яя его из очеред и. Есл и очередь пуста, возвращает пустое значение null Возвращает эле мент, н аходя щийся в хвосте двусторонней очеред и, од новременно удаляя его из очереди . Есл и очередь пуста, возвращает пустое значение null Возвращает элемент, н аходя щийся в голове двусторонней очереди, одновременно уд аляя его из очереди. Ге нерирует исклю чение типа NoSuchElementException, если очередь пуста Вводит задан ный объект в голову двусторонней очереди. Если в очереди фиксированной дл ины нет свободного ме­ ста , ге нерирует исклю чение типа IllegalStateException Возвращает элемент из головы двусторонней очереди, одно­ временно уд аляя его из очереди. Ге нерирует искл ю чение типа NoSuchE181 118 ntException, есл и очередь пуста
Метод Ьoolean remove FirstOccurrence (OЬject об6екm) Е removeLast() Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra mewo rk 579 Окончание mali ii . 18.7 Описание Удаляет первый экземпляр заданного объехта из очереди . Возвращает логическое значение true при уд ачном исходе операции, а если двусторонняя очередь не содержит задан­ ный о6&ект - логическое значение false Возвращает элемент из хвоста двусторонней очереди, одн о­ временно удаляя его из очереди. Ге нерирует исключение типа NoSuchEleшentException, если очередь пуста boolean reшoveLast Occurrence Удаляет последний экземпляр заданного объехта и з о череди. Возвращает логическое значение true при уда чном исходе операции, а если двусторонняя очередь не содержит задан­ ный об&ект-логическое значение false (Object об6екm) Обратите вниман ие на то , что в состав интерфейса Deque входят методы push ( ) и рор ( ) , благодаря которым этот интерфейс может фун кционировать как стек. Обратите также внимание на метод de scendinglterator (),возвращающий итера­ тор, который обходит эл ементы очереди в обратном порядке , т.е . от хвоста очере­ ди к ее голове (или кон ца данного вида коллекции к ее началу). Реализация интер­ фейса Deque в виде двусторонней очереди может быть оzранu1U!'Ншrй по е.м:кости, т. е . в такую очередь может быть введено ограниченное количество элементов. В этом случае попытка ввести элемент в очередь может оказаться неудачной. Неудачный исход подобн ых операций интерпретируется в интерфейсе Deque двумя способами . Во-первых, методы вроде addFirst () и addLast ( ) генерируют искл ючение типа IllegalStateException, если двусторонняя очередь имеет ограниченную ем­ кость. И во-вторых, методы наподоб ие offerFirst () и offerLast () возвращают логи ческое значение false, есл и элемент не может быть введен в очередь. Классы коллекций А те перь, ко гда предстаалены интерфейсы коллекций, можно приступить к рас­ с мотрению стандартных кл ассов, которые их реал изуют. Одни из этих классов предоставляют полную реал изацию соответствующих интерфейсов и могут при­ меняться без изменений. А другие являются абстрактными , п редоста ал яя тол ько шаблонные реализации соответствующих интерфейсов, которые испол ьзуются в качестве отправной точки для создания конкретн ых колле кци й. Как правило, клас сы коллекци й не синхронизированы, но есл и требуется , то можно получить их синхронизированные варианты , как показано далее в этой гл аве . Базовые клас­ с ы коллекци й перечислены в табл. 18.8. В последующих разделах рассматриваются конкретн ые классы коллекци й и де­ монстрируется их применение. На заметку! Помимо классов колл екци й, некоторые классы, унаследованные из прежн их версий, например Ve ctor, Stack и НаshТаЫе, были переделаны для поддержки колл екций. Они также рассматриваются далее в этой главе.
580 Часть 11. Библиотека Java Та бл ица 18.8. Базовые кл ассы колл екций Класс Описание AЬstractCollection Реализует большую часть интерфейса Collection AЬstractLi st Расширяет кл асс AЬstractCollection и реал щует большую част ь интерфейса Li st AЬs tractQueue Р;кширяет кл асс AЬstractCollection и реал и зует отдсль· ныt: •1асти интt:рфейса Queue AЬstractSequentalLi st Расширяет кл асс AЬstractList для нрименения в колле кци· ях, щ: пол ьзую щ их последо вател ьности вместо слу чайного доступ а к элементам LinkedList ArrayList ArrayDeque AЬstractSet EnumSet HashSet LinkedНa shSet PriorityQueue TreeSet Кла сс ArrayList Реализует связный снисок, рас ширяя кл асс AЬs trac tSequentalList Реализует динамический массив, рас ширяя кл асс AЬs tractList Реализует динамическую двухстороннюю очередь, расши ряя класс AЬs tractCo llection и реализуя интерфейс Deque Расширяет класс AЬstractCollection и реалиаует большую часть интерфейса Зеt Расширяет кл асс AЬstractSet для применения вместе с эле· ментам и типа enum Расширяет класс AЬstractSet дл я применения вместе с хеш·таблицами Расширяет класс Ha shSet, разрешая итерацию с вводом эле· ментов в определенном порядке Расширяет класс AЬstractQueue для подде ржки очередей по приоритетам Реалиаует множество, хран имое в древовидной с труктуре. Расширяет класс AЬstractSet Класс ArrayList расширяет кл асс Ab stractLi st и реализует интерфейс L i st. Кл асс ArrayList является обобщенным и объявляется приведенным ниже обра· зо м, где параметр Е обозначает тип сохраняемых объекто в. class ArrayLis t<E> В классе ArrayList поддерживаются динамические массивы, которые мoryr наращиваться по мере надобности. Стандартные массивы в Java имеют фиксиро­ ванную дл ину. После того как массив создан , он не может увеличиваться или уме нь· шаться, а следовательно , нужно заранее знать, сколько элементов требуется в нем хранить. Но иногда еще до стадии выполнения неизвестно, насколько большой
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Framework 581 массив потребуется . В качестве выхода из данного положения в каркасе коллекций определяется класс Ar rayList. По существу, класс ArrayList представляет собой списочный массив объектных ссылок переменной длин ы. Это означает, что раз­ мер объекта типа Ar r а yL i s t может динамически увеличиваться или уменьшаться . Списочные массивы создаются с некоторым начальным размером. Когда же этого первоначального размера оказывается недостаточно, коллекция автоматически расширяется. А когда из коллекции удал яются объекты , она может сокращаться . В классе ArrayList определены следующие конструкторы: ArrayLi st () ArrayLi st ( Collection <? extends Е> с) ArrayList (int еюсос!l'ъ) Первый конструктор создает пустой списочный массив, второй - списочный массив, инициализируемый элементами из заданной коллекции с, а третий - спи­ сочный массив, имеющий начальную емко сть. Под ем костъю здесь подразумевает­ ся размер базового массива, используемого для хранения элементов данного вида коллекции. Емкость наращивается автоматически по мере ввода эл ементов в спи­ сочный массив. В следующем примере программы демонстрируется простое применение клас­ са ArrayList. В этой программе сначала создается списочный массив объектов типа String, затем в него вводится нес колько символьных строк. (Напомним, что символьные строки, заключенные в кавычки, преобразуются в объекты типа String.) Полученный в итоге список символьных строк выводится на экран . Некоторые элементы уд аляются из этого списка, после чего он выводится снова. 111 Продемонстрировать применение класса ArrayList import java .util .*; class ArrayLi stDemo puЫ ic static void ma in (String arg s[] ) ( 11 создать списочный ма ссив ArrayLi st<String> al = new Ar rayLi st<String> (); System. out .println ( "Начальный размер списочного массива al : "+al . size()); 11 ввести элементы в списоч ный ма ссив al .add("C ") ; al.add("A"); al .add("E ") ; al .add("B ") ; al .add("D ") ; al .add("F ") ; al .add(l, "А2") ; System. out .println ( "Разме р списочного ма ссива al после ввода элементов : " + al . size() ); 11 выве сти списочный массив System. o ut . println ( "Coдepжимoe списочного ма ссива al : "+al ) ; 11 удалить элементы из списочного ма ссива al. remove ("F"); al . remove (2) ;
582 Часть 11. Библиотека Java System. out .println ( "Размер сnис очного массива al после уд аления элементов : " + al .size()); System.out .println ("C oдepжимoe списочного массива al : "+al) ; Ниже приведен резул ьтат, выводимый данной программой. Обратите внима· ние на то, что списочный массив al изначально пуст и увеличивается по мере ввода в него элементов. Когда же эл ементы уд аляются из списочного массива, его размер сокращается . Начальный размер списочного ма ссива al : О Ра змер списочного массива al после ввода элементов : 7 Содержимое списочного ма ссива al : [С, А2. , А, Е, В, D, F] Размер списочного массива al после удаления элементов : 5 Содержимое списочного массива al : [С, А2. , Е, В, D] В приведенном выше примере содержимое коллекции выводится с преобразо­ ванием ти пов, выполняемым по умолчанию методом toString (), который насле· дуется от класса AЬ stractCollection. И хотя этот способ уд обен для написания коротких примеров программ, на практике им редко пользуются для вывода со­ держимого настоящих коллекций. Обычно для этой цели программисты предо­ стаw�яют свои процедуры вывода . Но для нескольких последующих примеров вполне подходит вывод, выполняемый методом toString () по умолчанию. Несмотря на то что емкость объектов типа Ar rayList наращивается автома· тически , ее можно увеличивать и вручную , вызывая метод ensureCapacity (). Это может потребоваться в том случае, если заранее известно, что в колл екции предполагается сохран ить намного больше элементов, чем она содержит в дан· ный момент. Увел ичив емкость списочного массива в самом начале его обработки, можно избежать дополн ительного перераспределения оперативной памяти впо­ следствии. Ведь перераспределение оперативной памяти - дорогостоящая опе­ рация с точки зрения затрат времени, и поэтому исключение лишних операций подобного рода способствует повышению производительности . Ниже приведена общая форма метода ensureCapacity (),где параметр емкость обозначает новую минимальную емкость коллекции. void enaureCapacity (int енжос�ь) С другой стороны, если требуется уменьшить размер базового массива, на ос­ нове которого строится объект типа ArrayList, до текущего количества храня­ щихся в действительности объектов, следует вызвать метод trimToSize ().Ниже приведена общая форма этого метода. void triaTo Size () П олуч е ние массива и з коллекции типа ArrayList При обработке списочного массива типа ArrayList иногда требуется полу· чить обычный массив, содержащий все элементы списка. Это можно сделать, вы­ звав метод toAr ray (), определенный в интерфейсе Collection.
Гл ава 18. Пакет java.utiL, часть 1. CoHections Framework 583 Имеется несколько причин, по которым возникает потребность преобразо­ вать коллекцию в массив. • Ус ко рение выполнения некоторых операций. • Передача массива в качестве параметра методам , которые не перегружают­ ся , чтобы принимать коллекции непосредственно. • Интеграция нового кода, основанного на коллекциях, с унаследованным ко­ до м, который не распознает коллекции. Неза висимо от конкретной причины преобразовать коллекцию типа Ar r а yLi s t в массив не составляет особого труда. Как пояснялось ранее , имеются два варианта метода toArray (), общие формы которых приведены ниже. OЬject [ J toArray О <Т> Т[] toArray(Т насеяв[]) В первой форме метод toAr ray ( ) возвращает массив объектов типа Ob ject, а во второй форме - массив элементов, относя щихся к типу Т. Обычно вторая форма дан ного метода удо бнее, поскольку в ней возвращается надлежащий тип массива. В следующем примере программы де монстрируется применение именно этой формы метода toArray (). / 1 Пр еобразовать списочный ма ссив ArrayList в обычный ма ссив imp ort java .util .*; class ArrayLi stToAr ray { puЫ ic static void ma in (String args[] ) { 11 создать списочный ма ссив ArrayLi st<Integer> al = new ArrayList<Integer> (); 11 ввести элементы в списочный ма ссив al .ad d(l) ; al .ad d(2) ; al .ad d(З) ; al.add(4); System. out . println ("C oдepжимo e списочного ма ссива al : "+al ) ; // получить обыч ный ма ссив Integer ia [] = new Integer [al.size()]; ia = al.toArray(ia); intsum=О; 11 суммировать элементы ма ссива for(int i : ia) sum += i; System . out .println ("Сумма : " + sum) ; Данная программа вы водит следующий результат: Содержимое списочного ма ссива al : [ 1, 2, З, 4] Сумма : 10
58.4 Часть 11. Библиотека Java Эта программа начинается с создания коллекции целых чисел. Затем вызы­ вается метод toAr ray () и получается массив эл ементов типа Integer. Далее со­ держимое массива суммируется в цикле for в стиле for each. У этой программы имеется еще одна любопытная особенность. Как вам должно быть уже известно, коллекции могут содержать только ссылки , а не значения примитивных типов. Но автоматическая упаковка позволяет передавать методу add ( ) значения типа i nt, не прибегая к необходимости заключать их в оболочку типа Intege r, как это демонстрируется в данной программе. Та ким образом, автоматическая упаковка ощутимо облегчает сохранение в коллекциях значений примитивных типов. Класс LinkedList Этот класс расширяет класс AЬ stractSequentalLi st и реализует интерфей­ сы List, De queue и Queue. Он предостав.J,Iяет структуру данных связного списка. Класс L i nkedList является обобщенным и объявляется следующим образом: class Linkec:ILi st<E> где Е обозначает тип сохраняемых в списке объектов. У класса L i nkedList имеют­ ся два конструктора: Linkec:ILi st () Linkec:ILi st ( Collection<? extends Е> с) hервый конструктор создает пустой связный список, а второй - связный спи­ сок и инициирует его содержимым коллекции с. В классе LinkedList реализуется интерфейс De que, и благодаря этому становятся доступными методы, определен­ ные в интерфейсе Deque. Например, чтобы ввести элементы в начале списка, до­ статочно вызвать метод addFi rst (} или offerFirst (}, а для того чтобы ввести элементы в конце списка - метод addLa st ( ) или offerLast (}. Чтобы получить первый элемент из списка, следует вызвать метод getLas t (} или peekLast (}, а для того чтобы уд алить первый элемент из списка - метод remove Fi rst () или po llFirst (}. И наконец, чтобы получить последний элемент из списка, следует вызвать метод ge tLast (} или peekLast (}, а для того чтобы уд алить последний эл емент из списка - метод removeLast (} или pollLast (}. В следующем примере программы демонстрируется применение кл асса LinkedList: 11 Продемонстрировать приме нение кл асса Linkec!Li st import java .util .*; class LiпkedLi stDemo puЬlic static void ma in (St ring args[] } { 11 создать свя зный список LinkedList<String> 11 = new LinkedList<String> (} ; 11 ввести элементы в связный список 11 .add("F ") ; 11.add("B "} ; 11 .add("D ") ; 11.add("Е"); 11.add("C ") ; 11 . addLast ( "Z") ;
Гл ава 18. Пакет java.utii, часть 1. CoHections Framewo rk 585 11. addFirst{"A "); 11 .add{l, "А2") ; System. out . println { "Исходное содержимое связного списка 11 : "+11) ; 11 удалить элеме нты из связного списка 11. remove {"F "); ll . remove {2) ; System.out . println { "Содержимое связного списка 11 " + "после уд аления элементов : "+11) ; 11 удалить первый и последний элементы из связного списка 11 . remove First (); 11 . remove Last {) ; System.out .println { "Содержимо е связного списка 11 после удале ния " + "первого и последнего элементов : "+ 11) ; 11 получить и присвоить значение String val = ll .get{2); ll . set {2, val +"изменено" ) ; System. o ut . println ( "Содержимо е связного списка 11 после изменения : "+11) ; Ниже приведен результат, выводимый данной программой. Исходное содержимое связного списка 11: [А, А2 , F, в , D, Е, С, Z) С оде ржимо е связного списка 11 после уд аления элементов : [А, А2 , D, Е, с , С одержимо е связного списка 11 после удаления первого и последне го элементов: [А2, D, Е, С] С оде ржимое связного списка 11 после изменения : [А2, D, Е изменено , С] Z] В классе LinkedList реализуется интерфейс List, и поэтому в результате вы­ зова метода add ( Е) элементы вводятся в конце списка, как это делается и при вы­ зове метода addLast ( ) . Чтобы ввести элементы в определенном месте списка, следует воспользоваться формой метода add ( int, Е) , как продемонстрировано выше на примере вызова аdd(1, "А2") . Обратите внимание, как трети й элемент связного списка 11 изменяется с по­ мощью методов get ( ) и set ().Чтобы получить текущее значение элемента, ме­ тоду get ( ) передается индекс позиции, на которой рас положен нужный элемент. А для того чтобы присвоить новое значение элементу на этой позиции, методу s e t ( ) передается соответствующий индекс и новое значение. Класс HashSet Класс Ha shSet расширяет класс Ab stractSet и реализует интерфейс Set. Он служит для создания коллекции, для хранения элементов которой используется хеш­ таблица. Класс Ha shSet является обобщенным и объявляется приведенным ниже образом, где Е обозначает тип объектов, которые будут храниться в хеш-множестве. clas s HashSet<E>
586 Часть 11. Библиотека Java Как известно, для хранения данных в хеш-таблице применяется механизм так на· зываемого хеширования, где содержимое ключа служит для определения однозначного значения , называемого хеш-кодам. Этот хеш-код служит далее в качестве индекса, по ко­ торому сохраняются дан ные, связанные с ключом. Преобразование ключа в хеш-код выполняется автоматически , хотя сам хеш-код недосrупен. Кроме того , в прикладном коде нельзя индекси ровать хеш"I'аблицу непосредственно. Преимущество хеширова· ния заключается в том, что оно обеспечивает постоянство времени выполнения ме­ тодов a dd (), c o ntains (), remo ve () и size () - даже для крупных множеств. В классе Ha shSet определены следующие конструкто ры: HashSet () HashSet (Collection<? extends Е> с) HashSet (int енхость) HashSet (int енхость , float хооффицмент_зап ОJ JН еюr•) В первой форме конструктора хеш-множество создается по умолчанию. Во второй форме хеш-множество инициируется содержи мым заданной колле кции с. В третьей форме задается емко сть хеш-м ножества (по умолчанию - 16), а в четвертой в качестве аргументов конструктора задается емко сть хеш-множества и коэ ффициент_ заполнения, иначе называемый емкостъю загру31Сu. Коэффициент заполнения должен быть в пределах от О,О до 1,О, которые определяют, насколь· ко заполненным должно быть хеш-множество , прежде чем будет изменен его раз· мер. В частности , когда количество элементов становится больше емкости хеш· множества, умноженной на коэффициент заполнения, такое хеш-множество рас· ширяется . В конструкторах, которые не принимают коэффициент заполнения в качестве параметра, выбирается значение этого коэфф ициента, равное О,75. В классе HashSet не определяется никаких дополнительных методов, поми· мо тех, что предоставляют его суперклассы и интерфейсы. Следует также иметь в виду, что класс HashSet не гарантирует упорядоченности элементов, поскольку процесс хеширования сам по себе обычно не приводит к созданию отсортирован· ных множеств. Если же требуются сортированные множества, то для этой цели лучше выбрать другой вид коллекции, например TreeSet. Ниже приведен пример программы, демонстрирую щий применение класса Ha shSet. 11 Продемонстрировать приме нение кла сса HashSet import java .util .*; clas s HashSetDemo { puЫ ic static void main (String args []) { 11 создать хеш-множество Ha shSet<String> hs new HashSet<String> () ; 11 ввести элементы в хеш-мн оже ство hs.add("Бета") ; hs.add("Aльфa"); hs .add ( "Этa" ); hs .add ( "Гaммa") ; hs .аdd("Э псилон" ); hs.add("OМeгa"); System. out . println (hs) ;
Гла в а 18. Пакет java.utiL, ч асть 1. CoHections Fram ewo rk 587 Ниже приведен результат, выводимый дан ной программой. Как пояснялось ра­ нее , элементы не сохраняются в хеш-множестве в отсортированном порядке , по­ этому порядок их вывода может варьиро ваться. [Гамма , Эта, Ал ьфа, Эпсилон, Омега, Бета] Кnacc LinkedВashSet Класс LinkedHashSet расширяет класс HashSet, не добавляя никаких новых методов. Этот класс является обобщенным и объявляется следующим образом: class LinkedВashSet<E> где Е обозначает тип объектов, которые будут храниться в хеш-множестве. У этого класса такие же конструкторы, как и у класса HashSet. В классе LinkedHa shSet поддерживается связный список элементов хеш­ множества в том порядке , в каком они введены в него. Это позволяет организо­ вать итерацию с вводом элементов в определенном порядке . Следовательно, ког­ да перебор элементов хеш-множества типа LinkedHa shSet производится с помо­ щью итератора, элементы извлекаются из этого множества в то м порядке , в каком они бьvш введены. Именно в этом порядке они будут также возвращены методом toString (), вызываемым для объекта типа LinkedHashSet. Чтобы увидеть эф­ фект от применения класса LinkedHashSet, попробуйте подставить его в исход­ ный код предыдущего примера программы вместо класса HashSet. После этого выводимый программой результат будет выглядеть так, как показано ниже , отра­ жая тот порядок, в каком элементы были введены в хеш-множество. [Бета, Ал ьфа, Эта , Гамма , Эпсилон , Омега] Кпасс TreeSet Класс TreeSet расширяет кЛасс AЬstractSet и реализует интерфейс NavigaЬleSet. Он создает коллекцию, где для хране ния элементов применяет дре­ вовидная структура. Объекты сохраняются в отсортированном порядке по нарас­ тающей. Время доступа и извлечения элементов достаточно мало , благодаря чему класс TreeSet оказывается отли чным выбором для хранения больших объемов от­ сортированных данных, которые должны быть быстро найдены. Класс TreeSet яв­ ляется обобщенным классом и объявляется приведенным ниже образом, где Е обо­ значает тип объектов, которые будут храниться в древовидном множестве. clas s TreeSet<E> В классе TreeSet определены следующие конструкторы: TreeSet () TreeSet ( Collection<? extends Е> с) Tree Set (CoJDparator<? super Е> хонпара !l'ор) TreeSet (SortedSet<E> ss) В первой форме конструктора создается пустое древовидное множество, элемен­ ты которого будут отсортированы в естественном порядке по нарастающей. Во вто­ рой форме создается древовидное множество, содержащее элементы заданной кол-
588 Часть 11. Библиоте ка Java лекции с. В третьей форме создается пустое древовидное множество, элементы кото­ рого будуг отсортированы заданным компара тором. (Компараторы рассматриваются далее в этой гл аве. ) И наконец, в четвертой форме создается древовидное множество, содержащее элементы заданного отсортированного множества ss. В приведенном ниже примере программы демонстрируется применение класса TreeSet. 11 П родемонс трировать приме нение класса TreeSet import java .util .*; class TreeSetDemo { puЫ ic static void ma in (String args[] ) { 11 создать дре вовидное мн оже ство типа TreeSet TreeSet<String> ts new TreeSet<String> (); 11 ввести элеме нты в древовидное множе ство типа TreeSet ts.add("C") ; ts.add("A"); ts.add("B ") ; ts .ad d("E") ; ts.add("F "); ts.add("D"); System. o ut . println (ts) ; Эта программа выводит следующий результат: [А,В,С,D,Е,F] Как пояснялось ранее, класс TreeSet сохраняет элементы в древовидной струкrу­ ре. Они автоматически располагаются в отсортированном порядке, что и подтверж­ дает выводимый программой результат. А поскольку класс TreeSet реализует интер­ фейс NavigaЬleSet, внедренный в версии Jаvа SE 6, то для извлечения элементов из древовидного множества типа TreeSet становятся доступными методы, определен­ ные в интерфейсе NavigaЬleSet. Допустим, в исходный код программы из предыду­ щего примера бьта добавлена следующая строка кода, где для получения множества ts, содержащего элементы от С (включительно ) до F (исключительно) , сначала вызы­ вается метод subSet (),азатем выводится результирующее множество: System. out . println (ts . subSet ("C", "F" ) ); Ниже приведен резул ьтат выполнения этой строки кода. При желании мо­ жете поэкспериментировать с другими методам и, определенными в интерфей­ се NavigaЬleSet. [С, D, Е] Клacc PriorityQueue Класс PriorityQueue расширяет клacc Ab stractQueue и реализует интерфейс Queue . Он служит для создания очереди по приоритетам на основании компара­ тора очереди . Класс PriorityQu eue является обобщенным и объя вляется следу­ ющим образом: clas s PriorityQueue<E>
Гл а ва 18. Пакет java.utiL, часть 1. CoLLections Fra m ewo rk 589 где Е обозначает тип объектов, которые будут храниться в очереди . Объект типа PriorityQu eue представляет собой динамическую очередь, которая может при необходимости расширяться. В классе Priori t yQueue определяются следую щие шесть конструкторов: PriorityQueue () PriorityQueue (int &N.itocтъ) PriorityQueue (int еижость , Comparator<? super Е> конnара тор) PriorityQueue (Collection< ? extends Е> с) PriorityQueue (PriorityQueue<? extends Е> с) PriorityQueue (SortedSet< ? extends Е> с) Первый кон структор данного класса создает пустую очередь. Ее первоначаль­ ная емкость равна 11. Второй конструктор создает очередь с заданной начальной емкостью . Тр етий конструктор создает очередь заданной емкости с указан ным компаратором. Последние три конструктора создают очереди, инициируе мые элементами коллекций, задаваемых в качестве параметра с. Но в любом случае по мере ввода элементов в очередь ее емкость автоматически наращивается. Если при построении очереди типа PriorityQueue компаратор не указан, то применяется компаратор, выбираемый по умолчанию для того типа дан ных, ко­ торы й сохраняется в очереди . Компаратор по умолчанию размещает эл ементы очереди по нарастающей. Та ким образом , в начале (голове ) очереди окажется элемент с наименьшим значением. Но, предоставляя свой компаратор, можно за­ дать другую схему сортировки элементов в очереди. Например, когда в очереди сохраняются эл ементы , содержащие метку времени, для этой очереди можно за­ дать приоритеты 1vаким образом, чтобы самые давние элементы располагались в начале очереди. Вызвав метод comp a rator () из класса PriorityQueue , можно получ ить ссылку на компаратор, используемый в очереди, как показано ниже. Coшparator< ? super Е> comparator () Этот метод возвращает компаратор. Если в данной очереди применяется есте­ ственный порядок сортировки , то возвращается пус тое значение nu 11. Следует, од­ нако , иметь в виду, что порядок перебора эл ементов очереди типа PriorityQueue не определен , несмотря на то, что их можно перебрать, используя итерато р. Чтобы правил ьно воспользоваться клас сом PriorityQueue, следует вызывать та­ кие методы, как offer () и poll (), определенные в инте рфейсе Que ue. Класс ArrayDeque Класс ArrayDeque расширяет кл асс Ab stractCol lect ion и реализует интер­ фейс De que. Он не добавляет свои методы. Кл асс ArrayDeque со:щает д инамиче­ ский масс ив, не имеющий огран ичений по емкости . (Интерфейс Deque подде р­ жи вает реализации с ограниченной емкостью , но не накладывает на се величину никаких ограничений.) Класс ArrayDeque является обобщенным и объявл яется следующим образом: class ArrayDeque<E>
590 Часть 11. Библ иоте ка Java где Е обозначает тип объекта, сохраняемого в коллекции. В классе ArrayDeque определя ются следующие конструкторы: ArrayDeque () ArrayDeque (int раsнер) ArrayDeque (Collection<? extendз Е> с) Первый конструктор создает пустую двустороннюю очередь, первоначальная емкостью кото рой равна 16. Второй конструктор создает двустороннюю очередь указанной емкости . Тр етий конструктор создает двусто роннюю очередь, иници· ал изируемую заданной коллекцией с. Но в любом случае емкость увеличивается при вводе новых элементов в двустороннюю очередь по мере надобности. В приведенном ниже примере программы демонстрируется применение клас­ са Ar rayDeque дл я организации стека. 11 Продемонстрировать приме нения класса ArrayDeque import java . util .*; class ArrayDeque Demo { puЬlic static void ma in (String args [] ) { 11 создать двухс тороннюю очередь ArrayDeque <String> adq = new Ar rayDeque<String> (); 11 использовать кл асс ArrayDeque для организации стека adq .push ("A ") ; adq.push("B "); adq.push("D"); adq .push ("E ") ; adq . push ("F") ; System. out . print ( "Извлeчeниe из стека : ") ; wh ile (ad q.peek () != null) System.out .print (ad q.pop() +" ") ; System. out . println (); Эта программа выводит следующий результат: Извлечение из стека: F Е D В А Класс EnumS et Класс EnumS et рас ширяет класс Ab stractSet и реализует интерфейс Set. Он служит для создания множества, предназначенного для применения вместе с клкr чами перечислимого типа enum. Это обобщенный класс , объявляемый следующим образом: class EnumSet<E extendз Enum<E>> где Е обозначает эл ементы перечислимого типа. Следует иметь в виду, что класс Е должен расширять класс Enum<E>, а это требует, что бы эл ементы относились к указанному перечислимому типу enum.
Глава 18. Пакет java.utiL, часть 1. CoLLections Framewo rk 591 В классе EnumS et конструкторы не определяются . Вместо этого для создан ия объектов испол ьзуются фабричные методы, перечисленные в табл . 18. 9 . Обратите внимание на неоднократную перегрузку метода о f ( ) . Это делается из соображе­ ний эффективности . Передать известное количество аргументов, когда оно неве­ л ико , можно быстрее , чем делать это с помощью параметра переменной дл ины. Таблица 18.9. Методы иэ класса EnumSet Метод Описание static <Е extendз Enum<E>> Создает множесгво типа EnwaSet, coдepжa- EnUl llS et<E> allOf (Class<E> t) щее элементы заданного перечисления t static <Е extendз Enum<E>> Создает множесгво типа EnUl llS et, дoпoлня- EnUl llS et<E> coшpl&1D&ntOf (EnWl lS et<E> е) ющее элементы, отсугствующие в заданном static <Е extendз Enum<E>> EnumSet<E> copyOf (EnWl lS et<E> с) atatic <Е extends Enum<E>> EnumSet<E> copyOf (Collection<E> с) static <Е extends Enum<E>> EnumSet<E> noneOf (Clas s<E> t) static <Е extends Enum<E>> EnumSet<E>of(Еv,Е ...аргуменпш �dлины) static <Е extends Enum<E>> EnumSet<E> of (Е v) at:&tic <Е extends Enum<E>> EnumSet<E> of(Е vl, Е v2) static <Е extends Enum<E>> EnumSet<E> of(Е vl, Е v2, Е vJ) st:&tic <Е extendз Enum<E>> EnumSet<E> of(Е vl, Е v2, Е v3, Е v4) static <Е extends Enum<E>> EnUl llS et<E> of(Е vl, Е v2, Е vJ, Еv4,Еv5) static <Е extends Enum<E>> EnUl llS et<E> ranqe(Е начшю, Е конец) множестве е Создает множество типа EnuшSet, содержа­ щее элементы из заданного множесгва с Создает множесгво типа EnWl lS et, содержа­ щее эл ементы из зада нной коллекции с Создает множесгво типа EnwaSet, содержа­ щее элементы, которые не входят в заданное перечисление t, которое по определению яв­ ляется пустым множеством Создает множество типа EnumSet, содержа­ щее элементы v и нуль или дополнительные значения перечислимого типа Создает множество типа EnumSet, с оде ржа­ щее элементы v Создает множссгво типа EnwaSet, содержа­ щее элементы vl и v2 Создает множество типа EnwaSet, содержа­ щее элементы от vl до vJ Создаст множество тина EnumSet, содержа­ щее элементы от vl до v4 Создает множество типа EnumSet, содержа­ щее элементы от vl до v5 Создает множество тиIIа EnumSet, содержа­ щее элементы в задан ных IIределах от начала идоконца Доступ к колле кциям ч ерез итерато р Нередко требуется перебрать все элементы коллеющи, юшример, выве­ сти кажды й ее элемент. Для этого можно, н а п р имер , воспользоваться иrпер ато­ ром - объектом класса, р еализующего оди н из двух и н те рфейс о в : Iterator или List iterator. В частн ости , интерфейс Iterator 1юзволяет организовать цикл для перебора коллекции, извлекая или удаляя и;} нее эл ементы. А инте рфейс
592 Часть 11. Библ иотека Java Li stlterator расширяет интерфейс Iterator для двустороннего обхода с писка и видоизменения его эле ментов. Интерфейсы Iterator и Listiterator являют· ся обобщенными и объя вля ются следующим образом: interface Iterator<E> interface Listi terator<E> где Е обозначает тип перебираемых объектов. В интерфейсе Iterator объявля· ются методы, перечисленные в табл . 18.10. А методы, объявляемые в интерфейсе Listlterator. перечислены в табл . 18.11 . В обоих случаях операции, видоизменя­ ющие базовую коллекцию, необязательны. Например, метод remove ( ) сгенериру· ет исключение типа Un s uppo rtedOpe rationException, если вызвать его для кол­ лекци и, досrупной тол ько для чтения. Возможны и другие исключения. Та бл ица 18.10. Методы иэ инте рфейса Iterator М етод de fault void forEachReшa.ininq (Consuшer<? super Е> действие) boolean hasNext () Е next () void r81DOve () Описание Выполняет заданное действие над каждым необработан· ным элементом коллекции (добавлен в версииJDК 8) Возвращает логическое значение true , если в кол · лекции еще имеются элементы, а иначе - логическое значение false Возвращает следующий элемент из кол- лекции. Ге нерирует исключение типа NoSuchElementExcep tion, если в коллекции больше нет эл ементов Уд аляет текущи й элемент из коллекции. Ге нерирует исключение типа IlleqalStateException, если предп ринимается поп ытка вы звать метод reшove () , которому не пред шествовал вызов метода next ( ) _ В варианте этого метода по умолчанию генерируется исключение типа UnsupportedOperationException Та бл ица 18.1 1. Методы иэ инте рфейса Listi terator М етод void add(Е oбi iexm ) default void forEachВemaininq (ConsWl ll ll r<? super Е> действие) Ьo o lean hasNext () Ьo o lean hasPrevious () Описание Вводит задан ный об�!ехт перед элементом, который дол­ же н быть возвращен в результате последующего вызова метода next () Выполняет заданное действие над каждым необработан· ным элементом коллекции (добавлен в версии JDK 8) Возвращает логическое значение true, если в списке имеется следующий элемент, а иначе - логическое зна­ чение fаlsе Возвращает логическое значение true, если в списке имеется предыдущий элемент, а иначе -логическое зна­ чение fаlsе
Гл ава 18. Пакет java.util, часть 1. CoHections Fra mework 593 Метод Е next () int nextindex () Е previous () int previou s!ndex () void remove () void set ( Е fN1Ы'Klll) Описание Во:111ра щаст и1с;1ую щиii :�лсмснт 1п п111ска. Ес.-1 11 c1 t·;11·ю­ щ1 1ii -�Лl'M(' llT отсу1 ·п ·вуст. то ГC llCJHIJ>H�l·o1 llПC ll011Cl lllt' п ша NoSuchElementException Во: 111р;1щаст 1111;tc l\c слс;11·юще1·0 :�л с�1с1 1та в п111скс. Ес1 11 (")!( "/\\ IOll\llii :�Лl'Mt'l IT ОТПТСТl\Н'Т. Т(' 110: 111ра щ;1сто1 n·11111;i п11 1(·ка Во:111р;1111;1ст 11рс;1ы;1ущ11ii :м см<'l lТ 11:1 п111ска. Ес:1 11 11pt·;11o1 - ;11·щ1 1ii >IЛl'Ml'l lT OTC)TCTl l\'l "Г , ТО П' lll'f H!pHTПI llПCllO'll'l lllt' п111а NoSuchElementException Во: 111р;1111аст 1111;1<:1«· llf>t'/\Ы/t\' 11\lTo :м смс1 rт;� в п111с кt". Есл11 11рс;1ы11ущ1 1ii :мt·мt·1 1т отс1тст вуст. то 1ш:тр;1111:1tтп1 :11 1а•н·1111с -1 �-/1ал нст п· к1щ11 ii :·1лt·мс11т 1ы пшска. Есл 11 �1сто;1 remove () 11ы:1ывасто1 ;10 \1сто;1а next () 11;r 11 previous () .то 1·с1 1t'р11ру�тп1 11с к.1 11 0•1с1 11tс т1111<1 IllegalS tateException / /р11св;11111аtт :i;щ;1 1111ыii 1Ип.ек111 TC К)' lltt' \.I\' :м сщ· 111т с1111· п;;1. : ·)то :-1л с\1с11т. 110:111ра ща смыii в рс:11л �;1·;пт 1нн :1t·,:11 1tт1 1 11ы:1011а жто;1а next () 11.1 1 11previou s () На за метку! В версии JDK 8 появилась возможность дл я циклического обхода коллекции сред­ ства ми интерфейса •:р ! i t(•r , 1t •ч. Да нный интерфейс действует иначе, чем интерфейс тr:r:rгt.с;r. и будет описандалее. Применение интерфейса Iterator Пре11це •1е\1 оfiр ап пъс н к коллl'J\1\1111 '1Ср е:1 11те рато р , слс;1уст 1юл у111п1. tто . В I< аж­ .'(ОМ ю1апт кoлJ1 c1;1111ii 11p e/\ocт;1ш1Ht'Т< "il .\l l'To;\ i t l' ra t- .о г ( ),во:шр ащающ1 1ii 11п· р <1л>р на 11а•1ало Ю>J1лсю 11 111. lfп10 ;11.:1\'И оfi·1,1 ·кт 11тер ато р а, чож110 1юл\·1 111п. ;1опУ1 1 к 1; <1Ж;\о­ му :�лс щ·1 п - у колж·юt1111 1111 011с1 н·ю1. В оfiщсм, 11р 1·1�с11с1111с ите р атор а ;i;1 11 11c p cfiop a CO.'\C j )Ж ll\101°0 KOJIJl('КJ !l lll ПIO.'!l lТOI 1\ 111>11111..'I JIClllllO ('Jl l '! \)'101 1\llX .'t(' ikл11 1ii. • �( т;11ю111 1п. 11н· р ;пор 11а 11;111ало кш1ж·ю 11 111, 1юлу •11111 с1п 11:1 \l('TO.' ta BJ.1:1ы 11ae\IOl0ll )l)IН KOJIJ l ('J\1 !1 111. • О р 1·;1 1111:10 11ат1 . н11кл. в котор оч 11ы:1ы ваетсн \Н'ТО/\ t1a sNroxt 1). l lcp ci) 11p ;1л. со/tер ж11\юе ко;1лс юt1111 ;10 тех нор . 11tн.:а \t сто;1 r1 asNex1 ': ) 11е 1ю.111р <1л п .' 1О- 1·1 11н·п<ое :11 1а11с1 111с !.. r: 11e. • Пол\·• 111л. в н1 1кле 1c 1 ;1<;1 ыii :-1 л1·,1с11т кош1еющн. 111.1:1ы11ан \l l' t o.· t •1• .. Что к;1саето1 1111,:1011 коллею 111ii, р с:1л 11:1У ю11111х 1111п· р фсiiс J,i �� 1· . то 111н\ •111п. ,1лн 1111х итератор \HJЖllO. вы:1ы11;1н \!('ТО.'\ i i :оt : t.i,· r а 1 r (). Как 11онп1н:юп. р:111ее. 1 пс р ато р с1111п«1 ofitт11e •111 11;1('T .' \ОСТ\ 11 к :�J1 l'\1e1пa \t та коii коллекц1111. к;н.: 11 11р н\\о\\. так 11 11 оfiр ап нш 11a1 1 p aвл('llllll. а т; 1кже 1ю:111ол нет 11щtо 11:1:-.н ·11нт�, :-1л 1·\1с 11 1ы п111п; ;1. л в ОСТ<IЛ Ы!О\1 1111н· рфеiiс ! 'iс; ;- т t ,�r.j t ''у lll НIM('Шl ('TCн T;\J((l\I Жt' ofipa:IO\I. K<IK 11 1 [(f- 1·epфciic I •. i: 'rдt ,,r·.
59' Часть 11. Библиоте ка Java В следующем примере программы выполняются все перечисленные выше действия и демонстрируется применение обоих интерфейсов, Iterator и L i s t Iterator. В данном примере в качестве перебираемой коллекции используется объ­ ект типа ArrayL i st, но общие принципы перебора содержимого с помощью итера· торов применимы к коллекциям любого вида. Безусловно, интерфейс Listiterator доступен только тем видам коллекций, которые реализуют интерфейс List. 11 П р одемонстр и р овать приме нение итер аторов import java .util .*; clas s IteratorDemo { puЫic static vo id main (String args[]) { 11 создать списочный ма ссив ArrayLi st<String> al = new ArrayList<String> (); 11 ввести э леме нты в списочный ма ссив al .add("C ") ; al.add("A"); al.add("E"); al .add("B ") ; al .add("D "); al .add("F ") ; 11 использовать итер аторы дл я вывода содержимо го 11 списочного ма ссива al System. out .print ( "Исходное содержимо е списочного ма ссива al : ") ; Iterator<String> itr = al .it erator () ; while( itr. hasNext ()) { String eleme nt = itr .next () ; System.out . print ( eleme nt + " ") ; } System. out . println () ; //видоизменить перебираемые объе кты Li stiterator<String> litr = al . listiterator (); while(litr. hasNext ()) { String eleme nt = litr . next (); litr .set (eleme nt + "+") ; System. out .print ( "Измененное содержимо е списочного ма ссива al : ") ; itr = al .it erator (); while (itr. hasNext ()) { String eleme nt = itr . next () ; System. out . print ( element + " ") ; } System. out . println (); 11 а теперь отобразить список в обра тном порядке System. out .print ( "Измeнeнный в обратном порядке список : ") ; wh ile (litr . hasPrevious ()) { String element = litr . previous () ; System.out . priпt ( eleme nt + " " ); } System. out . println () ; Ниже приведен результат, выводимый данной программой.
Гл ава 18. Пакет java .util, часть 1. CoHections Fra mework 595 Исходное· годержим,J е списоч ного ма ссива al : С 11 Е В D F' Модифицированно<> содержимое списочного ма ссива al : С+ А+ Е+ В+ [J + r' f Модифицированный в обратном порядке список : F+ О+ В+ Е+ А+ С+ ОСlраппс ocoCloe 111 11вы1111с на в ы в о;\ пшска в о б ратном 1юрн;1ке. После 1111- J\ОIПМе11е1111н пmска итератор 1 i t. r указы вает на ко11с11 сниска. (1 lа1ю!\ш11:-.1. •по мсто;t 1it· . r· . tl i:l sNext. ( ) во:шращаст лоп111сс кос :ш ачсние f а l se, сиш логп1ПI)Т кош·11 сшкка.) Лл н ш: рсбора п111п«1 в обратном 1юря;\Ке в да нно й прm·ра ч!\Н.' 110- 11рсж 11сМ\' 11<·1юл1.:1устс н итератор 1 i t: r, 1ю на :-нот ра:1 в нcii нровсрнстсн. сУщс­ ст11уст л11 11рс/1111с1·твующ11ii :'I JJC!\1t·1п. И ;\о тех нор , 1юка :с1то ;1t·л аt·тсн , выво;111·1тн ю1ж;1 ы i i :'lл с:-.1с1п. 1юл у 11ас\lыii 11:1 п111ска. Цикл for в стиле for each как альтернатива итераторам Есл11 11с т рсfiус то1 11щщ11:1мс1ш·1ъ со;tс р жимос коллею1ии или и:шлскат1, 11:1 нtт эж·мс1пы в обрап ю!\1 11орн;1кс , в та1<ом случае 1\ИКJI for в стиле for ,� a<�r1 :-.1 ожет ока:и1ъсн fioлcc v; 1оfнюй ал 1.тс р11атшюй ите раторам. 1 l шюмним, что в 1 1и клс for можно 11срсб11 рап. люfiую коллею\ию о fiъектов, рс;uш :1ующу ю 11 1 перфсiiс I t eratJl.e. л llO('KoJl l> K)' IS('(' IUl <\('( ' 1>1 KOJIЛCКI (И�i реал и:1уют :-JTOT И11Тt'рфсi1с , то И�НI можн() 011ср1ч ювал, 11 цш<лt· С о r. В сле11ующсм 11р11мсрс 111ю1·1мммы I\ИКJI fo r 11 стиле for еас�1 11пю:1 1 ,:1\·сто1 )1,JШ cyм !l>lll(>Oll<l llШI CO} \t' (IЖ l\: \1 0 1 '0 КОЛJ/t'Ю(ИИ: 11 ПрИМРн ение l\И KJlcl for р, '. �1·иле f or eact1 11 дл я перебора ЭЛСМt' i-Рrсв КО.ПJl !:-"l<'ЦИИ impor· t. jJ.\'a . iJ t i .l.'*; cldSS 1=-\·1 r-J·:a�·�t1 f)(�:r.1) { ptitJl ic st.at.ic •; oi1J ma н;(:·: t.. ri r19 .HcJs fJ) i / 1 С'О':3}\<:1 ·г �- , �' ПИС'(;ЧНЫЙ МгJССИР дл q uель.1х чисел Ап·,,у!,i '<t<тпt'f•:.JPr> ""'l" ,, P"W Arrаyi.ist<lпteqer>(); // веес•ги ч: t1 :_·лор,1.J Р ·-�н ач1_ ·, ния р, с11и:ссJЧНый массив \J al s.aci{if]); �1аls.аtid(�); <;ctl,;. "Jct<J()); "'а1:;.actct(�); 1; al s.acictf�,1 ; // О[.!Ганv.:�оват�) u.v1 кл ллs выЕ1.)Д::l чис.r!овых значений Sys"ern.O•Jt.рriпt 1 11 Исходное с()д":: ржиr.t.ае списочного ма ссир,а \r a l s: '1 ) ; tе;riiпt_ v : .._"ii1s:. Sуst;?rn.OlJt.рrint·(\� r "" .,; Sy::;t.€�i11 .01J t. •�: r:i11t. lп (); ! ! суммир оР.с\ ть 1-1 ис·. r. сь1- ,;�':. . · -�нг·-: Р11иq Р :н1 �-� :1е· for l!!t_ Sl.lr!l _--: О; fсr(iпt �J : '·/,-1l�"1 SiJШ t-'7"' \/;
596 Часть 11. Библиотека Java l l 11жc ll} >lllH' ,'((' 11 р с:1\Л I>Гат Bbl\\()J\ l\('l \llЯ ,' ( ;11111oii 11\ IO l'J >< IM\l l>I . И.�Х:"; ��J-: . �>:-' '\ !]!t'�)ЖИМОf' С� ПИСОЧНОГI) М-J.('СИ !:". . Г!. 1..1а]_;-: ] �11 (_�ytv!M A '-i И'.'.Г: С1ЕЫХ '=)Н.3ЧСНИЙ: l5 Ка1-: 111 ц11п· . о р 1·а1111:1овап. 1111 ю1 1(• с :11 1;1•11 1тслы10 н р ощс 11 ко р <Р1е. •1см 11пюль­ :ювал. 1пс ратор . l lo 011 1юдходит ,'f,JI H 11с ребор а :iЛС\1с1по11 KOЛ)ll'IO(llll тол ы'о в 1 1 ря­ \1О\1 11а11р <111л с111111 11 11(' 110:111оляст Bll)(Oll:l\ll'llHТI> :i Jl(' \Jeltтbl KOJl)I CIO(llll. Ите рато ры-раздел ител и В щ· р п 111 .J J)К Н в11е;(р с11 11овыii п 111 11те р ато р;1, 11;1:1ывас\1ыi i 11 111 1·jm 111 0Jю. н-Jm.J!!e­ _ 1111111: 11·.11 11 ш1ре/(еляе:ш.1ii в шпер<\>еiiсс Spl it е rаl . Итср;1торы-ра:цсл итсли 110:1110�·1н 1от 11срс б 11р ап. llOCJ\Cj(OB<Пl'JI ЫIOГIЪ :i Jl('Ml'llЛH\, 11 11 :i TO M OПIOllIClllШ 01 111 11<цо ГJ 11ы 01111са�111ым вы111с 1пс р ;по р ам. l l o llf J11\1<'11н1oти1 01111 1111;1•1<·. Kpo\IC топ>. 11 1111п·рфеiiсе Sp1 i 1: е r а t о r 11р<0/\осташ1н10тп1 :111ач11п·л ЫIО более 11111 рокие фп1ю(11О11алы1ые во:1мож11ости, ЧС\1 в 1111н·p<j1ciicc I t е r а t r)г или 1.• i st I t.erаtor. В с р онтно, 11a11fioлec важ ной ocofi t·1 111oпыo 1111п· рфс iiс ;1 Sp 1 i t cr·at пr нвлнетrя его С11Осо fi 1 юпъ 1 ю;\;\е р ж1шап. 11а р аллсл ы1у10 1пс р ацию 0·1лел ы1ых частс ii 11осле­ :tо ватсл ыюст1 1 :� лемсrпов, а слс;\оватсл ыю, 11 пар аллслыюе 111 ю1·р а\1l\1 11 р ованиr, 1нцро б 11tт р асс\1атр 1шас\10с 11 1лавс �Н. llo 111пс рфс ii<' Sp l i tcr at-or можно 11ри­ \1 t ·11н ·1ъ и в то \1 CJIP 1ac, коrла р ас1 1ар аллсю1ва1111с 11ы 11ш111нсмых 01 1cp aн11 ii нс т ре­ Г>н·тсн. O ;щo ii 11:1 11р 1Р11ш /(ЛЯ та к01·0 111 н1 \1с11с11ш1 и1пс р <!>сi i са Sp.l j t_ се;, ror может С:I\'ЖIПЪ то обсгоятслы-тво, '!ТО в 0/(110\1 (' /'( ) \IСТI ЩС ('()'J('T<llOTOI 0\lt' J ><IHllll, ВЫIЮЛ· 1\Нl' \11>\С \l l'T0,'(< 1\111 r1 asN"'xt. () И ПЕ'ХI () 11<1,' ( llCJ H' fill J ><lt'\lbl\111 :�J\ C\lt'IП<l\1 11. It 11тcpфc iic �) pJ .i tcгat oг нвлнсто1 o fiofi 1щ·1111ы\1 11 о б ы1 11лястсн c;1c1\\'IO IНll \I об­ р,1:ю\1 : interface Spliterator<T> 1',' (С Т обо:111а•1аст л 111 нсрсбирасмых :iЛС\Н'I П< )\\. В 1111п· рфсiiп· S р 1 i t_ '-" r а r_ г 0Гlыш­ лн ютп1 мслцы. 11с р с•111слс1111ые в та бл. 1 Н. 1 �- Та бл ица 18.12. Методы из инте рфейса Spliterator М етод int characteri stics () long estima te Size () de fault void forEachRemaining ( Consume r<? super Т> 1)eilcm11ue) Описание Во:шр;1 щ.�t·т х<1 ракп·р1 1гп1ю1 вы:1ывающс1·< ) 1псрат<� ра-р;�:щсл1пел н, 11ре;1п ;шлс 1111ыt· 11 111щс цслоч11и1с11- 1101·0 :11 1;р1с1 111я О11с111111<1ст кол11'1е<т110 :-1л с�1е1пов. которое оста­ лщъ псрсбрап" 11 110:1 11ращ;н·т пол\'чt·1111ыii рс:1\'Л1.­ тат. Есл и :по кол1Jt1сство 11еm.:1н 11олуч 1п1. 110 к<1- коii-1 111бую. 11р11ч1111с, то 110:111ра щастся :111а•1с1 111с ко 11ста1 пъ1 Long . МAX_VALUE Bы1IO.'l llН l"I :J<l)(<1 1111oc 1)ei1111111ue 1 1 ; щ 1\<IЖJ\l•l \I 11со6ра­ fiот<11111ы�1 :J;J t'�ll'I ПO�I ll llCTO' llllШC )\;!lllll>I X
Гл ав а 18. Пакет java.utiL, часть 1. CoLLections Fra mework 597 Метод dafault Coшparator<? super Т> qetCoшparator () default long getExactSizeI­ fltnown () default Ьoolean hasCharacteri stics (int val) Ьoolean tryAdvance (ConsU1 11e r<? super Т> действие) Spliterator<T> trySplit () Окон-чание табЛ. 18. 12 Описание Возвращает компаратор, используемый вызы­ вающим итератором-разделителем , или пустое значение nul l, если используется естественное упорядочение. А если последовательность не упорядочена, то генерируется исключение типа IllegalS tateException Есл и установлен размер вызывающего итератора­ разделителя, то возвращается кол ичество элемен­ тов, которое осталось перебрать, а иначе - значе ­ ние -1 Возвращает логическое значение true, если у вы­ зывающего итератора-разделителя имеются харак­ теристики, передаваемые в качестве параметра val, а иначе - логическое значение false Выполняет заданное действие над следующим эле­ ментом в итерации. Возвращает логическое значе­ ние true , если следующий элемент присутствует, а иначе - логическое значение false Разделяет, если это возможно, вызывающий итера­ тор-разделитель, возвращая ссылку на новый ите­ ратор-разделитель для последующего разделения, а иначе - пустое значение null. При уда чном исходе разделения исходный итератор-разделитель пере­ бирает одну часть последовател ьности, а возвращае­ мый итератор-разделитель - остальную ее часть Интерфейс Spliterator применяется для решения основных задач итерации очень просто. Для этого достаточно вызывать метод tryAdvance () до тех пор , пока он не возвратит логическое значение fa lse. Если же требуется выполнить одно и то же действие над каждым элементом последовательности , то для это й цели имеется более простая альтернатива - вызвать метод forEachRema ining ( ) . В обоих случаях действие, происходящее на каждом шаге итерации, определяется тем , что именно объект типа Consume r собирается делать с каждым элементом, где Consume r - это функциональн ый интерфейс, выполняющий действие над объ­ ектом. Этот обобщенный функциональный интерфейс определен в пакете j ava . ut il . function, подробно рассматриваемом в гл аве 19. В функциональном интер­ фейсе Consume r определяется еди нственный абстрактн ый метод accept (), об­ щая форма которого приведена ниже. void accept (Т саrлка_н а_ объект) Если на каждом шаге итерации вызывается метод tryAdvance (),то следующий эл емент последовател ьности передается по ссылке , обозначаемой параметром ссылка _ на_ объект. Зачастую интерфейс Consume r проще всего реал изовать с 110- мощью лямбда-выражения.
598 Часть 11. Библиоте ка Java В 111 н�нс;tе111юй ниже IIJIOГJ)aммc 1tс :\1011сч н1 руетс я нростоii 11р11мер 11ри:11е11е· 1111я 1111п·рфсiiса Sp l i t е r· a t: or. R этой щюгра мме J\С :\1011ггр11рустс я также нриме· 11е1111е обо их методо в, t ryAdvaлce ( ) и f о cE:. 1chR('! ma j п i rн1 ( ) . Обратите ш111ма1ше ш1 то , что :-н и метол.ы сочетают в од1 10:1.1 свос\t вы:юнс ;\сiiст1111н мстодов пехt () и !1"lSNex t (: н:1 н11н:рфейса Iterat:or. ! / Продем.тстрировать простое приме нение и11терфейсi.l Spli te rator import java .util .*; ci,_iss �� pl i t eratorC1emo puЫ ic static void ma in (String args[ ]) { // с:пд ать списоч ный масси в числовых Э11 3'Н� ний типд douhle ArrayLi st<DouЫ e> va ls = new ArrayLi st<> (); 11 ввести значения в списочный ма сси в чa ls .adcJ(l.0) ; va ls .ad d(2.0) ; va1 s.aci d(3.0) ; ·.; a ls.add (4.0); vals. acidi5.0); 11 выэ ва 1ъ ме тод tryAdvance () дл н вывода содержимо го / / спис:очнсн' о ма ссива vals System. out .pri.пt ( "Coдepжимoe списочного ма ссива ·v als : \11" ); Sp literator<Do uЫe> sp ltitr = va ls . spl1 terator (); wh i lгis pltitr .tr yAdv ance ((n) -> System. o ut .pr ir1tlл (п)1); System. out .pr intln () ; / / созда1ъ но вый списочный ма ссив , соде ржа щий к1ыдратные // корни числовых значений из списочного ма ссива va ls sp lt itr = va l s.spl iterator (); ArrayLi st<DouЫe> sqrs = new Ar rayLi s t<>() ; wh i lelspltitr.tryAdv ance ((n) -> sqrs .adJ (Ma th .sqrt (11) ))); / / вызвать метод forEachReшaining () щн� вывода содержи мо го // списочного ма ссива sqrs System. otrt .priпt ( "Coдe pжимoe списочного ма ссива sc1r-s : \11" ); sp ltitr = sqrs . spl iterator () ; spltitr . forEachRemaining ((п) -> System. aut .priпtln ln) ) ; Sys tem. out .println() ; 1lижс 11р1111е;\с11 р1::1уш,тат, 11 1.1 1ю;щ мыii J1.a 111юii 11ро1·раммоii. Содержимое списочного ма ссива va ls : l.о ?.о 3.0 4.0 '),() Содержимое списочного ма ссива sqrs : 1.r.) l.4l4?13�6?373095 l l.73 2050R0756B8172 2.() 2 .23606797749979 1 lесмотрн 11а то что в ла шюй проr·рамме ;\С :\1ОН(трирусп:н мсха11н:1м при менс· 11 11я 11 1 1тсрфсiiса Spl iterat.or, она нс р<1скры11ает веп. еп> 1ютс1 щнал . Как у1юми·
Гл а в а 18. Пакет java.utit, часть 1. CoHections Framework 599 налось выше, наибольшую выгоду применение интерфейса Spliterator прино­ сит в тех случаях, когда требуется параллельная обработка. Обратите внимание на методы characteristics () и hasCharacteristics (), перечисленные в табл . 18. 12. У каждого итератора-разделителя типа Spl i t е r а t о r имеются свойства, называемые характеристиками. Эти характеристики определя­ ются в статических полях типа int интерфейса Spliterator, в том числе SORTED, DISTINCT , SIZED и IММUTABLE и прочих полях. Для получения характеристик ите­ ратора-разделителя достаточно вызвать метод characteristics (), а для выяв­ ления отдел ьной характеристики у итератора-разделителя - метод hasCharacte ristics (). Зачастую получать характеристики итератора-разделителя не требу­ ется , но иногда они помогают в написании эффективного, устойчивого кода. На заметку! Рассмотрение интерфейса Spl iterator будет продолжено в гл аве 29, где обсуж­ дается его применение в контексте нового прикладного программного инте рфейса API по­ то ков да нных. Лямбда-выражения рассматриваются в гл аве 15, а распараллеливание и па­ раллельное программирование - в гл аве 28. Несколько подчиненных интерфейсов, вложенных в инте рфейс Sp l i t е r а to r, предназначены для применения вместе с примитивными типами данных douЬle, int и long. Это интерфейсы Sp literator . OfDouЫe, Spliterator . Ofint и Spliterator . OfLong. Имеется также обобщенный вариант интерфейса Sp literator . OfPrimitive, предоставляющий дополн ительные удобства и под­ чиненный упомянутым выше интерфейсам. Сохранение объекто в _ пользовательских классов в коллекциях Ради простоты во всех приведенных ранее примерах программ в коллекци­ ях сохранялись объекты встроенных классов наподобие String или Integer. Безусловно, сохранение в коллекции не ограничивается только встроенными объектами встроенных классов. Напротив, эффективность коллекций в том и со­ стоит, что в них можно хранить любой тип объектов, включая объекты тех клас­ сов, которые вы создаете сами . Рассмотрим в качестве примера следующую про­ грамму, где класс LinkedList используется для сохранения почтовых адресов: 11 Простой пример обработки списка почт овых адресов import java .util .*; class Addre ss { private St ring name ; private String street ; private String city; private String state ; private String code ; Address ( String n, String s, String с, String st, St ring cd) { name = n; street = s; city = с;
600 Часть 11. Библиотека Java state = st; code = cd; puЫic String toString () return n ame + "\n" + street + "\n" + city+""+state+""+code; class Ma ilList ( puЫic static void main (St ring args []) { Lin kedList<Add re ss> ml = new LinkedLi st<Add r ess>() ; 11 ввести элеме нты в связный список ml . add (new Address ("J.W. West", "11 Oak Ave", "Urbana" , "IL" , "б1801") ); ml . add (new Ad dress ( "Ralph Baker" , "1142 Maple Lane", "Mahom et ", "IL", "61853") ); ml . add (new Address ( "Tom Carlton", "867 Elm St", "Champaign" , "IL" , "61820") ); 11 вывести список почтовых адресов for (Address element : ml ) System. out .println ( eleme nt + "\n ") ; System.out . println (); 1 Iиже 11ривсдсн ре:Jул ьтат, выводимый данной программой. J.W . West 11 Oak Ave Urbana IL 61801 Ralph Baker 1142 Maple Lane Mah ome t IL 61853 Tom Carlton 867 Elm St Champaign IL 61820 Помимо сохранения объектов пользовател ьс ких кл ассов в коллекнии, дан н ая программа заслужи вает вн имания еще и потому, что она довольно короткая . Если принять во внимание, что для сохранения, извлечения и обработки почто вых адре­ сов понадобилось всего около 50 строк кода , то станет очевидной эффективность каркаса коллекций. Ведь если запрограммировать все эти функциональные воз­ можности вручную, то программа окажется в несколько раз дл иннее. Коллекции предлагают готовые решения для широкого кру1·а задач программирования. Их следует использовать всякий раз, когда позволяет ситуа ция. И нте рфейс RandomAccess Этот интерфейс не содержит ни одного члена. Но, реализуя его, коллекция из­ вещает о том, что она поддерживает эффективный произвольный доступ к своим
Гл ава 18. Пакет java.utit, часть 1. Cottections Fra m ework 601 элементам. Даже если в коллекции и поддерживается произвольный доступ к ее элементам, такой доступ может оказаться недостаточно эффективным. Проверяя интерфейс RandomAc cess во время выполнения, в прикладном коде можно выяс­ ни ть , допус кает ли ко нкретн ая коллекция некоторые виды опе ра ций произволь­ но го доступа, и, в частности , нас кол ько они применимы к крупным коллекциям. (Что бы определ ить, реализует ли класс коллекции интерфейс RandomA ccess, можно вос пользоваться оператором instanceof.) Инте рфейс RandomAccess ре­ ализуется в кл ассе ArrayList и, сре;щ прочего, в унаследованном клас се Ve ctor. Обращение с ото бражен иям и Отображение представляет собой объект, сохраняющий свя:ш между ключами и значениями в виде пар "ключ.-.тачение " . По заданному ключу м о жно найти его з н ачен ие. Ключи и значения являются объектам и. Ключи могут быть однознач­ ными , а :ш ачения - дублированными. В одних отображениях до пус ка ютс я пустые ключи и пустые значения , а в других - они нс допускаются . В отношении отображен и й необходимо иметь в виду следующее: они нс реа­ л изуют и нте рфей с IteraЬle. Это означает, что перебрать соде ржимое ото fi ра­ жсния, организовав цикл for в стиле for each, ие удаrrпсн. J:)олсс топ>, веЛI,:ш п олучить итератор отображения . Но, как будет показано ниже , м ож н о получ ил, представление отображения в виде коллекции, которое до пускает псрсGор со;tср­ жимого в цикле или с 1юмощью итератора. Интерфейсы отображений Интерфейсы отображений определяют их характер и осоос 1111ости , 110 :�то:1,1у начать рассмотрен ие отображений следует с и х интерф ейсов. ОтоGражс ния 111щ­ держиваются в ипп� рфсйсах, 11е р сч11сленных в табл . 18. 1 �. Каж;tый 11 :1 тп1 х инте р ­ фейсов рассм<1тр ивастся далее по отдсл ыiости . Та бл ица 18.13. Инте рфейсы, подде рж и вающие отображения Интерфейс Описан и е мар Отображает од но:шачные ключи на з11а•1с11ин Map.Entry Онисьшает элемент отображс 11ин ( 11 ару "ключ -:11 1а•1с1111с'") . :+ го в1 1\"Грс1 1- ний класс интерфейса мар NavigaЬleМap Рас 111ирнст интерфейс SortedМap ;tлн и:ш леч1:11ин :ыемснтов 11:1 01о()р.�­ же 11ия 110 критерию 1ю11ска 11аибол сс то 1111ого сов11<1лс1111н Sor tec!Мap Рап1111рнет интерфейс мар та ким обра:юм. •побы кл 101111 рап 1ол;11·ал 11с1. по 11араrта ющеi1 Интерфейс мар Интс рфсiiс Мар отображ ает одно:Jш1ч11ыс кл ючи на :11 1аче1111н. К ·1 ю11 - :i то 0() 1,­ скт, ИП IОЛl>:iуL'МЫЙ дл я IIOCЛ C/1,YJOЩ('I"<) И31\Лl'ЧСl lИЯ ita 1111ыx. :J;ц;шан l'Jl lO 'I 11 :1 11a•1e­ l!Ht', �юж но ра3мсща·1ъ :шачен и с в от0Gраж е ш 1 11, 11рс;tставлс111ю\1 оG ы·кто\1 п111а
602 Часть 11. Библиотека Java Мар. Сохра нив :ш ачение по ключу, можно 11олучи1ъ его обратно по этому же клю­ чу. Интерфейс Мар является обобщенным и объя вляется приведенным н иже 06- ра:ю:-.1, где К обо:шачсt.ет тип ключей, а V-тип хранимых в отображении значений. interface Мар<К , V> Мет<щы, обън нлнемые в интерфейсе Мар, перечислен ы в та.бл. 18. 14. 1 lекоторые и:J них генерируют исключен ие типа ClassCastException, если заданный объект 11есов:-.н.:тп1м с элем ентами отображения. Исключение тина Nul l PointerException 1·снерируется ври 1юпытке использовать пустой объект, когда дан ное отображение :-п01·0 не до нус кает. А исключение типа Un suppo rtedOpe rationExcept ion генери­ руется ври 11011ыткс иэмени·1ъ неиэменяемое отображение. Та бл ица 18.14. Методы из инте рфейса Мар Метод default V compute (K k, BiFunction<? super К, ? super V, ? extends V> функция) default V compute ifAЬsent (К k, Function<? super К, ? extends V> фу1 11щ ия) default V compute ifPresent (K k, BiFunction<? super К, ? super V, ? extends V> функция) void clear () Ьoolean containsКey (OЬject k) Ьoolean containsValue (OЬject v) Описание Вызы вает :щцанную g')'lfКЦ ию для п осгрос ния нового значения. Если функция возвращает непусrое :шаче­ ние, то в отображе ние вводится новая пара "ключ­ значение", удаляется любая ранее сущесrвовавшая пара и возвращается новое значение. А если функцuя возвращает пусrое значение null, то уд аляется любая ранее сущесrвовавшая пара и возвращается пусrое значение null (добавлен в версииJDК 8) Возвращает значение, связанное с указанным ключом k. В противном случае создается новое зна­ чен ие, для чего вызывается заданная функция, в ото­ бражение вводится новая пара "ключ-значение " и возвращается созданное значение. Если же новое значение создать нельзя, то возвращается пустое значение null (добавлен в версииJDК 8) Если в отображении присутствует указанный ключ k, то для создания нового значения вызывается за ­ данная функция и новое значение заменяет прежнее в агображении. В этом случае возвращается новое значение. Если же заданная фуюсция возвращает пусrое значение nul l, то из отображения удаляются сущесrвую щие в нем ключ и значение и затем воз­ вращается пусrое значение null (добавлен в вер­ сии JDК 8) Уд аляет все пары "ключ-значение" из вызывающего отображения Возвращает логическое значение true, если вызы­ вающее агображение содержит указанный ключ k, а иначе - логическое значение falae Возвращает логическое значение true, если вызы­ вающее отображение содержит значение v, а ина­ че - логическое значение falae
Гл ава 18. Пакет java.utiL, часть 1. CoHections Fra mework 603 Пр одолжmие табл. 18.14 Метод Описание Set<Иap . Ent:ry<К , V>> entrySet ( ) Возвращает множество типа Set, содержащее все записи из вызывающего отображения в виде объек­ тов типа Мар . Entry. Следовательно. этот метод воз­ вращает представ.ление вызывающего отображен ия в виде множества Ьo o lean equals (OЬject обr;екm) Возвращает логическое значение true, если задан­ ный о6 6е1С1 11 яв.ляется отображением типа мар, содер­ жащим одинаковые значения, а иначе -логическое значение false defaul t void forlach (BiConsW1 18 r<? super к, ?superV>деi iствш! ) v qet (OЬject k) default v qetOrDefault (OЬject k, Vзадан ное _значен ие) int hAshCocle () Ьo o lean isEl llp tyО Set<D keySet () default V 11 1S rqe(Kk, V v, Bi.Funotion<? super V, ? super V, ?extenclsV>функция) Vput(Kk,Vv) Выполняет заданное действш! над каждым эл емен­ том вызывающего отображения. Если в ходе этого процесса удаляется элемент, то ге нерируется иcклю­ чeниe типa ConcurrentмodificationException (добавлен в версииJDК 8) Возвращает значение, связанное с указанным клю­ чом k. Если же ключ не найден, то возвращается пустое значение null Возвращает значение, связанное с укааанным клю­ чом k, если оно присутствует в 11ызы вающем ото­ бражении, а иначе - заданное_значепие (добаален в версииJDК 8) Возвращает хеш-код вызывающего отображения Возвращает логическое значение true, есл и вызы­ вающее отображение пусто, а иначе -логическое значение false Возвращает множество, содержащее ключи из вызывающего отображен ия. Следовател ьно, этот метод возвращает предстааление ключей в вызы в<�­ ющем отображении в виде множества Если в вызывающем отображении опугствует указанный ключ k, то в него вводится п<�ра �ключ­ значение", определяемая параметрами k, v , а затем возвращается значение v. В противном случае задан­ ная функция возвращает новое значе ние, исходя и:� прежнего значения и кл юч обновляется для достуна к этому значению. а затем оно возвращ<�ется из �1е­ тода 11 1& rqe ( ) . Если же заданная функция возвращает пустое значение null, то ключ и значен ия, с;1цс­ сrвующие в вызывающем отображении . удаляются из него и затем возвращается пустое зн ачен ие nu ll (добаален в верси иJDК 8) Вводит новое значение v в вызьшающее отображе­ ние, перезаписывая любое предшествующее :ш ачt� ние, связанное с задан ным ключом k Возвращает пустое значение nul l, если ключ ранее не существо­ вал. В противном слу чае возвращается предыдущее :�начение, связан ное с кл ючом
604 Часть 11. Библиотека Java Метод void putAll (Мap<? extends К, ? extends V> 11 1 ) defau lt V pu tifAЬsent (К k, V v) V remove (Obj ect k) default boolean remove (Object k, Ob ject v) de fault Ьoolean replace (K k, V 1фе:нснее_з1 1а че11ие , V 11овое_.1 11а че11ие) default V replace (К k, V v) defau lt void replaceAll (BiFunction<? super К, ?superV,?extendsV>футщия) int size () Collection<V> values () Oh· m1чa111u' 11106. 1. 18. 14 Описание Вводит все за 1 1иси из з<щан1 1ого отоnражс1111я т в выэывающее отображсшн: Вводит 11ару "кшоч-:11 1ачеш1е " , 01 1 релсл яе,1ую 11ар;1· метрами k. v, 11 вы:1ывающее отображение, ecmt ш1а отсугствует в нем ил и же есл и :111;р 1 ение. свя :1<1 111юе с заданным кл ючом k, ока:1ы ваеп·я 11уст ы м . В это't случае воэвращает пустое :mаче1111е nu ll. а и11;1'1е - прежнее :шачение (добавлен в версии.JIЖ. Н) Уд аляет заn ис1. 1ю :щщ11 11юму клю•�у k Ее.ли 11ара "ключ-эначешн� " . 011реJ1сл нсман 11ара�1е· трами k, v. 11рисуrсгвует в вы:1ь11 1а ю щс м он 1fi1мже· нии, то она уд;u�яется и эатем во:ш ра щ аетс я ;юп 1Ч{" ское значение true, а ина че - лоп1чсское :и 1а•1е1111е false (дooall.l l eн в верси иj[)К Н) Если пара ·· ключ-значение " , ш1ределяемая 1�а рамс· трами v и 1фе;ж:нее_.тн ачен ие, ЩШС}'•·ствует в выэы­ � ю щем отоfiражс.:нии, то это значение заменнется на новое_значение и зате м во:шращается лш·и ч (· СК(JС значение true, а иначе -;ю1·и11с.:скос значение fal se (добавлен в версии.JПК Н) Есл и в вы:зыв<1ющсм отображении 11ри сутствует зада нный клю•1 k, то ЗН<l'll'ШIC 1ю :пому ю1 ючу эамt.' няется новым значением v и во:шращастся прежнее значение, а и11<1че - пустое :шачение null (добавлс11 в версии.JIЖ 8) Выполняет заданную ifJУНкцию д;1я каждого эле· мента в вызывающем отображении, .за меняя эл емент резул1>гатом, во:шра1щ1емым задан ной функцией. Если в ходе этого процесса уд аляет- ся элемент, то ге нерируется исключение типа Concurrentмodi.ficationException (дoбaвлeн в версииJIЖ 8) Возвращает коли чест в о п<1р "ключ-значение" в вы­ зы вающем отображении Возвращает коллекцию, содержащую значения из вызывающего отображенин. (' ,J Jедовательно, этот метод возвращает представлен ие зн<1чений в вызы· вающем отображении в виде коллекции Обраще ние с отображениями опирается на две основные операции, выполняе­ мые методами get ( ) и put ().Чтобы ввести значение в отображе ние, следует вы­ звать метод pu t ( ) , указав ключ И значение, а для того чтобы получить значение из отображения - вызвать метод get (), передав ему ключ в качестве аргумента. По этому ключу будет возвращено связанное с ним значение. Как упоминалось ранее, отображения не реализуют интерфейс Collection , хотя явля ются частью каркаса коллекций. Те м не менее можно получить представ-
Глава 18. Пакет java.utiL, часть 1. CoLLections Fra mework 605 ле ние отображения в виде коллекции. Для этого можно воспользоваться методом entrySet (), возвращающим множество , содержащее элементы отображения. Чтобы получить представление ключей в отображении в виде коллекции, следу­ ет вызвать м етод keySet ( ), а для того чтобы получить представление значений в ото бражении в виде коллекции -метод va lues (). Все три представления ото­ бражений и их элементов в виде коллекций относятся к тем средствам , с помощью кото рых отображения инте грируются в крупный каркас коллекций. Интерфейс SortedМap Этот интерфейс расширяет инте рфейс Мар. Он обеспечивает размещение за­ nисей в ото б р ажении по порядку нарастания ключей. Интерфейс SortedMap яв­ ляется обобщенным и объя вляетс я приведенным ниже образом, где К о б озн а ч ает тип ключей , а V- тип хранимых в отображении значений. interface SortedМap<K , V> Методы, объявляемые в интерфейсе SortedMap, перечислены в табл . 18.15. Некото рые из них генерируют исключение типа NoSuchElernen tException, если вызывающее отображение пусто. Исключение типа ClassCastException ге не· рируется в том случае, если заданный объект несовместим с эле ментами , храня­ щимися в отображении. Исключение типа Nu llPointerExcept ion гене р и руетс я nр и попытке использовать пустой объект, когда пустые объекты в ;�а ш юм отобра­ же нии не допускаются. А исключение типа Illega lArgumentException генери­ руется в то м случае , если указан н е в е рный аргумент. Та бл ица 18.15. Метод ы из инте рфейса SortedМap Метод Coшparator< ? super К> сошраrа tor () К firstкey () SortedНap<K , V> headНap (К конец) К lastKey () SortedНap<K , V> suЬМар (К нача.ли , К конец) SorteclМap<K , V> tailМap (К 11ачало) Значение Возвращает компаратор в ы зы ваю ще г о от сортирова 11 11ого отображения. Есл и в отоб ражении применяется естест вен­ ное упорядочение элементон, то возвращает(·я пустое :1 11J· чeниe null Возвращает первый кл юч из ны:Jывающе1·0 отоfi раже нин Во:Jн раща ст отсортиров;�нное ото б ражение. содержащее те эл е м ент ы из nызыва ющеrо отоfi ражсн ин. кл ю'111 кото рых меньше, чем указанный кт1ец Вознращает последн ий кл юч в вы:1ывающем отоб ражении Возвращает отоб ражение, содержа 111ее :�л е�1с1 1ты вы:н.шаю· щеrо отображения . кл юч кот орых болыне, •1е�1 иа чало, 11л11 равен ему и меньше, чем конец Возвращает от сортироnашюе отоб раже1111е, годержащсс те элем е н т ы вызывающего отоб ражения, кл юч которых бол ь· ше, чем указа нное 1ючй.11О Отсор ти ро ван н ы е отображен и я обеспечивают очеш, эффекти вное ма11ш1ул и­ рование подотпображеии.я.ми ( и н ы м и словами, по;1м1южссгва ми отобр<tжениii) . Лл я получения подотображений служат метод ы headMap (), tailMap () или subMap r). Подотобрnженис, возвращаемое этими м етода м и , 11о;щс ржи nастс я вы:1ы нающ11м
606 Часть 11. Библ иотека Java ото!iраже1 111е:v1 . При и:�менснии од ного изменяется другое. Для получения 11ерво- 1·0 кл юча 1п 11О/\:V11 южества отображения сле;1ует вы:шать метод f i rs tKey (),а ;1,1IЯ 110.'I YЧCIHIН llОСЛе;щего кл юча - метод lastKey (). Инте рфейс NаvigаЬlеМар Инте рфейс NavigaЬl eMap расширяет интерфейс SortedMap и онределяет 1 1 0- вс:tсние отоГ>ражсния, подде рживающего извлечение записей из него но наиболее то 11110\1у сош1а;1снию с заданным кл ючом или нескол ькими кл ючами. Интерфейс \а чigзЫеМар является обобщенным и объя вляется следующим образом: interface NavigaЬleМap<K , V> l'i\t' К обо:шачает ти п кл ючей, а V - тип значений, свн:3ан11ых с кл ючами. Поми мо \Iет<що в, 11асщ' 1 '\уе :v1 ых из интерфейса Sortec:!Ma p , в интерфейс NavigaЬleMap вве­ , '\t' llЫ чсто;\ ы, 11еречислен11ые в табл. 18. 16. Некоторые из них генерируют исключе· 1111е тина С l assCastException, сел и объект несовместим с кл ючами отображения. Исключение типа Nul 1 PointerException ге нерируется при попытке использовать пустой объе кт, ко гда пустые ключи в отображении не допускаются . А исключение тина I llegalArgt1mentException передается при указании неверного аргумен·га . Та бл ица 18.16. Метод ы из инте рфейса NaviqaЫeМap Метод мap .Entry<K , V> ceilinqEntry (К обьеюп) К ceilinqKey (K объект) Navi qaЬleSet<К> descendinqКeySet () NaviqaЬleМap<K , V> descendingМap () М&p .Entry<К, V> firstEntry () мap.Entry<К, V> floorEntry (К объект) К floorКey (К oбlieкm) Значение Выполняет поиск в отображении наименьшего ключа k по критерию k >= о&ект. Если такой ключ найден, то возвра· щается запись по этому ключу, а иначе - пустое значение null Выпол няет поиск в отображени и наименьшего ключа k по критерию k >= обr,ект. Есл и такой ключ найден, то он возвращается, а иначе - пустое значение null Возвращает множество типа NaviqaЬle Set, с одержащее ключи в вызывающем отображен ии в обратном порядке. Сл едовательно, этот мегод возвращаеr обратн ое пред­ ставление ключей в отображении в виде множества. Результирую щее множество оп ирается на выз ы вающее аrо­ бражение Возвращает множество типа NaviqaЬleSet, обратное вызы· вающему отображению. Результирующее множество опира­ ется на вызывающее отображение Возвращает первую запись в отображен ии. Это запись с наи­ меньшим ключом Выполняет поиск в отображении наибольшего ключа k по критерию k <= о&ект. Если такой кл юч найде н , то возвра· щаетсА запись по этому ключу. а иначе - пустое значение nul l Выполняет поиск в отображении наибольшего ключа k по критерию k <= о&ект. Если такой ключ найден, то он воз­ вращается, а иначе - пустое значение null
Гл а ва 18. П акет java.utiL, часть 1. CoHections Fra mework 607 Метод NaviqaЬleМap<К , V> h8adМap(К вфсня я _фlНЩ4а, Ьo o lean 8К/lючuтельно) Nap.Entry<К, V> hiqherEntry (К обr.екm) к hiqher:кey (К обr.екm) Nap.Entry<K, V> lastEntry () Nap.Entry<K , V> lowerEntry (К обr.екm) К lowerКey (К обr.екm) NaviqaЬleSet<К> naviqaЬleКeySet () Nap.Entry<К, V> pollFirstEn try () нар.Entry<К , V> pollLastEntry () NaviqaЬleМap<К , V> suЬМар (К ниж:ня я _граница , Ьoolean включая_ии.жтою_ границу,Кверхня я _граница , Ьo o lean включш�_верх1 1 юю_ границу) Пр одолджение табл. 18.16 Значение Возвращает множество типа NaviqaЬleSet, содержащее все записи из эызывающего отображения по 1U1ючам меньше, чем заданная верхня я _граница. Ее.ли параметр включите.льна принимает логическое значение true, то в результирующее множество в1U1ючается элемент, равный задан ной верхней_ границе. Результирующее множество опирается на вызываю­ щее отображение Выполняет поиск в отоб ражении наибол ьше1·0 ключа k по критерию k > объект. Если такой IUIIOЧ найден, то воз ­ вращается запись по этому ключу, а иначе - пустое значение null Выполняет поиск в отображении наибол ьшего ю1 юча k по критерию k > обьект. Есл и такой IU\ЮЧ 11айдс11, то он во:1· вращается , а иначе - пустое значение null Возвращает последнюю запись в отображе нии. Это :1а шк1, с наибольшим ключом Выполняет поиск в (УГображе нии наибол ьшего ю1 юча k по критерию k < объект. Есл и та к ой ю1 ю•1 11ай;1ен, то во:�­ вращается запись по этому ключу, а иначе - пустое :ша чение null Выполняет поиск в mображен ии наиболы11его ю1юч<1 k по критерию k < обr.ект. Есл и такой ключ най;tсн. то 011 110:1- вращается, а иначе - пустое значение null Возвращает множество типа NavigaЬleSet, содержащее ключи из вызывающего отображе ния. Резульп1 р\ю щее \1 110· жество опирается на вызывающее отоб раже н и е Возвращает первую зап ись в отображении, 1ю11утно \д<ur ян ее. Это завись по наименьшему ключу, шккол ыч· отоfi раже­ ние отсортировано. Есл и же отображение ока:�ы вастся 11у­ сты м, то возвращается пустое :1 11 а•1ение nu ll Возвращает последнюю зап ис1, n отображе1 11111, по1 1 �т1 ю удаляя ее. Это за пись 110 наибольшему клю11у, 11осю>Лы(\· ото­ браже ние отсортировано. Если же отображен ие ока:�ы настся пустым, то возвращается пустое значение null Возвращает отображе ние типа NavigaЬle Set. пщсржащсс все за писи и:� вызывающе1'0 ото б раж е н ин 1ю к;1 к>'1а�1 че111.­ ше, чем верхня я _граница, и больше, чем 111ан·11ЯJ1_грйница. Ес1 11 11араметр вк.лючпя_11и:ж:ню1о_гра11ицу 1 1р1 1 ннч а ст Л<н·11•1('С К< к· :�на чение true, то в рс:Jул ы·и рующее отоfiражс 1111с вю1 юча­ ется эл емент, равный зад;шной ии:нmей_г/хтице. Л сел и 11ара­ мстр 8К.1 1 ЮЧЛЯ_11и:ж:нюю_границу принимает лоп1ческос :111а­ чение true, то в резул ьтирующее отображение включае1·ся элемент, равн ый заданной неJ»тей_грани це. l'с:1ул 1;п1рующс<' ото б раже н ие 01ш рается на 11ы:1ы в<1 ющее отоб раже1111с
608 Часть 11. Библ иоте ка Java Оконцание табл. 18. 16 Метод Значение NavigaЬleМap<K , V> t.ailМap (К НUЖ7U1Я_граница, Ьo o lean включи тельно) Возвращает отображение типа NavigaЬleSet, содержащее все зап иси из вызывающего отображения по ключам бол ьше, чем заданная ниж:�tRЯ_гран ица. E<".JI И параметр включительно принимает логическое значение true, то в результирующее отображение включается элемент, равный задан ной нижней_ границе. Результирующее отображе ние опирается на вызыва· ющее отображение Интерфейс Мар . Entry Этот инте рфейс позволя ет обращаться с отдел ьными за писями в отображе- 11ии. Ва11омним, что метод entryS et () , объявляемый в интерфейсе Мар , воз· вращает множество типа Set, содержащее записи из отображения. Каждый элемент этого м11ожества 11редставляет собой объект типа Мар . Ent ry. Интерфейс Мар . Entry является обобщенным и объя вляется следующим образом: interface Мap .Entry<K , V> где К обозначает тип ключей, а V - ти п хранимых в отображении значений. В табл . 18.17 перечислены нестатические методы, объя вляемые в интерфейсе Мар . Entry. В версииJDК 8 в интерфейс введены два стати ческих мето да. Первый из них назы всtется compa ringByKe y () и возвращает компаратор ти па Comparator, сравнивающий записи в отображении по зманному ключу. А второй называется comparingByVa lue ( ) и возвращает компарсtтор типа Compa rator, сравнивающий записи в отображении по указанному значению. Та бл ица 18.17. Метод ы из инте рфейса Мap . Entry М етод Ьoolean equal s(Ob ject о6ъект) К 9еtкеу () V getValue () int hashCode () V setValue (V v) Значение Возвращает логическое значение true, если заданный обыкт п ред· ставляет запись из отображения типа Мар.Еntrу, ключ и значение в которой такие же , как и у вызывающего объекта Возвращает ключ дан ной записи из отображения Возвращает значение данной зап иси из от ображения Возвращает хеш-код данной записи из отображения Уст анавливает указанное значение v в да нной записи из ото­ бражения. Если значение v не относится к типу, допустимому для данного отображения, то генерируется исключение типа ClassCastExcep tion. Если же значение vуказано неверно, то генерируется исключение типа IlleqalArgwвentException. А если значение v оказывается пустым (null) и в отображении нельзя хранить пустые ключи, то генерируется исключение типа NullPointerException. И наконец, если в отображение нельзя вносить изменения, то генерируется исключение типа UnsupportedOperationException
Гл ава 18. Пакет java.utiL, часть 1 . CoLLections Fra mewo rk 609 Классы отображений Интерфейсы отображений реализуются в нескольких классах. Классы, которые могут быть использованы для отображений, перечислены в табл . 18 .18. Следует иметь в виду, что класс AЬstractMap служит суперклассом для всех конкретн ых реализаций отображений. Класс We akHashMap реализует отображение, в кото ром используются так назы­ вае мые "слабые ключи", что позволяет собирать в "мусор" запись из отображения как ненужный объект, когда ее ключ больше не используется. Этот класс подробно здесь не обсуждается , а прочие классы отображений описываются далее. Таблица 18.18 . Классы отображений Кnа сс AЬstractмap EnUDМap иаshМар ТrееМар WeakИ ashМap Описа ние Реализует бальшую часrь интерфейса мар Расширяет класс AЬs tractмap для применения вместе с ключами типа еnuш Расширяет класс AЬs tractмap для применения хеш-табл ицы Расширяет класс AЬs tractкap для применения древовидной ст руктуры Расширяет класс AЬstractмap для при менения хеш"Габл ицы со слабы­ ми ключами LinkedНashМap Рас ширяет класс ВаshМар, разрешая итерацию с вводом эл ементов в определенном порядке Identi tyВashМap Расширяет класс AЬstractмap и использует результаты проверки ссы­ лок на равенсrво при сравнении документов Класс HashМap Этот класс рас ширяет класс Ab stractMap и реализует интерфейс Мар. В нем и с ­ пол ьзуется хеш-таблица для хранения отображения , и бл а годаря этому обеспечи­ вается постоянное время вы полнения методов get () и put () даже в обраще нии к круп ным отображениям. Класс Ha shMap является обобщенным и объя вляется приведенным ниже образом, где К обозначает тип ключей , а V - ти н хранимых в отображении значений. class HashМap<K , V> В классе определены следующие конструкторы : HashМap () HashМap (Мар<? extends К , ? extends V> m) HashМap ( int енж ос'l'ь) HashМap (int енжос'l'ь , float жоэффицяен'l'_эаполненяя) В первой форме конструктора со:щастся хе ш-отображе ние по умолчанию. Во второй форме конструктора хеш-отображен ие инициируется элементами :�а­ данного отображен ия m. В третьей форме задается емкость хеш-отображения. И в четвертой форме емкость и коэффициент_ за полнения хеш-отображения за­ да ются в качестве аргументов конструктора. На:шачение е м кости и коэффициен-
610 Часть 11. Библ иоте ка Java та заполнения та кое же, как и в описанном ранее классе HashSet. По умолчанию емкость составляет 16, а коэффициент заполнения - О,7 5. Класс Has hMap реализует интерфейс Мар и расширяет класс Ab stractMap, не дополняя их своими методами. Следует иметь в виду, что хеш-отображение не гарантирует порядок расположения своих элементов. Следовательно, порядок, в кото ром элементы вводятся в хеш-отображение, нс обязательно соответствует тому порядку, в котором они извлекаются итератором . В следующем примере про­ граммы демонстрируется применение класса HashMap. В этой программе имена вкладчиков отображаются на остатки на их банковских счетах . Обратите внима­ ние, каки м образом получается и используется представление хеш-отображения в виде множества: import java .util .*; class HashMapDemo { puЫ ic static void ma in ( String args [] ) { 11 создать х еш-отображение HashMap<String, DouЬle> hm = new HashMap<String , DouЫ e> () ; 11 вве сти элементы в х еш-отображение hm .put ( "Джoн Доу" , new DouЬle (3434.34) ); hm .put ( "Toм Сми т ", new DouЬle (l23 .22) ); hm .рut ( "Джейн Бейкер", new DouЬle ( 13 78.00) ); hm .put ( "Toд Холл", new DouЬle (99.22) ); hm .put ("P aль ф Сми т", new DouЬ le (-19 .08) ); 11 получи ть мн ожество записей Set<Map .Ent ry<String, DouЫe>> set hm .entryS et () ; 11 вывести множе ство записей for (Map . Ent ry<String , DouЬle> me : set) { System.out . print (me . getKey () + " : ") ; System.out . priпtln (me.ge tVa lue () ); System. out . priпtln () ; 11 внести сумму 1000 на счет Джон а Доу douЫ e balance = hm .get ( "Джoн Доу" ); hm .put ( "Джoн Доу" , balance + 1000) ; System. out . print lп ( "Hoвый остаток на счете Джона Доу : " + hm .get ("Джoн Доу ") ); Ниже п р и веден результат, выводимый данной программой (точный порядок следо вания записей может отличаться). Раль ф Смит : -19 .08 Том Смит : 123 .22 Джон Доу : 3434 .34 Тод Холл: 99.22 Джейн Бейкер : 1378.0 Новый остаток на сче те Джо на Доу : 4434 .34 Выполнение данной программы начинаетс я с создания хеш-отображения, в кото рое затем вводятся имена и фамилии вкладчиков, отображаемые на оста'Г' ки на их банковских счетах. Далее содержимое хеш-отображения выводится
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra mework 611 с помощью его представления в виде множества, получаемого из метода entry­ Set ().Ключи и значения выводятся в результате вызова методов getKey ( ) и ge t Va lue (), определенных в инте рфейсе Мар . Entry. Обратите особое внимание на порядок внесения суммы на счет Джона Доу. Метод put () автоматически за­ меняет новым значением любое существовавшее ранее значение, связанное с ука­ занным кл ючом. Та ки м образом, после обновления остатка на счете Джона Доу хеш-отображение по-прежнему содержит только один счет Джона Доу. Класс ТrееМар Класс TreeMap расширяет класс AЬstractMap и реализует интерфейс Navi­ gaЬleMap. В нем создается отображение, размещаемое в древовидной структу­ ре. В кл ассе Tre eMap предоставляются эффективные средства для хранения пар �ключ-значение" в отсортирован ном порядке и обеспечивается их быстрое из­ влечение. Следует заметить, что , в отличие от хеш-отображения, древовидное ото бражение гарантирует, что его элементы будут отсо ртированы по порядку нарастания ключей . Класс TreeMap является обобщенным и объявляется следу­ ющим образом: class ТrееМар<К , V> где К обозначает ти п ключей, а V- тип хранимых в отображении значен ий. В клас­ се ТrееМар определены следующие конструкторы: ТrееМар ( ) ТrееМар (Coшparator<? super К> жонпара!rор) 'l'reeМap (мар< ? extends К, ? extends V> m) ТrееМар (SortedМap<K , ? extends V> sm) В первой форме конструктора создается пустое древовидное отображение, которое будет отсортировано с естественным упорядочением ключей. Во вто­ рой форме конструктора создается пустое древовидное отображение, кото­ рое будет отсортировано с помощью заданного компаратора ти па Comparator. (Компараторы обсуждаются далее в этой гл аве.) В третьей форме древовидное отображение инициализируется эл ементами из отображения m, которые будут отсортированы с естественным упорядочением ключей. И наконец, в четвертой форме создается древовидное отображение с элементами из отображе ния sm, ко­ торые будут отсортирован ы в том же порядке , что и в отображении sm. В классе TreeMap не оп ределяются допол н ительные методы , помимо тех, что имеются в интерфейсе NavigaЫeMap и классе AЬstractMap для обращения с ото­ бражениями. Ниже приведен вариант предыдущего примера программы, переде­ ланный с целью проде монстрировать применение класса TreeMap. import jav a.util . *; cla ss TreeMapDemo { puЫ ic static void ma in (String args[] ) { 11 созд ать дре во видное отображение TreeMap<String , DouЫe> tm = new TreeMap<String, DouЫ e> (); 11 ввести э леме нты в дре в овидное отображе ние tm . put ( "Джoн Доу" , new DouЬle (3434 .34) ); tm . put ( "Toм Смит", new DouЬle (123.22) );
612 Часть 11. Библиоте ка Java tm .рut ( "Джейн Бейкер ", пеw DouЬ le (l378 .00) ); trn . put ( "Toд Халл", пеw DouЬle (99.22) ); tm . put ("Paльф Смит ", пеw DouЬle (-19 .08) ); 11 получить мн оже ство записей Set<Map .Eпtry<Striпg , DouЫe>> set trn . eпtrySet () ; 11 выве сти множе ство записей for (Map . Eпtry<Striпg, DouЫe> те : set ) 1 System. out .priпt (rne . getKey () + ": ") ; System.out . priпtlп (me.ge tVa lue () ); Systern. out . priпtlп () ; 11 внести с умму 1000 на счет Джона Доу douЫe Ьаlапсе = trn . get ( "Джoн Доу" ); tm . put ( "Джoн Доу" , Ьаlапсе + 1000) ; Systern. out .priпtlп ("H oвый остаток на сч ете Джона Доу : " + tm .get ( "Джoн Доу ") ); Ниже приведе н результат, выводи мый данной программой. Дже йн Бейкер : 1378 .0 Джон Доу : 3434 .34 Ральф Смит : -19.08 Тод Халл : 99 .22 Том Смит : 123 .22 Но вый остаток на счете Джо на Доу : 4434 .34 Обратите внимание на то , что класс TreeMap сортирует ключи. Но в данном случае они сортируются по имени вместо фамилии. Та кое поведение можно изме­ нить, указав ко мпаратор при создании отображения, как поясняется далее. Кnacc LinkedНashМap Класс LinkedHashMap расширяет класс HashMap . Он создает связный список элементов, располагаемых в отображении в том порядке , в котором они вводились в него. Это позволяет организовать итерацию с вводом эл ементов в отображение в определенном порядке . Следовательно, при итерации представления отображе­ ния типа LinkedHashMap в виде коллекции его элементы будут возвращаться в том порядке , в котором они вводил ись в него. Можно также создать отображение ти па LinkedHashMap, возвращающее свои элементы в том порядке , в котором к ним осу· ществлялся доступ в последний раз. Класс LinkedHashMap является обобщенным и объявляется приведенным ниже образом, где К обозначает тип кл ючей, а V-ти п хранимых в отображении значений. class LinkedНashМap<K , V> В классе LinkedHashMap определяются следующие конструкторы: LinkedНashМap () LinkedНashМap (Мap<? extends К, ? extends V> m) LinkedНashМap (int енжость ) LinkedНashМap (int енжость , float жоэффицяент .эаполнения) LinkedНashМap (int енжость , float жоэффицяент:.эаполнения , Ьoolean порядож)
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra m ework 613 В первой форме конструктора создается отображение типа LinkedHa shMap по умолчанию. Во втqрой форме конструктора отображение типа LinkedHa shMap и нициализируется элементами из заданного отображения т. В третьей форме за­ дается емкость отображения, в четвертой - емкость и коэффицие нт_ з аполнения отображения. Назначение этих параметров такое же, как и у класса HashMap. По умол чанию емкость составляет 1 6 , а коэффициент заполнения - О,75. В послед­ ней форме конструктора можно указать поряд ок расположения элементов в связ­ ном списке: ввода или последнего доступа. Если параметр порядок принимает ло­ гическое значение true, то используется порядок доступа, а сели он принимает логическое значение false - порядок ввода элементов. В классе LinkedHa shMap добавляется тол ько один новый метод к те м, что опре­ деле ны в классе HashMap. Это метод remo ve EldestEntry (), общая форма которо­ го приведе на ниже. protected boo lean reшoveEldes tEntry (мap . Entry<K , V> е) Этот метод вызывается из методов put () и putAl l (). Самая старая запись а отображении передается в качестве параметра е. По умолчанию этот метод воз­ вращает логическое значение false и ничего не делает. Но если переопредел ить его, то можно уд алить самую старую запись из отображения типа LinkedHashMap. Для этого переопределенный метод должен возвратить логическое значение true. А для того чтобы сохранить самую старую запись в отображении, из пере­ определенного метода следует возвратить логическое значение false. Класс Identi tyHashМap Класс I d e ntityHa shMap расширяет класс Ab stractMap и реализует интерфейс Мар. Он аналогичен классу Ha shMap, за исключением того , что при сравнении эле­ ментов отображения в нем выполняется проверка ссылок на равенство. Класс Identi tyHa shMap является обобщенным и объявляется следующим образом: class IdentityHashМap<K , V> где К обозначает тип ключей , а V - тип хранимых в отображении значений. В до­ кументации на прикладной программный интерфейс API ясно сказано, что класс Iden ti tyHa shMap не предназначен для общего применения. Класс EnwnМap Класс EnumМ ap расширяет класс Ab stractMap и реализует интерфейс Ма р. Он специально предназначен для применения вместе с кл ючами типа enum. Это обоб­ щенный класс, объя вляемый следую щим образом: class EnUIDМap<K eжtends Enwn<К> , V> где К обозначает тип кл ючей , а V - тип хранимых в отображении значений. Следует иметь в виду, что класс К должен расширять класс Enum<K>, а для этого ключи долж ны быть типа enum. В классе EnumМ ap определены следующие конструкторы:
614 Часть 11. Б ибл и оте ка Java ЕnШDМар (Class<К> '.l.'JU1 жлюvа ) ЕnшаМар (Na.p<K , ? extёi i. dsV>m) EnumМa.p (Enul lМa p<K, ? extends V> еш) Первый конструктор создает пустое отображение типа EnumМap для хранения эл ементов, имеющих заданный тип_ ключа . Второй конструктор создает отобра­ же ние типа EnumМap с те ми же записями, что и в заданном отображении m. А тре­ тий конструктор создает отображение типа EnumМa p, инициируемое значениями из отображения em. Свои методы в классе EnumМa p не определяются. Комп арато ры Классы TreeSet и TreeMap сохраняют эл ементы в отсортированном порядке. Однако понятие "порядок сортировки" точно определяет применяемый ими ком­ паратор. По умолчанию эти классы сохраняют элементы , используя то, что в Java называется естественным упорядичением, т. е. ожидаемым упорядочением, когда по­ сле А следует В, а после 1 - 2 и т.д . Если же элементы требуется упорядочить иным образом , то при создании множества или отображения следует указать компара· тор типа Comparator. Это дает возможно точно управлять порядком сохранения элементов в отсорти рованных коллекциях. Интерфейс Comparator является обобщенным и объя вляется приведенным ниже образом, где Т обозначает тип сравниваемых объектов. interface Comparator<T> До версии JDK 8 в инте рфейсе Comparator определялись только два метода: compare () и equals ( ). Метод cornpa re (), общая форма которого приведена ниже, сравнивает два элемента по порядку. int compare (Т объех'.1'1 , Т объех'.1'2) Здесь параметры объект] и объект2 обозначают сравниваемые объекты. Обычно этот метод возвращает нулевое значение, есл и объекты равны; положи· тельное значение, если объект1 больше, чем объект2, а иначе - отрицательное зна· чение. Этот метод может сгенерировать исключение типа ClassCas tExcept ion, если типы сравниваемых объектов несовместимы. Реализуя метод compare (), можно изменить порядок расположения объектов. Например, чтобы отсортиро­ вать объекты в обратном порядке , можно создать компаратор, который обращает результат их сравнения. Метод equa ls (), общая форма которого приведена ниже , проверяет объект на равенство вызывающему компаратору. Ьoolean equals (object объеж'.1') Здесь параметр объект обозначает проверяемый на равенство объект. Метод equa ls () возвращает логическое значение true, если заданный объект и вызы· вающий объект относятся к типу Cornpara tor и упорядочиваются одним и тем же способом. В противном случае этот метод возвращает логическое значение false. Переопределение метода equals () не требуется , и большинство простых ком па· раторов в этом не нуждается.
Глава 18. Пакет java.utiL, часть 1. CoLLections Framework 615 Многие годы в интерфейсе Comp arator были доступ ны только оба упомянутых выше метода. Но после выпуска версии JDК 8 это положение коренным образом изменилось в луч шую сторону. В версии JDК 8 фун кциональные возможности ин­ терфейса Comp arator бы.ли значительно расширены благодаря внедрению мето­ дов по умолчанию и статических методов, рассматриваемых ниже по отдел ьности . Используя метод reversed (), можно по.лучить компаратор, изменяющий на о братное упорядочение сравниваемых объектов, с которым этот компаратор вызывался. Ниже приведена общая форма данного метода. default Comparator<T> reversed () Этот метод возвращает компаратор с обратным упорядочением. Та к, если в ис­ ходно м компараторе используется естественное упорядочение символов от А до Z, то ком паратор с обратным упорядочением расположит букву В перед А, букву С перед В и т.д. С методо м reversed ( ) тесно связан метод reverseOrde r (),общая форма которого выглядит следующим образом: static <Т extends ComparaЬle< ? super Т>> Comparator<T> reverseOrder () Этот метод возвращает ко мпаратор, изменяющий на обратное естественное упорядочение сравниваемых элементов. С другой стороны, можно получить ком­ паратор с естественным упорядочением сравниваемых элементов, вызвав стати­ ческий метод naturalOrde r ().Ниже приведе на его общая форма. static <Т extends ComparaЬle<? super Т>> Comparator<T> naturalOrder () Если же требуется компаратор, способный обрабаты вать пустые значения nu ll, то для этой цели служит метод nullsFirst ( ) или nul lsLast ().Ниже при­ ведены общие формы этих методов. static <Т> Comparator<T> nul lsFirst(Comparator< ? super Т> жонпара тор) static <Т> Comparator<T> nu llsLas t(Comparator<? super Т> жон:пара тор) Метод nul lsFirst () возвращает компаратор, рассматривающий пустые значе­ ния nu ll как меньшие остальных значений. А метод nullsLast () возвращает компа­ ратор, рассматривающий пустые :ша•1ения nu ll как бальшие остальных значений. Но в любом случае задан ный компара тор выполняет сравнение, если оба сравнивае мых значения не являются пустыми. Если же заданному компара тору передается пустое значение nu ll, то все непустые значения рассматриваются им как равнозначные. В верс ии JDК 8 в интерфейсе Comp arator появился еще один метод по умол­ чанию, выполняющий второе сравнение, если результат первого сравнения ука­ зывает на равенство сравниваемых объектов. Следовательно , с помощью этого метода можно составить последовател ьность "сравнить сначала по Х, а затем по У". Например, при сравнении городов можно сначала сравнивать их назва­ ния, а затем названия штатов. Та к, в естественном ал фавитном порядке название Спрингфилд, Иллинойс, будет предшествовать названию Спрингфилд , Миссури . У метода thenComp aring ( ) имеются три общие формы объя вления. Ниже приве­ дена первая общая форма, позволяющая указать второй компаратор, передав эк­ зе мпляр объек10. типа Comp arator. В этой форме параметр второй_ компаратор обозн ачает компаратор, вызываемый в том случае , если первый компаратор воз­ вращает признак равенства сравн иваемых объектов. default CoП1parator<T> thenComparing (Comparator< ? super Т> .второii_ жонпара тор)
616 Часть 11. Библиоте ка Java В двух других формах метода thenCompar ing () можно указать стандартный функциональный интерфейс Function , определенный в пакете java . util . function. Обе эти формы приведе ны ниже. default <U extends ComparaЫe< ? super U> Comparator<T> thenComparinq (Function<? auper Т, ? extends U> получ11'l'ъ_ж:.точ) default <U> Comparator<T> thenComparinq (Function< ? super Т, ? extends U> получи'l'ь_ж:люч , Compar ator< ? super U> ж:оипара 'l'ор_хлJDЧей) В обеих формах параметр получить_ ключ обозначает функцию, получаю­ щую следующий ключ для сравнения. Этот кл юч испол ьзуется в том случае , если в результате первого сравнения возвращается признак равенства сравнивае м ых объекто в. В последней форме да нного метода параметр компара тор_ключей обо­ значает компаратор, используемый для сравнения ключей . (Здесь и далее U обо­ зн ачает тип ключа.) Интерфейс Comp a rator дополнен также специальными вариантами методов последующего сравнения примитивных типов дан ных . Их общие формы приведе­ ны ниже , где параметр получить_ ключ обозначает функцию, получающую следу· ющий ключ для сравнения. default Comparator<T> thenComparinqDouЫe (ToDouЬleFunc tion< ? super Т> получи'l'ъ_ж:люч) defaul t Compar ator<T> thenComparinqint ( TointFunction< ? super Т> получи'l'ъ_ж:люч) default Comparator<T> thenComparinqLonq (ToLonqFunction<? super Т> получи'l'ъ_ж:люч) И наконец, в версии JDK 8 интерфейс Comparator дополнен методом comp a r ing (). Этот метод возвращает компаратор, получающий кл юч дл я с рав· н е ния от функции, передаваемой данному методу в качестве параметра. Ниже нриведе ны обе формы объя вления метода comparing (). static <Т , U extends ComparaЫe<? super U>> Coшparator<T> comparinq (Function< ? super Т, ? extends U> получи'l'ь_ж:люч) static <Т , U> Comparator<T> comparinq (Function< ? super Т, ? extends U> получи'l'ь_ж:люч , Compara tor< ? super U> ж:оипара'l'ор_ жлюч еli i ) В обеих формах параметр получить_ ключ обозначает функцию, получаю­ щую следующий кл юч для сравнения. Во второй форме данного метода параметр компара тор_ клю чей обозначает компаратор, используемый для сравнения кл ю­ чей . Интерфейс Comparator дополнен также специальными вариантами метода comparing () для сравнения примитивных типов дан ных. Их общие формы при· веде ны ниже , где параметр получить_ ключ обозначает функцию, получающую следую щий ключ для сравнения. static <Т> Comparator<T> Coшparin9DouЫe ( ToDouЫeFunction<? super Т> получж'l'ь_ж:люч) static <Т> Comparator<T> Comparinqint (TointFunction< ? super Т> получ11'l'ь_ ж:люч) static <Т> Comparator<T> Comparin9Lon9 (T0Lon9Function<? super Т> noлyЧJl l 'l'.i>_ ж:люч)
Гл ава 18. Пакет java.utit, часть 1. CoHections Fra mework 617 Применение компараторов Ниже приведен пример программы, демонстрирующий эффективность специ­ альн ых ко мпараторов. В этой программе реализуется метод compare ( ) для срав­ нения символ ьных строк в порядке , обратном обычному. Это означает, что эле­ менты древовидного множества сортируются в обратном порядке . 11 Исполь зоть спе циальный компаратор import jav a.util .*; 11 Компаратор для сравнения символь ных строк в обратном порядке class MyC omp implements Comparator<String> { puЫic int compare (String а, String Ь) { String aS tr, bStr; } aStr = а; bStr = Ь; 11 выполни ть сравнение в обратном порядке return bStr. comp a reTo (aStr) ; 11 переопределять ме тод equ als () не требуе тся clas s Comp Demo { puЫic static vo id ma in (String args[] ) { 11 создать др евовидное мн оже ство типа TreeSet TreeSet<String> ts new TreeSet<String> (new MyComp() ); 11 ввести эл еме нты в др евовидное мн оже ство ts.add("C "); ts.add("A"); ts.add("B") ; ts.add("E ") ; ts .add("F "); ts .add("D "); 11 вывести элементы из др евовид ного мн ожества for ( String eleme nt : ts) System . out .print ( element +"") ; System. out . println () ; Как показывает приведен ный ниже результат выполнения дан ной программы, др ево видное множество те перь отсортировано в обратном порядке. FE DCBA Обратите внимание на класс MyComp , реализующий интерфейс Comparator и ме­ тод compare ().Как упоминалось выше, переопределение метода equa ls () не тре­ буется и вообще не принято . Переопределение не требуется и методам по умолча­ нию, внедренным в интерфейсы, начиная с версииJDК 8.) В теле метода compare ( ) для объекта типа String вызывается метод compareTo (),сравнивающий две сим­ вольные строки. Но метод compareTo () вызывается для строкового объекта bStr, а н е aStr. Благодаря этому результат сравнения получается обратным. Несмотря на то что способ реализации компаратора с обратн ым упорядочени­ ем в предыдущем примере программы вполне пригоден, в версииJDК 8 появился еще один способ сделать то же самое - просто вызвать метод reversed () для ком-
618 Часть 11. Б иблиоте ка Java паратора с естественным упорядочением. Этот метод возвратит эквивалентный компаратор, который, од нако , сравнивает объекты в обратном порядке. Так, предыдущий пример программы можно переделать, внеся следующие изменения в класс MyClass: class MyComp implements Comp a rator<String> { puЫ ic int compare (String aStr, String bStr) returп aStr. compareTo (bStr) ; Далее можно воспользоватьс я следующим фрагментом кода для создания дре­ вовидного множества типа TreeSet, где строчные элементы располагаются в об­ ратном порядке: MyComp mc = п е w MyComp() ; // создать компаратор 11 передать вариант компара тора типа МуСоmр с обратным 11 упорядоч ением древовидному множе ству т ипа TreeSet TreeSet<Striпg> ts = new TreeSet<Striпg> (mc.reversed () ); Если ввести этот новый код в предыдущий пример программы, то будет полу· чен тот же самый резул ьтат ее выполнения. В данном примере применение метода reversed () не дает никаких преимуществ, но в тех случаях, когда требуется соз­ дать компараторы с естественным и обратным упорядочением, метод reve r sed () упрощает получение ко мпаратора с обратным упорядочением, не требуя написа· ния допол нительного кода специально для этой цели. Начиная с версии JDK 8 создавать класс MyComp , как показано в предыдущих примерах, фактически не требуется , поскольку его можно легко заменить соо1' ветствующим лямбда-выражением. В частности , класс MyComp можно полностью удал ить, а вместо него создать компаратор символ ьных строк, используя следую­ щий фрагмент кода: 11 исполь зовать лямбда -выр ажение для ре али зации 11 комп ара тора типа Comparator< Strinq> Comparator<Striпg> те = (aStr, bStr) -> aStr . compareTo (bStr) ; Следует также заметить, что в данном простом примере компаратор с обра1' ным упорядочением можно задать и с помощью лямбда-выражения непосред· ственно в вызове конструктора класса TreeSet, как показано ниже. 11 передать компаратор с обратным упор ядочением конструктору 11 класса TreeSet чере з лямбда- выражение TreeSet<String> ts = пеw TreeSet<Striпg> ( (aStr, bStr) -> bStr. compareTo (aStr) ); Внеся эти изменения , можно значительно сократить исходн ый код упомянуrой выше программы, как показывает приведенный ниже окончательный ее вариант. 11 Использовать лямбда -выраже ние для создания компаратора 11 с обратным упорядочением import j ava .util .*; class CompDemo2 puЬl ic static void ma in (String args []) { 11 передать компаратор с обратным у пор ядоче н ием
Гл ава 18. Пакет java.uЩ часть 1. CoHections Framework 619 // древовидному множе ству типа TreeSet TreeSet<String> ts = new TreeSet<String> ( (aStr, bStr) -> bStr . compareTo (aStr) ); // ввести элементы в др евовидное мн оже ство ts .add("C ") ; ts.add("A"); ts .add("B ") ; ts.add("Е"); ts .add("F ") ; ts .add("D ") ; // вывести элеме нты из древовидного множе ства for (String eleme nt : ts) Sys tem.out . print ( eleme nt +"") ; System. out . println () ; Рассмотрим более полезное применение компараторов на примере переделан­ ного варианта представленной ранее программы, где имена и фамилии вкладчи­ ков и остатки на их банковских счетах сохраняются в древовидном отображении типа TreeMap. В предыдущем варианте этой программы счета сортировались по имен и каждого вкладчика, а в новом, приведенном ниже ее варианте - по его фамилии. Для этого в ней испол ьзуется компаратор, сравнивающий фамил ии каж­ дого вкладчика. В итоге получается отображение, отсортированное по фамилиям вклад чиков. /! исполь зовать компаратор для сортиров ки счетов / / по фамилиям вкладчиков import java .util .*; // сравни ть последние слова в обеих симв оль ных строках class TComp implements Comp a rator<String> { puЫ ic int compare (St ring aStr, String bStr) { inti,j,k; } 11 найти индекс симв ола , с которого начинается фамилия i aStr. lastindexOf (' '); j = bStr . lastindexOf (' '); k = aStr. substring (i) .compa reTo (bStr . substring (j) ); if(k==O ) // Фамилии совпадают , проверить имя и фамилию полностью return aStr . compareTo (bStr) ; else return k; 11 переопределя ть ме тод equals () не требуе тся class TreeMapDemo2 { puЫic static vo id ma in (String args[] ) 11 создать дре вовидное отобр аже ние TreeMap<String, DouЫ e> tm = new Tre eMap<String, DouЫe> (new TComp()); 11 ввести элементы в др евовидное отображение tm . put ( "Джoн Доу" , new DouЬle (3434.34) ); tm .put ( "Toм Смит ", new DouЬle (l23 .22) ); tm . рut ( "Джейн Бейкер ", new DouЬle (l3 78 .00) );
620 Часть 11. Библиотека Java tm . put ( "Toд Халл" , new DouЬle (99.22) ); tm. put ("Paльф Смит" , new DouЬle (-19.08)); 11 получить мн оже ство элементов Set<Map .Entry<String , DouЫe>> set tm . entrySet (); 11 вывести элементы из мн оже ства for (Map . Entry<String, DouЬle> me : set ) { System. out .print (me . getKey () + ": ") ; System. out .println (me.getValue () ); System.out .println () ; 11 внести сумму 1000 на счет Джона Доу douЫe balance = tm. get ( "Джoн Доу" ); tm. put ( "Джoн Доу ", balance + 1000) ; System. out .println ("H oвый остаток на счете Джона Доу : " + tm. get ( "Джoн Доу")); Ниже приведен результат, выводимый данной программой. Обратите внима· ние на то , что счета те перь отсортированы по фамилиям вкладчиков. Дже йн Бейкер : 1378 .0 Джон Доу : 3434 .34 Ральф Смит : -19 .08 Том Смит : 123 .22 Тод Халл: 99.22 Новый остаток на счете Дж она Доу : 4434 .34 Компаратор типа TComp сравнивает две символьные строки , содержащие имя и фамилию. Сначала он сравнивает фамилии. Для этого осуществляется поиск по­ зиции последнего пробела в каждой строке с последующим сравнением подстро­ ки, начинающейся с этой позиции. В том случае, если фамилии один аковы , ср ав· ниваются имена. В итоге получается древовидное отображение, отсортированное по фамилиям вкладчиков, а в пределах одинаковых фамилий - по именам. Можете убедиться в этом сами, поскольку имя и фамилия Ральф Смит в приведенном выше результате выполнения данной программы располагаются выше имени и фами· лии 1о м Смит. Начиная с версии JDK 8 предыдущий пример программы можно переделать таким образом , чтобы отсортировать древовидное отображение сначала по фами­ лии, а затем по имени вкладчика, используя метод thenComparing () . Напомним, что метод thenComparing () позволяет указать второй компаратор, который ис· пользуется в том случае , если вызывающий компаратор возвращает признак ра· венства сравниваемых объектов. Именно такой способ сортировки счетов вклад· чиков реализован в приведенном ниже переделанном варианте предыдущего п ри­ мера программы. 11 Исполь з овать ме тод thenComparinq () для сортировки 11 счетов вкладчиков сначала по фамилии, а затем по имени import jav a.util .*; 11 Комп а ратор , сравнивающий фамил ии вкладчиков
Гл ава 18. Пакет java.utit, часть 1. CoHections Fra mework 621 cl ass CompLastName s implements Comparator<String> puЫic int compare ( String aStr , String bStr) { int i, j; 11 найти индекс симв ола , с которого начинается фамилия i = aStr. lastindexOf (' '); j = bStr. lastindexOf (' '); return aStr . substring (i) .compareToi gnoreCase (bS tr. substring (j) ); 1 11 отсортировать счета вкладчиков по Ф .И.О . , если фамилии одина ковы class CompThenByFi rstName impleme nts Comp a rator<String> { puЫ ic int compare(String aStr , String bStr ) { int i, j; return aStr . compa reToi gnoreCase (bStr) ; cla ss TreeMapDemo2A { puЫic static void main ( String args [ J) { 11 исполь зовать ме тод thenCoшparing () для создания 11 компара тора , сравнивающе го сначала фамилии , а затем 11 Ф.И .О . вкладчиков , если фамилии одинаковы CompLaз tName з comp LN = new CompLastNames () ; Comparator<String> comp La stThenFi rst = comp LN .thenComparing (new Comp ThenByFi rstName () ); 11 создат ь древовидное отображение TreeMap<String, DouЫ e> tm = new Tre eMap< String , DouЫe> ( compLastThenFirst) ; 11 ввести элементы в др ев овидное отображение tm .put ( "Джoн Доу" , new DouЬle (3434 .34) ); tm . put ( "Toм Смит ", new DouЬle (123 .22) ); tm . рut ( "Джейн Бейкер ", new DouЬle (1378 .00) ); tm . put ( "Toд Халл" , new DouЬle (99.22 ) ); tm . put ("P aль ф Смит ", new DouЬle (-19.08) ); 11 получить множество элементов Set<Map .Entry<String , DouЫ e>> set tm . entrySet (); 11 вывести элементы из мн оже ства for (Map . Entry<String , DouЫ e> те : set ) System .out .print (me . getKey () + " : ") ; System. out . println (me . getValue () ) ; } System. out . println () ; 11 внести сумму 1000 на счет Джона Доу douЫ e balance = tm .get ( "Джoн Доу " ); tm .put ("Джон Доу" , balance + 1000 ) ; System. o ut . println ("H oвый остаток на счете Джона Доу : " + tm .get ( "Джoн Доу ") ); Результат выполнения данного варианта рассматриваемой здесь программы такой же, как и предыдущего ее варианта. Их отл ичие ли шь в том, как реализуется сорти­ ровка счетов вкладчиков. Прежде всего обратите внимание на создание компаратора
622 Часть 11. Б и блиоте ка Java тиш1 CompLa s tNames. Этот ком11аратор сравнивает только фамилии вкладчиков, то� ;щ как вто рой компаратор типа Comp T henByFirstName - Ф.И.О. вкладчиков, начиная с имени. Ниже показано, каким образом создаются оба эти компаратора. CompLastName s comp LN = new Comp LastNames () ; Comparator<String> compLa stThenFi rst = compLN .th enComp a ring (new Comp ThenByFirstName () ); Эдесь пер вый компаратор присваивается переменной compLN в в иде экзе мпля­ ра класса Comp La stNames. Для него вызывается метод thenComparing (), кото­ ро '\1у в качестве параметра передается экземпляр кл асса CompThenByFirstName . Полученный резул ьтат присваивается переменной comp La stThenFirst. Этот второй компарато р используетс я для построения древовидного множества тип а TreeMap, как показано ниже. TreeMap< String, DouЫe> tm = new TreeMap<String, DouЫe> ( comp La stThenFirst) ; 1еперь сортировка счетов вкладчиков выполняется по Ф.И.0., если их фами· л и и оказываются оди наковыми. Это означает, что имена вкладчиков упорядочива· ются 110 фамилиям, а в пределах одинаковых фамилий - по именам. И последнее замечание: ради наглядности в дан ном примере оба компаратора создаются явным образом в виде классов CompLastN ame s и ThenByFi rstNames, но вместо них можно воспол ьзоваться лямбда-выражениями. Попробуйте сделать это сами в качестве упраж нения, следуя тому же самому общему образцу из п риве­ де нного выше примера с классом CompDemo 2. Алгоритмы коллекций В каркасе коллекций определяется ряд ал горитмов, которые можно применять к коллекциям и отображениям. Эти алгоритмы определены в виде статических методов из кл асса Co llections, перечисленных в табл . 18.19. Как упоминалось ранее , в версииJПК 5 все алгоритмы были переделаны с учетом обобщений. Табл ица 18.19. Ал го ритм ы, определенные в классе Collections Метод static <Т> Ьoolean addAll ( Collection<? super Т> с , т... э.лемент ы ) st&tic <Т> Queue<T>asLifoQueue (Deque<T> с) static <Т> int binarySearch (List<? extends Т> список, Т значение, Comparator<? super Т> с) Описание Всrавляет заданные мементы в указанную кол­ лекцию с. Возвращает логическое значение true, если элементы были добавлены, а и наче - логиче­ ское значение false Возвращает представление заданной коJ1 11 екции с в виде стека, действующего по принципу " п ослед· ним пришел - первым обслужен" Осуществляет поиск указанного значения в за­ данном списке, упорядоченном в соответствии заданным компаратором с. Возвращает позицию указанного зшzчения в заданном спш:хе или отрица­ тельное значение, если зночениене найдено
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra mework 623 Метод static <Т> int binarySearch (List<? Extends ComparaЫe<? super Т>> спиrок, Т значение) static <Е> Collection<E> checkedCol lection (Collection<E> с , Class<E> t) static <Е> List<E> checkedList (List<E> с, Class<E> t) static <К, V> мар«, V>checltedМap (мap<К, V> с, Class<К> тип_ключа , Clas s<V> тип_значения) static <К, V> NaviqaЬleМap<К , V> checltedNaviqaЬleМ&p (NaviqaЬleМap<К , V> пт, Clas s<E> тип_ключа , Class<V> тип_значения) static <Е> NaviqaЬleSet<E> checkedNaviqaЬleSet (NaviqaЬleSet<E> ns, Class<E> t) static <Е> Queue<E> checltedQueue (Queue<E> q, Clas s<E> t) static <Е> List<E> checltedSet (Set<E> с, Clas s<E> t) static <К, V> SortedМap<К , V>checltedSorteclМap (SorteclМap<К , V> с, Class<D тиn_ключа , Class<V> тип_значения) static <Е> SortedSet<E> checltedSortedSet (SortedSet<E> с, Clas s<E> t) Пр одалжсние таба. 18.19 Описание Осуществляет поиск указанного значения в задан­ ном спшже, кагорый должен быть агсортирован. Возвращает позицию указанного значения в задан­ ном списке или отрицательное значение, если :JНDЧеНие не найдено Возвращает динамически типизируемое представ­ ление кол л екции. Попытка ввести несовмести­ мый элемент в коллекцию вызовет исключение типa ClassCastException Возвращает динамически типизируемое представ­ ление списка типа Li st. Попытка ввести несо­ вместимый элемент в список вызовет исключение типa ClassCastException Возвращает динамически типизируемое представ­ ление отображения типамар. Попытка ввести несовместимый элемент в агображение вызовет исключение типа СlаssСаstЕхсерtiоn Возвращает динамически типизируемое представ­ ление отображения типаNaviqaЬleМap. Попытка ввести несовместимый элемент в отображение вызовет исключение типа ClasscastException (добавлен в версииJDК 8) Возвращает динамически типизируемое представ­ ление в множество типа NaviqaЬleSet. Попьrrка ввести несовместимый элемент в множество вы­ зовет исключение типа ClassCastException (до­ бавлен в версииJDК 8) Возвращает динамически типизируемое представ­ ление очереди типа checltedQueue. Попытка вве­ сти несовместимый элемент в множество вызовет исключение типа ClassCastException (добавлен в версииJDК 8) Возвращает динамически типизируемое пред­ ставление множества типа Set. Попытка ввести несовместимый элемент в множество вызовет ис­ ключение типа ClassCastException Возвращает динамически типизируемое представ­ ление отображения типа SortedМap. Попытка ввести несовмеL-тимый элемент в агображение вызовет исключение типа ClassCastException Возвращает динамически типизируемое представ­ ление множества типа SortedSet. Попытка вве­ сти несовместимый элемент в множество вызовет исключение типа Сlаsвсаs tЕхсерtiоn
624 Часть 11. Библиотека Java Метод static <Т> void copy (List<? super Т> спш: :ок l , Li st<? Extendз Т> список2) static Ьo o lean di sjoint ( Collection<?> а , Collection<?> h) static <Т> Enumeration<T> emptyEnume ration () static <Т> Iterator<T> emptyiterator () static <Т> Li st<T> emptyList () static <Т> Li sti terator<T> eшptyLi sti terator () static <К, V> мар<К, V> ешрtуМар () static <К, V> NaviqaЬleМap<К , V> eшptyNavigaЬleНap () static <Е> NavigaЬleSet<E> eшptyNaviqaЬleSet () static <Т> Set<T> eшptySet () static <К, V> SortedМap<К, V> eшptySortedМa.p () static <Е> SortedSet<E> eшptyS ortedSet () static <Т> Enumeration<Т> enumeration (Collection<T> с) static <Т> void fill (List<? superТ>cnucoк,Тoбi ieюn ) static int frequency (Collection<?> с, oЬject oбi ieюn ) static int indexOfSuЬList (List<?> сnисок, List<?> nодсписо к ) llродолжениr табл. 18./9 Описание Копирует эл ементы из спш:ка2 в спш: :ок l Сравнивает эл ементы колл<.'КJ \ИЙ а и Ь. Возвращает логичес кое значение true, есл и обе кол лекци и не содержат общие эл ементы (т.е. не­ п ересекающ иеся множества элементов) , а иначе - логическое зна•1ение fal se Возвращает пустое перечисление, т . е. переч исле­ ние без эл ементов Возвращает пустой итератор, т.е. итератор без элементов Возвращает неизменяем ый, 11устой список типа, вы водимого из интерфейса Li st Возвращает пусто й итератор списка, т. е . итератор списка без элементов Возвращает неизменяемое, пустое отображение типа, вы водимого из интерфейса мар Возвращает неизменяемое, пустое отображение типа, вы водимого из интерфейса NavigaЬleМap (добавлен в версииJDК 8) Возвращает неизменяемое, 11устое множество типа, вы води мого из интерфейса NaviqaЬleSet (добавлен в версииJDК 8) Возвращает неизменяемое, пуст ое множество типа, вы водимого из интерфейса Set Возвращает неизменяемое, пустое отображение типа, вы водимого из интерфейса Sortec!Мap (до­ бавлен в версииJDК 8) Возвращает неизменяемое, пустое множество типа, вы водимого из интерфейса SortedSet (до­ бавлен в версииJDК 8) Возвращает перечисление эл ементов из за­ данной коллекции с. (См. раздел "Интерфейс Enumeration" далее в этой гл аве) Присваивает указанный объект каждому элементу заданного списка Подсчитывает количество вхожде ний указанного объеюпа в заданной коллекции с и возвращает ре­ зультат Осуществляет поиск первого вхождения указанно­ гоподсписк а в заданный список. Возвращает индекс первого совпадения или значение -1, если совпа· дение не обнаружено
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Framework 625 Метод static int lastindexOfSuЬList (List<?> спиаж, List<?> подспш:r ж ) static <Т> ArrayLi st<'l'>list (EnUJl l8 ration<Т> перечш:.ление) static <Т> Т шax(Collection<? extendsT> с, Coшparator<? super Т> КОМ1 1й раmоjJ) static <Т extends OЬject & СошраrаЫе<? super Т>> Т1 11ах (Collection<? extends Т> с) static <Т> Т IDin ( Collection<? extends Т> с, Coшparator<? super Т> компаратор> static <Т extends OЬject &СошраrаЫе<? superТ>>Т ll li n (Collection<? extends Т> с) static <Т> List<Т> nCopies (int количество,Тобl;екm) static <Е> Set<E> newSetFrol!Мap (Мap<E , Вoolean> т) static <Т> Ьo o lean replaceAll (List<'l'> спш:ок, Тстарый,Тновый) static void reverse (List<'l'> спиrок) static <Т> Coшparator<'l'> reverseOrcler (Coшparator<Т> КОМ1 1й раmоjJ) static <Т> Coшparator<Т>reverseOrder () static void rotate (List<Т> сnисок, int n) Про далжение таб!I. 18.19 Описание Осущесrв.ляет поиск последнего вхождения ука­ занного подспшжа в заданный сnисок. Возвращает индекс первого совпадения или значение -1, если совпадение не обнаружено Возвращает списочный массив типа ArrayLi st, содержащий элементы заданного перечшления Возвращает максимальный элемент из ука­ занной коллекции с, определяемый заданным компара.тор ам Возвращает максимальный элемент из указанной коллекции с, определя емый есrесrвенным упорядо­ чением. Колл екция доткна бьrгь отсортированной Возвращает минимальный элемент из указан- ной колл екции с, определяемый заданным КОМ1 1й рат.оро. м. Коллекция необязательно должна быть отсортированной Возвращает максимальный элемент из указанной колл екции с, оп ределя емый есrественным упоря­ дочением Возвращает заданное количество копий указанно­ го об6еюпа, содержащихся в неизменяемом списке. Значение п араметра ко.лuчес тво должно быть больше или равно нулю Создает и возвращает множество, исходя из ука­ занного отображения т, которое должно быть пустым на момент вызова данного метода Заменяет все вхождения заданного элемента старый на элемент новый в указанном спшже. Возвращает логическое значение true, если п ро­ изведена хотя бы одна замена, а иначе - логиче­ ское значение f'alse Изменяет на обратный порядок следования эле­ ментов в указанном спшже Возвращает компаратор, обратный переданному компаратору. Следовательно, возвращаемый ком­ паратор обращает результат сравнения, выпол­ ненного заданным компаратором Возвращает обратный компаратор, который об­ ращает результат сравне ния двух элементов кол­ лекции Смещает указанный спш:ок на п позиций в право. Для смещения влево следует указать отрицатель­ ное значение параметра n
626 Часть 11. Библиоте ка Java Метод static void shuffle (List<T> тиаж, Random r) static void shuffle (List<Т> muaж) static <Т> Set<T> singleton (Т объект) static <Т> List<T> singletonList (T обl.екm) static <К, V> Нар<К, V>singletonМap (K k, V v) static <Т> void sort (List<T> muaж , Comparator<? super Т> компаратQ/J ) static <Т extends ComparaЫe<? super Т>> void sort (List<T> спиаж) static void swap (List<T> ашсок, int инОексl , int индекс2) static <Т> Collection<Т> synchroni zedCollection (Collection<T> с) static <Т> Li st<T> synchronizedLi.st (List<T> ашсок) static <К, V> мар<К, V>synchronizedМap (Нap<К , V> m) static <К, V> NavigaЫeМap<К , V> synchronizeclNavigaЫeМap (NavigaЫeМ&p<К , V> nm) static <Т> NavigaЫeSet<Т> synchronizedNavigaЬleSet (NavigaЬleSet<Т> ns) static <Т> Зеt<Т> synchroni zedSet (Set<T> s) static <К, V> SortedМap<К, V> synchronizedSortedИap (SortedМap <К,V>sm) Продолжm 111' табл. 18.19 Описан ие Перетасовывает ( случайным обра:ю м ) элементы ука:Jанного списка, испол ьзуя значение параметра r в качестве исходного для п о.l l}ч ения сл учайных чисел Перетасоны вает (случай ным образом) элементы указанного ашска Возвращает аадан ный обr.ект в вилс неизменяемо­ го множества. Это простейший способ преобразо­ вать оди ночный объект в множество Возвращает :щ1щнный обr.ект в виде неизменяемо­ го списка. Это простейший способ преобразовать ОДИ НО'IНЫЙ объект в список Возвращает пару k, v " ключ-значение " в виде неиз­ меняемого отображе н и я . Это п ростейш и й способ преобразовать пару "ключ-значен ие" в отображе­ ние Copтиpyt. . vr элементы указанного списка в соответ­ ствии с заданным компарапюром Со1ли рует эл ементы указанного ашска в соответ­ ствии с естествен н ы м упорядочением Меняет местами эл ементы указанного спш:ка, обо­ значаемые параметрами и11дексl и индекс2 Возвращас_vr потокобезопасную коллекцию, исхо­ дя из указа нной кою1екции с Возвращает потокобезопасный сп и сок , исходя из указанного ашска Возвращаеr потокобезопасное отображение, ис· ходя из указанного отображения т Возвращает синхронизированное навигационное отображение, исходя из указанного отображе ния пт (добавлен в версииJDК 8) Возвращает синхронизированное навигационное множество, исходя из указанного множества ns (добавлен в ве рсииJDК 8) Возвращает потокобезопасное множесrво, исходя из указанного множества s Возвращает потокобезопасное отсортированное отображение, исходя из указанного отображе­ ния sm
Гл ава 18. Пакет java.utiL, часть 1. CoHections Fra m ewo rk 627 Метод static <Т> SortedSet<Т> synchroni.­ zedSortedSet ( SortedSet<Т> sr) static <Т> Collection<Т> WUl lOdi fiaЫe Collection (Collection<? extends Т> с) static <Т> List<Т> WUl lOdi fiaЬleList (List<? extends Т> спиrок) static <К, V> Мар<К, V>wul lodi. fiaЬleМap (Мap<? extends К,?extendsV>m) static <К, V> NavigaЬleМap<К , V> WUl lOdi fiaЬleNavigaЬleМap (NavigaЬleМap<К, ? extends V> nm) static <Т> NavigaЬleSet<Т> WUl lOdi fiaЬleNavigaЬleSet (NavigaЬleSet<Т> m) static <Т> Set<Т> unmod.ifiaЬleSet (Set<? extends Т> s) static <К, V> SorteclМap< К , V>WUl lOdi fiaЬleSorted- мap (SortedИap<К , ?extends V> sm) static <Т> SortedSet<Т> WIDIOClifiaЬleSortedSet (SortedSet<Т> ss) Окон1Шние таlИ. 18.19 Описание Возвращает потокобезопасное аrсортщюванное множесгво, исходя из указанного множесгва sr Возвращает неизменяемую кол л екцию, исходя из указанной кол л екции с Возвращает неизменяемый список, исходя из ука­ занного ama ai Возвращает неизменяемое отображение, исходя из указанного отображения т Возвращает неизменяемое навигационное ото­ бражение, исходя из указанного отображения пт (добавлен в версии JDK 8) Возвращает неизменяемое навигационное множе­ ство, исходя из указан н ого множесгва т (добав­ лен в версииJDК 8) Возвращает неизменяемое множество, исходя из указанного множества s Возвращает неизменяемое отсортированное ото­ браже ние, исходя из указанного отображения sm Возвращает неизменяемое отсортированное мно­ жесгво, исходя из указанного множесгва sr При попытке сравнить несовместимые типы некоторые из перечисленных выше методов могут сгенерировать исключение типа ClassCastException, а при попытке видоизмен ить неизменяемые коллекции - исключение типа Unsuppo rtedOpe rationException. В зависимости от конкретного метода воз­ можны и другие исключения. Особое внимание следует удел ить ряду nроверяемwс методов вроде метода checkedCollection ( ), возвращающего то, что в документации на прикладной программный интерфейс API называется "динамически ти пизируе мым представ­ лением" коллекций. Та кое представление служит ссылкой на коллекцию, которая во время выполнения контролирует вводимые в коллекцию объекты на предмет совместимости типов. Любая попытка ввести в коллекцию несовместимый объект вызовет исключение типа ClassCastException. Пользоваться этим представле­ нием уд обно на стадии отладки , поскольку оно гарантирует наличие в коллекции достоверных элементов. К числу проверяемых относятся методы checkedSet (), che ckedLi st ( ) , checkedMap ( ) и т.д. Они позволяют получить динамически типи­ зированное представление указанной коллекции. Следует заметить, что некоторые методы наподобие synchronizedList ( ) и synchronizedSet ( ) служат для получения синхронизированных (nоттсобезопас­ ных) копий различных коллекций. Как упоминалось ранее, стандартные реализа-
628 Часть 11. Б иблиоте ка Java ции коллею\иЙ, как нравило, не синхронизированы. Для синхронизации следует применять синхронизирующие ал горитмы. И еще одно замечание: итераторы синхронизированных коллекций должны иснол ьзоваться в пределах блоков кода с модификато ром доступа synchronized. Ряд н,еизменяем ых методов, имена которых начинаются с префикса unrnodi f iаЫе, возвращают представления различных коллекций, кото рые не мо1уг быть измене­ ны. Это может оказаться уд обным в том случае, если требуется гарантировать досrуп к коллекциям только для чтения и без права заниси. В интерфейсе Col lect ion определены три статические переменные: ЕМРТУ_ SET, ЕМРТУ LIST и ЕМРТУ_МАР. Все они неизменяемы. В приведенном ниже примере программы демонстрируются некоторые ал го­ ритмы коллекций. В этой нрограмме создается и инициируется связный список. Метод reverseOrde r () возвращает компарато р типа Comparator, который обра­ щает результат сравнения объектов типа Integer. Эл ементы списка сначала со­ ртируются в соответствии с этим компаратором , а затем выводятся. Далее этот список перетасовывается методо м shuffle (), после чего из него вы водятся ми­ нимальное и максимальное значения. 11 Продемонс трировать приме нение различ ных алгоритмо в коллекций impo rt java .util .*; class Algorithms Demo { puЫic static void ma in (St ring args[] ) { 11 создать неинициали зи рованный связный список LinkedLi st<Integer> 11 = new LinkedLi st<Integer> () ; ll .add (-8) ; ll .ad d(20) ; ll.add(-20); ll .ad d(8) ; 11 созд ать к о мп аратор с обратным упор я д о чением Comparator<Integer> r Col lections . reve rseOrder () ; 11 отсортировать список с помощью этого компаратора Collections .sort (ll, r) ; System. out .print ( "Cпиcoк отсортирован в обра тном порядке : ") ; for(int i : 11) System. out .print (i+ " ") ; System.out . println () ; 11 пере тасовать список Collections .shuffle (ll) ; 11 вывести перетасованный список System. out .print ( "Cпиcoк перетасован : ") ; for(int i : 11) System.out .print (i +" ") ; System.out . println () ; System. out .println ( "Mинимyм : "+Col lections .min (ll) ); System. out . println ( "Maкcимyм : "+Collections .max (ll) );
Гл а в а 18. Пакет java .utiL, часть \. CoLLections Framework 629 Ниже приведен результат, выводимый данной программой. Сnисок отсортирован в обратном порядке : 20 8 -8 -20 Список перетасован : 20 -20 8 -8 Минимум : -20 Максимум : 2О Обратите внимание на то , что методы min ( ) и max ( ) оперируют списком по­ сле того , как он был перетасован . Ни один из этих методов не требует, чтобы спи­ сок бьи отсортирован . Масс ивы Класс Arrays предоставляет различные методы, уд обные для обращения с мас­ сивами. Эти методы помогают восполнить пробел между коллекциями и массива­ ми. Каждый метод , определенный в классе Arrays , рассматривается далее в этом разделе. Метод asLis t ( ) возвращает список, исходя из указанного массива. Иными сло­ вами, список и массив ссылаются на одно и то же место в оперативной памяти . Ниже приведена общая форма этого метода , где параметр ма ссив обозначает кон­ кретный массив, содержащий данные. static <Т> List asList (Т . . . насеяв) В методе binarySearch () алгоритм двоичного поиска применяется для обна­ ружения заданного значения. Этот метод следует применять к отсортированным массивам. Ниже приведены некоторые формы метода Ь inarySearch (), дополни­ тельн ые его формы обеспечивают поиск в значений в заданных пределах. static int :ЬinarySearch (:Ьу tе насеяв[ ] , byte эна vеюsе) static int ЬinarySearch (char насеяв[] , char эна vеюsе) static int ЬinarySearch (douЫe нассяв[] , douЫe энаvеюsе) static int ЬinarySearch (float на сеяв[] , float энаvеюsе) static int ЬinarySearch (int нассив (] , int эна vеюsе) static int ЬinarySearch (lonq нассив(] , lonq зна чение) static int ЬinarySearch (short насеяв[] , short эна vею.rе) static int ЬinarySearch (Object насс.в[] , Ob ject эна vеюrе) static <Т> int ЬinarySearch (T (] насс.а , Т э.на vеюsе , Coшparator< ? super Т> с) Здесь параметр ма ссив обозначает конкретн ый массив, в котором осуществля­ ется поиск, а параметр зна чение - искомое значение. В двух последних формах данного метода ге нерируется исключение типа ClassCastException, если указан­ ный ма ссив содержит эл ементы , которые нельзя сравнивать (например, объек­ ты типа DouЫ e и StringBu ffer) , ил и же если заданное зна чение несовмести мо по типу с элементами указанного ма ссив а. В последней форме компаратор с типа Comparator используется для определения порядка расположения эл ементов в ука­ занном ма ссив е. Но в любом случае возвращается индекс элемента, если заданное зна чение содержится в указанном ма ссиве, а иначе - отрицател ьное значение. Метод copyOf ( ) возвра щает копию массива и имеет следующие формы:
630 Ч асть 11. Библ иотека Java static boolean (] copyOf (Ьoolean [] •с'l'оvн.ж , int дmrна) static Ьуtе [] copyOf (Ьyte [] •С'l'оvн.ж , int дmrна) static char [] copyOf ( char [] •с'l'оvн.ж , int дm1 11 а) static douЬle [] copyOf (douЬle [] •с!l'оvн.ж , int дmrна) static float [] copyOf (float [] •с!l'очюrх , int дmrна) static int[] copyOf (int [] •с'l'оvн.ж , int дmrна) static lonq [] copyOf (lonq (] •с'l'оч.юrж , int дmrна) static short [) copyOf ( short [] •C!l'ovн.x , int дmrна) static <Т> Т[] copyOf (T(] •C'l'O'I IOI Ж, int Д.IDUIB) static <Т, U> Т[] copyOf (U[] •С'l'оvн.ж , int дmrна , Class<? extends Т []> peзynъ 'l'JIP.V. ..X _ 'l'.lfD) Исходный массив задается в качестве параметра источник, а длина копи и - в качестве параметра длина . Если копия дл иннее, чем указанный источник, она дополняется нулями (для числовых массивов) , пустыми значениями nu ll (для массивов объектов) или логическими значениями false (для булевых массивов). Если копия короче, чем указанный источник, она усекается. В по­ следней форме резуль тирующий_ тип становится типом возвращаемого мае· сива. Если указанная длина отрицательна, то генерируется исключение ти па NegativeArraySizeException. Если указанный исто чник содержит пустое зна· чение nul l, то генерируется исключение типа NullPointerException. А есл и резуль тирующий_ тип несовместим с типом указанного исто чника , то генерирует­ ся исключение ти пa ArrayStoreException. Метод copyOfRange ( ) также возвращает копию массива в заданных п ределах и имеет следующие формы: static boolean [] copyO:fRanqe (Ьoolean [] Jl l C!l'OVНl l X, int наvало , int ханец) static byte [] copyOfRanqe (Ьyte ] •c!l'ovюrж , int наvало , int хонец) static char [] copyOfRanqe (char []•c!!'o'I IOl ж, int наvало , int жанец) static douЬle [] copyOfRanqe (douЬle [] Jl l C!!'ovюrж , int наvало , int ханец) static float [] copyO:fRanqe (float [] •с!!'оч.юrх , int наvало , int жонец) static int [] copyOfRanqe (int [] •с!!'оvюrж , int .наvало , int xoнetl) static lonq [] copyOfRanqe (lonq [] •с!!'очюrж , int наvало , int жонец) static short [J copyOfRanqe ( short [] •c!l'ovюrж , int наvало , int жонец) static <Т> Т[] copyOfRanqe (T[] •С!!'О'IЮIЖ, int яavaJZo , int жанец) static <Т, U> Т[] copyOfRanqe(U[] •с!!'оvюrж, int наvало, int жанец, Class<? extends Т [ ] > pesy.nъ!!'Jl lP ,,. ._- _�) Исходный массив задается в качестве параметра исто чник. Пределы для копи ро­ вания задаются индексами , передаваемыми в качестве параметров на чало и конец. Заданные пределы просrираются от позиции на чало до позиции конец- 1 . Есл и за· данные пределы длиннее, чем указанный источник, то копия дополняется нул ями (для числовых массивов), пустыми значениями nu ll (для массивов объектов) или логическими значениями false (для булевых массивов). В последней форме указан· ный резуль тирующий_ тип становится типом возвращаемого массива. Если заданное на чалоотрицательное ил и больше длины указанного источника , то генерируется исключение типа ArraylndexOutOfBoundsException. Если за· данное на чало больше, чем заданный конец, то генерируется исключение ти па IllegalArgumentException. Если указанный источник содержит пустое зна· чение nu ll, то генерируется исключение типа Nu llPointerException. А если резуль тирующий_ тип несовместим с типом указанного источника, то генерирует­ ся исключение ти па ArrayStoreException.
Гл ава 18. Пакет java.util, часть 1. CoLLections Framework 631 Мсто;t equa ! s () во: шранtаст лоп!'1с<·кос :1шt'1с11ис t·. rue, ('СЛ Н ofia сравнивае­ мых масс ива ра111 10: 111"ч11ы, ;1 11 11а'1(' - ;юп1чсп<о<· :111а11с11ис fa 1se. 1lи жс 11риве;1с- 11ы ра:ши•шыс фор:1-1ы \Н'ТО.'\а erpJ a J s (), 1·;tc нарамстры ма ссив 7 и ма ссив2 обо­ :1 юР 1ают сра11111шас\1ыс 11а ра венство \t ассивы. static boolean equal s (bool ean ма ссивl [] , boolean на ссив2 [] ) static boolean equal s (byte ма ссив l [] , byte на ссив2[] ) static boolean equals (char ма ссивl [] , char на ссив2 [] ) static boolean equal s(douЫe ма ссивl [] , douЬle ма ссив2 []) static boolean equals (float ма ссив l [] , float на ссив2 [] ) static boolean equals (int ма ссив l [] , int ма ссив2 [] ) static boolean equals (long ма ссивl [] , long ма ссив2 (] ) static bool ean equa ls (short ма ссив l [] , short ма ссив2 [) ) static boolean equals (Obj ect ма ссивl [] , Ob ject на ссив2 []) Мсто,:t ci eepEчua 1 s () 1ю:111ол н('Т 011ре;tслить. являютсн 11 11 ра111ю:111а•шы:.н1 ;�;ва \t асс11 в;1 , которые мш·vт с< цержал. 11лож с1111ыс :-.1асп111ы. Этот :\1 ето;t оfiъя вл нстся CЛC/t)'IOll\IHI ofipa:IO\I: static boolean deepEqua ls (Object [] а, Ob ject [] Ь) Мсто;\ ck�epE·::jUil. l s () 110: 111ращаст ;ю п111е скос :11 1ачс11ис t rue. есл и 11срс;tа1 111ыс ему \1 а сп 111ы а 11 Ь ра111 ю:ша1 111ы. Есл и массивы а и Ь сщtсржат 11л ожс1111ые \t асс11вы, они та кже сра111 11111а 1отсн. Есл 11 же \1 ;кп1в1.1 а 11 tJ или вл оже1111ыс в 1111х \1 аспшы от­ ш1 11аюто1 . ;ta 1111ы ii \I ('ТO) t 11о:шр;1 1наст ;ю1·1111с п\ос :ша11с1111с f d l se. Мспщ f L J l 1) 11р11сва11 11<1ет :11 1а•н·111н· всем :�л ементам массива. И11ы:1-111 слова-.111, 011 :1а1юл11нст \·tаспш ука:1а1111ы\1 :111;111с1111с\1. У ''ето;tа f i 11 ( ) И\н·ютп1 :tва ва риа11т<1 . В 11с1ню\1 ва р11а1пс. ф ор\1ы которо� п IIJH IВ<"i\CIIЫ 1111жс, эа 1юл ш1етсн веп, часп 1 11. static vo id fill (bool ean ма ссив[] , boolean зна чение) static vo id fill (byte ма ссив[] , by te зн ачение) static vo id fill (char ма ссив[) , char зн ачени е) static vo id fill (douЬle ма ссив[] , douЫ e зна чение) static vo id fill (float ма ссив[] , float зн ачение) static vo id fill (int ма ссив [] , int зна чение) static vo id fill (long ма сси в[ ] , long зна чение) static vo id fill (short ма ссив[] , short зна чение) static vo id fill (Obj ect ма ссив[] , Ob ject зна чение) ],:tсп. :1<1ita I11юc :та чеш1е 11р11с11: 11111ается IK<.'M :�л смснта\t ука:1а111юго ма ссива. Во 1по ро\1 ва рианте \t cтo;t:i 1 i. 1 1 () :1а;tа111юс :111а•1с1111с 111н1сваи11астс н 11щ\\I11ожс­ ству \l <ICCI IB<I. Мсто;t soгt_ ( ) сорти рует \1 <нт 1111 та к11\1 оfiра:юм, чтобы у1 1орн;�; о1111п , <.то :� лс­ \t с 1 пы 110 11apacтaю11tcii. У метола sог1· r) имеютс я itBa ва рианта. В IICJHIO\t вар11а11- тс . ф ормы кото ро 1·0 111н111с;tе11ы 111\Жl' , сорт11 р\·етс я llCCI> \1 <\ССИ В. static vo id sort (byte ма ссив[ ]) static vo id sort (char ма ссив[ )) static vo id sort (douЬ le ма сси в( ]) stati c vo id sort (float ма ссив[] ) static vo id sort (int ма ссив[] ) static vo id sort ( long ма ссив[ ]) static vo id sort (short ма ссив[] ) static vo id sort (Obj ect ма ссив[ ]) static <Т> void sort (T ма ссив[] , Comp arator< ? super Т> с) :�;tсп. 11ара\·1 <.т р ма ,·r:: ин ofio:11 1;111acт сорп 1русчый массив. В 11o cщ·.: t11cii фор\tс ll<l)M\ll"l'I' ,·: 0Г,0: 111;1•1аст KO \lll<IJ 'aтop т1111а СС.! П ра гаt ог, IICIIOJI I,:l\'C \I Ыil :tл н \'I IOJJH-
632 Часть 11. Библиоте ка Java ;(O'ICllllH :� ЛCMCllTOll у ка:1а�111 ш·о ма сси ва. в ;щух lf()('JI C/(l lllX фор�1ах '\ЮЖСТ П' IICJНI• ровап.сн 11сКJ110•1с1111е тина ClassC ast.. Exco;p t.ioп, ccm 1 :�лсмс1пъ1 сортируемо1·0 :-.1 асс 11ва 11ссовмсстимы. Во вто ром вари а нте \I CTOJ(a so r t. ( .1 имсстсн во:3можность \•ка :1ал. 11р е;\ел ы, в кото р ых тр сГ>уется отсорт11 роват1, \1 ;11т11в. В всрпш.J 1 >К Н класс Arr а ys юнюл11е11 J Hl,'\O!l.I новых мсто;10 11. l l a11fi oлce важным 11:1 них, веронп ю, нвлнстсн м ето д pa r allel:)ort. (), 1юпшл ы<v 011 п1ач;u1а сортиру· ст 11аралл с;1 1,1 ю от;1сл ы1ы(: •mст11 массива в 1101 нщкс 11араста1111н, а :1атсм объсд1111я· ст 110.' 1\·че1111ыс р е:1ул1.таты . l:iлаго;\а р я :�то м у : 1 11а• 111 п ·л ы10 сокращасто1 в ремя <"О · рт1 1ровю1 :. . 1аспша . Как и у мстщ1а sort ().у • 1н·то;1а pa ra 1lelSc> rt () имеются два ос 11ош 1ых ва риа11та , 11ричсм кажды й с 11ес кш1 1.юнн1 ll(' ) >(Тружас мымн формами. В 11е1ню:-.1 ва р 11а11тс, ф ормы которш·о 11р и11с;(с 11ы 1111же, сортируется веп. массив. static void paral lelSort (byte ма ссив[] ) static void parallelSort (char ма ссив[]) static void parallelSort (dou Ыe ма ссив[] ) static void parallelSort (float ма ссив[] ) static void par allelSort (int ма ссив[]) static void para llelSort(l ong ма ссив[]) static void parallelSort (short ма ссив[] ) static <Т extends ComparaЬle<? super Т>> vo id paral lelSort (T ма ссив[] ) static <Т> vo id parallelSort (T ма с сив[] , Compar ator<? super Т> с) 3жт 1. 11ара:v1стр ма ссив о Г> о:та•�аст cop л1pyt':v1 1.1i i :v1 асп1 11. В 1юсж·;111сii форме 11а р а�((:тр ,� 0 Gо:111;1 '1аст комш1 рато р , 11п1ш11.:1усмыi i :1лн у�ю р 1що11с1111н :мемсн· то в :-.1 а сп1 11а . В /\В)'Х 1юслсд1 111х ф о р мах :v1 ожст 1·с11ср 11р ш�ат1.п1 11п<J1 IO 'f('llllC тина ClassC ast.Exccpt i on, есл и :iл смснты сор тн руt·:-.1ш·о масс11ва 11сп 111�н·сти мы. Во вто ро�1 ва рианте мсто;\а paгalle1Sort ( ) 11м(ттп1 110:1�·юж 11осл. ука: �;пъ 11рс;1сл ы, в кото р ых тр ебуется отсортир оват1, массив. l la•11111aн с верп111JОК Н в iu1a(TC Arтays 11о;ис р ж1111а 1 отсн также 1псраторы·ра:1· 11слнтсл11 . И ,'\<:лается :·но, в частности, с 11о:vющыо :v1стщ\а spli terа t.or ( ) . У :ноrо �1 сто.'t<1 1н1е10то1 i\IЫ оп ювных ва р ианта . В нервом ва рианте, формы котор ого при· 1и·;(с ны 1111жс. 110:111р ащаетс н 11 тсратор-ра:щсл 1пс1 1. i\Л Я впто маспша. static Spl i te rator . OfDouЫe spliterator (douЫe ма ссив[ ]) static Spli ter ator .Ofint spliterator (int ма ссив[ ]) static Spli terator . OfLong spliterator (long массив[] ) static <Т> Spliterator spliterator (T ма ссив[] ) : l;\сп. 11а ра:v1стр ма ссив обо:11 1i1'l<ll'T 11с р с б 11р асч1>1 ii 11те р ;1т< 1р1 ш·р а:щсл ителем :v1ассив. Во втором варианте мсто;\а spli t� e са tог ( ) 11мlттсн во:шожнопъ указать 11рс;1ел ы, в кото р ых трсбуето1 11сребрат1, :i лсмснты маспша. 1 lа•11111ан с всрсии JОК Н в IU1 <1tтe Аrгау�. 1ю1u1с ржи11аетсн также 11011ыii и нтср· фciic S t и:аm. 0 111 1сы1ыс :-.1 ы й n 1:11аве ��1. И ;�сластен :iто , в •1аплогп1 , с 1юмощыо ме· то :tа s t. cearn ( ) .у:i ТОГО мето;tа име ютп1 }\IJ;\ O('llOllllЫX ва рна11та. 1 l и ж с ll( >IШC/tCHЬI 11е р с1·р ,·1ю1сщ.1е формы 11с р во1·0 ва рианта !l.1 ето;1а s t r- eгm (}. static DouЫeS tream stream (dou Ыe ма ссив[] ) static IntS tream stream (int ма ссив[ ]) static LongS tream stream ( long ма ссив[] ) static <Т> Stream stream (T ма ссив[] ) :!;(сп. 11ара:v1етр ма ссив о бо:шачает 1<011крел 1ыii жнт1111, 11;1 1< 0·1лр ы il ссылается 1юток 11110;1а-111.1 1юi\<1 . Во вто р о м вар 1 1 а11 п· �н ·тода s t. геаrп ( ) 11ме стсн B<l:!!\IOЖllOCТb
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Framework 633 указать пределы, в которых требуется обратиться к элементам массива из потока ввода-вывода. Помимо упомянутых выше методов, в версии JDК 8 класс Arrays дополнен еще тремя новыми методами. Два из них называются setAll () и parallelSetAl l () и присваивают значения всем элементам массива, причем метод ра rа l le1 SetAl l ( ) позволяет делать это парал л ел ьно. В качестве примера ниже приведена одна из пе­ регружаемых форм каждого из этих методов. У каждого из них имеются также пере­ гружаемые формы для обработки массивов типа int, long и обобщенного типа. static void setAll (douЫe насс:ив[] , IntToDouЬleFunction<? extends Т> rенера�ор sнaveюr•) static void parallel SetAll (douЬle насС1 1r а[] , - IntToDouЬleFunction<? extends Т> reнepa�op_.sнaveюr•) И наконец, в версииJDК 8 класс Arrays имеет еще одно привлекательное допол­ нениев виде методаparаllelРrefix () , который видоизменяет массивтаким обра­ зом, чтобы каждый последующий его элемент содержал н акапливаемый результат операции , выполняемой над всеми предыдущими элементами. Та к, если это опера­ ция умножения, то после возврата из данного метода массив будет содержать значе­ ния, связанные с текущим произведением исходных значений. В качестве примера н иже приведена одна из перегружаемых форм метода parallelPrefix (). static void paral lelPrefix (douЬle насеяв(] , DouЬleBinaryOperator фунжцжя) Здесь параметр ма ссив обозначает обрабатываемый массив, параметр функция - операцию, выполн яемую над массивом, а тип DouЫeВ in arуОре r а to r - функциональный интерфейс , определенный в пакете jav a.util . function. Уме­ тода parallelPrefix ( ) имеются и другие перегружаемые формы для обработки массивов типа int, long и обобщенного типа, а также отдел ьных элементов мас­ с ива в задан ных пределах. Кроме того , в классе Arrays предоставляются методы toString () и hash­ Code ( ) для массивов различных типов. В нем предусмотрены также методы deep­ ToString ( ) и deepHashCode ( ) для эффективной обработки массивов, содержа­ щих вложенные массивы. В следующем примере программы демонстрируется применение некоторых методов из класса Arrays: 11 Продемонстрировать применения некоторых ме т одов из класса Arrays import java.util .*; class ArraysDemo { puЫic static vo id ma in (String args[] } { 11 выдели ть и инициализировать ма ссив int array [] = new int[lOJ ; for(inti=О;i<10;i++) array[i] = -3 * i; 11 вывести , отсортировать и снова вывести содержимое ма ссива System . out .print ( "Иcxoдный массив : ") ; display (array ) ; Arrays .sort (array) ; System. o ut . print ( "Oтcopтиpoв aнный ма ссив : ") ; display (array) ;
634 Часть 11. Библиотека Java j 11 залолнить массив и вывести его содержимое Aпays .fill (array, 2, 6, -1) ; System.out . print ( "Maccив после вызова МЕ"l'Ода fil l () : "j ; display {array) ; // отсортиро вать и вывести содержимое ма ссива Ar rays .sort (arra y) ; System. out .print ( "Maccив после п о вто р ной сортировки : ") ; display (array) ; /1 выполнить двоичный поиск значения -9 System.out .print ( "Знaчeниe -9 находи тся на позиции ") ; int index = Ar rays . binarySea rch (array, - 9) ; System.out .println ( index) ; static void display (int array[) ) for (int i: array) System. out .print (i +"" ) ; System.out . println() ; Ниже приведе н резул ьтат выполнения да нной 11рограммы. Исходный масси в : О -3 -6 -9 -12 -15 -18 -21 -24 -27 Отсортиро ванный массив : -27 -24 - 2 1 -18 -15 -12 -9 -6 -3О Массив после вызова ме тода fill () : -27 -24 -l -1 -1 -1 -9 -6 -3О Массив после повторной сортировки : -27 -24 -9 -6 -3 -1 -1 -1 -1О Значение -9 на х одится на позиции 2 Унаследованные классы и интерфейсы К<�.к пояснялоо, в начале этой [Лавы, в нсрвых версиях пакет·а j аvа . u ti1 OTC}'I' ствовал кар1<ас коллекций. Вместо него был 0t1рс;1слен ряд ю�ассов и интерфейсов, предоставля ющих спе1�иальные мето;1ы ;1,ля хранения объе кто в. А [(оrда были вне­ дрены [(Оллею1ии (начиная с ве рсииj2SЕ 1.2), то некоторые исходные кла(ТЫ были псределсшы для шщде ржки инте рфейсов коллсющй. Сле;1оватсл ыю, они и теперь форма.11ыю являютс я частью каркаса колле1щий Collcctioнs 1'' 1·ю11сwш-k. И несмотря на то что новые коллекнии ;1убл ируют фун книи уста рев1ш1х ю�ассов, на нрактике обычно ис1ю11 1"зуются классы новых кол1н.�ю 1ий. В общем , унаследо ванные классы 1юд д ерживаются пото му, что существует КО/\, 11 1<ото ром они иоюл 1,зуются. Следует также име-1ъ в ВИ/(у, что ни один иа современных кл ап:ов коллекций, ониса1 111ых в этой гл аве, не синхронизирован, тогда ка к все устаревшие классы синхрони:шрован ы. В некоторых слу •�аях это отличие может иметь бол ьшое зна· чение. Безусл овно, колле ю1ии нетрудно пшхрони:шровать, 11рименяя оди н из ал· го ритмов , 11 редоставляемых н виде методов ю1асса Col lections. Ниже перечис· левы унасле;1ованные классы, онределс нныс в 11а1<ете j а va . u t i 1. Dictionary HashtaЫ e Properties Stack Ve ctor Имеется та кже один унаследованный инте рфейс, на:�ываемый Enume ration. Этот инте рфейс и каждый из унаследованных кл ассов рассматри ваются по очере­ ди в последующих ра:щелах.
Глав а 18. Пакет java.utiL, часть 1. CoLLections Fram ework 635 Интерфейс Enum.eration В интерфейсе Enumeration определяются методы, с помощью которых можно перебирать элементы из коллекции объектов, получая их по очереди . Этот унас­ ледованный интерфейс был заменен интерфейсом Iterator. И хотя интерфейс Enumeration применяется до сих пор, он считается не рекомендованным для упо­ требления в новом коде . Те м не менее он применяется в некоторых методах из унас­ ледо ванных классов (например, из классов Ve ctor или Prop erties), а также в ряде других классов прикладного программного инте рфейса API. В связи с тем что этот и нтерфейс все еще употребляется в унаследованном коде , он был переделан в обоб­ щен ную форму при разработке версииJDK 5. Интерфейс En ume r а t i on объявляется п риведенным ниже образом, где Е обозначает тип перечисляемых элементов. interface Enumeration<E > В интерфейсе Enumeration оп редел ены следующие методы: Ьoolean hasMoreElements () Е nextElement О При реализации метод hasMoreElements () должен возвращать логическое значение true до тех пор, пока в перечислении еще остаются извл екаемые эле­ менты , а когда эл ементы уже перечислены - логическое значение false. Метод ne xtElement () возвращает следующий объект в перечислен ии. Следовател ьно, в результате каждо го вызова метода nextEleme nt () возвращается следующий объект в перечислении. По окончании перечисления этот метод генерирует ис­ ключение типа NoSuchElemen tException. Класс Ve ctor Этот класс реал изует динамический масс ив. Он подобен кл ассу ArrayList, но и меет два отличия: синхронизирован и содержит немало устаревших методо в, дубл ирующих функции методо в, определенных в каркасе коллекций Collectioнs Framewoгk. С появлением коллекций класс Ve ctor бьш переделан как расширение КJra<·ca Ab s tractList и допол нен реализацией интерфейса List. В версии JDК 5 он бьш переделан под синтаксис обобщений и дополнен реализацией инте рфейса IteraЫe. Это означает, что кл асс Vector стал полностью совместимым с коллек­ циями, допус кая перебор своего содержимого в усовершенствованном цикле fo r. Класс Ve ctor объявляется нриведенным ниже образом, где Е обозначает т ип с охраняемых элементов. class Ve ctor<E> Ниже перечислены конструкторы кл асса Ve ctor. Vector () Vector (int размер) Ve ctor (int размер , int .янхренен т) Vector (Collection<? extends Е> с) В первой форме конструктора создается вектор по умолчанию, имеющий на­ чальный размер 10. Во второй форме конструктор<t со;щается вектор, начальная
636 Часть 11. Библиотека Java емкость кото рого определяется параметром р азмер. В третьей форме создается вектор, имеющий заданный начальный р азмер и инкремент. Инкремент опре­ деляет кол ичество элементов, которые будуг резервироваться всякий раз, когда увеличивается размер вектора. И наконец, в четвертой форме создается вектор, содержащий эл ементы из указанной коллекции с. Все векторы начинаются с некоторой начальной емкости . Когда эта емкость ис черпана, при последующей попытке сохранить объект в векторе автоматиче­ ски увеличивается количество вьщеляемого пространства памяти по мере роста вектора. Это увеличение важно, поскольку выделение памяти - дорогостоящая по времени операция. Общий объем выделяемого каждый раз дополнительного пространства памяти задается величиной инкремента , указываемого при созда­ нии вектора. Если не указать величину инкремента, размер вектора будет уд ваи· ваться каждый раз при выделении памяти . В классе Ve ctor определяются следующие защищенные эл ементы данных: int capacityincreшen t; int elementcount ; Ob ject [] elementData ; Значение инкремента сохраняется в поле capacitylncrement. Количество элементов , находя щихся в данный момент в векторе, хранится в поле element Count . Массив, содержащий сам вектор, хранится в поле elernentData. Помимо методов коллекций, определенных в интерфейсе List, в классе Ve ctor определя етс я ряд унаследованных методов, перечисленных в табл. 18.20. Класс Ve ctor реализует интерфейс List, и поэтому вектор можно использо­ вать та ким же образом, как и экземпляр класса ArrayList. Вектором можно также манипулировать, используя один из унасл едован ных методов. Например, после создания экземпляра класса Ve ctor можно вызвать метод addE l ernent (), чтобы ввести элементы в вектор. Чтобы получить элемент, находя щийся на определен­ ной позиции в векторе, достаточно вызвать метод eleme ntAt (); чтобы получ ить первый элемент вектора - метод firstEleme nt ();адля того чтобы получить по­ следний элемент вектора - метод lastEleme nt (). Индекс отдел ьного элемента можно получить методом indexOf () или last lndexOf ().Чтобы удал ить элемент из вектора, следует вызвать метод remo ve Eleme nt () или removeElementAt (). Та бл ица 18.20 . Ун аследованные метод ы иэ класса Ve ctor Метод void addElement (Е э.леменm) int capacity () OЬject clone () Ьoolean contains(Ob ject э.лемент) void copyinto (OЬject A«UnU![] ) Описание Вводит в вектор объект, оп ределяемый параметром мг.мент Возвращает емкость вектора Возвращает дубликат вызывающего объекта Возвращает логическое значение true, есл и задан ный э.лемент содержится в векторе, а иначе - логическое значение false Элементы, содержащиеся в вызывающем векторе, ко пируются в указанный A«UnU1
Гл а в а 18. Пакет java.util, часть 1. CoLLections Framewo rk 637 Метод Е elel llEl ntAt (int индекс) EnWDSration<E> eleшents () void ensureCapacity (int размер> Е fi rstEleшent () int indexOf ( OЬject элемент) int indexOf (OЬject элемент , int начшю) void insertEleшentAt (E мемент , int индекс) Ьo o lean isEшpty () Е la stEl8!1 18n t() int lastindexOf (OЬject эм мен т) int lastindexOf (OЬject мемент, int IШ'/Ш/о) void r81 11D veA11Eleшents () Ьo o lean reJDOveE leшent (OЬject мемент) void r81 110 veEl81 118 ntAt (int индекс) void setE181 118 ntAt(E элемент, int индекс) void setSize (int размер> int size () Strinq toStrinq ( ) void trimТoSize О Окончание табл. 18. 20 Описание Возвращает элемент 110 указанному wЮексу Возвращает перечисление элементов вектора Ус танавливает минимальную емкость вектора равной задан ному размеру Возвращает первый элемент вектора Возвращает индекс первого вхождения заданного мемента. F. .с ли объект не найден в векторе, возвраща­ ется значение -1 Возвращает индекс первого вхождения заданного з.лемента, начиная с указанной позиции 1UlЧй.IW. Если объект не найден в векторе, возвращается значение -1 Вводит задан ный элемент в вектор по указанному индексу Во:шращает логическое значение true, если вектор пуст, а иначе -логическое значение fal se Возвращает последний элемент в векторе Возвращает индекс последнего вхождения заданного элемента. Если объект не найден в векторе, возвраща­ ется значение -1 Возвращает индекс последнего вхождения заданного элемента до указанной позиции начшю. Если объект не найден в векторе, возвращается значение - 1 Очищает вектор. После выполнения этого метода раз­ мер вектора равен нулю Уд аляет заданный элемент из вектора. Если в векторе содержится больше одного экземпляра заданного эле­ мента, то уд аляется только первый из них. Возвращает логическое значение true , если эл емент удал ен, а если объект не найден - логическое значение fal se Уд аляет из вектора элемент по указанному индексу Ус танавливает заданный элемент по указанному индексу Уt:танавливает количество элементов вектора равным заданному размеру. Есл и новый размер меньше старо­ го , элементы теряются. Если же новый размер больше старого, добавляются пустые элементы Возвращает количество эл ементов, содержащихся в векторе Возвращает строковый эквивалент вектора Уст анавли вает емкость вектора равной количеству элементов, содержащихся в нем на данный момент
638 Ч асть 11. Библиоте ка Java В приведенном ниже примере про граммы вектор используется для с охранения разных типов числовых объектов. В данном примере демо нстрируется ряд унасле­ дованных методов, определенных в классе Ve ctor. Кроме того , в данном примере по казан о , как обращаться с интерфейсо м Enumeration. 11 Продемонстрир овать р азличные операции с вект ором import java .util .*; class Ve ctorDemo { puЫ ic static void ma in (String args []) { 11 началь ный размер вектора - 3, а и нкремент - 2 Ve ctor<Integer> v = new Ve ctor<Integer> (З, 2) ; System. out . println ( "Haчaль ный размер вектора : "+v. size()); System. out . println ("H aчaльнaя емкость вектора : "+v. capacity ()); v. addE leme nt (l) ; v. addEl ement (2) ; v. addElement (3) ; v. addElement (4) ; System. out .println ( "Емкость ве ктора после ввода четырех элементов : " + v.capacitу () ) ; v. addEleme nt (5) ; System. out .println ( "Teкyщa я емкост ь вектора : "+v.capacity () ); v. addElement (б) ; v. addEl eme nt (7) ; System.out . println ("T eкyщa я емкость вектора � "+v.capacity () ); v. addElement (9) ; v. addEleme nt (lOJ ; System. out . println ( "Teкyщa я емко сть вектора : "+v.capacity () ); v. addElement (ll) ; v. addElement (l2) ; System. out . println ( "Первый элемент вектора : "+v. firstElement () ); Systern. out .println ( "Последний элемент вектора : "+v.lastEleme nt ()); if (v . contains (З) ) System.out . println ("B eктop содержит З.") ; 11 переч и с ли ть элементы вектора Enumeration<Integer> vEnurn = v. eleme nts () ; Systern. out . println ("\nЭлeмe нты вектора:") ; wh ile (vEnum .ha sMoreEl emen ts () ) System.out . print (vEnum .nextElement () +" ") ; System. out . println () ; Ниже приведен результат, выводимый данной программо й. Началь ный ра змер вектора : О Н ачальная емкость ве ктора : З
Гл ава 18. Пакет java .uШ, ч асть 1. Cottections Fram ework 639 Емкость вектора после ввода ч е тырех элементов : 5 Текуща я емкость ве ктора : 5 Текуща я емкость ве ктора : 7 Текущая емкость век тора : 9 Первый элемент в ектора : 1 Последний элемент вектора : 12 Вектор с оде ржи т 3. Элеме нты в е ктора : 12345679101112 Вместо того чтобы полагаться на перебор объектов в цикле, как это делается в пр иведенном выше примере программы, можно воспользоваться итератором. Для этого в данную программу можно, например, подставить следующий фраг­ мент кода: 11 Использовать итератор для вывода содержимого ве ктора Iterator< Integer> vitr = v. iterator (); System. out . println ("\nЭлeмe нты вектор а:") ; whi le (vitr . hasNext () ) System. o ut . print (vitr .next () +"" ); System. out . println (); Для перебора элементов вектора можно также организовать цикл for в стиле for each, как показано в следующей ве рсии предыдущего фрагмента кода: 11 Ис п ол ьзовать усоверше нствованный цикл for в стиле for each 11 для вывода элеме н тов вектора System.out . println ("\nЭлeмe нты ве ктора :") ; for(int i : v) System. out . print (i +" ") ; System . out . println () ; Интерфейс Enumeration не рекомендуется применять в новом коде , поэтому для перебора всех элементов вектора обычно применяются итераторы и циклы for в стиле f or each. Безусловно, существует еще немалый объем кода, в котором используется инте рфейс Enumeration. Правда, перечисления и итераторы дей­ ствуют практически одинаково. Класс Stack Класс Stack является производным от класса Ve ctor и реализует стандарт­ ный стек, действующий по принципу "последним пришел - первый обслужен". В классе Stack определяется тол ько конструктор по умолчанию, создающий пустой стек. В версии JDК 5 класс Stack был переделан под синтакс ис обобщений и те­ пер ь объя вляется следую щим образом: class Stack<E> где Е обозначает тип элементов, с охраняемых в стеке. Класс Stack включает в себя все методы , определенные в классе Ve ctor, и дополняет их рядом своих методо в, перечисленных в табл . 18.21.
640 Часть 11. Библ иоте ка Java Та бл ица 18.21. Методы из класса stack Метод Описание Ьo o lean ешрtу () Возвращает логическое значение true, если сrек пусrой, а если он содержит эл ементы , то ло1·ическое значение f'alse Еpe ek () Е рор() Е push(E элемент) int search (OЬject элемент) Возвращает элемент из вершины сrека, но не уд аляет его Возвращает элемент из вершины сrека, уд аляя его Размещает заданный э.м мент в стеке и возвращает этот 3.1U'.Ме 11 11 1 Осущесrв.ляет поиск заданного iМеМеН1 11а в сrеке. Если заданный элемент найден, возвращается его смещение относительно верши· ны сrека, а иначе - значение -1 Чтобы раэместить объект на ве ршине стека, сл едует вызвать метод push ( ); чтобы удалить и возвратить верхний элемент из стека - метод рор ( ) , а для возвра­ та верхнего элемента без его уд аления из стека - метод рееk ( ) . Исключение типа EmptyStackException ге нерируется в том случае, если вызвать метод рор ( ) или рее k ( ) для пустого стека. Метод emptу ( ) возвращает логическое :шачение t rue, если сгек пуст. Метод search ( ) определяет, содержится ли объект в стеке, и возвращает количество вызовов метода рор ( ) , требующихся для перемещения объекта на верши­ ну стека. Ниже приведен пример программы, в которой создается стек и в нем разме­ щается несколько объектов типа Integer, а затем они извлекаются из стека. 11 Продемонстрировать приме не ние кл асса Stack import java .util .*; class StackDemo { static vo id showpush (Stack<Intege r> st , int а) 1 st .push (a) ; System. out . println ("push (" +а+ ") ") ; System. out .println ("cтeк : "+st) ; static void showpop (Stack<Integer> st) System. out . print ( "pop -> ") ; Integer а= st .pop() ; Systern.out . println (a) ; System. out . println ("cтeк : "+st ) ; puЫ ic static vo id main (String args [] ) { Stack<Integer> st = new Stack<Integer> () ; Systern.out .println ("cтeк : "+st ) ; showpush (st, 4 2) ; showpush (st, 66) ; showp ush (st, 99) ; showp op (st) ; showpop ( st); showpop (st) ; try 1 showpop (st) ; catch (Emp tyStackException е) { Systern.out . println ("c тeк пус т" );
Гл ава 18. Пакет java.utiL, ч асть 1. CoLLections Framewo rk 61.1 Ниже приведен результат, выводимый данной программой. Обратите внима­ ние на вызов обработчика исключений типа EmptyStackException, позволя юще­ го изящно выйти из положения, когда стек незагружен. стек: [] push(42) стек: [42] push ( 66) стек: [42, 66] push ( 99) стек: [42, 66, 99] рор -> 99 стек: [42, 66] рор -> 66 стек: [42] рор -> 42 стек: [J рор -> стек пу ст Следует также заметить, что класс Stack по-прежнему рекомендуется для упо­ требления. Те м не менее вместо него лучше выбрать класс ArrayDeque . Класс Dictionary Класс Dict ionary является абстрактным, предоставляет хранилище для пар "ключ-значение" и действует аналогично отображению типа Мар. Зада в ключ и значение, можно сохранить значение в объекте класса Dictionary. Как тол ь­ ко значение будет сохранено, его можно извлечь по связанному с ним ключу. Аналогично отображению, словарь ти па Dictionary можно рассматри вать как список пар "ключ-значение". И хотя класс Dictionary до сих пор рекоме ндуется для употребления, его можно считать устаревшим, поскольку его функции полно­ стью заменяет отображение. Те м не менее класс Di ctiona ry по-прежнему приме­ няется, и поэтому он расс матривается здесь. В версии JDK 5 класс Di ctionary был сделан обобщенным. Он объя вляется следую щим образом: class Dictionary<K , V> где К обозначает тип ключей, а V-тип значений. Абстрактные методы , определен­ ные в классе Dictionary, перечислены в табл . 18.22 . Чтобы ввести ключ и его значение в словарь, достаточно вызвать метод put (), а для того чтобы извлечь значение из словаря по заданному кл ю чу - метод get (). Ключи и значения могут быть возвращены в виде перечисления методами keys ( ) и elemen ts () соответственно. Метод size () возвращает количество пар "ключ­ значение" , хранящихся в словаре , а метод isEmp ty () -логическое значение true, если словарь пуст. Для уд ален ия любой пары "ключ-значение" из словаря сл едует вызвать метод remo ve ( ) . Помните! Класс Dictionary считается устаревшим. Дnя получения функционал ьных возможно­ стей, требующихся для хранения пар "ключ-значение". следУет реал изовать инте рфейс Мар.
6'2 Часть 11. Библиоте ка Java Та бл ица 18.22. Абстра ктные метод ы иэ кл асса Dictionary Метод Описание Enumeration<V> elements ( ) Возвращает перечисление значений, храня щихся в словаре V qet (OЬject ключ) Возвращает объект, содержащий значение, связан ное с за­ да нным ключом. Если заданный ключ не содержится в сл ова· ре , возвращается пустое значение null Ьoolean isEшpty О Возвращает логическое значение true, если словарь пусr, а если он содержит хотя бы один кл юч, то логическое значе­ ние fаlsе Enuшeration<R> keys () Возвращает перечисление ключей , хранящихся в словаре V put(К ключ, V значение) Вводит ключ и значение в словарь. Возвращает пустое значе­ ние null, если заданный ключ отсуrствует в словаре, а ина· че - предыдущее значение, связан ное с задан ным ключам V reшove (OЬject ключ) int size () Класс HashtaЬle Уд аляет заданный ключ и его значение из словаря. Возвращает значение, связанное с заданным ключом. Если заданный ключ отсуrствует в словаре, возвращается пустое значение null Возвращает количество элементов в словаре Класс HashtaЫe входил еще в исходный пакет java . util и является кон· кретной реализацией кл асса Dictionary. Но с появлением коллекций класс HashtaЫe был переделан та ким образом, чтобы реализовать также интерфейс Мар. Следовател ьно, класс На sh tаЫе интегрирован в каркас коллекций Collections Fraшework. Он подобен классу HashMap , но синхронизирован. Подобно классу HashMa p, класс HashtaЫe служит для хранения пар "ключ­ значение" в хеш-табл ице. Но ни ключи, ни значения не могут быть пустыми. Испол ьзуя класс HashtaЫe, следует указать объект, который служит ключом, а также значение, кото рое требуется связать с этим ключом . Ключ затем хеши­ руется, а результи рующий хеш-код используется в качестве индекса, по которому значение сохраняется в таблице. Класс HashtaЫe был сделан обобщенным в версииJDК 5. Он объявляется при­ веденным ниже образом , где К обозначает тип кл ючей, а V - тип значений. clas s HashtaЫe<K , V> В хеш-таблице могут храниться объекты только тех классов, в которых переопре­ деляются методы hashCode ( ) и equa ls (), определенные в классе Ob ject. Метод hashCode () должен вычислять и возвращать хеш-код объекта, а метод equals () - сравнивать два объекта. Правда , во многих классах, встроенных в Java, метод ha shCode ( ) уже реализован . Та к, в наиболее распространенной хеш-таблице типа HashtaЫe в качестве ключа испол ьзуется объект типа String. В классе String ре­ ализуются также методы ha shCode ( ) и equa ls (). Ниже приведены конструкторы кл асса Ha shtaЫe. HashtaЬle () HashtaЫe (int размер) HashtaЫe (int раsнер , float жоэффиЦJrент заполнени.l l ) HashtaЬle (нap<? extends К, ? extends V> m)
Гл ава 18. Пакет java.utiL, часть 1. CoLLections Fra mewo rk 6'3 В 11ср11ой форме объявлж·тся ко11п·руктор 110 умолчанию. Во второй фор:щ· кон­ структо ра <'<>:l/{ае1 <·я хе ш-табJ1111 {а , имеющая :�аданный 11<l'1<U1 ы1ыii ра змер, 1 10 умол­ чанию ра в н ы й 11. В тре1ъе ii фор:1.н' со:щается хе111-т,1бл ш{а, имеющая :�ада1111ый нач<u1ы1ыii ра змер и ко:;ффициент _ за полне ния. �") тот ко:�фф ш\11е 1п н ахою пс н в щк;1елах от О,О i{O 1,О и 011ре:кл нет, 11ас кол 1.ко 1юmюй долж1 1а быт�. хеш-таблшр, нрсЖi\С •1см она Г�ужт \><1<· 1ш1реш1. 'l(>'III('C говоря , когда число :� лсментон н р евы ш а ­ ет емю 1с1ъ хс11 1-таfiл11цы, ум11ожс1111ую на ко:-Jффициент :�а1 1олш.·1111н , хс111-табл11на расширнстсн. Есл и ко:� фф11цнс11т :1а1юm1с1111я не ука:�ывастся, то ипюл1.:iустс н его значение 110 умш1 11а11ию, ра1шос О,7 5. И наконец, в четвертой форме констр\"КJ "О \М со:щаето1 хеш -табл ица, 1111111{иали:111рус:-.1ая элс:-.1е11тами и:� ука:ы шюii колл ею 1 1 111 ш. По У'IН>Л11<111ню в ы fi н р астс н ко:-Jфф111 111с11т аа� юл 11е1111н, ра1ш ый О,7 5. Пом н�ю �1 сто;1о в, 011 рс;1сле1111ых в интерфейсе Мар, к ото ры ii ·1·е11ср1. рс<u1 11 : 1\·ет­ ся в классе Наs r1 t at' le. 1 1 :·н о�1 классе 011ре;1сл яются также унаслс;10 11<11 111ыс мето; {ы, пере•шслснные 11 таfiл . 1 Н.�:{. При 11011ыткс ип юю.:юват1, нустоi·i КJ1 10•1 11с1,оторые 11:! :них мстодо н п· 11ср11руют 1кю110•1е1111е тина Nu l l Ро i n t. e r Е хсер t . i.·�JП. Та блица 18.23. Ун а следованные метод ы из класса HashtaЬle Метод Описание void clear () Ус та 11а 11m1 11; 1ст 11 НСХО/(110(' ( "OГГOЯ llllC 1 1 0•111щает Х('11l ·Т;1()."1 \Щ\ Ob ject clone () Во:111ращаt·г л:убл икат вы:�ы 11а ю1щто объекта Ьoolean contains (Object Во:шращает ;ю1 ·11чес кое :таче1111с true. t •сл 11 :1ала1 111ое значе11ш•) .11ючпше р.111110 11е которо�1у :111аче1111ю 11 хе111-табт111е. Ес1 11 же :1;ща1111ое ;11щ•1е11ие 11с 11aii;1e1 10. то 110:1 11ращается л о п1•1ес к� 1е :�1 1ачс1 111е false Ьoolean containsKey (Object ключ) Ьoolean containsValue (Object значе11111') Enumera tion<V> elements () V get (Object к.1юч ) Ьoolean isEmpty () Enumeration<K> keys () Во:шра щает лоп1чес кое :11 1аче11ис true , есл 11 :1a;1a1111ыii к.1юч р;шсн 11t·кото1 юч\· кл ючу 11 хе111-табл1ще. Ес:1 11 же :i;щ;1 11111>1 ii ключ не 11;1ii1t<·11. то 11о:шращастся лог11 •1еское :i11<1чt·1111t· false Во:1вра щает , ·1 оп1•1сс кое :т а11с111н· true . ( '< "Л ll :1ала1111ое . таче1 1 ие р:шно 11екоторо М\' :11 1а•1t·1 111ю 11 хе111-табл1111<- . Есл 11 же :1;щ;1 111 юt· .t1ючпше 11с 11aiiдс110, то 110:111ра щ;t('ТПI лш·1 1 • lt'<" кос :11 1а•1с1 111е false Во:шр; 1 щает 11сре•11н·ле1111е :11 1aчe1111ii, со;1сржащ11хп1 в хеш· таr.т111е Во:111ра щает оr.ъект. кото рыii С(Щержит :н 1аче11щ-. пт:1а 11110(' (" :ia;1;1 1111 1>1 \I к.1ючо.ч . Есл и :1ада11 11ыii ключ 11е 11aii)t(·11 11 хс111- таfi;11ще, ·1 о 110:111р ащаето1 ll)TToe :11 1аче1 111с null Во:111ра 111ает :юп 1 чес кое :111ачс11 11(· true. ссл 11 xc111-т;1fi:1 111ta 11\'СТа . а ec'l lf со;1ерж1п хотя fiы 0;11111 1ию11 -- л о1·11•1сп;оt· :11 1а­ •1с1111с false Во:тра щаст 11срсч11и1<·1 111с КJ1 ю•1eii. r1 щержа щ11 хсн 11 хс111- таr._ · 1 1111е V put (К к.1юч , V .11ш•11'ltul') В11(щ11т 11 :-.c111-т; 1fiJ1 1щ\· :1;ща1111ыс к.1юч 11 .maчe1tu1> . Во:m р; 1щает 11\"П О!' :111;1'11"1 111(' null. ('( "JI И :1aj\a1111ыii КЛ/01/ ОТПТСТВ\ ('Т 11 XC lll· 1·afi:1 111t(". а 111 1;1 'Н' -- 1 1ре;1ыдущее :11 1а•1е111н-. пнпа11110(' (" :П ll\I KЛJO' IO\I
6'' Часть 11. Библиоте ка Java М етод Описание Uканч.ан1u? табл. 18.23 void rehash () Увел ичивает размер хеш-таблицы и повторно хеширует все се ключи V remove (Object ключ) int size () String toStrinq () Уд аляет заданный ключ из хеш-таблипы. Возвращает значе­ ние, связан ное с задан ным ключом. Есл и заданный ключ аr­ сугствует в хеш-табл ице, то возвращается пустое значение nu ll Возвращает количество элементов в хеш-табл ице Возвращает строконый эквивалент хеш-таблицы Приведенный ниже пример является переделанным вариантом представлен· ной ранее програ:\fмы ведения банковских счетов вкладчиков. В да нном варианте класс HashtaЫe применяется для хранения Ф.И.0. вкладчиков и остатков на их те кущих банковских счетах. // Продемонс трировать применения кла сса HashtaЫe impo rt jav a.util .•; class HTDemo ( puЬlic static vo id maiп (String args(] ) { HashtaЫe<String , DouЫ e> balance = new HashtaЫe <Striпg, DouЫe> () ; Enum eration< String> names; St ring str; douЫe ba l; balance .put ( "Джoн Доу" , 3434 .34 ); bal ance .put ( "Toм Смит ", 123 .22) ; Ьаlаnсе .рut ( "Джейн Бейкер ", 1378 .00) ; balance .put ( "Toд Холл", 99 .22) ; balance .put ("P aльф Смит", - 19 .08 ); 11 пока з ать в с е счета в хеш-таблице name s = balance .keys (); wh ile (name s . hasMo reElements () ) { str = names . next Eleme nt (); System.out . println (str + "· "+balance .get (str) ); ) System.out . println () ; // внес ти сумму 1000 на счет Джона Доу bal = balance .get ( "Дж oн Доу" ); balance .put ( "Джoн Доу" , Ьа1+1000); System. o ut . println ("Hoвый остаток на сч ете Джона Доу : " + bal ance .get ("Д жoн Доу ") ); Эта программа выводит следующий результат: Тод Холл : 99 .22 Ральф Смит: -19.08 Джон Доу : 3434 .34 Джейн Бейке р: 1378 .0 Том Смит : 123.22 Новый остаток на сче те Джона Доу : 4434 .34
Глава 18. Пакет java.uЩ часть 1. CoHections Fra mework 6'5 Следует особо заметить, что , подобно отображению, класс HashtaЫe не под­ держивает итерато ры непосредственно . Та к, в приведенной выше программе для отображения содержимого объекта balance используется перечисление. Те м не менее можно получ ить представления хеш-таблицы в виде множеств, которые допускают применение итерато ров . Для этого досrаточно воспользоваться одним и з методов представления в виде коллекций, о пределенных в интерфейсе Мар, на­ пр имер, методом entrySe t ( ) или keySet ().Так, м ожно получить представление всех ключей в виде множества и перебрать его , используя итератор или организо­ вав усовершенствованный цикл for в стиле for each. Та кой прием демонстриру­ ется в приведенно м ниже переделанном варианте предыдушей программы. 11 Прим енить ите раторы в м есте с кл ассом HashtaЫe import java .util .*; class HTDerno2 { puЫic static void rna in ( String args [] ) { HashtaЫe<String, DouЫ e> balance = new HashtaЫe<String, DouЫ e> (); String str; douЫe bal ; balance .put ( "Джoн Доу" , 3434.34) ; balance .put ("Toм С мит ", 123 .22) ; Ьаl аnсе .рut ("Джейн Б ейке р" , 1378 .00) ; balance .put ( "Toд Холл", 99 .22) ; balance .put ("Paльф Смит ", -19 .08) ; 11 Вывести все сч ета в хеш-таблице . Сначала получить 11 предс та вление все х ключ ей в виде множе ства Set<String> set = bal ance .keySet (); 11 получи ть итератор Iterator<String> itr wh ile (itr . hasNext () ) set . iterator () ; str = itr. next(); Systern.out . println (str + "· " + balance .get (str) ); Systern. out . println (); 11 вне сти сумму 1000 на счет Джона Доу bal = balance .get ( "Джoн Доу"); balance .put ( "Джo н Доу", bal +lOOO); Systern. out .println ( "Hoвый остаток на счете Джона Доу : " + balance .get ("Джoн Доу" ) ); Класс Properties Класс Properties является производным от класса HashtaЫe. Он служит для поддержки списков значений, в кото рых ключами и значениями являются объекты типа String. Класс Prope rties испол ьаустся в ряде других классовjа"· а .
61.6 Часть 11. Библиотека Java Та к, 11ри 1юлу•1с11ии :иычений 11еремен11ых окружения выаывастся метод System. getPr·ope гt ies ( ) , который во:шращаст тип объекта. Несмотря на то что сам класс Pгop erties нс является обобщенным, некото рые его методы объя вля ются как обобщенные. В классе Properties определяется сл едующая переменная :-.каемпляра: Proper ties defaults ; �-)т;� 11ереме1111ая содержит п1исок свойств 1ю умолчанию, связанных с объе ктом тип;� Properties. В кл ассе Prop erties онре;\сляются следующие конструкторы: Properties () Properties (Properties свойство _ по_унолчанию) В первой форме конструкто ра создастся об-ьект типа Р r o pe r t i е s, не имеющий значений 110 умолчанию. А во второй форме со:щается объt.· кт, испош,зующий за· данное свойство_по _умол ча нию дл я установки своих аначений по умолчанию. В обоих случаях с1111сок свойств пуст. Помимо методов, н;�следуемых классом Properties от класса HashtaЫe, в это м классе 011ре;\еля ются свои методы, перечисленные в т<1. бл . 18.24 . В клас· се Prope rties имеется также один не рекомендованный к употреблению метод save ().Этот метод был за менен методом store (), 11оскол ьку он нс обрабатывал 0111ибки должным образом. Та бл ица 18.21.. Методы из класса Prope rties Метод String getproperty (String ключ) String getproperty (String ключ , String со ойство _по_умолч тшю ) void list ( Prin ts tream поток_въиюО О ) void list ( PrintJJriter поток_ вывода) void load (Inputstreaш. поток_ввода) throws IOExoeption void load (�r поток_ввода) thrcnrs IOExoeption void loadFro.XМL ( InputStreaa поток_ ввода) throws IOException , InvalidPropertiesForsatвzception En1D1&ration< ?> propertylf-s () Описание Возвращает значение, связан ное с зада нным ключам.Ес.1 1 и же задан ный ключ (rrсуrствует как в текущем списке, так и в сниске свойств по умол· чанию, то во;�вращается пустое значение null Возвращает значение, связанное с :щданным ключам. Если же задан ный ключ m·сугствует как в текущем списке, так и в списке свойств по умо.л· чанию, то возвращается указанное свойапво_rю _ умолчанию Направляет список свойств в поток вы вода, с ко­ торым связан указан ный поток_вывода Направляет список свойств в поток вывода, с ко­ торым связан указанный поток_вывода Вводит список свойств из потока ввода, с кото­ рым связан указанный поток_ ввода Вводит список свойств из потока ввода, с кото­ рым связан указанный nоток_ввода Загружает список свойств из ХМL-документа, с которым связан указанный поток_ввода Возвращает перечисление ключей. В него включакr r ся также ключи, находящиеся в спи­ ске свойств по умолчанию
Глав а 18. Пакет java.utiL, часть 1. CoLLections Framewo rk 647 Метод OЬject setProperty (String ключ , Strinq значение) void store ( OUtputStream поток_ 8Ы8ода , Strinq описание) throws IOExcep tion void store (Wri ter поток_вьюода , Strinq описание) throws IOException void storeToXМL (OUtputS treaш поток_ВЪU1ода 1 Strinq описание) throws IOException void storeToXМL ( OUtputStream поток_ВЪU1ода 1 Strinq описание , Strinq кодировка) Set<Strinq> stringPropertyN aшes () Окон'tание табл. 18. 24 Описание Связывает заданные значение и К!lюч. Возвращает предыдущее значение, связанное с заданным Кl l ЮЧОМ, а есл и такая связь отсут­ ствует - пустое значение null После вывода символ ьной строки, указанной как описание, список свойств вы водится в по­ ток, с которым связан указанный поток_вьюода После вывода символ ьной строки , указанной как описание, список свойств вы водится в по­ ток, с которы м связан указанный поток_8Ьlвода После вывода символ ьной строки, указан- ной как описание, список свойств вы водится в ХМL-документ, с которым связан указанный поток_8Ьlвода Список свойств и строка , указанная как описание, выводятся в документ XMI, , с ко то­ рым связан указан ный поток_вывода, для чего применяется заданная кодиротш символов Возвращает множество ключей Класс Properties удо бен, в частности, те м, что он позволяет указывать зна­ чения по умолчанию, кото рые возвращаются, если ни одно из значений не связано с определенным кл ючом. Например, значение по умолчанию может быть указано вместе с ключом в методе ge t Property () следующим образом: ge tPrope rty ("имя" , "значение_п о_умолчанию" ).Есл и ключ "имя " не найден, то возвращается "значение_по_умолчанию". Создавая объект класса Properties, м ожно передать ему другой экземпляр класса Р roper t i е s в качестве списка свойств п о умолчанию для нового экземпляра. Та к, если метод get Property ( "fo o" ) вы­ зывается для заданного объе кта типа Properties и кл юч " foo " не существует, то его поиск осуществляется в его объекте типа Properties по умолчанию. Это до­ пускает наличие произвольного числа уровней вложения свойств по умолчанию. В приведенном ниже примере программы демонстрируется применение кл асса P r ope r ties. В этой программе создается список свойств , где ключами являются наз вания штатов, а значениями - названия их столиц. Обратите внимание на то , что в попытку найти столицу штата Флорида включается значt�ние по умолчанию. 11 Продемон с т р ир овать п риме н е ние списка свойств import java . util .*; class PropDemo { puЫic static void ma in (String args [] ) { Prope rties capitals = new Prope rties (); сар itаls . р ut ( "Иллинойс", "Спрингфилд " ); capi tals .put ( "Ми ссури " , "Джеффе р сон-Сити ") ; capital s .put ( "Вашингтон ", "Олимпия ") ; сар itаls . р ut ("Калифор ния", "Сакраменто") ; capitals .put ( "Инди ана ", "Индианаполис" ) ;
61.8 Часть 11. Библиоте ка Java // получить мн оже ство ключ ей Set< ?> states = capi t als . keySet (); // показать все штаты и их столицы for (Obj ect пате : states ) Systern. out . priпtlп ("Cтoлицa штата " + пате + " - " + capi tal s.getPrope rty ((String ) пarne ) + ".") ; Systern.out . priпtlп (); 11 найти шт ат , отсутствующий в списке , указав значения , // выбираемые по умолч анию Striпg str = capital s.getPrope r ty ("Флopидa ", "не найдена" ) ; Systern.out . priпtlп ( "Cтoлицa штата Флорида "+str + "."); Ниже приведен резул ьтат, выводимый да нной программой . Штат Флорида отсутствует в списке, поэтому выбирается значение по умолчанию (в данном слу· чае - символьная строка "не найдена "). Столица штата Ми ссури - Джефферсон-Сити. Столица штата Иллинойс - Сприн гфилд . Столица штата Индиана - Индианаполис . Столица штата Калифорния - Сакраменто . Столица штата Вашин гтон - Олимпи я. Столица штата Флорида не н айде на . Ук азывать значение по умолчанию при вызове метода get Prope rty () вполне допустимо, как показано в предыдущем примере, тем не менее для большинства приложений списков свойств имеется лучший способ обращения со значениями по умолчанию. Список свойств по умолчанию удобнее задавать при создании объ­ екта класса Properties. Если требуемый ключ не найден в гл авном списке, его поиск осуществляется в списке по умолчанию. В качестве примера ниже приведен немного измененный вариант предыдущей программы, где применяется список штатов по ум олчанию. Те перь, ко гда запрашивается столица штата Флорида, она обнаруж ивается в списке по умолчанию. // Использовать список свойств по умолча нию irnp ort java .util .*; class PropDernoDe f { puЫ ic static void rna iп (String arg s[] ) { Properties de fList = пеw Prope rties (); de fLis t.put ( "Флopидa ", "Тэлесси" ) ; de fList.put ("B иcкoнcин ", "Мэдисон" ); Properties capitals = пеw Prope rties (defList); сарi tаl s.рut ( "Иллинойс ", "Спрингфилд" ); capitals .put ("Миccypи ", "Джефферсон-Сити" ) ; сарitаls .рut ("В ашингтон ", "Олимпия" ); сарitаls .рut ("К алифорния ", "Сакраменто" ); capitals .put ("И ндиана" , "Индианаполис" ) ; // получить мн оже ство ключ ей Set<?> states = capitals . keySet (); // вывести все штаты и их столицы for (Obj ect пате : states ) Systern.out . priпtlп("Cтoлицa штата " + narne + " - " + capital s.getPrope rty ((Striпg ) пarne ) + ".") ;
Гл ава 18. Пакет java.utiL, часть \. CoLLections Framework 61.9 System.out . println () ; !/ Теперь штат Фл орида буде т найден в списке по умолчанию St ring str = capi tal s.get P roperty ( "Флopидa") ; System.out .println ("C тoлицa Флориды - " + str + ".") ; Применение методов store () и load() Одна и:� наиболее удоб ных особе1111остей класса Properties состоит в то м, что дан ные, содержа щиеся в объе кте кл асса Properties, могут быть легко сохранены и за 1·ружс 11ы с диска метода ми store () и load (). В любой момент объект класса Properties можно вынести в ното к или ввеп·и е го обратно и:\ пото ка. Это дела­ ет списки пюйств особс11110 у;1об11ыми дл я реализации простых баз щ1 1шых. Та к, в п риведенном ниже 11римере нрограммы список <: войств используетс я дл я соз­ дания простого телефошюго с11равоч11ика, хранящего имена и номера телефо­ нов або11е11тов. Чтоб ы 11аiiти номер абонента , следует ввести его имя. В данной программе метод ы store () и load () применяются дл я сохранения и получения списка обрат110. При вы1юлне1ши этой 11рограммы сначала предп ринимается по­ пытка загру:нпъ сш1со1< и:3 фаiiла phoneboo k.da t. Если этот файл суще<· т вует, он загруж<tетс я. Зате м ll сrшсок мо1rт быть добавлены новые элементы . В этом случае новы й список сохра няется по :1авсршс11ии программы. Обратите внимание, на­ скол ько ко мш1кт1 1ый ко;\ требуется дл я рсшш зации небольшой , но в rюл нс работо­ rпособ1юй авто мати:зирова111юii телефонной 1<ниги. /* Простая база данных т елефонных номер ов , построенная на основе списков свойств . */ import jav a.io. *; impo rt jav a.util .*; class Phonebook { puЫ ic stat ic void ma in (String args [)) throws IOException ( Prope rties ht = new Prope rties (); BufferedReade r br = new Bu fferedReader ( new InputStreamReader (System. in) ); String паmе , nшnt>e r; Fi leinput Stream fin = nul l; boolean changed = false ; / / Попыта �ъся о·гкрыт ь файл phonebo ok . dat try { fin = new Fi leinputSt ream ("p honebook.da t") ; са tc!-1 ( Fi leNot FouпdExcept ion е) { // игнорировать отсутствующий файл /* Если телефонная книга уже существует, загрузить существующи е телефонные номера . */ try ( if(fin != 11ull) ( ht.load(fiп); fin.close (); catch (IOExcept ion е)
650 Часть 11. Б иблиотека Java Systeт. out .println ( "Oшибкa чтения файла.") ; } 11 разрешить поль зователю вводить новые имена и 11 номера телефонов абонентов do{ Systeт. out . println ("B вeдитe имя " + " ('выход ' для завершения ): ") ; пате = br . readLine () ; if (naтe .equals ("в ыxoд " )) continue ; Systeт.out . println ( "Bвeдитe номер : ") ; numЬe r = br . readLine (); ht .put (naтe , numЬ er) ; changed = true ; } wh ile (!naтe .equals ("в ыx oд" )); 11 сох ранить телефонную книгу , если она изме нилась if(changed ) { } Fi l eOutputStreaт fout = new FileOutputStreaт ("p honebook .dat" ); ht . store ( fout , "Телефонная книга") ; fout .close () ; 11 искать номер по име ни абонента do{ Systeт. out . println ("B вeдитe имя для поиска " + " ( 'выход ' для завершения) : "); пате = br . readLine (); if (naтe .equals ("в ыx oд" )) continue; numЬe r = (String ) ht .get (naтe) ; Systeт. out . println (numЬ er) ; wh ile (!naтe .equals ( "выx oд" )); \ Заключ ит� ьные соображения по поводу коллекций Каркас коллекций Collections Framework предоставляет эффективный ряд тща· тельно спроектированных решений для некоторых наиболее часто встречающих· ся задач программирования, связанных с сохранением и извлечением дан ных. Не следует, однако, заб ывать, что коллекции не предназначены только для "крупных задач" вроде ко рпоративных баз данных, списков почтовых рассылок или систе м учета запасов. Они не менее эффективны и для решения мелких задач. Например, коллекция типа TreeMap может отлично подойти для хранения струкrуры катало­ гов или ряда файлов. А класс TreeSet может оказаться очень уд обным для хр ане· ния информации по управлению проектом. В общем, виды задач, решая которые средствами коллекций можно получить существенный выигрыш, ограничи ва· ются только вашим воображением. И наконец, работая с коллекциям, не следу· ет забывать и о потоках данных, которые теперь интегрированы с коллекциями. Подробнее о новом прикладном программном интерфейсе API потоков ввода-вы· вода речь пойдет в главе 29.
19 Пакет jav:a . util, часть 11. Прочие служебн ые классы В этой гл аве обсужде ние пакета j ava . util продолжается рассмотрением классов и интерфейсов, не входя щих в состав каркаса коллекций Collectioпs Framework. К их числу относятся классы, разбивающие символьные строки на лек­ семы, оперирующие датами , генерирующие случайные числа , связывающие ресур­ сы и наблюдающие за событиями. Кроме того, описываются классы Fo rmatter и Scanne r, упрощающие чтение и запись форматированных данных , а также но­ вый класс Op tional, предоставляющий изящный выход из положения, когда зна­ чение просто отсутствует. И наконец, вкратце упоминаются подпакеты , входящие в состав пакета java.util. Особый интерес представляет подпакет j ava . ut il . function, в котором определяется ряд функциональных интерфейсов. Класс StringTokenizer Обработка текста зачастую предполагает синтаксический анализ или разбор форматированной входной строки. Синтакси'Чf!ский анализ подразумевает разделе­ ние текста на ряд отдел ьных частей, так называемых лексем, которые способны пе­ редать в определенной последовательности некоторое семантическое значение. Класс StringTokenizer обеспечивает первую стадию процесса синтаксического анализа, и поэтому его зачастую называют лекси-ческuм аналшатором или просто ска· нером. Этот класс реализует интерфейс Enume ration. Та ким образом, задав вход­ ную строку, средствами кл асса StringTokeni zer можно перечислить содержащи­ еся в ней отдельные лексемы. Чтобы воспользоваться классом StringTo ken izer, следует указать входную и символьную строку, содержащую разделители. Разделители - это символы , раз­ деляющие лексе мы. Каждый символ в символьной строке разделителей рассма­ тривается ка к допустимый разделитель. Например, символьная строка 11 , ; : 11 уста­ навливает в качестве разделителей запятую, точку с запятой и двоеточие. Набор разделителей, выбираемых по умолчанию, состоит из пробельных символо в: про­ бела, знака табуля ции, перевода строки и возврата каретки. Ниже приведены конструкторы класса StringTokenizer. StringТokeni zer (Strinq строка ) StringТokeni zer (Strinq строка , Strinq paэдe.IDltTeJDl l ) StringТokeni zer (Strinq строка , Strinq pasд8.1DltT8J1 11 , boolean раэделятел• _ как _ лежсена)
652 Часть 11. Библиотека Java Во всех трех формах конструктора параметр строка обозначает разделяе­ мую на лексемы символьную строку. В первой форме используются разделители по умолчанию, во второй и в третьей формах параметр ра зделители обознача· ет символьную строку, задающую разделител и. В третьей форме разделители воз· вращаются в качестве отдельных лексем при синтаксическом анализе символьной строки, если параметр разделитель_ ка к _ лексема принимает логическое значе­ ние true. В противном случае разделители не возвращаются. Разделители не воз­ вращаются и в первых двух формах конструктора данного класса. Как только объект класса StringTokenizer будет создан, можно вызвать его метод nextToken (), чтобы извлечь последовательные лексемы. Метод hasMore­ Tokens () возвращает логическое значение true до тех пор, пока для извлечен ия еще имеются лексемы. А поскольку класс StringTo kenizer реализует интерфейс Enumeration, то его методы hasMoreEleme nts () и nextEleme nt () также реали· зованы и действуют точно так же, как и методы hasMoreTokens () и nextToken () соответственно. Методы из класса StringTokeni zer перечислены в табл. 19. 1 . Та блица 19.1. Методы мэ класса StrinqТokeni zer Метод int countTokena () Ьoolean hasМoreElements () Ьoolean hasМoreTokens () OЬject nextEleamt () Strinq nextToken () Strinq nextToken (Strinq раздмители) Описание Исполь.зуя текущий набор разделителей, определяет коли· чество лексем, которые осrалось разобрать и во3вратить в качестве результата Возвращает логическое 3начение true, если в символьной строке остается одна лексема или больше, а иначе -логиче­ ское значение fal.se Возвращает логическое значение true, если в символьной строке остается одна лексема или больше, а иначе -логиче­ ское 3начение false Возвращает следующую лексему в виде объекта типа CЬject Возвращает следующую лексему в виде объекта типа String Возвращает следующую лексему в виде объекта типа OЬject и .задает символьную строку ра:щелителей в соответствии со значением параметра разде.литми Ниже приведен пример программы, в которой объект класса Stri ngTokeni zer создается для синтаксического анализа Пар "ключ-значение". Последовательный ряд пар "ключ-значение" разделяется точкой с запятой. // Продемон стрировать применение кл асса StrinqТokeni zer import java.util . StringTokeni zer; class STDemo ( static String in = "Название= Jаvа . Полное руководс тво ;" + "Автор=Шил дт ;" + "Издательство=МсGrаw-Нill;" + "Авторс кое пр аво=2 014"; puЫ ic static vo id ma in (String args [J ) { StringTokeni zer st = new StringTokeni zer (in, "=; "); whi le ( st . hasMoreTokens () ) { String key st . nextToken (); St ring val = st . nextToken () ;
Глава 1 9. Пакет java.utiL, часть 11. П роч ие служебные классы 653 System.out . println (key + "\t" + val) ; Эта программа выводит следующий результат: Назв ание Java . Полное руко водство Автор Шилдт Издательство McGraw-Hill Авторское право 2014 Кл а сс BitSet Этот класс служит для создания специального типа массива, содержащего би­ товые значения в виде логических значений. Размер массива типа Bi tSet можно, если требуется, увеличивать. Благодаря этому он становится похожим на битовый вектор . Ниже приведены конструкторы к.Ласса BitSet. BitSet () BitSet ( int раs.мер) В первой форме конструктора создается объект по умолчанию. А вторая форма позволяет указать начальный размер (т.е. количество битов, которые можно со­ хранить) . Все биты инициируются логическими значениями false. В классе BitSet определяются методы, перечисленные в табл. 19 .2. Табл ица 19.2 . Методы иэ класса Bitset Метод Описание void and (BitSet множество_битов) Выполняет логическую операцию И над содержи· мым вызывающего объекта типа BitSet и заданного множеств а _битов . Результат размещается в вызыва­ ющем объекте void andNot (BitSet множество_ битов) int cardinali ty () void clear () vo id clear (int индекс) void clear (int началъный_индекс , int конечный_индекс) OЬject clone () Ьo o lean equals (OЬject множество_ битов) void flip (int ишkкс) Для каждого бита, установленного в заданном множе;ст ве _битов, сбрасывается соответствующий бит в вызывающем объекте типа Bi tSet Возвращает количество установленных битов в вы­ зывающем объекте Уст анавливает все биты в нуль Уст анавливает в нуль бит по указанному U1«Эекq Уст анавливает в нуль биты от позиции начальный_ uндексдо позиции конечный_индекс- 1 Дублирует вызывающий объект Возвращает логическое значение true, если вызы­ вающее множество битов равнозначно заданному множеству_битов, а иначе -логическое значение false Обращает бит по указанному индекq
65' Ч а сть 11. Б иблиоте ка Java Метод void :fli.p (int начальный_инiJекс , int КfJНеЧНf Jt й_инiJекс) Ьoolean qet (int индекс) BitSet qet (int начальный_индекс, int конечнь�й_индекс) int haвhCocle () Ьoolean interвectв (BitSet мнwкество_битов) Ьo o lean iвl:Japty () int lenqth () int nextClearBit(int НФШЛЬный_ индекс) int nextSetвit(int начальный_ индекс) void or (Bitset множеств о _битов) int previouвClearBit(int НФШЛЬный_индекс) int previouвSetвit (int началЫ1Ь 1й _индекс) void веt (int инде�а:) void set (int индекс, Ьoolean v) Прод олжение табл. 19.2 Описание Обращает биты от позиции нача!lЬН Ьl й_индексдо по­ зиции конечный_uпдекс- 1 Возвращает текущее значение бита по указанному � Возвращает объект типа BitSet, состоящий из бит от позиции нача!lЬН Ьlй _инде�а:до позиции �­ индекс-1. Вызывающий объект не изменяется Возвращает хеш·код вызывающего объекта Возвращает логическое значение true, если уста­ новлена хотя бы одна пара соответствующих битов в вызывающем объекте и заданном множеаnв е _бuтов Возвращает логическое значение true, если все биты вызывающего объекта сброшены Возвращает количество битов, требующихся для хранения содержимого вызывающего объекта типа BitSet. Это значение определяется по положе­ нию последнего установленного бита Возвращает позицию следующего сброшенного бита (имеющего логическое значение falвe) , начиная с указанной позиции начальнь�й_индекс Возвращает позицию следующего установленного бита (имеющего логическое значение true ), начи­ ная с указанной позиции начальный_инitекс. Если ни один из битов не установлен, возвращается значе­ ние -1 Выполняет логическую операцию ИЛИ над содер­ жимым вызывающего объекта класса Bi tSet и за­ данного JIOIOЖf! Cl1/8Q _ бumoв. Результат размещается в вызывающем объекте Возвращает позицию следующего сброшенного бита (имеющего логическое значение falae), рас­ положенную до заданной позиции НilЧО.4Ь Ш>lil _индеlа: включительно. Если сброшенный бит не найден, возвращается значение -1 Возвращает позицию следующего установленного бита (имеющего логическое значение true), рас­ положенную до заданной позиции НJl'IШIЬ НЬlй _индеlа: включительно. Если устаноаленный бит не найден, возвращается значение -1 Уст анааливает бит по указанному индексу Уст анавливает бит по указанному � равным за­ данному значению параметра v. Если этот параметр принимает логическое значение true, то бит уста­ навливается, а если он принимает логическое значе­ ние false, то бит сбрасывается
Глава 19. Пакет java.utiL, часть 11. Прочие служебные классы 655 Метод void set (int Нl lЧO.l lЫfbl й_uнiJera: , int конгчный_индехс) void set (int НDЧ41 1Ыf&1 й_инiJекс, int конечный_индекс , Ьo o lean v) int size () IntStreaia streua О Ьуtе[] toВyteArray() lonq [) toLongArray () Strinq toString () static BitSet valueOf (Ьyt.e[] v) static ВitSet valueOf (Вytduffer v) static BitSet valueOf (lonq []v) static BitSet valueOf (LonqBuffer v) void xor (BitSet МШJЖество_битов) Окон-чание табл. 19. 2 Описание Уст анавливает биты ar позиции начальны й _индекt: ДО ПОЗИЦИИ конечный_индекс- 1 Уст анавливает биты ar позиции на'lаJlf >НЬl й_индекс до позиции конечный_uндекс- 1 равными заданному значению параметра v. Если этаr параметр прини­ мает логическое значение true, то биты устанавли­ ваются, а если он принимает логическое значение fal -. то биты сбрасываются Возвращает количество битов в вызывающем объ­ екте типа Bi tSet Возвращает паrок ввода-вывода, содержащий пози­ ции всех установленных битов: от младшего до стар­ шего (добавлен в версииJDК 8) Возвращает массив типа Ьуtе, который содержит вызывающий объект типа BitSet Возвращает массив типа lonq, кmорый содержит вызывающий объект типа BitSet Возвращает строковый эквивалент вызывающего объекта типа Вitset Возвращает объект типа Bi tSet, содержащий биты из указанного массива v Возвращает объект типа Bi tset, содержащий биты из указанного буфера v Возвращает объект типа Bi tSet, содержащий биты из указанного массива v Возвращает объект типа Bi tSet, содержащий биты из указанного буфера v Выполняет логическую операцию исключающее ИЛИ над содержимым вызывающего объекта типа BitSet и заданного .мньжесm ва _ битов. Результат раз­ мещается в вызывающем объекте Ниже приведен пример программы, демонстрирующий применение IOiacca BitSet. 1 1 Продемон стрировать приме нение класса BitSet import java .util . BitSet; class BitSetDemo { puЫ ic static void main (String arg s[] ) BitSet bitsl new BitSet (lб) ; BitSet bitэ2 = new BitSet (lб) ; 11 ус тановить нек отор ые б иты for (int i=O ; i<lб; i++) { if( (i%2) 0) bitsl .set (i) ; if ( (i%5) != 0) Ьits2 .se t(i) ;
656 Часть 11. Библиотека Java System.out . println ( "Начальная комоинация битов в оОъе кте Ьitsl : ") ; System. out . println (Ьitsl ); System. out . println ( * "\n Начальная комбинация битов в объе кте Ьits2 : ") ; System.out . println (Ьits2) ; 11 выполнить логичес кую о перацию И над битами Ьits2 . and (bitsl) ; System.out .println ("\nЬits2 AND Ьitsl : ") ; System.out .println (Ьits2 ) ; 11 выполнить логическую операцию ИЛИ над битами Ьi ts2 .or (Ьitsl) ; System. out . println ("\nbits2 OR bitsl: ") ; System. out .println (Ьits2 ) ; 11 выполни ть логическую операцию исключ ающе е ИЛИ над Оитами Ьits2 .xor (Ьitsl) ; System.out . println ("\nЬits2 XOR Ьitsl : ") ; System.out . println (bits2 ); Результат выполнения этой программы приведен ниже . Когда метод toString () преобразует объект типа BitSet в его строковый эквивалент, каждый установлен· ный бит представлен номером своей позиции. Сброшенные биты не показаны. Начальная комОинация битов в объе кте Ьitsl : {О,2,4,6,8,10,12,14} Начальная комОинация битов в объе кте bits2 : (1,2,3,4,6,7,8,9,11,12,13,14} Ьits2 AND Ьitsl : (2,4,6,8,12,14} Ьits2 OR Ьitsl : /0,2,4,6,8,10,12,14} Ьits2 XOR Ьitsl: {} Клacc ы Optional,OptionalDouЬle, Optionalint и OptionalLong ВверсииJDК 8 внедрены классы Ор tiоnаlDоuЫе, Op t ionallnt и Op tionalLong, предосга вля ющие изящный выход из положения в тех случаях, когда значение может отсутствовать. В прошлом для того чтобы обозначить отсутствие значения, обычно использовалось пустое значение null, но это могло привести к исключениям в связи с пустыми указателями при попытке разыменовать пустую ссылку. Поэтому во избе­ жание подобных исключений требовались часть1е проверки на наличие пустого зна· чения nul 1. Классы, рассматриваемые в этом разделе, предоставляют более удобный способ разрешить данное затруднение.
Глава 19. П акет java.utiL, часть 11. Прочие служебные классы 657 Первым и самым общим из них является класс Op t ional. Поэтому именно е му и уделяется основное внимание в этом разделе. Ниже приведена общая форма его объявления. class Optional<T> Здесь Т обозначает тип сохраняемого значения. Следует иметь в виду, что экзем­ пляр класса Op tional может содержать значение типа т или быть пусты м. Иными словами, объект типа Op tional совсем не обязательно должен содержать значе­ н и е. У класса Op tional вообще отсугствуют конструкторы, но в нем определяется ряд методов для обращения с объектами данного класса. С их помощью можно , н апри мер, определить наличие значения , получить значение, если оно присуг­ ствует, а если оно отсугствует - значение по умолчанию, и даже создать значение ти па Opt ion al. Методы, определенные в классе Op tional, приведены в табл. 19.3 . Таблица 19.З. Методы иэ кпасса Optional Метод static <Т> Optiona1<Т> upty () Ьo o lean equals (Сlфюt нrобR3ате.лъный) Optiona1<Т> fil ter ( Predicate<? superТ>.)'CIUIO Ш1 ) U Optiona1<0> flatмap (Function<? super Т, Optiona1<0>> uтобража ющая _фующия> т get() int hashCode () void ifPresent (ConsW1 1& r<? superТ>фуюсция> Ьo o lean isPresent () u Optiona1<0> map (Function<? superТ,?extendaU>> uтобража ющая _ фующия) Описание Возвращает объект, для которого метод isPresent () возвращает логическое значение fa1se Возвращает логическое значение true, если вызыва­ ющий объект равен объекту, определяемому параме­ тром необязате.льн6lй, а иначе - логическое значение false Возвращает зкземпляр класса Optional, соде ржащий такое же значение, как и у вызывающего объекта, если это значение удовлетворяет заданному условию, а иначе - пустой объект П рименяет заданную отображлющую_фуюсцию к вы­ зывающему объекту, если этот объект содержит зна­ чение, и возвращает полученный результат, а иначе - пустой объект Возвращает значение в вызывающем объекте. Но если в нем отсутствует значение, то генерируется ис­ ключение типа NоSuсhЕ18D18ntЕхоерtiоn Возвращает хеш-код вызывающего объекта Вызывает заданную фующию, если в вызывающем объекте п рисутствует значение, п ередавая этот объ­ ект вызываемой фующии. В отсутствие значения ни­ каких действий не производится Возвращает логическое значение true, если вызы­ вающий объект содержит значение, а в отсутствие значения - логическое значение f'а1 ве П рименяет заданную ото6раж. а юцrую_фующию к вы­ зывающему объекту, если этот объект содержит зна­ чение, и возвращает получе нный результат, а иначе - пуст ой объект
658 Часть 11. Библиоте ка Java Метод static <Т> Optional<Т> of(Т значе�ше) static <Т> Optional<Т> ofNullaЬle (Т значение) Т orElse (Т определя юще е _ значение) Т orElseGet (Sup p lier<? extend.8 Т> фyнкцUR_OOl l)"Um UЯ) <Х extends ТhrowaЬle> Т orElseТhrow (Sup p lier<? extendsХ>фунqия_шж.1 1 ючения) throws Х extends ТhrowaЬle Stri.Dg toStri.nq () Окончание табя. 19.3 Описание Создает экземпляр класса Optional, содержащий за· данное зна'tеНие и возвращает полученный результат. Заданное значение не должно быть пустым (null) Создает экземпляр класса Optional, содержащий за· данное значение и возвращает полученный результат. Но если значение задано пустым, то возвращается пу· стой экземпляр класса Optional Если вызывающий объе�сr содержит значение, то возвращается именно оно, а иначе - заданное определя юще е _значение Если вызывающий объе�сr содержит значение , то возвращается именно оно, а иначе - значение, полу· чаемое из заданной функции_тмучения Возвращает значение, содержащееся в вызывающем объе�сrе. Но если такое значение отсугствует, то за­ данная ifiункция_tиЖ.!IЮЧJ!НWI генерирует исключение Возвращает строковое представление вызывающего объекта Понять принцип действия класса Op tional луч ше всего на конкретном при· мере, в котором демонстрируется применение основных методов этого класса. Основными в классе Optional являются классы isPresent ( ) и get ().Для того чтобы выяснить, присутствует ли в экземпляре класса Optional значение, доста· то чно вызвать метод isPresent (). Если значение присутствует, этот метод воз­ вращает логическое значение true , а иначе - логическое значение false. Итак, если значение присутствует в экземпляре класса Op tion al, его можно получить, вызвав метод get (). Но если вызвать метод get ( ) для объекта, не содержащего значение, то генерируется исключение типа NoSuchElementException. И ме нно поэтому следует сначала убедиться в наличии значения , а затем вызывать метод get ( ) для объекта типа Op tional. Безусловно, необходимость вызывать два метода для получения искомого зн а· чения влечет за собой дополнительные издержки на доступ к этому значению. Правда, в классе Op t ional определяются методы, сочетающие в себе фун кции про­ верки и извлечения искомого значения. Одним из них является метод orElse (). Та к, если объект, для которого этот метод вызывается, содержит значение, то воз· вращается именно оно, а иначе - значение по умолчанию. Какупоминалось выше, в классе Ор t i ona 1 конструкторы не определены. Вместо этого для создания экземпляра данного класса вызываются его методы. Например. создать экземпляр класса Op tional, содержащий конкретное значение, можно, вызвав метод of ().А для того чтобы создать экземпляр класса Op tional, не содер­ жащий конкретное значение, достаточно вызвать метод empty (). В следующем примере программы демонстрируется применение упомянуrых выше методов:
Гл ава 19. Пакет java.uШ, часть 11. П рочие служебные кл ассы 659 / I Продемонстриров ать приме нение несколь ких / / ме тодов из обобщенного класса Optional<Т> import java.util . *; class OptionalDemo { puЫic static void ma in (St ring args[J ) { Optional<String> noVa l = Op tional .empty() ; Op tional<String> hasVal = Op tional .of("AВCDEFG" ); if(noVa l .isPresent ()) System. out .println ("He подлежит выводу" ); else System. out . println ( "Oбъe кт noVal не содержит значени е") ; if (hasVal .isPresent () ) System.out . priпtln ( "Объе кт hasVal содержит следующую строку : "+hasVal .get () ); St ring de fStr = noVal .orElse ( "Cтpoкa по умолч анию" ); System. out . println (defStr) ; Ниже приведен результат, выводимый данной программой. Объе кт noVa l не содержит значе ние Объе к т hasVal содержит следующую строку : AВCDEFG Строка по умолчанию Как следует из приведенного выше результата выполнения данной программы, значение может бьrгь получено из объекта типа Op tional только в том случае, если он присугствует в нем . Этот элементарный механизм позволяет предотвра­ тить исключения , возникающие в классе Op tiona l в связи с пустыми указателями. Аналоmчным образом действуют классы Op tionalDouЫe, Op tionallnt и Op tionalLong, за исключением того что они специально предназначены для об­ работки значений типа douЫe, int и long соответст ве нно. И для этой цели вместо метода get () в них определены методы getAsDouЬle (), getAsI n t () и getAsLong ( ) соответст ве нно. Но в то же время в них не поддерживаются методы filter(), ofNullaЫe (),rnap () и fla tMap (). Класс Date Класс Da te инкапсулирует текущую дату и время. Прежде чем рассматривать класс Da te, следует заметить, что этот класс ощугимо изменился по сравнению с его первоначальным вариантом, определенным в версииjаvа 1.0 . Когда была вы­ пущена версияJava 1.1, многие функции исходного класса Da te были перемещены в классы Calenda r и Da te Forrna t и, как следствие, многие исходные методы клас­ са Date из версии Java 1.0 стали не рекомендованными к употреблению в новом коде , поэтому они здесь и не обсуждаются. В классе Da te поддерживаются следую­ щие конструкторы: Date () Date (long ЮU UDt сежунд)
660 Часть 11. Библ и оте ка Java Первый конструктор инициализирует объект те кущ ими датой и временем. А второй конструктор принимает один аргумент, обо:шачающий кол и ч ество мил ли секунд, прошедш их с полуночи 1 января 1970 1� Методы из кл асса Da te, ре· к омендо ванные к употреблению, переч ислены в табл . 19.4 . Класс Da te реализует также и нтерфейс ComparaЫe. Табл ица 19.,, Методы иэ кл асса Date, рекомендованные к употреблению М етод Ьoolean after (Date дата) Ьoolean Ьefore (Date дата) OЬject clone () int compareTo (Date дата) Описание Возвращает логическое значение true, если вызываю­ щий объект типа Date содержит более позднюю дату, чем указанная дата, а иначе - логическое значение false Возвращает логическое значение true, если вызы ва­ ющий объект типа Date содержит более раннюю дату, •1ем указанная дата, а иначе - логическое значение false Дублирует вызывающий объект типа Date Сравнивает значение вызывающе!'о объекта с указан­ ной датой. Возвращает нулевое значение, если срав- ниваемые значения равны; отрицательное значение, если вызывающий объект содержит более раннюю дату, чем указанная дата; или положительное значе­ ние, если вызывающий объект содержит более позд­ нюю дату Ьoolean equals (Object дата) Возвращает логическое значение true, если вызыва­ ющий объект типа Date содержит те же самые время и дату, что и указанная дата, а иначе - логическое зна­ чение false lonq qetTiшe () Возвращает количество миллисекунд, прошедших с полуночи 1 января 1970 г. in t hashCode ( ) Возвращает хеш-код вызывающего объекта void setTime (lonq время) Ус танавливает время и дату в соответствии с параме­ тром время, который обозначает количество миллисе­ кунд, прошедших с полуночи 1 января 1970 г. Strinq toS trinq ( ) Преобразует вызывающий объект типа Date в сим­ вольную строку и возвращает результат Как следует из табл. 19.4, новые средства класса Da te не позволяют получать составляющие даты и времени по отдел ьности . И как демонстрируется в приве­ де нном ниже примере программы, получить можно только дату и время в милли­ секундах или в строковом представлении по умолчанию, возвращаемом методом toString (). А для того чтобы получить более подробные сведения о времени и дате, следует воспользоваться средствами описываемого далее класса Calendar. 11 Вывести дату и время , исполь зуя только ме тоды из кл асса Date import java.util .Date;
Гл ава 19. П акет java.uШ, часть 11. П рочие служебные классы 661 cl ass DateDemo { puЫ ic static void ma in ( String args[] ) 11 создать объе кт типа Date Date date = new Date() ; 11 вывести дату и время ме т одом toString () System. o ut . println (date) ; 11 вывести количество миллисе кунд , прошедших 11 с 1 января 1970 г. по Гринвичу long ms ec = date . getTime (); System. o ut . println ("K oличecтвo миллисекунд , прошедших с " + "1 января 1970 г. по Гринвичу = "+ms e c) ; Ниже приведен пример выполнения данной программы. Sat Jan 01 10 :27:33 сsт 2011 Количество миллисе кунд , прошедших с 1 января 1970 г. по Грин вичу = 1293899253417 Кл асс Calendar Абстрактн ый класс Calendar предоставляет ряд методов, позволяющих преоб­ разовывать время в миллисекундах в целый ряд удо бных составляющих. К приме­ рам видов предоставляемой информации о дате и времени относятся год, месяц, день, часы, минуты и секунды. Назначение класса Calenda r - обеспечить своим подклассам конкретные функциональные возможности для интерпретации ин­ формации о дате и времени по их собственным правилам. Это еще одна особен­ ность библиотеки классов jаvа, которая позволяет писать программы, способные работат ь в разных интернациональных средах. Примером такого подкласса мо­ жет служить класс GregorianCa lendar. На заметку! Для инте рпретации даты и времен и в версии JDK 8 определен новый прикладной про­ граммный инте рфейс API , входя щий в соста в пакета j ava . t ime и рассматриваемый в гл а­ ве 30. Им можно пользоваться при разработке новых прикладных программ на Java. У класса Cal endar отсутствуют открытые конструкторы. В этом классе опре­ деляется ряд защищенных переменных экземпляра. В частности, переменная areFieldsSet содержит значение типа boolean, обозначающее, были ли уста­ новлены составляющие времени. Переменная fie lds содержит массив целочис­ ленных значений, обозначающих составляющие времени, а переменная isSet - массив значений ти па boolean, обозначающих, была ли установлена конкретная составляющая времени. Переменная time типа long содержит текущее время для данного объекта. И наконец, переменная isTime Set содержит значение типа boolean, указывающее на то , что б ыл о установлено текущее время . Некоторые наиболее употребительные методы из класса Calendar приведены втабл . 19.5 .
662 Часть 11. Библ иоте ка Java Таблица 19.5. Наиболее употребительные методы иэ класса Calendar Метод aЬstract void acld(int rocma8JI ЯIO ЩQЯ, int значение) Ьoolean after (OЬject календарный_обыкт) Ьoolean Ьefore (OЬject �й_обыкт) final void clear () final void clear (int rocma8JI ЯIOЩQЯ ) OЬject clone () Ьoolean equal.s (OЬject �иuендар н ый_о6&ект) int get (int rшлендар ное _twЛе) atatic Locale [) getAvailaЬleLocalea() atatic Calendar getinstance () static C&l8Ddar getinstance ( Ti.8eZone tU UOвOU _IIOl lC ) static Calendar getinstance (Locale реZШ1 1U1А ЬНШ1_настроiЛ tи ) static C&lendar getinstance (Til l& Zone 1fl lCOflOй _ пояс, Locale peZUOНlJ Jlt. НЪU!_ настройх u ) final Date getTU. . () Описание Складывает заданное значгние с указанной сосmавля ющей даты ил и времени. Чтобы отнять за­ данное ЗШJ'lеНие, его следует указать отрицательным. В качестве параметра соапавляющая может быть указа· но одно из полей, определенных в классе Calendar, н.апример C&lendar . BOOR Возвращает логическое значение true, если вызы­ вающий объект типа Calendar содержит более позд­ нюю дату, чем указанный rw.ленil а рm. . й_обыкт, а ина· че -логическое значение falae Возвращает логическое значение true, если вызыва· ющий объект типа Calendar содержит более раннюю дату, чем указанный rw.ленil а рный_о6 6ект , а иначе -л� ги ческое значение false Обнуляет все составляющие времени в вызывающем объекте Обнуляет указанную соста аля ющую времени в вы зыва· ющем объекте Возвращает дубл икат вызывающего объекта Возвращает логическое значение true, если вызыва· ющий объект. типа Calendar содержит такую же дату, как и заданный календарный_обыкт, а иначе - ло гиче­ ское значение falae Возвращает значение одной из составляющих вы­ зывающего объекта. Эта составляющая обозначается параметром rw.ленil а рное_ по.ле. К примерам составляю­ щих, которые можно запросить, относятся значения в полях Calendar . YEAR, Calendar . N»r rl l , Calendar . МINO'lE и т.п. Возвращает массив объектов типа Locale, содержа· щий региональные настройки, для которых в системе доступны календари Возвращает объект типа Calendar для региональных настроек и часового пояса по умолчанию Возвращает объект типа Calendar для региональных настроек по умолчанию и указанного wи:ового_пояеа Возвращает объект типа C&lendar для указанных �-ши:троек и часового пояса по умолчанию Возвращает объект типа Calendar для указанных реZШ1Н4АЬН61%_ши:троек и чaroвoгo_RORaJ · Возвращает объект типа Date, содержащий такое же время, как и время вызывающего объекта
Глава 19. Пакет java.utiL, ч а сть 11. П роч ие служебные кл асс ы 663 Метод TiиleZone getTiJDeZone () final Ьoolean iaSet (int СОС71 1Q&1 1.Я ЮЩQ.Я) void set (int составл. я ющая, int значение) final void ••t (int год , int месяц , int день_меа�ца) final void set (int год , int месяц , int денъ_месяца, int час, int минута) final void set (int год, Окончание malia. 19. 5 Описание Возвращает часовой п ояс вызывающего объекта Возвращает логическое значение true, если указан­ ная СОС1 1U U1 11.Я ЮЩая времени установлена, а иначе - ло­ гическое значение f'аl- Ус танавливает заданное знаvние в указан ной оосmавляющей даты или времени вызывающего объ­ екта . Параметр оосmо& &Я ЮЩая должен иметь значе­ ние одного из п олей класса Calendar, нап ример Calendar .BOOR Ус танавливает в вызывающем объекте различные со­ ставляющие даты и времени Ус танавливает в вызывающем объекте различные со­ ставляющие даты и времени int меащ, int день_мео�ца, int час,intминута,int�) final void setTi.8 8 (Date d) Ус танавливает в вызывающем объекте различные со­ ставляющие даты и времени Ус танавливает в вызывающем объекте различные со­ ставляющие даты и времени. Нужные сведения п олу­ чаются из задан ного объекта dти п а Date Ус танавливает указанный часовой_пОJU: для вызываю­ щего объекта void setTiшeZone (TimeZone часовой_пояс) final Instant toina tant () Возвращает объект типа Ina tant, соответствующий вызывающему объекту ти п а Calendar (добавлен в вер­ сии JDK 8) В классе Calendar определены перечисленные ниже целочисленные кон­ станты , применяемые при получении или установке составляющих календаря. (Константы с суффиксом FORМAT или STAN DALONE были внедрены в версииJDК 8.) AL L STYLES BOUR OF DAY РМ АМ JANUARY SAТURDAY АИРМ JULY SECOND APRIL JUNE SЕРТЕМВ ВR AUGUST LONG SBORT DATE LONG FORМAT SBORT FORNAT DAY 01!' NoNТВ LONG STANDALONZ SBORT STANDALONE DAY OF ПЕК МАRСВ SUNDAY DAY0'1'ПЕКINНОNТВ МАУ TВURSDAY DAYOFп:АR ИILLISl!:COND ТUl!:SDAY DECEIOЗER МINО'П UNDECIМВ&R DST OFFSET МONDAY WEDNE SDAY ERA НОNТВ ПЕК01!'МОNТВ FEВRUARY NAR ROW l!'ORNAT ПЕКOFYEAR FIELD COON'1' NAR ROW STANDAI.a a. YEAR FRIDAY NOVEМВER ZONI!: OIТSBT ВООR ОСТОВВR
66' Ч асть 11. Библиотека Java В следующем примере программы демонстрируется применение некоторых методов из класса Calendar: 11 Продемон стрировать приме нение класса Calendar import java.util . Calendar; class CalendarDemo { puЫ ic static void ma in (String args[J ) String mon ths [J = { "Ja n", "Fe b", "Mar", "Apr ", "Мау", "Jun", "Jul", "Aug", "Sep", "Oct ", "Nov", "Dec" } ; 11 создать календарь , инициали зируемый 11 текущими датой и временем с уч етом региональ ных 11 настроек и часового пояса по умолчанию Calendar calenda r - Calendar . getins tance () ; 11 вывести текущие дату и время System.out . print ( "Дaтa : ") ; System.out . print (months [ calendar .get (Calendar . MONTH) ]); System. out . print (" " + calendar .get (Calendar. DATEJ +" ") ; System.out . println ( calendar .get ( Calendar . YEAR ) ); System.out . print ( "Bpeмя : "); System. o ut . print (calendar .get (Calendar . HOUR ) + ":") ; System.out . print ( calendar .get (Calendar . MINU TEJ + ":"); System.out . println ( calendar .get (Calendar . SECON D) ); 11 ус тановить дату и время и вывести их calendar .set (Calendar . HOUR, 10) ; cal endar .set (Calendar . MINUTE, 29) ; calendar . set (Calendar . SECOND, 22 ) ; System. out .print ( "Измe нeннoe время : ") ; System.o ut . print ( calendar .get (Calendar . HOUR ) + ":") ; System. out .print ( calendar .get (Calendar . MINU TE) + ":"); System.out . println ( calendar .get (Calendar . SECOND) ); Ниже приведен пример выполнения данной программы. Дата : Jan 1 2014 Время: 11:29:39 Измененное время : 10:29:22 Клacc GreqorianCalendar Класс GrеgоriаnСаl еndаr служит в качестве конкретной реализации привычно­ го григорианского календаря средствами класса Calendar. Метод getinstance О из класса Calendar обычно возвращает объект типа GregorianCalendar, иници· ируемый текущими датой и временем с учетом региональных настроек и часового пояса по умолчанию. В классе GregorianCa lenda r определяются два поля , АО и ВС, обозначающих две эры , определенные в григорианском календаре. Имеется также ряд конструк· торов для построения объектов класса GregorianCalendar. В часmости, кон·
Глава 19. Пакет java.utii, часть 11. Прочие служебные классы 665 структор по умолчанию GregorianCalenda r () инициализирует объект данного класса текущими временем и датой с учетом региональных настроек и часового пояса по умолчанию. Другие конструкторы класса GregorianCalendar перечис­ лены ниже по мере увеличения уровня их специализации. GregorianCalendar (int год , int несRц , int денъ_.меаца ) GreqorianCalendar (int год , int Н8СRЦ1 int двн._хвсаца , int vac , int юrву!l'а ) GreqorianCalendar (int год , int НесRЦ1 int двн. хесаца , int vac, int Нl/Uf.)"!l'З , int сеж.УИда) Во всех трех формах конструкrоров устанавливаются параметры день_месяца , месяц и rод , причем нулевое значение параметра месяц обозначает январь. В пер­ вой форме конструктора время устанавливается в полночь, во второй форме - в часах и минутах , а в третьей форме - еще и в секундах. Имеется также возможность создать объект типа GregorianCalendar, указав региональные настройки и/или часовой пояс. Следующие конструкторы создают объе кты, инициализируемые текущими временем и датой с учетом заданных реги­ ональных настроек и часового пояса. GreqorianCalendar (Locale Р8Мfанальнuе нac!l'pO:iJaf) GregorianCal endar (Ti11 1& Zone vaco.EJO.i no°Rc) GregorianCalendar (TimeZone vacoso.i= no•c , Locale per•oнa.nware_нa c!Z'pOJiж.) В классе GregorianCalenda r предоставляется реализация абстрактных мето­ до в из класса Calenda r. Кроме того , в нем определены некоторые дополнитель­ ные методы. Самым интересным из них, вероятно, является метод isLeapYear (), п роверяющий, является ли год високосным. Ниже приведена его общая форма. Ьoolean isLeapYear (int год) Этот метод возвращает логическое значение true, если указанный год явля­ ется високосным, а иначе - логическое значение false. В версии JDK 8 класс GregorianCa lenda r дополнен следующими методами: from ( ) и toZonedDate Time (),поддерживающими новый прикладной программный интерфейс API даты и вре ме ни, а также метод getCalendarType (), возвращающий тип календаря в виде символьной строки (в данном случае - " gregory" , т. е . григорианский календарь) . В следующем примере программы демонстрируется применение класса GregorianCa lendar. / / Продемонс трировать применение кл асса типа GreqorianCalendar import java .util .*; class GregorianCalendarDemo { puЬlic static void ma in (String args(] ) String mo nths [J = { '1Jan" , "Feb", "Mar" , "Apr", "Мау" , "Jun", "Jul", "Aug", "Sep'' , "Oct ", "Nov" , '•Dec"} ; int yea r; 11 создать григорианский календарь , инициали зируемый 11 те кущими датой и временем с уч етом региональ ных 11 настроек и часового пояса по умолчанию GregorianCalendar gcalendar = new GregorianCalendar () ;
666 Ч асть 11. Библиоте ка Java 11 вывести текущие время и дату System.out . print ( "Дaтa : ") ; System.out . print (months [ gcalendar .get (Calendar . MONTH) ]); System . out .print (" "+gcalendar .get (Calend ar . DA TE) +"") ; System.out . println (year = gcalendar .get (Calend ar . YEAR ) ); System.out . print ( "Bpeмя : ") ; System . out .print (gcalendar .get (Calendar . HOUR ) + ":") ; System.out . print (gcalend ar .get (Calendar . MI NUTE ) + ":") ; System.out . println (gcalendar .get (Calendar . SECOND) ); 11 проверить , является ли те кущий год високосным if (gcalendar .isLeapYear (year) ) { System.out . println ( "Teкyщий год високосный" ); ) else { System. out .println ( "Teкyщий год не високосный") ; Ниже приведен пример выполнения данной программы. Дата : Jan 1 2014 Время : 1:45:5 Текущий год не високосный Кл а сс TimeZone Еще одн им, имеющим отношение ко времени является класс TimeZone. Он позволяет оперировать временем с учетом часовых поясов, смещенных относи· тельно среднего времени по Гр инвичу (GMT) , называемого также универсальнш скоординированн'ЫМ вр еменем (UCT) . В классе Time Zone учитывается также летнее время . В нем поддерживается только конструктор по умолчанию. Избранные ме· тоды , определенные в классе TimeZone, перечислены в табл. 19.6. Табл ица 19.6. Избранные методы иэ класса Ti.meZone Метод C:Ьject clone () st&tic Stri.nq [) qetAvai.laЬlema () st&tic Strinq [] getAvai. laЬlema (int разница_во_ времени) st&tic TШ.Zone qetDefault () Stri.nq qetm О Описание Возвращает конкретный вариант метода clone () для класса TШ.Zone Возвраща�:т массив символьных сrрок, представ.ля· ющих названия всех часовых поясов Возвращает массив символьных сrрок, представ.ля· ющих названия всех часовых поясов, имеющих за· дан ную разнщrу_во_вре.мени относительно среднего времени по Гр инвичу Возвращает объект типа TШ.Zone, предсrавляю­ щий часовой пояс по умолчанию, используемый на главном ком пьютере Возвращает имя вызы вающего объекта т и п а Til lle Zone
Гл ава 1 9. Пакет java.uШ, часть 11. П рочие служебные классы 667 Метод aЬstract int getoffset (int ttpa, intюд,intмеащ, int день_меаща, int день_недеди, int �) aЬstract int getRawOffset () static TU.Zone getTi.JDeZone (Strin9 название_ чаrовою_nоясо ) aЬstract Ьo o lean inDayli9htTi.JD8 (Date d) Окончание табл.. 19. 6 Описание Возвращает смещение, которое должно быть до­ бав.лено к среднему времени по Гр инвичу, чтобы рас­ считать местное время. Рассчитанное время коррек­ тируется с учетом летнего времени. Параметры дан­ ного метода задают составляющие даты и времени Возвращает исходное смещение (в мил л исекундах), которое должно быть добавлено к среднему време­ ни по Гр инвичу, чтобы рассчитать местное время. Рассчитанное время не корректируется с учетом летнего времени Возвращает объект типа TiJD&Zone для заданного названия_'lйС080го_пш�са Возвращает логическое значение true, если за­ данная дата d представлена в вызывающем объекте с учетом летнего времени, а иначе - логическое значение false static void иtDefault (T1.D18Zone Ус танавливает чтхнюй_rюsи: по умолчанию для дан- ttа аJвОй _ rюяс) ного гл авного компьютер, где чтхнюй_rюsи:- сс ылка на используемый объект типа T�Zone void setID (Strin9 название_ чаrовою_поясо ) aЬstract void setRawOffset(int �) Zoneid toZoneid ( ) aЬstract Ьo o lean useDayliqht­ Ti.1 18 () Уст анавливает ШJ.1t10ние_чаrового_nш�са (т.е . его иден­ тификато р)ы Уст анавливает смещение относительно среднего времени по Гр инвичу в МIL!t4� Преобразует вызывающий объект в объект класса Zoneid и возвращает результат. Класс Zoneid вхо­ дит в состав пакета java. ti.JD8 (добавлен в версии JIЖ 8) Возвращает логическое значение true, если в вы­ зывающем объекте учитывается летнее время, а ина­ че - логическое значение fa1se Класс SimpleTime Zone Класс SimpleTime Zone является служе бным подклассом, производным от клас­ са TimeZone. Он реализует абстрактные методы из класса Time Zone и позволяет работать с часовыми поясами для григорианского календаря . В этом классе учиты­ вается также летнее время . В классе Simp leTime Zone определяются четыре конструктора. Общая форма объя вл ения первого из них приведена ниже. Sil lp leTi-Zone (int раЭЮ1 1 ца_во_sрененж , Strinq нa.ssaняe_ vacosoro_noяca) Этотконструктор создает объект типа SimрlеТ imе Zоnе. Здесь параметр ра зница_ во_ времени обозначает смещение относительно среднего времени по Гр инвичу, а па-
668 Часть 11. Библиотека Java раметр на звание_ ча сов ого_пояса - конкретное название часового пояса. Второй конструктор данного класса объявляется следующим образом: SimpleTimeZone (int разнжца .во .вреNеня , String идент•Ф•жа �ор часового пояса , int нвсяцО , int днеi i -несяцаО,- int денъО, int вреняО , int нвсяцl , int днвi i _ нвсяцаl, int, двнъl, int вреня1) Здесь смещение среднего времени по Гр инвичу задается параметром разница_ во_ времени, а конкретное название часового пояса - параметром идентифика тор_ ча сов ого_ пояса . Начало действия летнего времени определяется параметрами ме сяц О, дней_ме сяца О, день О и время О, а окончание действия летнего времен и - параметрами ме сяцl , дней_ме сяца l, день l и времяl . Тр етий конструктор класса SimpleTime Zone объявляется таким образом: SimpleTimeZone (int ра.зюrца во .врвNвюs , String идентяфвжа �ор часового пояса , int иесяцО , int днвi i -н есяцаО, int деньО, int вреняО , int несRцl , int дна несяца l, int денъl, int вреня1, int раэюrЦа _ в _ лв�нве _ .вреня) где параметр ра зница_ в _ ле тне е_ время обозначает количество миллисекунд, сбе· регаемых в течение летнего времени. И наконец, четвертый конструктор класса Simp leTime Zone объявляется следующим образо м: SimpleTimeZone (int раэюrца во врененя , Strinq ядентяфхжа �ор часового пояса , int нвсяцО , int дна - нвсяца О, -i nt денъО, int вреняО , int ре"-;, .врененяО ,int несяцl , int дна нвсяца l, ·int-денъ 1, int .вреня1 , int рв.а(ji_.врененя1 , int раэюrца _ в_лв�нве_вреня) где параметр режим_ времениО обозначает режим начального времени, а параметр режим_ времениl - режим конечного времени. Ниже перечислены допустимые значения этих режи мов в виде констант. 1 STANDARD ТIМЕ IWAL L ТIМЕ \u:тс т:о о: Режим времени определяет, каким образом интерпретируются величины вре· мени. По умолчанию во всех остальных конструкторах используется режим време­ ни, обозначаемый константой WA LL_T IME. Кл а сс Locale Класс Locale предназначен для создания объектов, каждый из которых опи · сывает географический или культурный регион. Это один из нескольких классов, обеспечивающих возможность напйсания программ для выполнения в интерна· циональных средах. Например , форматы, применяемые для отображения даты, времени и чисел , в разных регионах отличаются. Вопросы интернационализации программ нajava выходят за рамки рассмотре­ ния дан ной книги. Но для интернационализации большинства прикладных про­ грамм требуются только самые элементарные действия, включая установку теку· щих региональных настроек.
Глава 1 9 . Пакет java.utiL, часть 11. Проч ие служебные кл ассы 669 В классе Locale определяются приведенные ниже константы, удобные для обра­ щения с наиболее часто употребляемыми региональными настройками. Например, выражение Locale . CANADA обозначает объект типа Locale для Канады. CANADA GDIOW КORDN CANADA_FRВNCВ GElUQNY PRC CЯINA ITALIAN SIМPLIFIED CBINESE. - CИINESE ITALY TAI1 1AN ENGLISB JAPAN ТRADITIONAL CBINESE FRANCE JAPANESE uк FRENCB кош us Ниже перечислены конструкторы класса Locale. Locale (Strinq яsrж) Locale (Strinq яsrж , Strinq страна ) Locale (Strinq я.sмж , Strinq страна , Strinq .вар•ант) Эти конструкторы создают объект ти па Locale для представления конкретно­ го языка, а два последн их конструктора - еще и страны. Эти значения должны со­ держать стандартные коды стран и языков. В качестве параметра вариа нт могут быть предоставлены различные вспомогательные сведения. В классе Locale определяется ряд методов. К числу самых важных относится метод setDefault (), общая форма которого приведена ниже . static void setDefaul t(Locale объежт_реr rr аналъ.ншr_настроеж) Этот метод устанавливает региональные настройки , используемые по умол­ чанию в виртуальной машине JVМ и обозначаемые параметром объект_ региональных_ на строек. Ниже приведены общие формы объявления других ин­ тересных методов из данного класса. final Strinq qetDisplayCountry () Ма.1 Strinq qe tDisplayLanquaqe () маl Strinq qe tDisplayNaшe () Эти методы возвращают уд обочитаемые символьные строки , которые можно использовать для отображения названий стран , языков и полного описания реги­ он альных настроек. Регио нальные настройки по умолчанию можно получить , вызвав приведен ный ниже метод getDefault ( ) . static Locale qetDefaul t () В верс ии JDK 7 были внесены существенные изменения в класс Locale, ко­ торый с тех пор поддерживает стандарт Internet Engineering Ta sk Force (IETF) ВСР 47, определяющий дескрипторы для идентификации языков, а также стан­ дарт Unicode Te chnical Standard (UТS) 35, определяющий язык разметки реzиона1 1:1т ных данных (LSML) . Для поддержки стандартов ВСР 47 и UТS 35 в класс Locale пришлось ввести ряд средств, в том числе несколько новых методов и класс Lo cale .Builde r. К их числу относится новый метод ge tScript (), получающий
670 Часть 11. Библиоте ка Java сценарий региональных настроек, а также метод toLanguageTag (),получающий символьную строку с дескриптором языка в региональных настройках. В классе Locale . Builder создаются экземпляры класса Loca le. Этим гарантируется, что спецификация региональных настроек будет сформирована правильно по стан­ дарту ВСР 47. (Конструкторы класса Locale не обеспечивают такую проверку.) Ряд новых методов был введен в класс Locale и в версии JDK 8. К их числу от­ носятся методы, поддерживающие фильтрацию, обработку исключений и по иск. Классы Calenda r и GregorianCalendar служат примерами классов, действую­ щих с учетом региональных настроек. Классы Da teForma t и SimpleDateFormat также зависят от региональных настроек. Кл а сс Random Класс Random служит в качестве генератора псевдослучайных чисел. Они на­ зываются псевдослучайными, поскольку представляют собой сложные распреде­ ленные последовательности. В классе Random определяются следующие ко нструк­ то ры: Random () Random (lonq начальное_число) В первой форме конструктора создается ге нератор псевдослучайных чисел , ис­ пользующий однозначное начальное число . А во второй форме это число можно указать вручную. Инициализировав объект типа Random начальн ым числом , можно определить начальную точку для генерирования последовател ьности случайных чисел. Если исnользовать то же самое начальное число для инициализации другого объекта ти па Random, то получится та же самая последовательность случайных чисел. Если же требуется получить разные последовательности случайных чисел, объекты типа Random следует инициализировать разными начальными числами . С этой целью можно , в частности , воспользоваться текущим временем, чтобы инициали­ зировать им объект типа Random. Благодаря этому уменьшается вероятность полу­ чения повторяющихся последовательностей случайных чисел. Основные открытые методы из класса Random перечислены в табл. 19.7. Эти методы уже давно доступ ны в классе Random, начиная с версииjаvа 1.0, и поэтому они нашли широкое применение в прикладном коде . Как видите, имеется семь типов случайных чисел, которые можно извлечь из объекта типа Random. Логические случайные значения можно получить с помощью метода nextBoolean (), случайные байты - с помощью метода nextBytes (),а це­ лые случайные числа - с помощью метода next int (). Случайные длинные целlГ численные значения, равномерно распределенные по диапазону допустимых зна­ чений, выдает метод nextLong (),тогда как методы ne xtFloat () и next DouЫe () возвращают случайные значения типа flo a t и douЬ le, равномерно распределенные в пределах от О,О до 1,О. И наконец, метод nextGaussian () возвращает значение типа douЫe со стандартным отклонением на 1,О от центральной точки О,О . Это так называемая кривая нормал:ьного распредР.!tеНuя.
Гл ава 1 9. Пакет java.utit, часть 11. П рочие служебные классы 671 Табл ица 19.7. Основные метод ы иэ класса Random Метод Описание Ьo o lean nextВo o l.ean () Возвращает следующее случайное числовое значение ти па Ьoolean void nextвytes (� значения[]) douЫe nextDouЫe () float nextFl.oat () douЬle next.Gaussian () int nextint () int nextint (int 11) lonq nextLong () void setSe ed ( lonq НQ'l ll llЬНOe _ЧUCl lO ) Заполняет массив значения с.луЧаЙНо сгенерированными число­ выми значениями Возвращает следующее случайное числовое значение типа douЬle Возвращает следующее случайное числовое значение ти паflоаt Возвращает следующее случайное числовое значение из нор­ мального распределения Возвращает следующее случайное числовое значение ти па int Возвращает следующее случайное числовое значение типа int впределахотОдоп Возвращает следующее случайное числовое значение ти па l.onq Устанавливает НOЧQJl1i НIJe _чш:.ло (т.е . начальную точку для генери­ рования случайных чисел) Ниже приведен пример программы, демонстрирующий случайную последова­ тельность, формируемую методом nextGaussian (), получающим 100 случайных значений из нормального распределения и усредняющим их. В данной программе подсчитывается также количество значений, попадающих в пределы двух стандарт­ ных отклонений с шагом О , 5 в положительную или отрицательную сторону в каж­ дом случае . Получаемый результат графически отображается боком на экране. 11 Продемон стрировать ге нерирование случ айных значений 11 с нормаль ным распределением impo rt java.util . Randorn; class RandDerno { puЫ ic static void rna in (String args []) { Randorn r = new Random() ; douhle val ; douЫ e surn О; int bell [] = new int(lOJ ; for (int i=O ; i<lOO; i++) { val = r .nextGaussian ( ); surn += val; 1 douЫe t = -2; for (int х=О; x<lO; х++, t += 0.5) if(val < t) { bell [х] ++; break; Systern.out . println ( "Cpeднee всех значений : "+ (sum/ 100)); // вывести кривую распределения for (int i=O ; i<lO; i++) { for ( int x=bell(i]; х>О; х--) System. out . print ("*") ;
672 Часть 11. Библ иотека Java System.out . println () ; Ниже приведен пример выполнения данной программы. Как видите , получает­ ся колоколоподобное рас п ределение чисел. Среднее всех значений : 0.0702235271133344 ** ******* ****** *************** ****************** ***************** ************* ********** ******** *** В версииJDK 8 класс Randorn был дополнен тремя новыми методами douЫ es (), ints () и long s () , поддерживающими новый прикладной программный интер­ фейс API потоков данных, рассматриваемый в гл аве 29. Каждый из них возвра· щает ссьVIку на поток дан ных, содержащий последовательность псевдослучай ных числовых значений указанного типа. Для каждого из этих методов определ яется несколько перегружаемых вариантов. Ниже приведены простейшие формы объ· явления этих методов. DouЬleStreaa douЫes () IntStre&1 11 ints О LonqStre&1 11 lonqs О Метод douЫes () возвращает поток данных, содержащий псевдослучайные числовые значения ти па douЫ e в пределах от О,О до 1,О. Метод ints () возвра· щает поток данных, содержащий псевдослучайные числовые значения типа int, а метод longs () - поток данных с псевдослучайными числовыми значениями типа long. Но в любом случае поток дан ных оказывается, по существу, бесконеч· ным. Для каждого из этих методов определено несколько перегружаемых вари· антов, позволя ющих задавать размер потока данных, начало отсчета и ве рхнюю границу. Класс ObservaЬle Класс Ob servaЫe служит для создания подклассов, за которыми могуг наблю­ дать остальные части прикладной программы. Когда в объекте такого подкласса происходят изменения, об этом извещаются наблюдающие классы. Наблюдающие классы должны реализовать инте рфейс Ob serv er, в котором определен метод up­ da te ( ) . Этот метод вызывается , когда наблюдатель получает извещение об изме­ нении наблюдаемого объекта. В классе Ob servaЫe определяются методы, приведенные в табл . 19.8. Наблюдаемый объект должен следовать двум простым правилам. Во-первых, если он изменяется , то должен вызывать метод setChanged ( ). Во-вторых, когда он
Гл ава 19. Па кет java.uШ, часть 11. Прочие служебные классы 673 готов известить наблюдателей об этом изменении , то должен вызвать метод no­ tifyObservers ().Это вынуждает наблюдающий объект (или объекты ) вызывать метод upda te ().Следует, однако, иметь в виду, что если объект обращается к ме­ тоду noti fyObserve rs (),не вызвав предварительно метод setChanged (),то ни­ какого действия не последует. Наблюдаемый объект должен вызвать оба метода, s etChanged () и not i fyObserve rs (),прежде чем будет вызван метод upda te (). Таблица 19.8. Методы иэ класса ObservaЫe Метод void ad dOЬ server (OЬserver о6мхт) protected void clearChanqed () int counteьservers О void daleteCЬeerver (OЬserver обюап) void delet.cьeervere ( ) Ьo o lean hasehanged () void notify<:Ьservers () void notify<:Ьservers (OЬj ect обюап> protected void setchanged () Описание Вводит заданный о&мК'l' в список объеК"rов, наблюдаю­ щих за вызывающим объектом Возвращает неизмененное состояние вызывающего объекта Возвращает количество объеК"rов, наблюдающих за те­ кущим объектом Удал яет заданный об6ект из списка объектов, наблюда­ ющих за вызывающим объектом Удаляет все наблюдатели вызывающего объеК"rа Возвращает логическое значение true, если вызываю­ щий объеК"r был изменен , а иначе - логическое значе· ниe false Извещает наблюдателей, что вызывающий объеК"r был изменен методом update ().Вкачестве второго параме­ тра методу update ( ) передается пустое значение null Извещает наблюдателей, что вызывающий объеК"r был изменен методом update О . В качестве второго пара· метра методу update () передается заданный обr.ект Вызывается при изменении вызывающего объеrn Следует заметить, что у метода notifyObserve rs () имеются две формы : с ар­ гументом и без такового. Если вызвать метод noti fyObserve rs () с объектом в качестве аргумента, этот объект передается методу update () наблюдателя в ка­ честве второго параметра. В противном случае методу upda te ( ) передается пу­ стое значение nu ll. В качестве второго параметра можно передать объект любого ти па , подходя щий для прикладной программы. Инте рфейс Ob serve r Чтобы организовать наблюдение за объектом, следует реализовать интерфейс Observe r. В этом интерфейсе определяется следующий единственный метод: void update (ObservaЫe иабтодаемый_о&ъект , Ob ject аргумент) где параметр на блюда емый_ объект обозначает объект для наблюдения , тогда как аргумент - значение, передаваемое методом noti fyObserve rs ( ) . Метод up­ da t e () вызывается при изменении наблюдаемого объекта.
671+ Часть 11. Библ иотека Java Пример набпюдения эа объектами Ниже приведен пример программы, демонстрирующий обращение с наблюдае· мым объектом. В этой программе создается класс наблюдателя Wa tcher, реализую­ щий интерфейс Ob server, а также наблюдаемый класс BeingWatched, расширяю­ щий класс Ob servaЫe и содержащий метод counter (), который просто выполня­ ет обратный отсчет от заданного значения, вызывая метод sleep () для ожидан ия в течение 10 секунд в промежутках между последовательными отсчетами. Каждый раз, когда счетчик изменяется, вызывается метод noti fyObserve rs (), которому в качестве аргумента передается текущее значение счетчика. Это вынуждает вы· зывать метод update () из класса Wa tcher, который выводит текущее значение счетчика. В теле метода ma in ( ) сначала создаются наблюдающий и наблюдаемый объекты классов Wa tcher и BeingWatched, называемые ob serving и оЬsеrvеd со­ ответственно. Затем объект ob serving вводится в список наблюдателей за объек­ тами ob served. Это означает, что метод observing . upda te () будет вызываться всякий раз, когда метод counter () вызьIВает метод no tifyObservers (). / * Продемонс трировать примененик класса OЬservaЫe и интерфейса OЬserver */ import java .util .*; 11 Это наблюдающий класс class Watche r implements Ob serve r puЫ ic void update (ObservaЫe obj , Ob ject arg) { System. out . println ( "Метод update () вызван, отсчет count равен " + ((Integer) arg) .intValue () ); 11 А это наблюдаемый кл асс class BeingWatched extends Ob servaЫ e { void counter (int period) { for ( ; period >=О ; period --) { setChanged (); noti fyObservers (new Integer (period) ); try { Thread .sleep (lO O) ; catch (InterruptedExc eption е) { System. out . println ("Oжидaниe прервано" ); class Ob serve r Demo { puЫ ic static void ma in (String args[] ) { Be ingWatched observed = new BeingW atched () ; Watche r observing = new Watcher (); / * Ввести наблюдающий объе кт в список наблюдателей за наблюдаемым объе ктом */ observed . addObserv er (observing) ; observed .count er (lO) ;
Гл ава 19. Пакет java . utiL, часть 11. Прочие служебные кл ассы 675 Ниже приведен результат, выводимый данной программой. Метод update () вызван, отсч ет count равен 10 Метод upda te () вызван, отсчет count равен 9 Метод update () вызван, отсч ет count равен 8 Метод update () вызван, о тсчет count равен 7 Метод update () вызван , отсчет count равен 6 Метод update () выз ван , отсчет count равен 5 Метод update () вызван, отсчет count равен 4 Метод upda te () вызван, о тсчет count равен 3 Метод upda te () вызван, отсчет count равен 2 Метод update () вызван, отсчет count равен 1 метод upda te () вызван , отсчет count равен о Наблюдателями могут быть несколько объектов. Та к, в следующем примере программы реализуются два класса наблюда'rелей , и объекты каждого из них вво­ дятся в список наблюдателей за объектом класса Be ingWa tched. Второй наблюда­ тель ожидает до тех пор, пока счетчик достигнет нулевого значения, после чего подается звуковой сигнал. /* За одним объе ктом могут наблюдать несколько наблюдателей . */ import java .util .*; / / Класс первого наблюдателя clas s Watcherl impl ements Observer ·{ puЫic void update (ObservaЫe obj , Object arg) { System.out . println ( "Метод update () вызван, отсчет count равен " + ( ( Integer) arg) . intVa lue () ); // Класс второго наблюдателя class Watcher2 implements Observe r { puЫic void update (ObservaЫe obj , Ob ject arg) 11 по о кончании выд ать звуковой сигнал if( ((I nteger) arg) .intValue () == 0) System.out . println ( "Гoтoвo" + '\7' ); / / Наблюдаемый класс class BeingWatched extends Ob servaЫe { void counter(i nt period ) { for ( ; period >=О ; period --) { setChanged (); noti fyObservers (new Integer (period) ); try { Thread .sleep (lOO) ; catch (InterruptedException е) { System.out . println ( "Oжидaниe прервано" ); class TwoObservers { puЬlic static void ma in (String args[J ) { BeingWatched observed = new BeingWa tched ();
676 Ч асть 11. Б иблиоте ка Java Watcherl observingl = new Watcherl () ; Wa tcher2 ob serving2 = new Wa tcher2 (); 11 ввести в список оба наблюда теля ob se rved . addObserver (observi ngl ); ob se rved . add0bserv er (observing2 ); ob se rved .counter (lO) ; Класс Ob servaЫ e и интерфейс Ob server позволяют реализовывать изощренные программные архитекrуры, основанные на методике "документ-представление" . Кл а ссы Timer и TimerTask В пакете j ava . u t i l предоставляется интерес ная и удобная возможность пла­ нировать запуск задания в определенный момент времени в будущем. Такую воз· можность предоставляют классы Time r и TimerTas k. Испол ьзуя эти класс ы, мож· но создать поток, исполняющийся в фоновом режиме и ожидающий в течение задан ного времени. По истечении зада нного времени запускается задание , свя· занное с этим потоком исполнения. Различные параметры позволяют заIUiаниро­ вать запуск задания на повторное исполнение или на определенную дату. И хотя с помощью класса Thread можно всегда запланировать задание вручную на за пуск в определенный момент времени, тем не менее классы Time r и TimerT ask значи· тельно упрощают этот процесс. Классы Timer и TimerT ask действуют совместно. В частности , класс Time r служит для планирования выполняемого задания. Планируемое задание должно быть экземпляром класса TimerTask. Следовательно , чтобы запланировать зада· ние, следует создать сначала объект класса TimerTas k, а затем запланировать его запуск с помощью экземпляра класса т ime r. Класс TimerTask реализует интерфейс RunnaЫe. Это означает, что он может быть использован для создания потока исполнения. Ниже приведен его конструктор. TimerTask () В классе Time rTa sk определены методы, перечисленные в табл. 19 .9 . Обратите внимание на то , что метод run ( ) является абстрактным, а это означает, что он дол· жен быть переопределен. Метод run ( ) , определенный в интерфейсе RunnaЫ e, со­ держит исполняемый код. Следовательно, простейший способ запланировать запуск задания по таймеру - расширить класс Т ime rTa s k и переопределить метод r un ( ) . Как только задание будет сформировано, его выполнение планируется с помо­ щью объекта класса Timer. Ниже приведены конструкторы класса Time r . Timer () Timer (boolean лo!l'oжo.IWJi денан) Tiшer (String .,. . ло!l'ожаТ Timer (String ., , ,. . :ло!l'ожа , Ьoolean ло!l'ожо.вшi_денан) В первой форме конструктора сначала создается объект ти па Т ime r, а затем он запускается как обычный поток исполнения. Во второй форме используется пото­ ковый демон, если параметр по токовьт.й_демон принимает логическое значение
Глава 19. Пакетjava.utit, часть 11. Прочие служебные классы 677 true. Потоковый демон будет исполняться только до тех пор, пока выполняется остальная часть программы. Тр етья и четвертая формы конструкторов позволяют указывать имя объекта типа Timer. Методы , определенные в классе Timer, пере­ числены в табл. 19. 1 О. Табл ица 19.9. Методы из класса TimerTask Метод Описание Ьoolean cancel () Прерывает задание. Возвращает логическое значение true, если выполнение задания прервано, а иначе - логическое значение f'alse aЬs tr act void run () Содержит код задания, запускаемого таймером lon9 Возвращает момент времени, на который запуск scheduledExecu tionTime () задан ия планировался в последний раз Табл ица 19.10. Методы из класса Timer Метод Описание void canoel () Прерывает поток исполнения таймера int purqe() Уд аляет прерванные задания из очереди таймера void schedule (Til lle rТask заilание_по_ Планирует указанное зоiJтше_по _таймеру для вы­ mоi iмеру , lonq ожидание) полнения через промежуток времени в миллисе- void schedule (Til lle rTask эадание_ no_moi i.мefry , lonqожиiJани е , lonq ooвrnoJ1) void schedule (Til lle rТask заiJание_по_ moi iмepy , Dateзадаю юе _ере.мя) void schedule (Til lle rTask задmше_по_ moi iмepy , Dateзадаю юе _время, long ooвrnoJ1) кундах. определяемый параметром ожuдапие Планирует указанное зоiJтше_по_таймеру для вы· rюлнения через промежуток времени в мил л и· секундах, определяемый параметром ожидание. Выполнение задания затем повторяется через промежуток времени в мил л исекундах, определя­ емый параметром повтор Планирует указанное задтше_по _таймеру для вы­ полнения на задтnюе_ере.мя Планирует указанное зоiJтше_по_таймеру для вы­ полнения на задаюwе_вре.мя. Выполнение задания затем повторяется через промежуток времени в миллисекундах, определяемый параметром повтор void scheduleAtFixedRate (Til lle rТask Планирует указанное заiJание_по _таймеру для вы - зодание_IW_тоймеру, lonq ожидание, полнения через промежуток времени в милли- lоng повтор) секундах, определяемый параметром ожuдапие. Выполнение задания затем повторяется через промежуток времени в мил л исекундах, определя­ емый параметром повтор. Время каждого повтора задается относительно первого запуска, а не предыдущего. Следовательно, общая частота по­ второв остается фиксированной
678 Часть 11. Библиотека Java Метод Октrоюние таliя.19. 10 Описание void schedu1eAtl'ixedRate ('l'iaer'rask зО Ооние _nо_-тмеру, Date заdан ное _ врем!I,loaqnotl lltD/J ) Планирует указанное зO Ooнue _no_'lnl liUифy для вы· полнения на ЗDiJою юе _время. Выполнение задания затем повторяется через промежуток времени вмил л исекундах, определяемый параметром повтор. Время каждого повтора задается от­ носительно первого запуска, а не предыдущего. Следовательно, общая частота повторов остается фиксированной Как только объект класса Т ime r будет создан, запуск задания можно запланиро­ вать, вызвав метод schedule () из этого класса. Как следует из табл . 19 .10, имеется несколько форм метода schedule (), позволяющих запланиро вать задание разны· ми способами. Если задание не формируется как демон, то по завершении программы придет­ ся вызвать метод cance l (), чтобы прервать это задание. И если не сделать этого, то программа может на некоторое время "зависнугь". В приведенном ниже примере программы демонстрируется применение клас· сов Timer и TimerTask. В этой программе определяется задан ие , запускаемое по таймеру, а при его выполнении метод run ( ) выводит сообщение "Задание по таймеру выполняется". Это задание планируется для запуска каждые полсекун· ды после первоначальной паузы в течение одной секунды. 11 Продемонстрировать применение классов 'l'ill8r и Ti.DlerTask import java . util .*; class MyTimerTask extends Timerтask puЫic void run () { System.out . println ("З aдaииe по таймеру выпол няется ."); class тте st { puЫ ic static void main (St ring args []) { MyTimerTask myTask = new MyTimerTas k() ; Timer myTimer = new Timer (); /* Установить первоначаль ную паузу в течение одной секунды, а затем повторять задание каждые полсе кунды */ myTimer.schedule(rnyTa sk, 1000, 500) ; try { Thread .sleep (5000) ; } catch (InterruptedException ехс ) {} rnyT imer. cancel () ; Кла сс Currency Класс Currency инкапсулирует сведения о денежной единице. В этом классе кон­ структоры не определяюrся. Методы из класса Currency перечислены в табл. 1 9 .ll. В следующем примере программы демонстрируется применение класса Currency:
Гл а в а 19. Пакет java.utiL, ч а сть 11. П рочие служебные кл а ссы 679 11 Продемонстрировать приме нение класса Currency import java .util .*; class CurDemo ( puЫic static void main (St ring args[] ) ( Currency с; с=Cur rency .getlnstance (Locale .US) ; System. out .println ( "Cим вoл : "+c.ge tSymЬol () ); System. out .println ( "Количество цифр в дробной части числа по умолчанию : "+ c.getDe faul tFractionDigi ts () ); Ниже приведен результат, выводимый дан ной программой . Символ : $ Количество цифр в дробной части числа по умолчанию : 2 Табл ица 19. 11. Методы из класса currency Метод static Set<Currency> getAvailaЬleCurrencies () Strinq qetCurrencyCode () int ge�aultFractionDiqits () Strinq getDi splayName () Strinq qetDisplayNa:me (Locale ретональные_настроШn l ) static Currency getinstance (Locale обr.екm_ регионад ьных _настроек) static Currency getinstance (Strinq код) int getNwaericCode () Strinq qetSyшЬol () Strinq qetSyшЬol (Locale обr.екm_ ретональных_настроек) Strinq toString () Описание Возвращает ряд поддерживаемых денежных единиц Возвращает код денежной единицы по сrандаргу ISO 42 17 для вызывающего объекта Возвращает количество цифр после десятичной точки, которые обычно указываются в денежной еди­ нице для вызывающего объекта. Нап ример, для сумм в долларах после десятичной точки указываются две цифры Возвращает название денежной единицы из регио­ нальных настроек по умолчанию для вызывающего объекта Возвращает название денежной единицы из указан­ ных регштадьНЬIХ_настроек для вызывающего объекта Возвращает объект типа Currency для реrионал1:r ных настроек, обозначаемых параметром обr.екm_ регитю.льных_настроек Возвращает объект типа Currency, связанный сука­ занным коО О.w денежной единицы Возвращает числовой код денежной единицы по стандаргу ISO 42 17 для вызывающего объекта Возвращает знак денежной единицы (например , $) для вызывающего объекта Возвращает знак денежной единицы (например , $) для региональных настроек, обозначаемых параме­ тром обr.еюп_региона .ль ных_настроек Возвращает код денежной еди ницы для вызывающе­ го объекта
680 Ч асть 11. Библиоте ка Java Класс Formatter В ос нову системы подцержки форматированного вывода положен класс Fo rmatter. Он выполняет преобра,зование формата, позволяющее выводить числа, строки , время и даты практически в любом виде. Этот класс действует подобно фун кции print f () в С/С++, и если у вас имеется некоторый опыт программи· рования на С/С++, то освоить класс Formatter вам не составит большого труда , а также упростит перенос кода из С/ С++ наJ ava. Но даже если у вас нет опыта про­ гр аммирования на С/С++, вам все равно будет нетрудно научиться форматировать данные средствами класса Formatter. На заметку! Несмотря на то что класс Fo rma tter очень похож на функцию p rintf ( ) в С/С++, он все же имеет некоторые отличия и новые средства форматирования данных. Поэтому, есл и у вас имеется некоторый опыт программирования на С/С++, рекомендуется внима· тел ьно проч итать этот раздел . Конструкто р ы кл а сса Formatter Прежде чем воспользоваться классом Formatter для форматирования выво­ димых дан ных, следует создать его объе кт. В общем, объект класса Fo rmatter преобразует в формати рованный текст двоичную форму данных, используемых в приклад ной программе. Он явно сохраняет форматированный текст в буфере, содержимое которого может быть доступно из прикладной программы в любой удо бный момент. При этом имеются следующие возможности: разрешить объекrу ти па Fo rmatter предоставить такой буфер автоматически , указать его я вно при создании объекта типа Formatter или позволить объекту класса Formatte r выво­ дить содержимое его буфера в файл. В классе Fo rmatter определяется немало конструкто ров, позволяющих созда· вать его объекты самыми разными способами. Ниже перечислены лишь некото­ рые конструкторы этого класса. Formatter () Formatter (AppendaЫe буфер) Formatter (Ap pe ndaЬle буфер, Locale реrwоналъюrе_нacтpoJi.ur) Forшatter (Strinq •Нl l фаJiла) throws FileNotFoundl:xception Formatter (String ЯНJ1 - фаJiла , Strinq набор �ов) throws FileNotFoundl:xception , Unsup po rteciEncodingException Formatter (File фаJiл вuвода) throws FileNotFoundException Formatter (OutputStream по!l'ож_.вшюда) Здесь параметр буфер обозначает конкретный буфер для хранения отформатиро­ ванных выводимых данных. Если указанный буфер пуст, то объект типа Formatter автоматически выделяет объект типа StringBu ffer для хранения отформатиро­ ванных выводимых данных. Параметр региональные_ на стройки обозначает ис· поль.зуемые реmональные настройки. Если региональные настройки не заданы, исполь.зуются региональные настройки по умолчанию. Параметр имя_ файла обо­ значает имя того файла, который будет принимать отформатированные выводимые дан ные. Параметр на бор_ симв олов обозначает используемый набор символов. Если
Глава 19. Пакет java.utiL, ч а сть 11. Прочие служе бные кл асс ы 681 конкретный набор символов не указан, используется набор символов по умолчанию. Параметр файл_вывода обозначает ссьшку на открытый файл, который должен при­ нимать выводимые данные. И наконец, параметр по ток_ вывода обозначает ссьшку на тот поток вывода, куда будут направлены выводимые данные. Если для вывода ис­ пользуется файл , то выводимые данные также записываются в этот файл. Чаще всего применяется первый из перечисленных выше конструкторов рас­ сматриваемого здесь класса, поскольку у него отсутствуют параметры. Он автома­ тически использует региональные настройки по ум олчанию и выделяет объект типа StringBu ffer для хранения отформатированных выводимых дан ных. Методы из класса Formatter В классе Fo rmatter определяются методы , перечисленные в табл . 19 .12. Табn ица 19. 12. Методы из класса Fo rmatter Метод void close () void flush () For1 11& tter forшat (Strinq фQ/М«Jтирующая_строка , Object... арzу.менты) For1 11& tter fora aa t(Locale регион41 1 ьные_настройки , Strinq форматирующая_ строка , Ob ject арг-ументы) IOException ioException ( ) Locale locale () Ap pe ndaЬle out () Strinq toStrinq () Описание Закрывает вызываемый объект типа l'orшattar. В итоге освобождаются все ресурс ы, используемые этим объектом. После закрытия объекта типа For8atter его больше нель­ зя использовать. Любая попытка использовать закрытый объект типа Formatter приведет к исключению типа ForшatterClosedException Очищает буфер отформатированных данных. В итоге все дан ные, находя щиеся в буфере, выводятся по месту на­ значения. Этот метод вызывается в основном для объекта типа Forшatter, связанного с файлом Форматирует указанные арzу.менты в соответствии со спецификаторами формата, которые содержит заданная форматирующая_строка. Возвращает вызываемый объект Форматирует указанные арzу.менты в соответствии со спецификаторами формата, которые содержит заданная форматирующая_строка. П ри форматировании использу­ ются заданные региональные_настройки. Возвращает вызы­ ваемый объект Если базовый объект, котор ый служит в качестве адресата, генерирует исключение типа IOException, то возвращает­ ся именно оно, а иначе - пустое значение null Возвращает региональные настройки для вызывающего объекта Возвращает ссылку на базовый объект, который служит в качестве адресата для выводимых дан ных Возвращает объект типа Strinq, содержащий отформати­ рованные выводимые дан ные
682 Часть 11. Б ибл иотека Java Основы форматирования Как только объект класса Fo rmatter будет создан , его можно применять для создания формати рованных строк. Для этой цели служит метод format ( ) . Ниже приведена его наиболее часто используемая форма. Forшatter fошаt ( Strinq форна !1'JWP.YJDl lr aR_С!l'р()жа , Obj ect . . . apr.vнeн!nl) Здесь параметр форма тирующа я_ строка состоит из элементов двух ти пов. К первому типу относятся символы, которые просто ко пируются в буфер выво­ да, а ко второму типу - специфик.атор'Ьt формата, определяющие способ, которым должны выводиться указываемые далее аргуме н ты. В простейшей форме спецификатор формата начинается со знака процента с последующим специфик атором преобразования. Все спецификаторы преобразова· ния формата состоят из единственного знака. Например, спецификатор формата чисел с плавающей точкой обозначается следующим образом: %f. В общем , до лж· но быть указано столько аргументо в, сколько задано спецификаторов формата, причем соответствие аргументов устанавливается слева направо . Рассмотрим в ка· честве примера следующий фрагмент кода: Formatter fmt = new Fo rmatter () ; fmt . fоrmаt ("Ф орматиров ать %s очень просто : %d %f", "средствами Java" , 10, 98 .6) ; В этом фрагменте кода создается объект типа Fo rma t ter, содержащий следую' щую отформати рован ную строку: Форма тировать средствами Java очень просто : 10 98 . 600000 В данном примере спецификаторы формата %з, %d и %f замещаются аргумента· ми, следующими за строкой формата. В частности, спецификатор формата % s за· меняется символьной строкой "средствами Java ", спецификатор формата %d­ числовым значением 10, а спецификатор формата %f - числовым значением 98 . б . Все остальные символы в форматирующей строке используются без изменен ия. Как и следовало ожидать, спецификатор формата %s обозначает символьную стро­ ку, а спецификатор формата %d - целочисленное значение. Как упоминалось вы ше, спецификатор формата %f означает числовое значение с плавающей точкой. Метод f о rma t ( ) принимает широкое разнообразие спецификаторов формата , перечисленных в табл. 19.13. Обратите внимание на то , что многие спецификато­ ры формата имеют как прописную, так и строчную форму. Когда используе тся про­ писная форма, буквы отображаются в верхнем регистре, а в остальном обе формы равнозначны. Следует иметь в виду, что каждый спецификатор формата проверя· ется в Java на соответствие типу аргумента. Если такое соответствие отсутствует, ге нерируется исключение типа IllegalFormatException. Как только символьная строка будет отформатирована, ее можно получить, вы· звав метод toString (). Например, в следующей строке кода получается отформа· тированная символьная строка, содержащаяся в объекте fmt : String str = fmt .toString () ;
Гл ава 19. Пакет java.utiL, часть 11. П роч ие служеб ные кл ассь1 683 Таблица 19.13. Спецификаторы формата Спецификатор Применяемое преобразование формата \а \А %Ь %8 \с %d %h \В %е %1 \f \g \G %о %n %8 \S %t \Т \х \Х " Шестнадцате ричное значение с плавающей точкой Логическое значение Символ Десятичное целое значение Хеш-код аргумента Экспоненциальное представление числа Десятичное число с плавающей точкой Испол ьзует спецификатор формата %е или %f в зависимости от форма­ тируемого значения и задан ной точности Восьмеричное целое число Вставляет знак перевода строки Символьная сrрока Время и дата Шестнадцатеричное целое число Вставляет знак % Безусл овно, если требуется лишь вывести отформатированную строку, то для этого ее совсем не обязательно присваивать ее сначала объекту типа String. Например, когда объект типа Fo rmatter передается мeтoдy println (),его метод toString () вызывается автоматически . Ниже приведен краткий пример программы, где демонстрируется порядок со­ сrnвления и вывода отформатированной строки. 11 Оч ень простой пример приме нения кл асса ForD1atter import java .util .*; class FormatDemo ( puЫ ic static void ma in (String args[] ) Fo rm atter fmt = new Formatter () ; fmt . foxmat ( "Форматировать %s nросто %d %f", "средствами Java '', 10, 98 .6) ; System. out . println (fmt) ; fmt .close () ; Следует также иметь в виду, что ссылку на буфер вывода можно получить, вы· звав метод ou t (). Этот метод возвращает ссьшку на объект типа Ap pendaЫe.
68' Ч асть 11. Б иблиоте ка Java Те перь, когда разъяснен общий механизм создания форматированных строк, рассмотрим подробнее каждый из перечисленных выше видов преобразования форматируемых данных в отдельности. Попутно поясним такие параметры, как выравн ивание, минимальная ширина поля и точность. Форматирование строк и символов Для форматирования отдельных символов служит спецификатор формата %с. В итоге соответствующий символьный аргумент будет выводиться без всяких изме­ нений. А для форматирования символьных строк служит спецификатор формата %s. Формати ро вание чисел Для формати рования целых чисел в десятичном формате служит специфика· тор 'isd, для формати рования чисел с плавающей точкой в десятичном формате - спецификатор 'isf, а для форматирования чисел с плавающей то чкой в экспонен· циальном представлении - спецификатор 'ise. Числа в экспоненциальном пред· ставле нии указываются в следующей общей форме: x. dddddde +/-yy Спецификатор формата 'isg пред писывает классу Formatter использовать спецификатор 'isf или 'ise в зависимости от форматируемого значения и заданной точности , которая по умолчанию составляет 6 цифр. В следующем примере про­ граммы демонстрируется применение спецификаторов формата 'isf или 'ise: 11 Продемонс трировать применение спецификаторов 'f и 'е import java.util .*; class FormatDemo2 { puЫ ic static void ma iп (String args [] ) { Formatter fmt = new Formatter () ; for (douЫ e i=l .23; i < 1.Ое+6; i 100) fmt . format ("%f %е", i, i) ; System.out . println (fmt) ; fmt .close (); Эта программа выводит следующий результат: 1.230000 1.23 0000е+ОО 1.230000 l .230000e+OO 123 . 000000 1.230000е+02 1.230000 1.230000е+ОО 123 . 000000 1.230000е+02 12300 . 000000 1.230000е+04 Для вывода целых чисел в вос ьмеричном или шестнадцатеричном формате слу· жит спецификатор 'iso или 'isx соответственно. Например, выполнение следующе­ го фрагмента кода: fmt .format ( "Шестнадцатеричное число : %х, восьмеричное число : %о" , 196, 196) ;
Гл ава 1 9 . Пакет java.utiL, ч асть 11. П роч ие служеб ные классы 685 привод ит к выводу такого результата: Ше стнадцатеричное число : с4 , вос ьмерич ное число : 304 Для вывода чисел с плавающей точкой в шестнадцатеричном формате служит с пецификатор %а. На первый взгляд, форматирование с помощью спецификато­ ра % а может показаться не совсем обычным. Дело в то м, что для такого представ­ ления чисел используется форма, подобная экспоненциальному представлению и состоящая из шестнадцатеричной мантиссы и десятичного показателя в степе­ ни 2.Ниже приведен общий формат представления чисел с плавающей то чкой в ш естнадцатеричном виде. Oxl . sigpexp Здесь s ig содержит дробную часть мантиссы, ехр - показатель степени, а сим­ вол р обозначает начало показателя степени. Например, следующий вызов метода: fmt . forrnat("%а", 512.0); приводит к выводу такого результата: Oxl . Op9 Форматирование времени и даты Одно из наиболее эффективных преобразований форматируемых данных задае'J'­ ся с помощью спецификатора формата %t, который позволяет форматировать све­ дения о дате и времени. Спецификатор формата %t действует несколько иначе, чем другие спецификаторы, поскольку он требует указывать суффиксы для описания части или точности формата времени и даты. Суффиксы перечислены в табл. 19. 14. Например, чтобы вывести время в минутах, следует использовать спецификатор % tм, где М обозначает минуты в поле из двух символов. Аргументы , соответствующие спецификатору %t, должны иметь тип Calendar, Da te, Long или long. Табл ица 19.1,. Суффиксы формата времени и даты Суффикс Заменяется на а Сокращенное название дня недели А Полное название дня недели Ь Сокращенное название месяца В Полное название месяца с Стандартная строка даты и времени в таком формате: день месяцдатачч::мм:сечасовойJЮЯСгод С Первые две цифры года d День месяца в десятичном представлении (01-31) D месяц/день/ год е День месяца в десятичном представлении (1-31) F Формат «Год-месяц-день» h Сокращенное назван ие месяца в Часы (от 00 до 23) I Часы (от 01 до 12)
686 Часть 11. Б иблиоте ка Java Суффикс Заменяется на j День года в десятичном представлении (от 001 до 366) k Часы (от О до 23) 1 Часы (от 1до 12) L Миллисекунды (от ООО до 999) ш. Месяц в десятичном представлении (от 01 до 13) К Минугы в десятичном представлении (от 00 до 59) N Наносекунды (от 000000000 до 999999999) Окиwшши1 таi iя .19. 14 Р Региональный эквивалент времени до полудня (АМ) или после п олудня (РМ) в нижнем регистре Q Количество миллисекунд, прошедших с даты 01/01/1970 r чч:мм : се ( 12-часовой формат ) R чч:мм (24-часовой формат) S Секунды (от 00 до 60) s Количество миллисекунд, прошедших с даты 01/01/1970 в формате времени UTC Т чч :мм: се (24-ч асовый формат) У Год в десятичном п редставлении без указания столетия (от 00 до 99) У Го д в десятичном п редставлении, включая столетие (от 0001 до 9999) z С мещение относительно времени UТС Z Наименование часового пояса Ниже приведена программа, демонстрирующая применение некоторых фор­ матов даты и времени. 11 Форма тирование времени и даты import java . util .*; class TimeDateForma t { puЫ ic static void ma in (String args[] } { Formatter fmt = new Formatter (}; Calendar cal = Calendar .get!nstance (} ; 11 вывести время в стандартном 12-часовом форма те fmt . format ("%tr" , cal} ; System. out . println (fmt} ; 11 вывести все сведения о дате и времени fmt = new Formatter (} ; fmt . format ("%tc" , cal} ; System. out . println (frn t) ; 11 вывести толь ко часы и минуты fmt = new Forrnatter () ; frnt . forrnat ("%tl : %tM" , cal, cal} ; Systern. out . println (frnt} ; 11 вывести наз вание и номер ме сяца fmt = new Formatter (} ; fmt . forrnat("%tB %tb %trn", cal, cal, cal}; System. out . println (frn t} ;
Гл ава 19. Паке т java.utiL, часть 11. П роч ие служе б ные классы 687 fmt .close () ; Ниже приведен пример выполнения дан ной программы. 03 :15:34 РМ Wed Jan 01 15 :15:34 CST 2014 3:15 Janua ry Jan 01 Спецификаторы формата %n и %% Спецификаторы формата %n и %% отличаются от других тем, что они не со­ поставляются с аргументом, а представляют управляющие последовательности , вс тавля ющие символ в выводимую последовательность. В частности , специфика­ тор %n вставляет знак перевода строки , а спецификатор %% - знак процента. Ни оди н из этих символов не может быть введен непосредственно в форматирующую строку. Безусловно, чтобы вставить, например, знак перевода строки, можно так­ же воспользоваться стандартной управляющей последовательностью \n. В следующем примере программы демонстрируется применение спецификато­ ров формата %n и %%: 11 Продемон стрировать приме нение специфи каторов форма та \n и %\ import java . util . *; cla ss FormatDemo 3 { puЫ ic static void ma in (String args[]) Formatter fmt = new Formatter (); fmt .format ( "Копирование файла% nПередача завершена на %d%% ", 88) ; System. out . println (fmt) ; fmt .close () ; Эта программа выводит следующий результат: Копир ование файл а Передача за вершена на 88% Указание минимальной ширины поля Целое число, указан ное между знаком % и кодом преобразования формата, дей­ ствует в качестве спецификатора минималъной ширины поля, дополняющего выво­ ди мые дан ные пробелами, чтобы обеспечить определенную минимальную длину. Если символьная строка или число получается дл иннее этого указанного миниму­ ма, они будут выведены полностью. По умолчанию дополнение осуществляется п робелами. Если же выводимые данные требуется дополнить нуля ми, перед спец­ и ф икатором ширины поля следует указать О. Например, формат %05d означает до­ полнение нулями числа, состоящее менее чем из 5 цифр, чтобы его общая ширина была равна пяти знакам. Спецификатор ширины поля можно применять вместе с остальными спецификаторами формата, кроме %n.
688 Часть 11. Библиоте ка Java В следующем примере программы демонстрируется применение специф ика· тора минимальной ширины поля вместе со спецификатором преобразован ия % f: 11 Продемонстрировать приме нение спецификатора ширины поля import java . util .*; class Fo rma tDemo 4 { puЬlic static vo id ma in ( String args[] ) Formatter fmt = new Formatter () ; fmt .fo rmat (" l%fl%nl%12f l%nl%012f l", 10 . 12345, 10 .12345, 10. 12345) ; System. out . println (fmt) ; fmt .close (); Эта программа выводит следующий результат: 110.123450 1 1 10. 1234501 100010 .123450 1 В первой строке приведенного выше результата выводится число 10 , 12345 с шириной по умолчанию. Во второй строке выводится это же число в поле из 12 символов. А в третьей строке оно выводится в поле из 12 символов, дополняемом начальными нуля ми. Минимальный модификатор ширины поля часто используется для составле­ ния таблиц, состоящих из строк и столбцов. В следующем примере программы выводится таблица квадратов и кубов чисел от 1до 10. 11 Составить таблицу квадратов и кубов заданных чисел import java . util .*; class FieldWidthDemo { puЫic static void ma in (String arg s[] ) ( Fo rmatter fmt ; for (int i=l; i <= 10; i++) { fmt = new Fo rmatter () ; fmt . format ("%4d %4d %4d", i, i*i, i*i*i); System.out . println (fmt) ; fmt .close () ; Ниже приведен результат, выводимый данной программой. 1 1 1 2 4 в 3 927 41664 525125 636216 749343 в64512 981729 10 100 1000
Гл ава 19. Пакет java.utiL, часть 11. Прочие служеб н ы е КJ1ассы 689 Указание точности Спецификотор тичности можно применять вместе со спецификаторами форма­ та % f, %е, \g и \а. Он указывается после спецификатора минимальной ширины поля (если таковой имеется) и состоит из точки и целого числа. Его конкретное значение зависит от типа данных, к которому он применяется . Если спецификатор точности применяется к данным с плавающей точкой вме­ сте со спецификатором формата \f или \е, он определяет количество отображае­ мых десятичных цифр. Например, формат %10 . 4f означает вывод числа шириной не меньше 1 О знаков с четырьмя цифрами после запятой. Если же применяется спецификатор %9, то чность определяет количество значащих десятичных цифр. Точн ость по умолчанию составляет 6 цифр после запятой. Применительно к символьным строкам спецификатор точности задает макси­ мальную ширину поля . Например, формат %5 . 7s означает вывод строки длиной минимум пять символов, но не больше семи символов. Есл и же строка оказывается длиннее максимальной ширины, ко нечные символы отбрасываются. В следующем примере программы демонстрируется применение спецификато­ ра точности : 11 Продемонс трировать приме нение спецификатора точности import java.util . *; class Preci sionDemo ( puЫ ic static void ma in (String args [] ) Formatter fmt = new Fo rmatter (); // Форма т с четырьмя цифрами после десятичной точки fmt . forma t ( "% . 4f", 123.1234567); Systern.out . println (fmt) ; frnt .close () ; 11 Форма т с двумя цифрами после десятичной точки // в поле из 16 символов fmt = new Formatter () ; fmt . forma t ("%16 .2e", 123 . 1234567) ; System.out . println (fmt) ; fmt .close () ; // вывести максимум 15 симв олов из строки fmt = new Formatter () ; fmt . forma t ("%.15s", "Форма тировать в Java теперь очень пр осто. ") ; Systern.out . println (frnt) ; frnt.close(); Ниже приведен результат, выводимый данной программой. 123 .1235 1.23е+О2 Форматировать в Применени е при знако в формата В классе Formatter распознается ряд приз наков формата, с помощью которых можно управлять различными аспектами преобразования форматируемых данных.
690 Часть 11. Б иблиотека Java Все признаки формата обозначаются одн им символом, который указывается после знака % в спецификаторе формата. Признаки формата перечислены в табл. 19.15. Табл ица 19.15 . Признаки формата ПрИ3 нак • о nробе.п + Действие Выравнивание по левому краю Альтернативный формат п реобразования Вывод имые данные доп олняются нулями вместо п робелов Положительные числа выводятся с предшествующим пробел ом Положительные числа выводятся с предшествую щим знаком + Числовые значения содержат г рупповые разделители Отр ицател ьные число вые значения заключаются в круглые с кобки Признаки формата применяются не ко всем спецификаторам формата. В по­ следующих разделах они поясняются более подробно. Выравнивание вывод имы х да нны х По умолчанию все выводимые данные выравниваются по правому краю. Иными словами, если ширина поля больше, чем выводимые данные, то эти данные будут размещены по правому краю поля . Но выводимые данные можно выровнять и по левому краю , указав знак "минус" сразу после знака %. Например , формат %-10 .2f обозначает выравнивание по левому краю числа с плавающей точкой и двумя циф· рамп после десятичной точки в пределах поля из 1 О символов. Рассмотрим в каче· стве примера следующую программу: 11 Продемонс трировать выравнивание по левому кр аю import java .util .*; class Le ftJustify { puЫic static void maiп ( Striпg args[)) { Formatter fmt = new Formatter ( ); 11 выровнять по правому краю (по умо лчанию ) fmt . format (" 1%10 .2f1 ", 123 .123) ; System. out . println (fmt) ; fmt .close (); 11 а теперь выровнять по левому краю fmt = new Fo rmatter (); fmt . format (" l%-10 .2f l", 123 .12 3) ; System. out . println (fmt) ; fmt .close {) ; Ниже приведен результат, выводимый данной программой. Как видите , вто рая строка результата выровнена по левому краю в пределах поля из 10 символов. 1 123 .12 1 1123 .12 1
Гл ава 19. Пакет java.uШ, часть 11. Прочие служебные кл ассы 691 Признаки пробела, +, о и ( Чтобы выводить знак + перед положительными числовыми значениями , достаточ­ но указать признак + . Например, в результате выполнения следующей строки кода: fmt . format ( "%+d", 100); выводится такая строка: +100 При составлении столбцов из чисел иногда удобно выводить пробел перед по­ ложительными числами, чтобы положительные и отрицательные числовые зна­ чен ия выводились в одном столбце . Чтобы добиться этого, достаточно указать признак пробела, как показано в приведенном ниже примере программы. 11 Продемонстрировать приме нение пробела в качестве 1 1 спецификатора форма та impo rt java .util .*; cla ss FormatDemoS { puЫ ic static void ma in (String args []) Formatter fmt = new Fo rmatter () ; fmt . format ("% d", -100) ; System. out . println ( fmt-) ; fmt .close () ; fmt = new Formatter () ; fmt . format ("% d", 100); System.out . println (fmt) ; fmt .close () ; fmt = new Formatter (); fmt . format ("% d", -200); System.out . println (fmt) ; fmt .close () ; fmt = new Formatter () ; fmt . format ("% d", 200); System.out . println (fmt) ; fmt .close () ; Ниже приведен результат, выводимый данной программой. Обратите внима­ ние на то , что положительные значения в данном результате имеют начальный пробел , что обеспечивает ровное расположение разрядов в столбце . - 100 100 -200 200 Чтобы вывести отрицательные числовые значения в скобках вместо началь­ ного минуса, достаточ но указать признак ( .Например, в результате выполнения следующей строки кода : fmt . forma t ("% (d", -100) ; выводи тся такая строка : (100)
692 Часть 11. Б и бл иотека Java А если указать признак О, то выводи мые данные дополняются нулями вместо пробелов . Признакзапятой При выводе больших чисел зачастую удо бно указывать разделители групп, котор ыми в англоязычной среде являются запятые. Например, числовое зна­ чение 1 23456 7 легче читается, когда оно отформатировано в следующем виде: 1,2 3 4,5 67. Для указания спецификаторов группирования служит признак запя­ той ( , ). Например, в результате выполнения следующей строки кода: fmt . forma t ("%, .2f", 4356783497.34) ; выводится такая строка: 4, 356, 783, 497 . 34 П ризнак # Этот признак может применяться вместе со спецификаторами формата %0, %х, %е и %f. Для спецификаторов формата %& и %f признак * обеспечивает налич ие де­ сятичной точки даже в том случае , если отсутствует дробная часть числа. Так , если перед спецификатором формата %х указать признак f, то шестнадцатеричное чис­ ло будет выведено с префиксом Ох. Если же указать признак * перед спецификато­ ром формата %0, вос ьмеричное число будет выводиться с начальным нулем. П ропис ные формы специфи каторов формата Как упоминалось ранее, некоторые спецификаторы формата имеют пропис· ные формы, которые предписывают употреблять прописные буквы при преобра­ зовании там , где это возможно. В табл . 19. 16 описывается действие прописных форм спецификаторов формата. Табл ица 19.16. Прописные формы спецификаторов формата Спецификатор Действие %А %8 %Е %G %9 %S %Т %Х Шестнадцате ричные цифры от а до f выводятся прописными буквами, т.е. от Адо F. Кроме того, префикс Ох отображается как ОХ, а показатель степени р-как Р Логические значения true и false выводятся прописными буквами Знак экспоненты е выводится прописной буквой Знак экспоненты • выводится прописной буквой Шестнадцатеричные цифры от а до f выводятся прописными буквами, т.е. отАдоF Со от ветствующая символьная строка выводится прописными буквами Весь алфавит выводится прописными буквами Шестнадцатеричные цифры от а до f выводятся прописными буквами , т.е. от Адо F. Кроме того, префикс Ох отображается как ОХ, если он присугст вует
Гл ава 1 9 . Пакет java.utН, ч асть 11. П р очие служе б ные классы 693 Например, в результате выполнения следующей строки кода: fmt . format ("%X" , 250) ; выводится такая строка: РА А выполнение следующей строки кода: fmt . format ( "%Е" , 123 .1234) ; приводит к выводу такой строки: 1.231234Е+02 Применение индекса аргумента В состав класса Fo rmatter входит очень удо бное средство, позволяющее ука­ зать аргумент, к которому должен применяться конкретный спецификатор фор­ мата . Обычно порядок следо вания аргументов и спецификаторов формата сопо­ ставляется слева направо. Это означает, что первый спецификатор формата от­ носится к первому аргументу, второй - ко второму аргументу и т.д . Но, используя индекс ар rу.мента, можно явно управлять сопоставлением аргументов со специфи­ каторами формата. Индекс аргумента указывается после знака % в спецификаторе формата. Он имеет следующий вид: n$ где п обозначает индекс нужноrо аргумента, начиная с 1. Рассмотрим следующий пример кода: fmt . forma t ("%3$d %1$d %2$d", 10, 20, 30) ; В результате выполнения этого кода выводится такая строка: 301020 В да нном примере первый спецификатор формата соответствует 3 0, второй - 10, а третий - 20. Следовател ьно , аргументы используются в порядке , отличаю­ щемся от строгого порядка следования слева направо. Одно из преимуществ индексирования аргументов заключается в том , что оно позволяет повторно использовать аргумент, не указывая его дважды. Например , в результате выполнения следующей строки кода: fmt.format ("Ч иcлo %d в шестнадцатеричном форма те равно %1$х" , 255) ; выводится приведенный ниже результат. Как видите , аргумент 255 используется с обои ми спецификаторами формата. числ о 255 в шестнадцатеричном форма те равно ff Существует удо бное сокращение, которое называется относитмънъtм индексом и позволяет повторно использовать аргументы, совпадающие с предшествующим спецификатором формата. Для этого достаточно указать знак < вместо индекса
69' Часть 11. Библиотека Java аргумента . Н апример, следующий вызов метода forma t () приводит к такому же результату, что и в предыдущем примере кода : fmt . forma t ("%d в ше с тнадцатеричном форма те равно %<х" , 255) ; Относительные индексы особенно удобны при создании пол ьзовательских форматов времени и даты. Рассмотрим следующий пример программы: 11 Исполь зовать относитель ные индексы, чтобы упростить 11 создание поль зовательских форма тов даты и времени import java.util .*; class FoпnatDemo б { puЫ ic static void ma in (String args[] ) { Foпnatter fmt = new Foпnatter () ; Calendar са! = Calendar . ge tinstance (); fmt .format ( "Today is day %te of %<tB, %<tY" , cal) ; System.out . println (fmt) ; fmt .close () ; Ниже приведен пример выполнения данной программы. Благодаря относи· тельной индексации аргумент cal может быть передан только один раз вместо трех. Today is day 1 of January, 2011 Закрытие о бъекта типа Formatter Обычно объект типа Formatter следует закрывать, когда завершается его нс· пользование. Благодаря этому освобождаются все используе мые им ресурсы, что очен ь важно не только при форматировании данн ых , выводимых в файл, н о и в других случаях. В приведенных выше примерах демонстрировался один из спо­ собов закрыть объект типа Formatter, который состоит в том, чтобы вызвать явным образом метод close ().Но н ачиная с версии JDК 7 класс Formatter реа· лизует интерфейс AutoCloseaЫe. Это означает, что он поддерживает оператор try с ресурсами. Применяя такой подход, можно автоматически закрывать объект ти па Formatter, когда он больше не нужен . Применение оператора try с ресурсами для обращения с файлами описывает­ ся в главе 13, поскольку файлы относятся к одним из н аиболее часто используемых ресурсов, которые следует закрывать. Но те же осно вные принципы распростра· няются и на закрытие объекта типа Fo rrnatter. Ниже приведен первый пример применения объекта типа Fo rrnatter, переделанный с учетом автоматического управления ресурсами. 11 Использование автоматиче ского управления ресурсами 11 для закрытия объекта типа Fora a tter import java .util .*; class Fo rma t Demo { puЬl ic static void ma in (St riпg arg s[]) { try (Form.atter fmt = new Fo rm.atter () )
Гл ава 1 9 . Пакет java.utiL, часть 11. П роч ие служебные кл ассы 695 ( fmt . format ( "Форматировать %s пр осто %d %f", "средствами Java", 10, 98 . 6) ; System.out . println (fmt) ; Эта версия программы выводит такой же результат, как и прежняя ее версия. Аналог функции printf О в Java Хотя в непосредственном применении класса Formatter, как это делалось в пре­ дыдущих примерах, нет ничего формально неверного, для форматирования данных, вьmодимых на консоль, имеется более удо бная альтернатива в виде метода print f (). Этот метод автоматически использует класс Formatter для создания отформатиро­ ванной строки. Затем он <УПiрааляет эту строку в стандартный поток вывода System. out, который по умолчанию представляет консоль. Метод print f ( ) определен в обо­ их классах, Pr intStream и Pr in tWri ter, и подробно рассматривается в гл аве 20. Класс Scanne r Класс Scanner служит дополнением класса Formatter. Он читает отформати­ рованные вводимые данные и преобразует их в двоичную форму. Класс Scanner можно применять для чтения данных, вводимых с консоли, из файла, символь­ ной строки или из другого источника, реализующего интерфейс ReadaЫe или Re adaЫeByteChanne l. Например, класс Scanner можно использовать для чте­ н ия числа с клавиатуры и присвоения его значения переменной. Как будет пока­ зано далее , класс Scanne r очень прост в употреблении, несмотря на то , что он обладает развитыми фун кциональными возможностями. Ко н структоры кл а сса Scanner В классе Scanne r определяются конструкторы, перечисленные в табл. 19.17. В общем, объект класса Scanner может быть создан для объекта класса String, Input Stream, Fi le или объекта любого другого класс<1;, реализующего интерфейс ReadaЫ e либо Re adaЫeByteChannel. Ниже приведены некоторые тому примеры. В следующем фрагменте кода создается объект типа Scanner для чтения дан­ ных из файла Test. txt: FileReade r fin = new Fi leReader ( "Test .txt" ); Scanner src = new Scanner (fin) ; Этот код оказывается работоспособным потому, что класс FileReade r реали­ зует интерфейс ReadaЫe. Следовательно, вызов конструктора инте рпретируется кaк Scanner (ReadaЬ le ). В следующей строке кода создается объект типа Scanner для чтения данных из стандартного потока ввода , которым по умолчанию является клавиатура: Scanner conin = new Scanne r (System . iп ) ;
696 Часть 11. Библиоте ка Java И этот код оказывается работос пособным потому, что стандартный поток вво­ да System. in относится к типу Inp utStream. Следовательно, вызов конструктора преобразуется в вызов Scanne r ( Inp utStream) . В приведенном ниже фрагменте кода создается объект типа Scanner для чте· ния данных из символ ьной строки. String instr = "10 99 .ВВ сканировать очень просто ."; Scanne r c o nin = new Scanne r (instr) ; Табл ица 19.17. Ко н структоры класса scanner Конструктор Soan n er (File �) throwa FileNotFouncl l:x ception Scan n er (File �, String нобор_ СUМВО/Ю8) throwa FileNotFouncli:x08ption Soan n er (Inputs trea a orтty0 0 ) Soan n er ( Inputstreaш �. String нобор_ С'U.М80. ll08 ) Soan ne r(Path �) throwa IOException Soan n er (Path �. String нобор_СUМВО/Ю8) throwa IOZXception Scan ne r (R8ac:laЬle ormtyO O ) Soan n er (ReadaЬleВyteCha nn el tт т)1да ) Soan n er (Reac:laЬldyteChan ne l оrпкуда, String нО Оор _сuмволов) Soan n er (StrinCJ °" "')'до ) Описание Создает объект типа Scan ne r, использующий указан· ный файл ormtyдa в качесrве исrочника для ввода дан· ных Создает объект типа Scan n8 r, использующий указан­ ный файл orтtyO O c эаданной кодировкой нобор_ СUМ80.l l08 в качесrве исrочника для ввода данных Создает объект типа Scan n er, использующий указан· ный поток ввода � в качесrве источника для ввода данных Создает объект типа Soan n er, использующий указан· ный поток ввода � с эаданной кодировкой набор_ симвмов в качесrве исrочника для ввода данных Создает объект типа Scan n er, использующий ука· эанный файл �вкачесrве исrочника для ввода данных Создает объект типа Scan n er, использующий указан­ ный файл откус}ос эаданной кодировкой нобор_ СUМ80Jl0 8 в качесrве исrочника для ввода данных Создает объект типа Soan n er, использующий указан­ ный объект ormtyдa типа Reac:laЬle в качесrве исто чника для ввода данных Создает объект типа Scan n er, использующий указан­ ный объект откус}о типа ReadaЬleВyteChan nel в каче­ стве источника для ввом данных Создает объект типа Scan ne r, использующий указан­ ный объект ormtyдa типа Reac:laЬleВyteCha nne lсзадан· ной кодировкой нобор_ СUМ80/ /08 в качесrве исrочника для ввода данных Создает объект типа Scan n er, использующий указан­ ную символьную сrроку °" "')'да в качесrве исrочника для ввода данных Основы ска ниро в а ния Как только объект типа Scanner будет создан , его очень просто использовать для чтения отформатированных вводимых данных. В общем, объект типа Scanne r
Гл ава 1 9. Пакет java.utiL, часть 11. П рочие служе б ные класс ы 697 читает лексемы из некоторого базового источника, указываемого при создании этого объекта. С точки зрения класса Scanner лексема - это порция вводимых дан­ ных, разграничиваемая рядом раздел ителей, которыми по умолчанию являются пробелы. Лексема читается по совпадению с конкретным регулярн:ы.м выражением, за­ дающим формат данных. И хотя класс Scanner позволяет определить конкретный тип регулярного выражения для совпадении в следующей операции ввода, в состав этого класса входит немало предопределенных шаблонов для сопоставления с эле­ ментарными типами вроде int и douЫe, а также символьными строками. Иными словами, указывать шаблоны для сопоставления , как правило, не требуется. Таким образом , для применения класса Scanner необходимо придерживаться следующей процедуры: • Определить, доступен ли конкретный тип вводимых данных, вызвав одним из методов типа Scanne rhasNextX, где Х- требуемый тип данных. • Если вводимые данные досrупны, прочитать их одним из методов типа Scanne rnextX. • Повторить пп. 1 и 2 вплоть до исчерпания вводимых данных. • Закрыть объект типа Scanner, вызвав метод close () . Как упоминалось выше, в классе Scanne r определяются два ряда методов, ко­ торые позволяют читать вводимые данные. К первому ряду относятся методы ти па hasNextX, перечисленные в табл. 19.18. Эти методы определяют, доступен ли указанный тип ввода. Например, в результате вызова метода hasNext int () воз­ вращается логическое значение true только в том случае, если следующая чита­ емая лексема оказывается целым числом. Если же требуемые данные доступны, они читаются од ним из методов типа Scanne rnex tX, перечисленных в табл. 19.19. Например, чтобы прочитать следующее целое число, следует вызвать метод nex­ t int (). В приведенном ниже фрагменте кода показано, каким образом организу­ ется чтение списка целых чисел, вводимых с клавиатуры. Scanner conin = new Scanner ( System. in) ; int i; 11 читать списох целых значений whi le (con in . hasNextlnt () ) { i = conin .nextlnt () ; 11 ... В этом фрагменте кода цикл whi le прерывается, как только следующая лексема не оказывается целым числом. Следовател ьно, чтение целых чисел в цикле пре­ кращается , как только в потоке ввода обнаруживается значение, отличное от це­ лочисленного ти па данных. Если метод типа next не в состоянии найти тип данных, который он ожидает, то генерируется исключение ти па InputMi smatchException. А исключение типа N o S u chElementException передается в том случае, если доступные для ввода дан­ ные исчерпаны. Поэтому сначала лучше проверить с помощью метода типа has­ N e xt, что данные требуемого типа доступны, прежде чем вызывать соответствую­ щий ему метод типа next.
698 Часть 11. Библиотека Java Табл ица 19.18. Методы типа hasNext и э класса Scanner Метод Ьoolean hasNext О Описанttе Возвращает логическое значение true, если для чтения дсr ступна следующая лексема любого типа, а иначе - логическое значение fal ae Ьoolean haaNext (Pattern Возвращает логическое значение true, если для чтения дсr шаWюн) ступ на лексема, совпадающая с заданным шаблонам , а иначе - логическое значение false Ьo o lean hasNext (Strinq шаблон) Ьoolean hasNextвigDeciJU.l () Ьoolean hasNextвiqinteqer () Ьoolean hasNextвiginteqer (int оенованuе) Ьoolean haaNextвo o lean () Ьoolean hasNextвyte () Возвращает логическое значение true, если для чтения дсr ступна лексема, совпадающая с заданным шаблонам , а иначе - логическое значение false Возвращает логическое значение true, если для чтения до­ ступно значение, которое можно разместить в объекте типа BiqDecil lla l, а иначе -логическое значение false Возвращает логическое значение true, если для чтения до­ ступно значение, которое можно разместить в объекте типа Biginteger, а иначе -логическое значение false. ( По умол· чанию используется основание 10.) Возвращает логическое значение true, если для чтения до­ ступно значение по указанному основанию, к<УГорое можно разместить в объекте типа Вiqinteger, а иначе - логическое значение false Возвращает логическое значение true, если для чтения до­ ступ но значение типа Ьoolean, а иначе - логическое значение fal se Возвращает логическое значение true, если для чтения до­ ступно значение типа Ьуtе, а иначе - логическое значение fal•• Ьoolean hasNextвyte (int Возвращает логическое значение true, если для чтения дсr основани е ) ступно значение типа Ьуtе по указанному основани ю , аиначе­ лоrическое значение false Ьoolean hasNextDouЬle () Ьoolean hasNextFloat () Ьoolean hasNextint () Ьo o lean hasNextint (int основани е ) Ьoolean hasNextLine () Возвращает логическое значение true, если для чтения до­ ступно значение типа douЬle, а иначе - логическое значение false Возвращает логическое значение true, если для чтения дсr ступно значение типа float, а иначе - логическое значение false Возвращает логическое значение true, если для чтения до­ ступно значение типа int, а иначе - логическое значение falи (По умолчанию используется основание 10.) Возвращает логическое значение true, еслидля чтения дсr ступно значение типа int по указанному OCНOtJaН UIO , аиначе- логическое значение :falи Возвращает логическое значение true, если для чтения дсr ступна строка вводимых данных
Глава 1 9. Пакет java.utН, часть 11. Прочие служебные классы 699 Метод Ьo o lean ha8NextLonq () 0кUltЧaH'Шi matiJt. 19.18 Описание Возвращает логическое значение true, если для чтения до­ ступно значение типа lon9, а иначе - логическое значение false (По умолчанию используется основание 10.) Ьo o lean ha8NextLon9 (int Возвращает логическое значение true, еслидля чтения дo- lonq основани е ) ступно значение типа lonq по указанному основанию, а иначе - логическое значение falи Ьo o lean hasNextзhort () Ьo o lean Ьa1Nextзhort (int основтше) Возвращает логическое значение true, если для чтения до­ ступно значение типа short, а иначе - логическое значение falи (По умолчанию используется основание 10.) Возвращает логическое значение true, если для чтения до­ ступно значение типа short по указанному основш ш ю, а ина­ че - логическое значение false Табл ица 19.19. Методы типа next иэ класса Scanner Метод Strinq next () Strinq next (Pattern шаблон) Strinq next (Strinq шаблон) Вigl)eoil ll& l nextвie#)eciшal () Biqinteqer nextвiqinteqer () Biginteqer nextвiqint:eqer (int ооwвание) Ьo o lean nextвoolean ( ) Ьу1:8 nextвyte () Ьу1:8 nextвyte (int основтше) douЫe nextDouЫe ( ) float nextl'loat () int nextint () int nextint (int ооwвание) Strinq nextLine () Описание Возвращает следующую лексему любого типа из источника ввода данных Возвращает следующую лексему, совпадающую с указанным шабдтю.м, из источника ввода данных Возвращает следующую лексему, совпадающую с указанным иш6лтюм, из источника ввода данных Возвращает следующую лексему в виде объекта типа BiC)Decil ll& l Возвращает следующую лексему в виде объекта типа Biqinteger. (По умолчанию используется основание 10.) Возвращает следующую лексему в виде объекта типа Biqinteqer по указанному основанию Возвращает следующую лексему в виде значения типа Ьoolean Возвращает следующую лексему в виде значения типа Ьуtе. (По умолчанию используется основание 10.) Возвращает следующую лексему в виде значения типа byte по указанному основанию Возвращает следующую лексему в виде значения типа douЬle Возвращает следующую лексему в виде значения типа float Возвращает следующую лексему в виде значения типа int. (По умолчанию используется основание 10.) Возвращает следующую лексему в виде значения типа int по указанному основани ю Возвращает следующую строку вводимых данных
700 Часть 11. Библиотека Java Метод Описание Окончание та6л. 19.19 lonq nextLonq О Возвращает следующую лексему в виде значения типа lonq. (По умолчанию используется основание 10.) lonq nextLonq (int основани е ) short nextShort () short nextShort (int основание) Возвращает следующую лексему в виде значения типа lonq по указанному основш ш ю Возвращает следующую лексему в виде значения типа short. (По умолчанию используется основание 10.) Возвращает следующую лексему в виде значения типа short по указанному основани ю Н екоторые прим еры прим енения кл а сса Scanner Класс Scanne r позволяет существенно упростить решение задачи, которая в проти вном случае оказалась бы трудоемкой . Рассмотрим несколько примеров его применения. В следующей программе подсчитывается среднее из списка чи· сел, введенных с клавиатуры: // Исполь зовать кла сс Scan n er для вычи сления среднего 11 из списка введенных число вых значений import java.util .*; class AvgNums { puЫ ic static void ma in (String args [] ) { Scanne r conin = new Scanner (System.in) ; int count = О; douЫ e sum = О.О; System. out . println ("B вeдитe числа дл я подсчета среднего .") ; 11 читать и суммировать ч Исловые значения while (conin . hasNext ()) { if (conin . hasNextDouЬle () ) { sum += conin . nextDouЬle () ; cou nt++; } else { String str = conin . next () ; if (str . equals ("г oтoвo" )) break; else { System. o ut . println ( "Oшибкa форма та данных.") ; return ; conin .close () ; System.out . println ( "Cpeднee равно "+sum / count ); Эта программа читает числа с клавиатуры и суммирует их до тех по р, пока поль­ зователь не введет символьную строку " готово ". В таком случае она прекращает ввод и выводит среднее значение введенных чисел. Ниже приведен пример вы· полнения данной программы.
Гл ава 1 9 . Пакет java.uШ, часть 11. П рочие служеб ные кл ассы 701 введите числа для подсчета средне го . 1.2 2 3.4 4 готово Среднее равно 2.65 Рассматриваемая здесь программа читает числа до тех пор, пока не получ ит лексему, которую нельзя интерпретировать как достоверное значение типа dou­ Ыe. Когда это происходит, она проверяет, соответствует ли введенная лексема си мвольной строке " готово " . Если она соответствует этой строке , то программа завер шается нормально. В п роти вном случ ае она выводит сообщение об ош ибке. Обрати те внимание н а то, что числ а читаются путем вызова метода next Dou­ Ыe ( ) . Этот метод читает любые числа, которые могут быть приведены к типу douЫe, включая целые значения вроде 2 и значения с плавающей точ кой , подоб­ ные З.4 . Следовательно , число, прочитанное методом nextDouЫ e (),не требует наличия десятичной точки . То т же общий принцип действует во всех остальных методах типа ne х t. Они обнаружат совпадение и прочитают данные в любом фор­ мате, который может представлять данные запрашиваемого типа. Еще одна примечател ьная особенность класса Scanner состоит в том, что одну и ту же методику можно применять для чтения данных из разных источников. В качестве примера ниже представлен а версия предыдущей программы, переде­ ланная для вычисления среднего из списка чисел , вводимых из файла. 11 Исполь зовать кл асс Scan n er для вычисления средне го 11 из списка чисел , вводимых из файла import java .util .*; import java.io. *; class Av gFi le { puЫ ic static void ma in (Striпg args[] ) throws IOException { iпt couпt = О; douЫ e sum = О.О; 11 вывести данные в файл Fi leWriter fout = new Fi leWri ter ("test . txt" ); fout .write {"2 3.4 5 6 7.4 9.1 10 .5 готово" ); fout .close (); FileReader fin = new FileReader{ "Test . txt" ); Sсаппеr src new Scanner {fin) ; 11 читать и сум ми ровать число вые значения whi le (src.hasNext {) ) { if {src . hasNextDouЬle {) ) { sum += src . nextDouЬle {) ; count++; } else { String str = src.next () ; if (str . equal s("гoтoвo" )) break; else { System.out . println ("OшиCiкa форма та файл а.") ; returп ;
702 Часть 11. Библиотека Java src. close (); System. out . println ( "Cpeднee равно "+sum / count ); Ниже приведен результат, выводимый данной программой. Среднее равно 6.2 Данный пример программы иллюстрирует другую важную особенность клас· са Scanne r. Обратите внимание на то , что поток чтения из файла , доступ ный по ссылке fin, не закрывается непосредственно. Вместо этого он автоматически за· крывается , когда объект src вызывает метод close (). Когда закрывается объект типа Scanne r, связанный с ним объект типа ReadaЬle также закрывается (если он реализует интерфейс CloseaЬle). Поэтому в данном случае файл, доступный по ссылке fin, автоматически закрывается вместе с объектом src. Начиная с версии JDK 7 класс Scanner реализует также интерфейс Au toCloseaЫe. Это означает, что объектом этого класса, представляющим ска· нер, можно управлять в блоке оператора try с ресурсами. Как пояснялось в главе 13, когда используется оператор try с ресурсами, сканер автоматически закрыва· ется в конце блока этого оператора. Так , в приведенном выше примере програм· мы можно было бы организовать управление объектом src следующим образом: try ( Scanner src = new Scanne r (fin ) ) { 11 читать и суммировать числа while (src . hasNext ()) { if (src . hasNextDouЬle () ) { sum += src . nextDouЬle (); count++ ; } else { String str = src.next () ; if (str . equal s("гoтoвo" )) break; else { Systern. out . println ( "Oшибкa форма та файла.") ; return; С целью продемонстрировать закрытие объекта типа Scanne r в приведенных далее примерах программ метод close () вызывается явным образом. Это позволя· ет также скомпилировать их в версияхJаvа, предшествующихJDК 7. Но оператор try с ресурсами упрощает прикладной код и позволяет предотвратить возможные ошибки , поэтому его рекомендуется применять при написании нового кода. Следует также заметить, что ради краткости примеров программ , представ.лен· ных в этом разделе, исключения , возникающие при вводе-выводе, просто генери· руются в теле метода ma in ().Н о в реальном коде они, как правило, обрабатьmа· ются непосредственно. Класс Scanne r можно использовать для чтения разнотипных вводимых дан· ных, даже если порядок их следования заранее неизвестен. Для этого достато чно выяснить, какого типа данные доступны, прежде чем их читать. Рассмотрим в ка· честве примера следующую программу:
Гл ава 19. Паке т java.utН, часть 11. Прочие служебн ы е кл ассы 703 11 Исполь зовать кл асс Scan n er дл я чтения разнотипных данных из файла import java .util .*; import java.io.*; class Scanмixed { puЫ ic static void main (String args[] ) throws IOException { int i; douЫ e d; Ьооlеап Ь; String str; 11 вывести данные в файл FileWriter fout = пеw Fi leWriter ("test . txt ") ; fout .write ( "Tecтиpoвaниe Scanner 10 12 .2 о дин true два false" ); fout .close () ; FileReader fin = new FileReader ( "Test . txt") ; Scanner src = пеw Scanner (fin) ; 11 читать данные до конца файла while (src. hasNext ()) { if (src . hasNextint () ) { } i = src.ne xtint (); System. out . println ("int : "+i) ; else if (src.hasNextDouЬle () ) } d = src.nextDouЬle (); System. out . println ( "douЫe : "+d) ; else if (src . hasNextBoolean () ) { 1 Ь = src.nextBoolean () ; System. o ut . println ( "boolean : "+Ь) ; else { str = src.next(); System. out . println ("String : "+str) ; src . close (); Ниже приведен результат, выводимый данной программой. String : Тестирование String: Scanner int: 10 douЫe : 12 .2 String : один boolean : true String : два boolean : false При чтении разнотипных данных, как в данном примере программы , следует вн имательнее следить за порядком, в котором вызываются методы next. Та к, если поменять в цикле порядок вызова методов nextlnt {) и nextDouЫe (), то оба числовых значения будут прочитаны как относящиеся к типу douЬle, поскольку метод ne xtDouЫe {) обнаруживает совпадение с любой символьной строкой , со­ держащей число, которое может быть представлено типом douЫe.
70' Часть 11. Библ иотека Java Уста новка раздел ител е й Для того чтобы определить, где начинаются и оканчиваются лексемы, в классе Scanne r применяются разделители. По умолчанию в качестве разделителей выби· раются пробельные символы , как было показано в предыдущих примерах. Но тип разделителей можно изменить, вызвав метод useDelimi ters (), общие формы ко­ торого приведены ниже. Scan n er useDelimi.ter (String иабпон) Scan n er useDe limi.ter (Pattern •абпан) Здесь параметр ша блон обозначает регулярное выражение, определяющее на· бор разделителей. В качестве примера ниже приведена переделанная версия пред · ставленной ранее программы для чтения из списка чисел, разделя емых запятыми и любым количеством пробелов. 11 Исполь зовать класс Scan n er дл я вычисления среднего 11 из списка чисел , разделяемых запятыми import java .util .*; import java.io.*; class SetDelimiters puЫ ic static vo id ma iп (String args[] ) throws IOException { int count = О; douЫ e sum = О.О; 11 вывести данные в файл Fi leWriter fout = new FileWri ter ("test .tx t") ; 11 а теперь сохранить данные в списке , разделив их запятыми fout .write ("2, 3.4, 5, 6, 7.4, 9.1, 10 .5, готово" ); fout .close () ; FileReader fin = new Fi leReade r ("Test . txt " ); Scanner src = new Scanner (fin) ; // ус тановить в качестве разделителей запятые и пробелы src . useDelimi ter (", *") ; // читать и суммировать число вые значения while ( src. hasNext ()) { if (src . hasNextDouЬle () ) { sum += src . nextDouЬle () ; count++; } else { String str = src . next(); if (str . equals ( "гoтoвo" )) Ьreak; else { src . close (); System.out .pr intln ("Oшибкa форма та файла. ") ; return ; System.out . println ( "Cpeднee равно " + sum / count} ; В этой версии программы числа, записанные в файл test . txt, разделяются за· пятыми и пробелами. Ука занный шаблон разделителей 11 , *11 предписывает объ·
Гл ава 1 9 . Пакет java.utiL, часть 11. Прочие служе б н ы е кл ассы 705 екту типа Scanner воспринимать наличие запятой, отсугствие ил и наличие про­ белов во вводимых данных как разделители. Эта версия программы выводит такой же резул ьтат, как и предыдущая ее версия. Текущий шаблон разделителей можно получить, вызвав метод del imi ter (). Ниже приведена общая форма этого метода . Pattern delimiter () П рочие с редства кл а сса Scanner В классе Scanne r определяется ряд других методов , помимо упомянугых ранее. В частности, од ним из наиболее полезных в некоторых случаях является метод find inLine ( ) . Его общие формы представл ены ниже. Strinq findinLine (Pattern 1raб.lraи) Strinq findinLine (Strinq .аrа б.паи) Этот метод осуществляет поиск на совпадение с указанным шаблоном в следу­ ющей строке текста . Есл и совпадение обнаружено, то соответствующая этому ша­ блону лексема употребляется и возвращается. В противном случае возвращается пустое значение nu ll. Это метод действует независимо от установленного набора разделителей. Он уд обен , если требуется обн аружить совпадение с конкретным шаблоном. Та к , в следующем примере программы сначала обнаруживается поле воз раста во введенной символьной строке , а затем выводится его содержимое. 11 Продемон стрировать приме нение ме тода findinLine () import java .util .*; class Findi nLineDemo { puЫic static void ma in (String args [] ) { Striпg instr = "Имя : Том Возраст : 28 ID: 77"; Scanner conin = new Scanne r (instr) ; 11 найти поле возраста и вывести его содержимое conin . find inLine ("Boэpacт :") ; if ( conin .hasNext () ) System. o ut . println (conin . next () ); else System.out . println ( "Omибкa !") ; conin .close () ; В резул ьтате выполнения этой программы будет выведено знач ение 28. В этой программе метод findinL ine ( ) вызывается для поиска совпадения с шаблоном "Возраст : ". Как только совпадение будет обнаружено, читается следующая лексе­ ма со значением из поля возраста . С методом findinLine ( ) связан метод findWi thinHorixon ().Н иже приведены общи е формы его объявления. Strinq findWithinВorizon (Pattern 1rаб.пон , int .a:omrvec!l'&O) Strinq findWithinВori zon (Strinq •аблон , int жomrvec!l'ao) Этот метод пытается обнаружить совпадение с указанным шаблоном в после­ дующем количе стве символов . При уд ачном исходе метод findWi thinHorixon () возвращает результат совпадения с шаблоном, а иначе - пустое значение nu ll.
706 Часть 11. Библиотека Java Если параметр количе ство принимает нулевое значение , то поиск осуществляет­ ся во всех вводимых данных до тех пор, пока не будет обнаружено совпадение или достигнут конец вводимых данных. Чтобы пропустить шаблон, достаточно вызвать метод skip ().Ниже приведе· ны общие формы его объявления. Scan n er skip (Pattern шаблон) Scan n er skip (Strinq шаблон) Если обнаружено совпадение с заданным шаблоном, метод skip ( ) просто пропускает его и возвращает ссылку на вызывающий объект. Если же совпаде­ ние с шаблоном не обнаружено, метод skip () генерирует исключение типа NoSuchElementException. В классе Scanne r имеется также метод radix (), возвращающий основание си· стемы счисления по умолчанию, используемое в этом классе для чтения чисел; метод useRadix (), задающий основание системы счисления; метод reset (),устанавлива· ющий сканер в исходное состояние; а также метод close ( ) , закрывающий сканер. Клacc ы Re sourceBundle , Lis tRe sourceBundle и PropertyRe sourceBundle В состав пакета j а va . и t i l входят три класса, предназначенные для интерна· ционализации прикладных программ. Первым из них является абстрактный класс Re sourceBundle. В нем определяются методы, позволяющие управлять коллекци· ей ресурсов, зависящих от региональных настроек, например , символьных строк, используемых в качестве меток в элементах пользовательского интерфейса при· кладных программ. Для этого можно определить два или более набора символьных строк, переведенных на разные языки (например, английский, немецкий или ки· тайский) , причем каждый набор переведенных строк будет находиться в отде л ьном комплекте ресурсов. Тр ебующийся комплект ресурсов можно затем загружать в со­ ответствии с текущими региональными настройками и использовать переведенные строки для построения пользовательского интерфейса прикладной программы. Комплекты ресурсов обозначаются именем семейства, называемым иначе ба· зовъtМ именем. К имени семейства может быть добавлен двухс имвольный код язы­ ка, обозначающий конкретный язык. В этом случае испол ьзуется данная версия комплекта ресурсов, если запрошенные региональные настройки соответствуют коду языка. Например, комплект ресурсов под именем семейства Samp l eRB может иметь немецкую версию SampleRB_de и русскую версию SampleRB_ru. (Обратите внимание на то , что знак подчеркивания связывает имя семейства с кодом языка.) Та ким образом, будет применяться комплект ресурсов SampleRB _de , если Locale . GERМAN -текущие региональные настройки. Имеется также возможность задать конкретные варианты языка , кото рые ОТ" носятся к определенной стране, указав код страны после кода языка. Код стра­ ны - это двухсимвольный идентификатор прописными буквами, например, AU для Австрии ил и IN для Индии. Коду страны также предшествует знак подчеркива· ния, когда он связывается с именем комплекта ресурсов. Комплект ресурсов, име-
Гл ава 19. Пакет java.utit, часть 11. Прочие служебные кл ассы 707 ющий только имя семейства, применяется по умолчанию. Он выбирается, когда н едоступны комплекты ресурсов для поддержки конкретных языков. На заметку! Коды языков оп ределены в стандарте ISO 639, а коды стран - в стандарте 150 3166. Методы , определенные в классе Re sourceBundle, переч исл ены в табл . 19.20. Следует, однако , иметь в виду, что пустые кл ючи не допускаются , и поэтому не­ которые методы генерируют исключение типа NullPo interException, есл и они получают пустой ключ . Следует также обратить внимание на вложен ный класс Re sourceBundle.Control, внедренный в версии Java SE 6 и предназначенный для управлен ия про цессом загрузки комплектов ресурсов. Таблица 19.20. Методы иэ класса ResourceВundle Метод static final. void clearCache ( ) static final void clearCache (ClassLoader загрузчи к ) Ьo o lean oontainslt&y (String k) String getвas.ВundleNU8 ( ) static final ResouroeВUDdle qetвundle (String WfЯ_ reмei iaмa ) static final RasouroeВUDdle getвundle (String WCl l _�, Local• �-нocmpoiD ru > static ResourceВundle qetвundle (Strinq WС1 1 _сисейтиа, Locale �-носmртiк и , ClassLoader заг/1JЗ' IUК ) static 6nal RasouroeВundle getвundle (String wся_ семейств а , RasourceВundle . Control контроль) Описание Уд аляет из кеша все комплеIСТЫ ресурсов, загружаемые по умолчанию загрузчиком классов Удал яет из кеша все комплекты ресурсов, загружаемые ука· занным ЗDфу:м1И DМ Возвращает лоmческое значение true , если заданный ключ k находится в вызывающем комплекте ресурсов (или его родител е) Возвращает базовое имя комплекта ресурсов, если таковое имеется, а иначе - пусrое значение null (добаален в вер­ сииJDК 8) Загружает комплект ресурсов, имеющий указан н оеWfЯ_ reмerima llJa, исПОЛЬ3}'Я реmональные насrройки и загруз­ чик классов по умолчанию. Ге нерирует исключение типа Мi••�•ouroeEzcвption, если не найден комплект ре­ сурсов, совпадающий с указанным uменем_� Загружает комплект ресурсов, имеющий указанное WfЯ_ reмerima llJa, используя заданные �-� и загрузчик классов по умолчанию. Ге нерирует исключение типа Мi••i.JщR8•ouroeЬc&ption, если не найден комплект ресурсов, совпадающий с указанным �-� Загружает комплект ресурсов, имеющий указанное WfЯ_ � ИСПОЛЬ3}'Я заданные �-НDmфойки и�классов. Iенерирует исключение типа М:issingReeource&zcept i on, если не найден комплект ре­ сурсов, совпадающий с указанным именем_� Загружает комплект ресурсов, имеющий указан н оеWfЯ_ reмerima llJa, используя реmональные насrройки и загрузчик классов по умолчанию. Процесс загрузки выполняется под упраалением заданного объекта JШНт/1ОА Ге нерирует исключение типа М:issingltesourCl lZxol lp tion, если не найден комплект ресурсов, совпадающий с указанным wсенис_rемгйапв о
708 Часть 11. Библиотека Java Метод static final ResourceВundle qetВundle (Strin9 имя_сгмейапв а ,Locale регионо. льньtе _жu:тройк и , Reaourceвundle .Control контроль) static ResourceВundle qe tвundle (String имя_сгмейств а , Locale регионо. льньtе _ жu:тройк и , ClasaLoader заzрузчик , ResourceВundle .Control контроль) aЬatract EnWDВration <Strinq>qetlt8ys () Locale qetLocale () final OЬject qeteьj ect (Strinq k) final Strin9 qetStrinq (Strin9 k) final Strinq[] qetStrinqArray (Strinq k) Продалженштабл. 19.20 Описание Загружает комШJект ресурсов, имеющий указанное wся_ сгмейств а , используя заданные регионал ьные _нш:тройки и загрузчик классов по умолчанию. Процесс загрузки вы­ полняется под управлением заданного объекта контроль. Ге нерирует исключение типа МissinqResourcdxC8ption, если не найден комШJект ресурсов, совпадающий с указан­ ным именем_� Загружает комШiект ресурсов, имеющий указанное wся_ сгмейств а , используя заданные регионо. льньtе _нш:тройк и и заzрузчик классов. Процесс загрузки выполняется под управлением заданного объекта контроль. Ге нерирует исключение типа МissinqResouroeException, если не найден комШiект ресурсов, совпадающий с указанным именDf_сгмейапв а Возвращает ключи из комШiекта ресурсов в виде перечисле­ ния символьных строк. Получаются также ключи из любого родителя данного комплекта ресурсов Возвращает региональные настройки, поддерживаемые в комШiекте ресурсов Возвращает объект, связанный с заданным ключом k. Ге нерирует исключение типа МissingReaouroeException, если заданный ключ k не найден в комплекте ресурсов Возвращает символьную строку, связанную с за- данным ключом k. Генерирует исключение типа МissinqResourceExoeption, если заданный ключ k не найден в комплекте ресурсов. Ге нерирует также исключение типа ClassCastl:xoeption, если объект, связанный с задан· ным ключом k, не является символьной строкой Возвращает массив символьных строк, связанных с заданным ключом k. Iенерирует исключение типа Мiaain9R8aouroeExoeption, если заданный ключ k не найден в комплекте ресурсов. Ге нерирует также исключение типа ClaasCastl:xoeption, если объект, связанный с задан­ ным ключом k, не является массивом символьных строк protected aЬatract C:Ь ject Возвращает объект, связанный с задан ным ключом k. handleGetCЬject (Strin9 k) Возвращает пустое значение null, если заданный ключ k не protected Set<S trinq> handldeySet () Set<S tring> keySet () найден в комплекте ресурсов Возвращает ключи из комплекта ресурсов в виде множе­ ства. Из родителя данного комШiекта ресурсов ключи не и звлекаются. Кроме того, ключи со значениями null не возвращаются Возвращает ключи из комплекта ресурсов в виде множества символьных строк. Получаются также ключи и з любого ро­ дителя данного комплекта ресурсов
Гл ава 19. Пакет java.utit, часть 11. П роч ие служеб ные кл ассы 709 Окон'Юние та6Л. 19. 20 Метод Описание protected void ••tParent (:ResouroeВundle родитель) Устанавливает родитмь в качестве родительского комплекта для данного комплекта ресурсов. Если ключ не найден в вы­ зывающем комплекте ресурсов, то его поиск продолжится в родительском комплекте ресурсов У класса Re sourceBundl e имеются два подкласса. Первым из них является класс Prope rtyRe sourceBundle, управляющий ресурсами с помощью файлов свойств. Этот класс не вводит свои методы . Вторым является абстрактный класс ListResourceBundle, управляющий ресурсами в массиве пар "ключ-зн ачение" . У это го класса имеется свой метод getContent s (),который должны реализовать вс е его подклассы. Ниже показано, каким образом объявляется этот метод. protected aЬstract OЬject[] [] qetContents () Этот метод возвращает двухмерный массив, содержащий пары " ключ-значе­ н ие", представляющие ресурсы. Ключи должны быть символьными строками, а значения - как правило, символьными строками , но они мoryr быть также пред­ ставлены объектами других типов. Рассмотрим пример, демонстрирующий применение комплекта ресурсов под именем семейства SampleRB. Два класса комплектов ресурсов из этого семей­ ства создаются расширением класса ListResourceBundle. Первый из них н азы· вается SampleRB и представляет комплект ресурсов по умолчанию для поддержки английского языка: import java .util .*; puЫ ic class SampleRВ extends ListResourceBundl e protected Ob ject [] [] getContents () { Obj ect [J [] resources = new Object [З] [2); resources (O] (0) resources [OJ (1) resources [l] (0) resources [l] [1] resources [ 2] [О] resources [2] [1] return resources; "title "; "Му Program" ; "S5topText "; "Stop"; "StartText "; "Start "; Второй класс комплекта ресурсов н азывается SampleRB_de и содержит пере· вод содержимого символьных строк на немецкий язык: im port java.util . *; 11 Версия на немецком языке puЫic class SampleRB_de extends ListResourceBundl e protected Obj ect (] [] getContents () { Object [] [] resources = new Object [З] [2) ; resources [O] [О] "title"; resources [OJ [1] = "Me in Programm" ;
710 Часть 11. Библиоте ка Java resource s [ 1] [О] resources [l] [1] resources [2] [О] resource s [2] (1) return resources; "StopText "; "Anschlag"; "Start . Text"; "Anfang" ; Примен ение обоих упомянугых выше комIUJектов ресурсов демонстрируется в приведенном ниже примере программы. С их помощью выводятся символьные строки, связанные с каждым ключом как для английской версии программы , вы· бираемой по умолчанию, так и для немецкой ее версии. 11 Продемонстрировать приме нение комплектов ре сурсов import java.util . * ; class LRBDemo ( puЫ ic static void main (String args[]) ( 11 загрузи ть компле кт ресурсов по умолчанию Re sourceBundle rd = Re sourceBundle.getBundle ("S ampleRB") ; System.out . println ( "Aнглийcкaя версия программы : ") ; System.out . println ( "Cтpoкa по ключу Title : " + rd . getString ("titl e") ); System.out . println ( "Cтpoкa по ключу StopText : " + rd . getString ("StopText") ); Systern.out . println ( "Cтpoкa по ключу StartText : " + rd . getStriпg ("S tartText") ); 11 загрузить комплект ресурсов для поддержки немецкого языка rd = Re sourceBundle . ge tBundle ("SampleRB" , Locale .GERМAN) ; System.out . println ("\nHeмeцкaя версия программы : ") ; System.out . println ( "Cтpoкa для ключа Title : " + rd . getString ("title" )); System.ou t.println ( "Cтpoкa по ключу StopText : " + rd . getString ("StopText") ); System.out . priпtln ( "Cтpoкa по ключу StartText : " + rd . getString ("StartText") ); Эта программа выводит следующий результат: Английская версия програъош : Строка по ключу Title : Му Program Строка по ключу StopText : Stop Строка по ключу StartText : Start Немецкая версия програъош : Строка по ключу Title : Mein Programm Строка по ключу StopText : Anschlag Строка по ключу StartText : Anfang Прочие служебные классы и интерфейсы Помимо описанных ранее классов, в пакет java.util входят классы, перечне· ленные в табл . 19.21.
Гл ава 19. Пакет java.utit, часть 11. П роч ие служебные кл ассы 711 Табл ица 19.21 . Дополнительные классы из пакета java . util Кnасс Описание Ваsе64 Поддерживает кодировку Base64. Определены также вложен­ ные классы Encoder и Deooder (добавлен в версии JDK 8) DouЬleSUl ll ll la ryStati atica Поддерживает сбор статистических данных типа douЫe. Доступны следующие виды статистических данных: среднее , минимальное, максимальное, подсчет и итог (добавлен в вер­ сии JDК 8) EventListenerProxy Расширяет класс Even tLiatener для передачи дополнитель­ ных параметров. Приемники событий рассматриваются вглаве 24 EventoЬject Суперкласс для всех классов событий. События рассматрива­ ютсявглаве24 ForшattaЬleFlaqs Определяет признаки форматирования, используемые в ин­ терфейсе ForuiattaЬle IntSU1 11 11 1a ryStati.sti.ca Поддерживает сбор статистических данных типа int. Доступны следующие виды статистических данных: среднее, минимальное, максимальное, подсчет и итог (добавлен в вер­ сииJDК 8) C:Ьjects Определяет различные методы для обращения с объектами PropertyPerшission Уп равляет правами доступа к свойствам ServiceLoader Предоставляет средства для поиска поставщиков услуг UOID Инкапсулирует универсальные уникальные идентификаторы (UUID) и управляет ими Интерфейсы, перечисленные в табл . 19.22, также входят в состав пакета jav a. ut il. Та бл ица 19.22. Дополнительные интерфейсы из пакета java . util Интерфейс Описание EventListaner Обозначает, что класс является приемником событий. События рассмат­ риваются в гл аве 24 ForшattaЬle Описывает класс, обеспечивающий специальное форматирование Подпакеты, входящие в состав пакета java . util ВJаvа определяются следующие подпакеты , входя щие в состав пакета java . util: • jav a.util . concurrent • jav a.util . concurrent . atomic • java . ut il . concurrent . locks
712 Ч асть 11. Б иблиоте ка Java • java.util . function • jav a.util .jar • jav a.util . logging • jav a.util . pre fs • java.util . regex • jav a.util . spi • java.util . stream • java.util . zip Все они вкратце описаны далее. Пa кeты java .util . concurrent, java .util . concurrent.atomi c , java .util . concurrent . locks Пакет java.util . concurrent вместе с двумя своими подпакетами java . ut il . concurrent . atornic и java . ut il . concurrent . locks служит для поддерж· ки парал л ельного программирования. Все эти пакеты предоставляют высокопр<r изводительную альтернативу применению встроенных в J ava средств синхрониза· ции , когда требуются потокобезопасные операции. С версииJDК 7 в пакете java . util . concurrent поддерживается также каркас Fork/Join Framework. Подробнее эти пакеты рассматриваются в гл аве 28. П a кeт java .util . function В этом пакете предопределен ряд функциональных инте рфейсов, предназна· ченных для создания лямбда-выражений или ссылок на методы. Эти функциональ­ ные интерфейсы широко применяются в прикладном программном инте рфей· се Java API. Все функциональные инте рфейсы из пакета j ava . ut il . function перечислены в табл . 19.23 вместе с кратким описанием их абстрактных методов. Следует, однако , иметь в виду, что для расширения функциональных возможно­ стей в некоторых из этих интерфейсов определяются также методы по умолча· нию или статические методы. Поэтому вам придется изучить их самостоятельно. (Подробнее о функциональных интерфейсах см. в гл аве 15.) Табл ица 19.23. Функциональные инте рфейсы иэ пакета java . util . function и и х абстрактн ые методы Интерфейс BiConaWDer<T , О> BiFunction<T , U, R> Абстрактный метод void accept (Т tVal, U uVal) Описание: оперирует арrументами tVal и uVal R apply(T tVal, U uVal) Описание: оперирует арrументами tVal и uVal и возвраща· ет полученный результат
Гл ава 19. Пакет java.utiL, часть 11. П роч ие служе б ные кл ассы 713 Интерфейс BinaryOperator<T> BiPredicate<T , U> BooleanSupplier ConsUl ll8 r<T> DouЬleВinaryOperator DouЬleConswaer DouЬleFunction<R> DouЬlePredicate DouЬleSupplier DouЬleTo intFunction DouЬleToLonqFunction DouЬleUna ryOperator Function<T , R> IntвinaryOperator Прод ал.женш 11iali ii . 19. 23 Абстрактный метод Т apply(Т vall, Т rюl2) Описание: оперирует двумя однотипными объектами и возвращает полученный результат того же самого типа boolean test(Т tVal, U uVal) Описание: возвращает логическое значение true, если оба аргумента, tVal и uVal, уд овлетворяют условию, зада· ваемому методом test ( ) , а иначе - логическое значение ralse boolean qetAsВoolean () Описание: возвращает логическое значение void accept (T val) Описание: оперирует аргументом val douЫe applyAsDouЫe (douЫe vall , douЬle val2) Описание: оперирует двумя аргументами, vall и rюl2, типа douЬle и возвращает результат того же самого типа void accept (douЬle val) Описание: оперирует аргументом val R apply (douЫe val) Описание: оперирует аргументом vаl типа douЫe и воз· вращает результат boolean te st (douЬle val) Описание: возвращает логическое значение true, если аргумент vаl удовлетворяет усл овию, задаваемому мето­ дом te st ( ) , а иначе - логическое значение ralse douЫe qetAsDouЬle () Описание: возвращает результат типа douЬle int applyAs int (douЫe val) Описание: оперирует аргументом vаl типа douЬle и воз· вращает результат типа int long applyAsLonq (douЬle val) Описание: оперирует аргументом vаlтипа douЬle и воз· вращает результат типа lonq douЬle applyAs DouЬle (douЬle val) Описание: оперирует аргументом vаlтипа douЬle и воз· вращает результат типа douЬle R apply(T val) Описание: оперирует аргументом val и возвращает ре· зультат int applyAsint (int rюl2 , int val2) Описание: оперирует аргументами val2 и val2 типа int и возвращает результат того же самого типа
71' Часть 11. Библиотека Java Интерфейс IntConsUJl l8 r IntFunction<R> IntPredicate IntSupplier IntToDouЫeFunction IntToLongFunction IntUnaryOperator LongBinaryOperator LongConsWl ll ll r LongFunction<R> Lon9Predicate LongSup p lier LongТoDouЫeFunction LongТointFunction ПродQЛЖение табл. 19.23 Абстрактный метод int accept (int val) Описание: оперирует аргументом val R apply (int val) Описание: оперирует аргументом vаl типа int и возвра· щает результат boolean test (int val) Описание: возвращает логическое значение true, если аргумент vаl удовлетворяет условию, задаваемому мето­ дом test () , а иначе - логическое значение fal se int getAaint () Описание: возвращает результат типа int douЫe applyAsDouЬle (int val) Описание: оперирует аргументом vаlтипа int и возвра· щает результат типа douЬle long applyAaLong (int val) Описание: оперирует аргументом vаl типа int и возвра· щает результат типа lon9 int applyAsint (int val) Описание: оперирует аргументом vаl типа int и возвра· щает результат типа int lonq applyAsLonq (long vall , long val2) Описание: оперирует аргументам vall и val2 типа long и возвращает результат того же типа void accept (lon9 val) Описание: оперирует аргументом val R apply ( long val) Описание: оперирует аргументом vаl типа long и воз вра­ щает результат boolean test ( long val) Описание: возвращает логическое значение true, если аргумент vаl удовлетворяет условию, задаваемому мето­ дом te st () , а иначе - логическое значение false lon9 getAaLong () Описание: возвращает результат типа long douЬle applyAaDouЬle ( long val) Описание: оперирует аргументом vаl типа long и воз вра­ щает результат типа douЬl• int applyAa int ( long val) Описание: оперирует аргументом vаl типа long и возвра· щает результат типа int
Гл а ва 19. Пакет java.uШ, часть 11. П роч ие служебные кл ассы 715 Интерфей с LonqUnaryOperator OЬjDouЬleConawaer<T> OЬj i ntConswaer<Т> ObjLonqConsUl ll8 r<T> Predicate<Т> Sup p lier<Т> ОкtтЧание табл. 19. 23 Абстрактный метод lonq applyAsLonq (lonq val) Описание: оперирует аргументом vаlтипа long и возвра­ щает результат типа lon9 void accept (Т vall , douЬle val2) Описание: оперирует аргументом vall типа Т и аргумен­ том val2 типа douЬle void accept (Т vall , int va12) Описание: оперирует аргументом vall типа т и аргумен­ том vаl2 типа int void accept (Т vall , lonq val2) Описание: оперирует аргументом vall типа Т и аргумен­ том val2 типа long boolean test (T val) Описание: возвращает логическое значение true, если аргумент vаl удовлетворяет условию, задаваемому мето­ дом te st () , а иначе - логическое значение falae т get() Описание: возвращает объект типа Т ToDouЬleВiFunction<T, U> douЬle applyAsDouЬle (Т tVal, U uVal) ToDouЬleFunction<T> TointвiFunction<T , U> TointFunction<T> T0Lon9Bil'unction<T , U> T0Lon9Function<T> Unary()perator<T> Описание: оперирует аргументами tVal и uVal и возвраща­ ет результат типа douЬle douЬle applyAsDouЬle (T val) Описание: оперирует аргументом val и возвращает результат типа douЬle int applyAsint (Т tVal, U uVal) Описание: оперирует аргументами tVal и uVal и возвраща­ ет результат типа int int applyA8 Int (T val) Описание: оперирует аргументом va1 и возвращает результат типа int lonq applyAsLonq (Т tVal, U uVal) Описание: оперирует аргументами tVal и uVal и возвраща­ ет результат типа lonq lonq applyAsLon9 (T val) Описание: оперирует аргументом val и возвращает результат типа long т apply(T val) Описание: оперирует аргументом va1 и возвращает результат
716 Часть 11. Б иблиоте ка Java Пакет java . util. jar Предоставляет средства для чтения и записи архивных файлов формата Java Archive UAR) . Пaкeт java .util . logging Обеспечивает поддержку журналов регистрации, которые м01уr быть исполь­ зованы для записи операций, выполняемых в программе, а также для обнаруже· ния ошибок и отладки программ. П акет java . util . prefs Обеспечивает поддержку гл обальных параметров, настраиваемых пользовате­ лями. Как правило, применяется для поддержки разных конфигур аций программ. Пaкeт java .util . regex Обеспечивает поддержку, требующуюся для обработки регуля рных выраже­ ний. Подробнее он рассматривается в гл аве 30. Пaкeт java .util . spi Обеспечивает поддержку поставщиков услуг. Пaкeт java .util .stream Содержит прикладной программный интерфейс API потоков ввода-в ывода вjava, внедренный в версииJDК В. Подробнее этот прикладной программный и н· терфейс рассматривается в гл аве 29. Пaкeт java .util . zip Предоставляет средства для чтения и за писи архивных файлов в распростра· пенных форматах ZIP и GZIP. Доступны также потоки ввода-вывода данных, архи· вируемых в форматах ZIP и GZIP.
20 Пакет java . io для ввода-вывода Эта гл ава посвящена пакету java. io, поддерживающему операции ввода-вы­ вода. В гл аве 13 был сделан кратки й обзор системы ввода-вывода вJava, включая основные методики чтения и записи файлов, обработки исключений ввода-выво­ да и закрытия файла. А в этой гл аве система ввода-вывода в Java рассматривается бол ее подробно. Как давно уже известно всем программистам , большинство программ не в со­ стоянии выполнять свои функции, не имея доступа к внешним данным. Данные извлекаются из источника ввода. А результат выполнения программы направляет­ ся адресату въ�вода. В Java эти понятия определяются очень широко. Например, источн иком ввода или адресатом вывода может служить сетевое соединение, бу­ фер памяти или дисковый файл , и всеми ими можно манипулировать с помощью классов ввода-вывода вjava. И хотя все ус тройства ввода-вывода отличаются фи­ зически , все они описываются единой абстракцией - потоком ввода-вывода. Как пояснялось гл аве 13, поток ввода-въtвода - это логический объект, который постав­ ляет или потребляет информацию. Поток ввода-вывода присоединяетс я к физи­ ческому устройству системой ввода-вывода в Java. Все потоки ввода-вывода ведуr себя сходным образом, несмотря на то , что физические устройства, к которым о ни присоединены, радикально отличаются . На заметку! Пото ковая система ввода-вывода , входя щая в пакет j ava . io и рассматриваемая в этой гл аве, была составной частью Java, начиная с первого выпуска и широко применя­ ется до сих пор. Но в версии 1.4 в Java была внедрена вторая система ввода-вывода. Она называется NIO (что первоначально означало New 1/0, т.е . новый ввод- вывод). Систе ма NIO входит в па кет j ava . nio и его подпа кеты и подробно рассматривается в гл аве 21. На заметку! Не следует путать потоки ввода-вывода из рассматриваемой эдесь системы ввода­ вывода с потока ми да нных из нового прикладного интерфейса API, внедренного в версии JDK 8. Несмотря на концептуал ьную связь между ними. это все-таки разные средства вво­ да -вывода . Следовател ьно, под термином поток ввода -вывода здесь подразумевается поток из системы ввода-вывода .
718 Часть 11. Библ и оте ка Java Классы и интерфейсы ввода-вывода Ниже перечисл ены классы ввода-вывода, определенные в пакете j ava . io. BufferedinputStream FileWriter PipedOutputStreaш BufferedOutputStream FilterinputStream PipedReader BufferedReader FilterOutputStream PipedКriter BufferedWriter Fil terReader PrintStream ВyteArrayinpu tStream FilterWriter PrintWriter ВyteArrayOutputStrea.1 11 InputStrea.1 11 PushЬackinputStreaш CharArrayReader InputStreaшReader PushЬack.Reader CharArrayWriter LineNwDЬerReader Randoll lAc cessFile Console OЬj ectinputStream Reader DatainputStrea.1 11 OЬj ectinputStream . GetField SequenceinputStreaш DataOutputS tream OЬjectOutputStrea.1 11 SerializaЬlePerшission File OЬjectOutputStream .PutField Streaш.Tokeni zer FileDescriptor OЬjectStreamClass StrinqReader FileinputStream OЬjectStreaшField StrinqWriter FileOutputStrea.1 11 OutputStrea.1 11 Writer FilePerшission OutputStreamWriter FileReader PipedinputStream Пакет j ava . io содержит также два устаревших класса, кото рые отсутству­ ют среди перечисленных выше. Это кл ассы LineNumЬ e rlnputStrearn и S t r ing Bu fferlnpu tStre arn. Их не рекомендуется употреблять в новом коде . Кроме того , в пакете j ava . io определены следующие интерфейсы: CloseaЬle FileFilter Ob jectinputValidation Datainput FilenaшeFilter Ob jectOutput DataOutput FlushaЬle Ob jectS treamConstants ExternalizaЬle Ob jectinput SerializaЬle Как видите , в пакете j ava . io имеется немало классов и интерфейсов. Они ре­ ализуют потоки ввода-вывода байтов и символов, а также сериализацию объектов (их сохранение и восстановление) . В этой гл аве рассматриваются наиболее упо­ требительные составляющие системы ввода-вывода вjava. Начнем их обсуждение с класса File - одного из наиболее характерных для ввода-вывода. Класс File Большинство классов, определенных в пакете j ava . io, оперируют потокам и вво­ да-вывода, чего нельзя сказать о классе File. Он оперируют неносредственно файла­ ми и взаимодействует с файловой системой. Следовательно, в классе File не опреде­ ляется , каки м образом данные извлекаются и сохраняются в файлах, но описы ваются свойства самих файлов. Объект класса File служит для получения таких сведений о файле на диске, как права доступа, время, дата и путь к каrалогу, ил и манипулирова­ ния эти ми сведениями, а также для перемещения по иерархиям подкаталогов.
Гл ава 20. Пакет java .io для ввода -вывода 719 На заметку! Интерфейс Path и класс Files, входя щие в систему ввода-вывода NIO, нередко слу­ жат эффекти вной ал ьтернативой классу File. Более подробно систе ма ввода-вывода NIO рассматривается в гл аве 21. Файлы служат первичными источниками и адресатами данных во многих про­ граммах. Несмотря на строгие ограничения, наIUiадываемые на использован ие файлов в аплетах из соображений безопасности , файлы по-прежнему остаются центральным ресурсом для хранения постоянной и обмениваемой информации. Каталог вjava инте рпретируется как объект IUiacca File с единственным дополни­ тел ьным свойством - списком имен файлов, которые могуг быть получены мето­ до м list (). Для создания объектов IUiacca Fi le можно воспользоваться следующими кон­ с трукторами: File (Strinq ny'.l'ъ ж жа'.1'алоrу) File (Strinq nу'.1'ъ-ж - жа'.1'алоrу , Strinq •юr файла) File (li'ile �еж'l'- жа !l'алоrа , Strinq -. ФЗж.па) File (URI об-Аеж 'l'_mu) - где параметр путь_ к _ ка талогу обозначает путь к файлу; параметр имя_ файла - и мя конкретного файла или подкаталога; параметр объект_ ка талога - объект типа Fi le, задающий каталог; а параметр объект_ UR I - объект типа UR I, описы­ ваю щий файл. В приведенном ниже примере создаются три файла в переменных fl, f2 и fЗ. Первый объект типа Fi le создается в каталоге, указываемом в качестве единствен­ ного аргумента конструктора; второй объект - с указанием пути и имени файла в ка­ честве двух аргументов конструктора; третий объект - с указанием пути, присваи­ ваемому переменной fl, а также имени файла, причем переменная fЗ ссылается на тот же самый объект типа Fi le, обозначающий файл , что и переменная f2. File fl File f2 File fЗ new Fi le ("/") ; new Fi le ("/", "autoexec .bat" ); new Fi le (fl, "autoexec .ba t") ; На заметку! В Java правильно интерпретируются раздел ители nутей к файлам, которые отличаются в UNIX и Windows. Даже есл и в качестве та кого разделителя используется знак косой черты (/) в версии Java для Windows, nуть к файлу будет все равно сформирован правильно. Не следует, од нако, забы вать, что для уп отребления знака обратной косой черты (\) в символь­ ных строках под Windows его следует экра нировать в виде упра вляющей последовател ь­ ности (\\). В классе Fi l е определяется немало методов для получения стандартных свойств файла, представленного объектом этого класса. Например, метод getName ( ) воз­ вращает имя файла, метод ge tParent () - имя родительского каталога, а метод e xists ( ) - логическое значение true, если файл существует, а иначе - логическое значение false. В приведенном ниже примере программы демонстрируется при­ менение некоторых методов из IUiacca File. При этом подразумевается , что в кор­ невом каталоге существует каталог j ava с файлом COPYRIGHT.
720 Часть 11. Б ибл и отека Java 11 Продемонстрировать применение некоторых ме тодов из кл асса File import jav a.io.File; class Fi leDemo { static vo id p(S tring s) { System.out . println (s) ; puЫ ic static vo id main (String args[] ) { Fi le fl = new Fi le ("/java/COPYRIGHT" ) ; р("Имя файла: " + fl.getName()); р("Путь : "+fl . getPath() ); р("Абсолютный путь : "+fl . getAЬsolutePath () ); р("Родитель ский каталог : "+fl . getParent () ); p(fl.exists () ? "существует" : "не суще ствует ") ; p(fl.canWrite () ? "доступен дл я записи" : "не доступен для записи" ); p(fl.canRe ad () ? "доступен для чтения" : "не доступен дл я чтени я") ; p(fl.isDirectory () ? "является каталогом" : "не является катало гом" ); p(fl.isFile () ? "является обычным файлом " : "може т быть именованным каналом" ); p(fl.isAЬsolute () ? "явл яется абсолютным" : "не является абсолютным" ); р("Последнее изменение в файле : "+fl . lastModi fied () ); р("Размер: " + fl. length() + " байт"); Н иже приведен пример выполнения данной программы. Имя файла : COPYRIGHT Путь : \java \COPYRI GHT Абсолютный путь : \java \COPYRI GHT Родитель ский каталог : \java суще ствует доступен для записи доступен для чтения не является каталогом является обычным файлом не является абсолютным Последнее изменение в файле : 1282832030047 Размер : 695 байт Назначение большинства методов из класса File самоочевидно, но метод ы i s­ File () и isAЬsolute ( )требуют дополнительных пояснений. В частности , метод isFile ( ) возвращает логическое значение true , если он вызывается с файлом, а если с каталогом, то логическое значение false. Кроме того, метод i s File О возвращает логическое значение false для некоторых специальных файлов, на· пример, драйверов устройств и именованных каналов, и поэтому, вызывая этот метод, можно убедиться , что файл действительно ведет себя как файл . ·л метод isAЬsolute ( ) возвращает логическое значение true, есл и файл и меет абсолют­ ный путь, а если относительный путь , то логическое значение f а 1 s е. В классе File имеются также два полезных служебных метода. Первым из них является метод renarneTo ( ) , общая форма объявления которого показана ниже. Ьoolean renuieтo (File новое _ ._)
Гл ава 20. Пакет java.io для ввода-вь1вода 721 Здесь имя файла, указанное в качестве параметра новое_имя, становится но­ вым именем вызывающего объекта типа File. Метод renameTo ( ) возвращает ло­ гическое значение true при уд ачном исходе переименования файла, а если файл не может быть переименован, поскольку в качестве нового имени указано имя уже существующего файла, то логическое значение false. Второй служебный метод, de lete ( ) , уд аляет с диска файл по пути , который предоставляется вызывающим объектом типа File. Ниже приведена общая фор­ ма объявления этого метода. Ьoolean delete () С помощью метода de lete () можно также уд алить каталог, если он пуст. Метод de lete () возвращает логическое значение true, если ему уд ается уд алить файл , а иначе - логическое значение false. Другие полезные методы из класса File перечислены в табл. 20. 1 . Табл ица 20.1 . Другие пол езные методы иэ класса File Метод void deleteOnВxit () lonq qetFre e Spaoe () lonq getTotalSpaoe () lonq getUsaЫeSpaoe () Ьo o lean isBidden () Ьo o l•an 88tLa8t:Мoclified (long мимиакуш } ) Ьo o lean setReacIOnly () Описание Уд аляет файл, связанный с вызывающим объектом , по заверше­ нии работы вир-гуальной машины.JVМ Возвращает количество свободных байтов, доступных в раз­ деле запоминающего устройства, связанном с вызывающим объектом Возвращает емкость раздела запоминающего устройства, свя­ занного с вызывающим объектом Возвращает количество пригодных для употребления свобод­ ных байтов, доступных в разделе запоминающего устройства, связанном с вызывающим объектом Возвращает логическое значение true, если вызывающий файл является скрьrгым, а иначе - логическое значение false Уст анавливает временную метку для вызываемого файла в виде заданного количества�. прошедших с 1 января 1970 г. , в формате времени UТС Делает вызывающий файл доступным только для чтения Кроме того, имеются методы, помечающие файлы как доступные только для чтения, записи или выполнения. А поскольку класс Fi le реализует интерфейс C omparaЫe, то в нем поддерживается также метод compareTo () . В версииJDК 7 класс File был дополнен методом toPath (), который объявля­ ется следующим образом: Path toPath O Метод toPath () возвращает объект типа Path, который представляет файл , инкапсулируемый вызываемым объектом типа File. (Иными словами, метод toPath ( ) преобразует объект типа File в объект типа Path.) Инте рфейс Path входит в состав пакета j аvа . niо . filе и является составной частью системы
722 Часть 11. Библ иотека Java ввода-вывода NIO. Та ким образом , метод toPath () наводит мост между старым классом File и новым интерфейсом Path. (Подробнее об интерфейсе Path речь пойдет в главе 21.) Каталоги Каталог является объектом типа File, содержащим список других файлов и ка­ талогов . После создания объекта типа File как каталога его метод isDirectory() возвратит логичес кое значение true. В таком случае для этого объекта можно вы­ звать метод 1 i s t ( ) , чтобы извлечь список других находящихся в нем файлов и ката· логов. У метода list () имеются две общие формы. Первая из них приведена ниже. Strinq [] list () Список файлов возвращается в виде массива объектов типа String. В следую­ щем примере программы демонстрируется применение метода list () для про­ смотра содержимого каталога: // Исполь зовать каталоги irnport java.io.File; class DirList { puЫ ic зtatic vo id rna in (String args[] ) String dirnarne = "/java "; Fi le fl = new File (dirпarne) ; if ( f1. isDirectory()) { Systern.out . println ( "Kaтaлoг "+dirnarne) ; String З[] = fl .list(); for (int i=O ; i < s.length; i++) { else File f = new File(dirnarne + "/" + s[i]); if (f.is Directory () ) { Systern.out . println (s [i] +"является каталогом" ); else { Systern.out . println (s[i] +" является файлом" ); Systern.out . println (dirname +"не является каталогом" ); Ниже приведен примерный результат, выводимый данной программой (у вас он может оказаться иным, в зависимости от того , что находится в каталоге). Каталог /java bin является каталогом lib является каталогом derno являе тся каталогом COPYRIGHT являе тся файлом README являе тся файлом index .html является файлом include является катало гом src . zip является файлом src является каталогом
Гл ава 20. Пакет java.io для ввода -вывода 723 Применение интерфейса FilenameFilter Нередко требуется ограничить количество файлов, возвращаемых методом l ist ( ) , чтобы включить в их число только те файлы, которые соответствуют определенному образцу имен, или фw�ътру. Для этого следует использовать вто­ рую форму метода list (), которая приведена ниже. String [] list (FilenameFilter FFOЬj ) В этой форме параметр FFObj обозначает объект класса, реализующего интер­ фейс Fi lenameFilter. В интерфейсе FilenameFilter определяется единствен­ ный метод accept ( ) , вызываемый один раз с каждым файлом из списка. Его об­ щая форма такова: Ьoolean accept (File жairaлor, String юаr_фdла) Метод accept ( ) возвращает логическое значение true для файлов из указан­ ного ка талога, которые должны быть включены в список (т.е. тех файлов, кото­ рые совпадают с заданным именем_ файла), а для файлов, которые следует исклю­ чить из списка, возвращается логическое значение false. Приведенный ниже класс On lyExt реализует интерфейс FilenameFilter. Он будет использован в видоизмененной версии программы из предыдущего приме­ ра , чтобы ограничить имена файлов, возвращаемых методом l i s t ( ) , только теми из н их, которые оканчиваются расширением, указанным при создании объекта этого класса. irnport java.io.*; puЫic class OnlyExt irnplernents Filenarne Filter String ext ; puЫ ic OnlyExt ( String ext ) this.ext= "." + ext; puЫ ic boolean accept (File di r, String name ) { return narne .endsWith (ex t) ; Ниже приведена видоизмененная версия дан ной программы для просмотра файлов в каталоге. Те перь она выведет только файлы с расширением . html. 1 1 Просмотреть каталог НТМL -файлов irnport java.io.*; class Di rLi s tOnly { puЫ ic static void ma in (String args[] ) { String dirname = "/java "; File fl = new File (dirname) ; FilenameFilter only = new On lyExt ("htrnl") ; String s[] = fl .list (only) ; for (int i=O; i < s.length; i++ ) Systern. o ut . println (s [i] );
72' Часть 11. Б иблиоте ка Java Ал ьтернативный м етод listFiles () Существует разновидность метода list () под назван ием listFiles (), кото­ рая может оказаться уд обной. Ниже приведены сигнатуры метода listFiles (). File [] listFiles () File [] listFiles (FilenameFilter FFOЬj ) File [] listFilea (FileFilter FOb j) Эти формы метода listFiles () возвращают список файлов в виде массива объектов типа F i 1 е вместо символьных строк. В первой форме этот метод возвра· щает все файл ы, во второй - только те файлы, которые уд овлетворяют условию, задаваемому объектом типа F i 1ename Fi 1 t е r в качестве параметра FFOb j. Помимо возвращения массива объектов типа File, обе эти формы метода listFiles () действуют таким же образом , как и эквивалентн ые им формы метода list (). Тр етья форма метода listFiles () возвращает те файлы, имена путей к кото­ рым уд овлетворяют условию, задаваемому объектом типа FileFilter в качестве параметра FObj . В инте рфейсе FileFilter определяется единственный метод accept (), который вызывается один раз для каждого файла из списка. Его общая форма такова: boolean accept (File пут�) Метод accept () возвращает логическое значение true для тех файлов, кото­ рые должны быть включены в список (т.е . совпадают с аргументом путь), и ло ги­ ческое значение false для тех файлов, кото рые следует исключить из списка. Созда ние каталогов В классе File имеются еще два полезных служебных метода: mkdir () иmkdi r s (). В частности, метод mkdir () создает каталог, возвращая логическое значение t rue приуд ачном исходе операции и логическое значение f al se при неудачном. Операция создания каталога может завершиться неудачно по разным причинам. Например, путь, указанный в объекте типа F i le, уже существует, или каталог не может быть соз­ дан, потому что полный путь к нему еще не существует. Чтобы создать каталог, пуrь к которому еще не создан , следует вызвать метод mkdirs ().Он создаст как сам ката­ лог, так и все его родительские каталоги. И нте рфейсы Au toCloseaЫe, CloseaЫe и FlushaЫe Эти три интерфейса имеют большое значение для классов потоков ввода-выво­ да. Два из них, интерфейсы CloseaЫe и FlushaЬle, были определены в пакете j ava . io и внедрены в версии JDK 5. А третий, инте рфейс Au toCloseaЬle, был внедрен в версииJDК 7 и входит в состав пакета j ava . lang. В версии JDK 7 интерфейс Aut oC loseaЫe обеспечивает поддержку нового оператора try с ресурсами , который автоматизирует процесс закрытия ресур-
Гл ава 20. Паке т java.io для ввода -вывода 725 са, как поясняется в главе 13. То лько объекты классов, реализующих интерфейс Au toCloseaЫe, МОJУГ управляться оператором try с ресурсами. Интерфейс Au toClos eable обсуждается в гл аве 17, но здесь ради уд обства приведен краткий его обзо р. В интерфейсе Au toCloseaЫe определяется еди нственный метод close (): void close () throws lxception Этот метод закрывает вызывающий объект, высвобождая любые ресурсы, ко­ торыми он может пользоваться. Метод close () вызывается автомати чески по за­ вершении оператора try с ресурсами , избавляя от необходимости вызывать его явным образом. Интерфейс Au toCloseaЫe реализуется всеми классами, откры­ вающими потоки потоков ввода-вывода, и поэтому эти потоки МОJУГ быть автома­ тически закрыты оператором try с ресурсами . Автоматическое закрытие потоков ввода-вывода гарантирует правильность их освобождения , когда они больше не нужн ы, предотвращая тем самым утечку памяти и другие осложнения. В интерфейсе CloseaЫe также определяется метод close (). Объекты класса, реализующего интерфейс CloseaЬle, могут быть закрыты. Начиная с версииJDК 7 интерфейс CloseaЫe расширяет интерфейс Au toCloseaЫe. Поэтому в версии JDK 7 любой класс, реализующий интерфейс CloseaЬle, реализует также интер­ фейс Au toC loseaЫe. Объекты класса, реализующего интерфейс FlushaЫe, МОJУГ принудительно направить буферизованные данные в тот поток вывода, к которому присоединен данный объект. В этом интерфейсе определяется единственный метод flush ( ) , как показано ниже. void flush () throws IOException Очистка потока вывода обычно приводит к тому, что буферизованные данные физически выводятся на базовое устройство. Этот интерфейс реализуется всеми классами, способными направлять дан ные в поток вывода. Исключ ения ввода - вывода Для организации ввода-вывода большое значение имеют два исключения. Первое из них, исключение типа IOException, имеет отношение к большин­ ству классов ввода-вывода, описанных в дан ной гл аве , поэтому при возник­ но вении ошибки ввода-вывода генерируется исключение типа IOException. Есл и файл нельзя открыть , то , как правило, генерируется исключение ти па Fi leNot FoundException. Класс исключения FileNotFoundException является пр оизводным от класса IOException, поэтому оба типа исключений МОJУГ быть перехвачены в одном блоке оператора catch, предназначенном для перехвата и обработки исключения типа IOException. Именно такой подход применяется ради краткости в большинстве примеров программ, представленных в этой гл аве. Но в своих прикладных программах вам, возможно, будет удо бнее обрабатывать их по отдельности . Для организации ввода-вывода иногда оказывается очень важным еще один класс исключения Securi tyException. Как пояснялось в гл аве 13, в тех случаях, когда при-
726 Часть 11. Библиотека Java сугствует диспетчер безопасности, некоторые классы обращения с файлами генери· руюr исключение типа SecurityException при попытке открыть файл с нарушени· ем безопасности . По умолчанию прикладные программы, запус каемые на выполне­ ние по команде j ava, не пользуются диспетчером безопасности . Поэтому в при мерах организации ввода-вывода, представ.ленных в данной книге , не отслеживается воз­ можность генерирования исключения типа SecurityException. Но аПJiеты обыч· но пользуются диспетчером безопасности, предоставляемым браузером , и поэтому при вводе-выводе в файл из аплета может быть сгенерировано исключение типа Securi tyException. В таком случае придется обработать и это исключение. Два спосо ба закр ытия потока ввода- вывода Как правило , поток ввода-вывода следует закрыть, когда он больше не нужен. Если не сделать этого , может п роизойти утечка памяти и истощение ресурсов. Способы закрытия потока ввода-вывода были описан ы в гл аве 13, но из-за особого значения этих способов напомним их вкратце , п режде чем перейти к рассм отре­ н ию классов потоков ввода-вывода. В версии JDK 7 появились два основных способа, которыми можно закрыть поток ввода-вывода. Первый с пособ подразумевает явный вызов метода close () для потока ввода-вывода. Это традиционный подход, который применялся с пер­ вого выпуска Jаvа. При таком подходе м етод close () обычно вызывается в бл оке оператора finally. Н иже п риведен уп рощенный шаблон тради цион ного способа за крытия потока ввода-вы вода. Эта общая методика (ил и ее разновидность) широ­ ко пр именялас ь в коде , написанном до появления версииJDК 7. try{ 11 открыть файл и получить доступ к нему catch ( •CA'.IПDЧ'eнJre .8 80 Rа-В1D1ода) { 11... - finally { 11 закрыть файл Второй способ закрытия потока ввода-вывода подразумевает автоматизацию дан ного п роцесса с помощью оператора try с ресурсами, который был внедрен в версии JDK 7 (и, разумеется, поддерживается в версии JDK 8). О ператор try с ресурсами является ус овершенствованной формой оператора try, имеющей сле­ дующий вид: try ( CDeQЖ"JrSQl l JI__ресурса) 11 использовать ресурс где параметр специфика ция_ресурса обозначает один ил и несколько операторов, в которых объявляется и иници ализируется ресурс , например, файл или другой ресурс, связанный с потоком ввода-вывода. В указанной специфика ции_ресурса обычно объявляется переменная , которая ини циализируется ссылкой на управля· емый объект. По завершении блока оператора try ресурс освобождается автома· тически . Для файла это означает его автоматическое закрытие. Следовательно, отпадает необходимость вызывать метод close () явным образом.
Гл ава 20. Пакет java.io для ввода-вывода 727 Ниже перечислены три гл авные особенности применения оператора t ry с ре­ сурсами. • Ресурсы, управляемые оператором try с ресурсами, должны быть объекта­ ми классов, реализующих инте рфейс Au toCloseaЫe. • Ресурс, объявляемый в блоке оператора try, неявно считается завершенным. • Управлять можно несколькими ресурсами, перечислив их списком через за­ пятую при объявлении. Не следует также забывать, что область действия объявляемого ресурса огра­ ничивается оператором try с ресурсами. Гл авное преимущество оператора try с ресурсами заключается в том, что ресурс (в данном случае поток ввода-вывода) закрывается автоматически по завершении блока оператора try. Та ким образом, исключается даже возможность забыть закрыть поток ввода-вывода по небрежно­ сти. Кроме того , способ закрытия потока ввода-вывода с помощью оператора t ry с ресурсами, как правило, приводит к более краткому и понятному исходному коду, который проще сопровождать. Благодаря неоспоримым преимуществам оператора try с ресурсами можно предположить, что он будет широко применяться в новом коде. Поэтому в боль­ шинстве примеров организации ввода-вывода из этой гл авы в частности и всей кн и г и вообще применяется именно этот оператор. Но поскольку все еще имеется немало унаследованного кода, программисты должн ы быть знакомы с традицион­ ным подходом к закрытию потока ввода-вывода. Вполне возможно, что вам при­ дется иметь дело с унаследованным кодом, в котором применяется этот традици­ онный подход, или же в среде , где испол ьзуется одна из прежних версий Java. Не исключено также , что автоматизированный способ окажется непригодным из-за других особенностей прикладного кода. Поэтому в некото рых примерах организа­ ции ввода-вывода в этой книге демонстрируется традиционный способ закрытия потока ввода-вывода, чтобы показать, каким образом он применяется на практике. И последнее замечание: примеры , в которых применяется оператор try с ре· сурса ми , следует компилировать современной версией компилятораjаvа. Их нель· эя скомпилировать прежним компилятором, выпущенным до версии Java SE 7. А примеры, в которых применяется традиционный способ закрытия потока вво­ да-вывода, могут быть откомпилированы прежними версиями компиляторов Java. Помните! Оператор try с ресурсами упрощает процесс освобождения ресурсов, исключая даже возможность забыть освободить испол ьзуемый ресурс по небрежности. Поэтому такой спо­ соб освобождения ресурсов рекомендуется применять при всякой возможности в процессе разработки нового кода . Кл ассы пото ков ввода -вывода Система потокового ввода-вывода в Java построена на основе следующих аб­ страктных классов: Inp utStream, OutputStream, Reade r и Wr iter. Эти классы уже описывались вкратце в гл аве 13. Они служат для создания ряда конкретных
728 Ч асть 11. Б иблиотека Java подклассов потоков ввода-вывода. Несмотря на то что операции ввода-вывода в прикладных программах выполняются с помощью конкретных подклассов , упо­ мянугые выше классы верхнего уровня определяют основные функциональные возможности , которые являются общими для всех классов потоков ввода-вывода. Классы Inp utStream и Ou tputStream предназначены для организации пото­ ков ввода-вывода байтов, а абстрактные классы Re ade r и Wr iter - для потоков ввода-вывода символов. Классы потоков ввода-вывода байтов и символов образу· ют отдел ьные иерархии. В целом классы потоков ввода-вывода символов следует использовать, оперируя символами или их строками, а классы потоков ввода-вы· вода байтов - оперируя байтами или другими двоичными объектами. Далее в этой гл аве расс матриваются потоки ввода-вывода как байтов, так и символов. П ото ки ввода- вывода байтов Классы потоков ввода-вывода байтов предоставляют богатую среду для органи· за ции байтового ввода-вывода данных. Поток ввода-вывода байтов можно испол1r зовать вместе с объектами любого типа, включая двоичные данные. Такая уни· версальность делает потоки ввода-вывода байтов важными для многих видов про­ грамм. Классы потоков ввода-вывода байтов происходят от классов InputStream и Ou tputStrearn, поэтому именно с них и следует начать обсуждение данной кате­ гории потоков ввода-вывода. Клacc inputS tream Класс Input Stream является абстрактным и определяет вjava модель потоко­ вого ввода байтов. Он реализует интерфейсы Au toCloseaЫe и CloseaЫe. При возникновении ошибок ввода-вывода большинство методов этого класса генери· руют исключение типа IOException. (К их числу не относятся методы mark () и ma rkSuppo rted ().)Втабл. 20.2 перечислены методы из класса InputStre am. Та блица 20.2. Методы иэ класса Inputstream Метод in t availaЬle () void close () void 11 1& rk (int кмичество_ байтов) boolean шarkSupported () Описание Воавращает количество байтов вводимых дан ных , до­ ступ ных в данный момент для чтения Закрывает источник ввода данных. При всякой после­ дующей попытке прочитать да нные иа этого источника будет сгенерировано исключение типа IOException Раамещает на текущей поаиции в потоке ввода метку, которая остается достоверной до тех пор, пока не будет прочитано ааданное 1W.1 1 ичество_байтов Воавращает логическое аначение true, если методы .ark ( ) и reset ( ) поддерживаются выаывающим пото­ ком ввода
Метод int read () int read (Ьyte буфер[] ) int read (Ьуt.е буфер [] , int смещение , int 1WJ1wrество_байтов) void re set () long skip (long количество_ байтов) Гл ава 20. Пакет java.io для ввода - вывода 729 Окончание та6Л. 20. 2 Описание Возвращает целочисленное представление следующего байта, доступ ного в потоке ввода. По достижении конца файла возвращается значение - 1 Пытается прочитать в указанный буфер количество бай­ тов, равное буфер . ддина, возвращая количество ус пешно прочитанных байтов. По достижении конца файла воз­ вращается значение -1 Пытается прочитать в указанный буфер заданное количество_байтов, начиная с позиции буфер [ смг.(lение] и возвращая кол ичество ус пешно прочитанных байтов. По достижении конца файла возвращается значение -1 Перемещает указатель ввода на установленную ранее метку Игнорирует (т.е . пропускает) задан ное для ввода ко.лUtUСтВо_байтов, возвращая кол ичество фактически проигнорированных байтов На заметку! Бол ьшинство методо в, переч исленных в та бл . 20 .2, реализуются в классах, произ­ водных от класса InputStream. Исключением из этого правила служат методы ma rk () и re set (). Поэтому обратите внимание на их присутствие или отсутств ие в каждом рас­ сматриваемом далее подклассе, производном от класса InputStream. Класс Outpu tstream Класс Ou t pu t S t ream является абстрактным и определяет потоковый вывод бай­ тов. Этот класс реализует интерфейсы Au toCloseaЬle, CloseaЫe и FlushaЫe. Большинство методов из этого класса возвращают значение типа vo id и генери­ руют исключение типа IOException при возникновении ошибок ввода-вывода. Методы из класса Ou tput Stream перечислены в табл. 20.3 . Таблица 20.3. Методы из класса Outputstream Метод int close () void flush () void write (int Ь) Описание Закрывает поток вывода. Последующие попытки вы­ вести данные в поток приведут к исключению типа IOException Делает конечным состояние вывода, очищая все буфера, в том числе и буфера вывода Записывает единственный байт в поток вывода. Обратите внимание на то, что параметр метода write ( ) от носится к типу int, что позволяет вызывать этот ме­ тод в выражении, не приводя полученный результат об­ ратно к типу byte
730 Часть 11. Библиотека Java Метод Описание 0кО'Н:ЧаНШ! табл. 20.J void vrite (Ьyte буфер[] ) void vrite (byte буфер [] , int смещение , int количество_байтов) Зап исывает весь массив байтов в поток вывода Зап исывает часть заданного колw.ества_байтов из указан· ного массива буфер, начиная с позиции буфер [ смещение] Кnacc Fi leinputStream В классе Fi leinputStream создается объект типа Inp utStream, который мож· но использовать для чтения байтов из файла. Ниже приведены наиболее ч асто употребляемые конструкторы этого класса. FileinputStream (String ny!l'ъ ж фЗJiлу) FileinputStream (File объеж!l'=ф8i i ла) Каждый из них может сгенерировать исключение типа Fi leNotFoundExcepti on. Здесь параметр путь_ к _ файлу обозначает полное имя пути к файлу, а параметр объект_ файла - объект типа File, описывающий файл . В следующем при мере кода создаются два объекта класса FileinputStream, использующих один и тот же файл на диске и оба конструктора дан ного класса. Fi leinputStrearn fO = new Fi leinputStrearn("/ autoexec .ba t") File f = new Fi le ("/autoexec .bat" ); Fi leinput Strearn fl = new Fi leinputStrearn(f) ; Хотя первый конструктор, вероятно, испол ьзуется чаще , второй конструк· тор позволяет подробно исследовать файл с помощью методов из класса File, прежде чем присоединять его к потоку ввода. Когда создается объект типа FileinputStream, определяемый им поток ввода открывается для чтения. В клас­ се FileinputStream переопределяются шесть методов из абстрактного клас­ са InputStream. В то же время методы ma rk () и reset () не переопредел яют­ ся, и поэтому все попытки использовать метод re set () вместе с объектом типа Fileinput Stream приводят к генерированию исключения типа IOException . В приведенном ниже примере программы показано, как прочесть один байт, массив байтов и часть массива байтов, а также демонстрируется применение мето­ да availaЫe () для определения оставшегося количества байтов и метода skip () для пропуска нежелательных байтов. Данная программа читает свой исходный файл , который должен присуrствовать в текущем каталоге. Обратите внимание на то , что в дан ном примере используется оператор try с ресурсами для автомати­ ческого закрытия файла, когда он больше не нуже н. 11 Продемонстрировать приме нение кла сса FileinputStrea.m . 11 В этой программе исполь зуется оператор try с ресурсами . 11 Тре буе тся установка комплекта JDK , начиная с версии 7 import java.io.*; clas s Fi leinputStreamDerno { puЫic static vo id rna in (String args[] ) { int size;
Гл ава 20. Пакет java.io дпя ввода-вывода 731 // Для автома тич еского закрытия потока ввода // исполь зуе тся оператор try с ресур сами try ( Fileinput Stream f = new Fi leinputStream ("F ileinputStreamDemo .java ") ) { System. o ut . println ( "Oбщe e количество доступных байтов : " + (size = f.availaЬle () )); int n = size/40; System.out . println ( "Пepвыe " + n + " байтов , " + "прочитанных из файла по очереди ме т одом read() ") ; for(inti=O;i<n;i++){ System. o ut . print ((char) f.read() ); System.out . println ("\nBce еще доступно : "+f.availaЬle () ); System. out .println ( "Чтeниe следующих " + n + " байтов по очереди методом read ( b[J )"); byte b[J = new byte [n] ; if (f.read (b) != n) { System. err . println ( "Нель зя прочи тать "+n + " байтов.") ; System.out . println (new String (b, О, n) ); System. out . println ("\nBce еще доступно : " + (size = f.availaЬle () )); System . out .println ( "Пропустить половину оставшихся байтов методом skip () ") ; f.skip (size/2) ; System . out .println ("Bce еще доступно : "+f.availaЬle () ); System. out . priпtln ( "Чтeниe " + п/2 + " байтов , разме щаемых в конце ма ссива") ; if (f.read(b, n/2, n/2) != n/2) { System. err . println ( "Нель зя прочи тать "+n/2 +"байтов. ") ; System. out . println (new String (b, О, b.leng th) ); System. o ut . println ("\nBce еще доступно : "+f.availaЬle () ); catch (IOException е) { System. out .println ( "Oшибкa ввода- вывода : "+е) ; Эта программа выводит следующий результат: Общее количество дос тупных байтов : 2258 Первые 56 байтов, прочи танных из файла по очереди ме т одом read () 11 Продемонстрировать применение кл асса FileinputS treaa все еще доступно : 22О2 Чтение следующих 56 байтов по очереди методом read(b[J ) 11 В этой програыме исполь зуется оператор try с ресурса все еще доступно : 2146 Пропус тить половину оста вшихся байтов ме т одом skip () Все еще доступно : 1073 Чтение 28 байтов, разме щаемых в конце массива
732 Часть 11. Библиотека Java 11 В этой программе использ ввода- вывода : " + е) ; Все еще доступно : 1045 Этот несколько надуманный nример демонстрирует чтение тремя способа· ми , пропуск вводи мых байтов данных и проверку количества байтов, доступных для ввода из потока. На заметку! В приведенном выше и других примерах из это й гл авы обрабатываются все исклю­ чения ввода-вывода, которые могут произойти, как описано в гл аве 13. (Подробнее об об· работке исключ ений см. в гл аве 13.) Кnacc FileOu tputStream В классе Fi l eOutputStream создается объект типа OutputStream, который можно использовать для записи байтов в файл. Этот класс реализует интерфейсы AutoC loseable, CloseaЫe и FlushaЫe. Ниже приведены четыре наиболее ча· сто употребляемых конструктора данного класса. Fi leOutputStream (Strinq ny'J.'ь ж фалу) FileOutputStream (File o65ex.., ,. - фlui лa) FileOutputStreaa (String ny'J.'ь-х фолу , boolean доба.ur'J.'Ь) FileOutputStream (File о65ех!1':фdла , boolean доба81 11 !1'J.) Все эти конструкторы могуг сгенерировать исключение ти па FileNot FoundException. Здес ь параметр путь_к_файлу о бозначает имя полного пуги к файлу, а параметр объект_файла - объект типа File, описывающи й файл. Если параметр доба вить принимает логическое значение true, файл открывает­ ся в режиме добавления данных. Создание объекта типа FileOutputStream не зависит от того , существует ли указанный файл . Он создается перед своим открытием при построении объ· екта класса FileOutputStream. При попытке открыть файл , доступный только для чтения , будет сгенерировано соответствующее исключение. В приведенном ниже примере программы организуется буфер байтов . Сначала в ней создается объект типа String, а затем вызывается метод getBytes () для из­ влечения его эквивалента в виде массива байтов. Далее создаются три файла. Первый файл filel . txt будет содержать каждый второй байт образца текста, второй файл file2 . txt - все байты из данного образца, а третий файл fileЗ . txt - только последнюю четверть дан ного образца. 11 Продемонс трировать приме нение класса FileOutputStreaш . 11 В этой программе исполь зуе тся традиционный способ закрыти я файла import java.io.•; class Fi l eOutputStreamDemo { puЫic static void ma in (String args[] ) { Striпg source = "Now is the time for all good me n\n" + " to соте to the aid of their count ry\n" + " and рау their due taxes ."; byte buf [J = source .getBytes () ; FileOutputStream fO null; FileOutputStream fl null; Fi leOutputStream f2 = null;
try { fO f1 f2 Гл ава 20. Пакет java.io для ввода-вы вода 733 new Fi l eOutputStrearn("filel .tx t") ; new Fi leOutputStrearn("file2 .tx t") ; new Fi l eOutputStrearn("fileЗ . txt " ); 11 записать данные в первый файл for (int i=O ; i < bu f.length ; i += 2) fO .write (buf [i]); 11 записать данные во второй файл fl .write (bu f) ; 11 записать данные в тре тий файл f2 .write (buf, buf .le ngth-buf .length/4 , buf . length/4 ); catch (IOException е) { Systern. o ut . println ( "Пpoизowлa ошибка ввода- вывода" ) ; finally { try { if(fO != null) fO. close (); } catch (IOException е) { Systern. o ut . println ( "Oшибкa закрытия файла filel .txt ") ; } try { if (fl != null) fl . close (); } catch (IOException е) { Systern. out . println ( "Owибкa закрыти я файла file2 .txt" ); } try { if(f2 != null) f2. close(); catch (IOException е) { Systern.out . println ( "Oшибкa закрытия файла fileЗ . txt" ); Ниже показано, как будет выглядеть содержимое каждого упомя нутого выше файла после выполнения данной программы. Сначала представлено содержимое файла filel . txt: Nwi h ie fralgo е toethiftercutynаhiuае. Затем содержимое файла file2 . txt: Now is the tirne for all good rne n to corne to the aid of their country and рау their due taxes . И н аконец, содержимое файла fileЗ . txt: nd рау their due taxes . Как следует из комментариев к исходному коду данной программы, в ней де­ мо нстрируется применение традиционного способа закрытия файла, когда он больше не нужен. Та кой способ требуется во всех версияхJаvа дo JDK 7 и широко применяется в унаследованном коде. В данном Примере можно обнаружить нема­ ло довол ьно неуклюжего кода, требующегося для явного вызова метода close (), поскольку при каждом его вызове может быть сгенерировано исключение типа IOException, если операция закрытия завершится неудачно. Программа из дан­ ного примера может быть значительно улучшена, если воспользоваться вторым
73' Часть 11. Библ иоте ка Java способом закрытия файла с помощью оператора try с ресурсами. Для сравнения ниже приведена переделанная версия этой программы. Обратите внимание на то, что ее исходн ый код намного короче и понятнее. 11 Продемонс трировать применение класса Fi leOutputStreaш . 11 Дл я закрытия файла в этой программе применяется оператор 11 try с ресурсами . Требуется ус тановка комплекта JDK , // начиная с версии 7 import j ava.io.*; class Fi leOutputStreamDemo puЬlic static voi d ma iп (Striпg args[] ) { String source = "Now is the time for all good me n\n" + " to come to the aid of their country\n" + " and рау their due taxes ."; byte buf (J = source .getBytes (); 11 приме нить оператор try с ресурс ами дл я закрытия файлов try (FileOutputStream fO new FileOutputStream("filel .txt " ); FileOutputStream f1 new FileOutputStream ( "file2 . txt " ); Fi leOutput Stream f2 = new FileOutputStream ( "fileЗ . txt") ) 11 записать данные в первый файл for (int i=O; i < buf.length; i += 2) fO.write (buf [i]); 11 записать данные во второй файл fl .write (bu f) ; 11 записать данные в третий файл f2 .write (buf , bu f . length-buf . length/4, bu f.length/4 ); catch (IOException е) { System.out . println ("Пpoизomл a ошибка ввода- вывода") ; Кnacc вyteArrayinputStream Класс ByteArraylnp utStream реализует поток ввода, использующий массив байтов в качестве источника данных. У этого класса имеются два конструктора, каждому из которых требуется массив байтов в качестве источника данных: ВyteArrayinputStreaш (Ьyte JQCCJUr[] ) ВyteArrayinputStreaш (Ьyte мacCJUJ [ ], int .наvало , int xomrvec!l'ao_бdтo.) где параметр ма ссив обозначает источник данных. Второй конструктор данного класса создает объект типа Inp utStream из подмножества массива байтов, начи· ная с символа на позиции, определяемой параметром на ча ло, и длиной, задавае­ мой параметром количеств о_ байтов. Метод close () не оказывает никакого влияния на объект класса ByteArray­ Inp utStream, поэтому вызывать его для данного объекта класса не нужно , хотя это и не считается ошибкой.
Гл ава 20. Пакет java.io для ввода -вывода 735 В сл едующем примере программы создается пара объектов класса ByteArray Inp utS tream, которая инициализируется байтами, представляющими англий­ ский алфавит: 11 Пр одемон стрировать применение кл асса ВyteArrayinputStream irnport java.io. * ; clas s ByteAr rayinputStreamDemo { puЫic static void main {String args[] ) throws IOException String tmp = "abcde fghij klmnopqrstuvw xyz "; byte Ь[] = tmp . getBytes (); ByteA rrayinp utStream input l ByteA rrayinp utSt ream input2 new ByteAr raylnput Stream (Ь) ; new ByteAr rayinput Stream{b ,0,3) ; Объект inputl содержит полный алфавит в нижнем регистре, в то время как объект inpu t2 -только первые три буквы. Класс ByteArraylnp utStream реали­ зует методы ma rk ( ) и reset ( ).Но если метод rnark () не вызывается , то метод r e set () устанавливает указатель н а начал о потока ввода (в данном случае - в н а­ чало мас сива байтов, передаваемого конструктору данного класса) . В приведен­ ном ниже примере показано , ка к пользоваться методом reset () для чтения одних и тех же вводи мых данных дважды. В данном примере программа читает и выво­ дит буквы "аЬс" сначал а в нижнем регистре , а затем в верхнем. irnport jav a.io. *; class ByteA rraylnp utStreamReset puЫic static void main {String args[] ) { String tmp = "аЬ с"; byte Ь[] = tmp.getBytes (); ByteAr raylnputStream in new ByteArraylnp utStream (Ь) ; for (int i=O; i<2; i++) { int с; while ((с = in. read()) != -1) { if(i==0){ System. o ut . print ((char) с) ; else { System . out .print (Character .toUppe rCase ((c har ) с) ); } System. o ut . println () ; in . reset (); В данном примере каждый символ сначала читается из потока ввода, а затем выводится без изменений в нижнем регистре . Далее поток ввода устанавливается в исходное состояние , и чтение данных начинается снова, но на этот раз каждый символ преобразуется в верхний регистр перед вы водом. В итоге выводится сле­ дующий результат: аЬс АБС
736 Ч асть 11. Библ иотека Java Клacc вyteArrayOutputStream Класс ByteArrayOutputStream реали3ует поток вывода, исполЬ3ующий мае· сив байтов в качестве адресата. В классе ByteArrayOutputStream имеются два конструкто ра, как показано ниже. ВyteArrayOutputS tream () ВyteArrayOutputStreaa (int жo.mrvec!l'so_б&Ji!l'o.в) В первой форме конструктора со3дается буфер ра3мером 32 байта, а во вто­ рой форме - буфер, ра3мер которого составляет 33.Данное количе ств о_ ба йтов. Этот буфер хранится в 3ащищенном поле buf класса ByteArr ayOutputStream. Ра3мер буфера увеличивается автоматически по мере необходимости . Кол и чество байтов, содержащихся в буфере, хранится в 3ащищенном поле count класса ByteAr rayOu tputStream. Метод сlоsе () не оказывает никакого влияния на класс ByteArrayOutputStream. Поэтому вы3ывать его для объекта класса ByteArrayOu tputStream не нужно, хотя это и не считается ошибкой. В следующем примере программы демонстрируется применение класса ByteAr rayOu tputStream: 11 Продемонстрировать применение класса ВyteArrayOutputS tream 11 В этой програNoс1е применяется оператор try с ресурсами . 11 Требуется установка комплекта JDK, начиная с версии 7 import jav a.io.*; class ByteArrayOutputStreamDemo { puЫ ic static vo id ma in (String args[]) { ByteArrayOutputStream f = new ByteArrayOutputStream () ; String s = "Эти данные должны быть выведены в ма ссив "; byte buf [J = s.getBytes () ; try { f.w rite (bu f) ; catch ( IOException е) { System.out . println ("O wибкa записи в буфер") ; return ; System. out .println ( "Бyфep в виде символь ной строки") ; System. out . println (f.toString () ); System. out . println ("B ма ссив" ) ; byte Ь[] = f.toByteArray (); for (int i=O; i<b . length ; i++) System.out . print ((c har ) b[i) ); System.out . println ("\nB поток вывода типа Ou tputStream() ") ; 11 использовать оператор try с ресурсами для управления 11 потоком ввода-вывода в файл try ( Fi leOutputStream f2 = new FileOutputStream("test .tx t") ) { f.writeTo (f2) ; catch (IO Exception е) System.out . println ("Oшибкa ввода- вывода : "+е) ; return ;
Гл ава 20. Пакет java .io для ввода-выв од а 737 System.out . println ( "Ycтaнoвкa в исходное состо яние ") ; f.reset () ; for (int i=O; i\<3; i++) f.write( 'Х'); System.out . println (f.toString () ); В результате выполнения этой программы выводится приведенный ниже ре­ зультат. Обратите внимание на то , что после вызова метода reset () сначала вы­ водятся три буквы ' Х ' • Буфер в виде символь ной строки Эти да нные должны быт ь выведены в массив В массив Эти данные должны быт ь выведены в массив В поток вывода типа OutputStream () Установка в исходное состояние кхх В данном примере для записи содержимого потока вывода f в файл test . txt вызывается служе бный метод writeTo ().Просмотр файла test. txt, созданного в предыдущем примере, обнаруживает такой результат, как и следовало ожидать. Эти данные должны быт ь выведены в массив Фил ьтруемые пото ки ввода-вывода ба й тов Фильтруем ьtе потоки ввода-вывода представляют собой оболоч ки , в котор ые за­ ключаются базовые потоки ввода-вывода для рас ш ирения их функциональных возможностей. Та кие потоки ввода-вывода обычно доступны методам, ко торые ожидают обобще нный поток, служащий суперклассом для фильтруемого потока ввода-вывода. Ти пичными рас ш ирениями функций этих потоков ввода-вывода яв­ ляются буферизация , преобразование символов и исходных да нных. Фильтруемые потоки ввода-вывода байтов реализуются в классах Fi lterinputStream и Fi l terOutputStream. Их конструкторы приведены ниже. FilterOutpu tS treaш (OUtpu tStreaш os) FilterinputStreaш (InputStreaш is) Методы , представленные в этих классах , ан алогичны методам из классов Input Strearn и Ou tputStream. Буфериэо ванные потоки в вода-вывода байтов Буферизовшн'н:ьrе потоки ввода-в'ЬЮода расширяют класс фильтруемого потока вво­ да-вывода, присоеди няя к нему буфер, выделяемый в памяти. Это т буфер позво­ ляет выполнять операции одновременного ввода-вывода не одного , а не несколь­ ких байтов, повышая тем самым производительность. Благодаря наличию буфера можно выполнять пропуск байтов, маркировку и установку потока ввода-вывода в исходное состоя ние. Буферизованные потоки ввода-вывода байтов реализуют-
738 Ч асть 11. Библиоте ка Java ся классами Bufferedinp utStrearn и BufferedOu tputStrearn, а также классом Pushbackinp utStrearn. Кnacc Bu fferedinputStream Буферизация ввода-вывода - очень распространенный способ оптимизации производительности. Класс Bufferedinp utStrearn позволяет заключить в обо­ лочку любой поток ввода типа Inp utStrearn и добиться повышения производи· тельности. У класса Bufferedinp utStrearn имеются два конструктора: ВufferedinputS treaш ( Inputs treaш по�ож .ваода) ВufferedinputS treaш ( InputStrea1 11 поток_:-ввода , int ра .sиер_буф ера) В первой форме конструктора создается буферизованный поток ввода, исполь· зующий размер буфера по умолчанию. Во второй форме конструктора задается конкретный ра эмер_ буфера . Рекомендуется указывать размер буфера, кратный размеру страницы памяти , дисковому блоку и т. п" и это окажет существенное по­ ложительное влияние на производительность. Но , с другой стороны, это зависит от конкретной реализации. Оптимальный размер буфера обычно зависит от опе· раци онной системы, объема доступной памяти и конфигур ации гл авной машины. Чтобы добиться эффективной буферизации, совсем не обязательно углубляться во все эти сложности. Поэтому целесообразно установить для потока ввода буфер размером 8192 байта или меньше. Та ким образом, низкоуровневая система сможет читать блоки данных с диска или из сети и сохранять результат в установле нном буфере. И даже если данные читаются байтами из потока ввода типа InputStream, то на практике придется, как правило , оперировать быстродействующей памятью. Буферизация потока ввода служит также основанием для поддержки опера· ции перемещения назад в потоке, имеющем свой буфер. Помимо методов read () и skip (), реализуемых в любом из классов Inp utStream и BufferedinputSt ream, поддерживаются также методы ma rk () и reset ().Наличие такой поддержки от­ ражает возврат логического значения true из метода Bu fferedinputStream. ma rkSuppo rted (). В приведенном ниже примере моделируется ситуация , когда с помощью мето­ да ma rk () можно запомнить место в потоке ввода, чтобы в дальнейшем вернуть· ся в это место с помощью метода re set ().Вданном примере производится син· таксический анализ содержимого потока ввода с целью обнаружить в нем сс ылку на элемент НТМL-разметки знака авторского права. Такая ссылка начинается со знака амперсанда ( &) и оканчивается точкой с запятой (;) без всяких промежу· точ ных пробелов. Образец введенных данных содержит два амперсанда, чтобы наглядно показать, когда происходит установка в исходное состояние с помощью метода re set () и когда этого не происходит. 11 Исполь зовать буферизованный ввод . 11 В этой программе применяется оператор try с ресурсами . // Требуе тся ус тановка комплекта JDK , начиная с версии 7 import java.io.*; class Bu fferedlnputStreamDemo puЫic static void ma in (String args [] ) { St ring s = "Это знак авторского пр ава &сору; " +
Гл ава 20. Пакет java .io Al'IЯ ввода-вывода 739 ", а &сору - нет .\n"; byte buf [] = s.ge tBytes () ; ByteArrayinputStream in = new ByteArrayinp utStream (bu f) ; int с; boolean ma rked = false ; 11 использовать оператор try с ресурсами для управления файлами try (BufferedinputStream f = new Bufferedinp utStream (in) ) { while ((с = f.read() ) != -1) switch (c) { } case'&': if ( !marked) { f.mark (32) ; marked = true; Jelse{ ma rked = false; break; case ';' : if (marked) ma rked = false; System. o ut . print (" (c)"); 1 else System . out .print ((c har) с) ; break; case ' '· if (marked) { ma rked = false; f.reset () ; System.out .print ("&") ; else System. o ut . print ((c har) с) ; break; default : if ( !marked ) System.out .print ((char) с) ; break; catch (IOException е) { System. out . println ( "Oшибкa ввода- вывода : "+е) ; Обратите внимание на то, что в данном примере делается вызов метода mar k ( 3 2 ) , в результате которого сохраняется метка для чтения следующих 32 байтов, чего достато чно для любых ссылок на элементы НТМL-разметки. Эта про­ rрамма выводит следующий результат: Это знак авторского nрава ©, а &сору - нет . Клacc BufferedOutputStreaш Класс Bu fferedOu tputStream подобен любому классу OutputStream, за ис­ ключением дополнительного метода flush (), обеспечивающего запись данных в буферизируемый поток вывода. Класс Bu fferedOu tput Stream предназначен
7'0 Часть 11. Библиотека Java для повышения производительности за счет сокращения количества операц ий за· писи данных, фактически выполняемых системой, и поэтому может возникнуrь потребность вызвать метод flush (), чтобы инициировать немедленную запись всех данных из буфера. В отличие от буферизованного ввода, буферизованный вывод не обеспечивает дополнительные функциональн ые возможности . Буфера вывода вJava требуются для повышения производительности. Ниже приведе ны два конструктора класса BufferedOu tputStream. ВUffered.OutputStream (OUtputStream поwож 11 11В ода) ВUffered.OutputStream (OUtpu tS tream поwох=11 11В ода , int раsнер_буфера ) В первой форме конструктора создается буферизованный поток вывода с ис· пользованием буфера, размер которого выбирается по умолчанию. Во второй форме конструктора задается ко нкретный ра змер_ буфера. Клacc Pu shЬackinpu tStream Одним из новшеств в буферизации ввода-вывода является реализация механиз· ма возврата в поток ввода. Этот механизм используется в потоке ввода, чтобы обе­ спечить чтение байта с последующим его возвратом в поток. Принцип действия возврата в поток ввода реализован в классе Pushbackinput Stream. Благодаря это­ му механи зму можно бегло просмотреть поток ввода и выяснить, что именно по­ ступит из него в следую щий раз, фактически не извлекая данные. В классе Pushb ackinputStream имеются следующие конструкторы: PushЬackinputStream ( InputStream поwож .в в ода) PushЬackinputStream ( InputStream поwож=ааода , int xOJIЯvecwвo _ бaJiwoв) В первой форме конструктора создается объект потока ввода, позволяющий возвратить один байт в указанный поток_ ввода . А во второй форме создается по­ ток ввода с буфером возврата указан ной длины. Это позволяет возвратить в поток ввода не од ин, а заданное количе ств о_ байтов. Помимо уже известных методов из класса Inp utStream, в классе Pushb ackinputStream предоставляется метод unread ().Ниже приведены общие формы его объявления. void unread (int Ь) void unread (Ьyte буфер(] ) void unread (Ьyte буфер , int снещеюrе , int .к:OJDl l' vecwso_бaJiwoa) В первой форме данного метода в поток ввода возвращается заданный млад· ший байт Ь. Это будет следующий байт, возвращаемый при последующем вызове метода read ( ) . Во второй форме в поток ввода возвращаются байты из указанно­ го буфера . А в третьей форме в поток ввода возвращается заданное колич е ство_ байтов из указанного буфера , начиная с позиции смеще ние . Исключение ти па IOException генерируется при попытке возвратить байт в поток ввода, когда бу· фер возврата заполнен. Ниже приведен пример программы, демонстрирующий применение класса Pushbac kinputStream и его метода unread ( ) в синтаксическом анализаторе языка программирования для различения операций сравнения (=) и присваивания (=).
rл ава 20. Пакет java .io для ввода -вы вода 741 11 Продемонстрировать приме нение ме тода unread () 11 из класса PushЬackinputStreaa . 11 В этой программе применяется оператор try с ресурсами . 11 Требуе тся установка комплекта JDK, начиная с версии 7 import jav a.io. *; class Pushbackinp utStreamDemo puЫ ic static void ma in (String args [] ) { Strings= "if(а==4)а=0;\n"; Ьуtе Ьuf [] = s.getBytes (); ByteArrayi nput Stream in = new ByteArrayinputStream (bu f) ; int с; try ( Pushbac kinputStream f = new PushbackinputStrearn (in) { while ((с = f.read()) != -1) { switch (c) { } case '=' : if((с=f.read())== '=') System.out . print (".eq. ") ; else { System.out . print ("<- ") ; f.unread(c) ; Ьreak; default: System.out . print ((char) с) ; Ьreak; catch (IOException е) { System.out . println ( "Oшибкa ввода-вывода : "+е) ; Ниже приведен результат, выводимый данной программой. Обратите внима­ ние на то, что операция "==" заменена на " . eq. " , а операция "=" - на "<-". if(а.eq.4)а<-О: ВНмманиеl Класс Pushbacki nput Stream обладает побочным эффектом, делая невозможн ым вызов методов ma rk () и reset () из класса Input Stream, применяемого для его соз­ да ния. Поэтому следует вызвать метод ma rkSupported (), чтобы проверить любой поток ввода , в котором предпола гается испол ьзовать методы ma rk () и reset (), на предмет поддержи этих методов. Кnacc sequence inpu tS tream Класс SequenceinputSt ream позволяет соединить вместе несколько потоков ввода типа Input Stream. Объект класса Sequence inp utStream строится иначе, чем объект класса InputStream. Конструктор класса Sequence inp utStream при­ нимает в качестве аргумента пару объектов класса InputStream или перечисле­ ние типа Enumeration объектов класса Inp utStream, как следует из приведенных ниже общих форм объявления конструктора этого класса.
71.2 Часть 11. Библиотека Java Sequenceinputstreaa ( InputStreaa uep8D[ DO!rOZ, InputStreaш S!ropoЖ_uO!rOZ) Sequenceinputstreaa ( - :ВnU8 8 ration <? extends Inputstreal l> uepe"cлeime_uo!rozoa) В процессе своей работы этот к.ласе делает запросы на чтение из первого по­ тока ввода типа InputStream до его исчерпания , а затем переходит к другому по­ току ввода. Если же указано перечисление типа Enumerati on потоков, этот класс обращается ко всем потокам ввода типа InputStream по очереди до исчерпания самого последнего из них. По достижении конца каждого файла связанный с ним поток ввода закрывается. Закрытие потока ввода, созданного средствами класса Sequence lnp utStream, приводит к закрытию всех остальных открытых потоков. Ниже приведен пример применения класса Sequencelnput Stream для вывода содержимого двух файлов. Ради наглядности в программе из этого примера при· меняется традиционный способ закрытия файла. В качестве упражнения попро­ буйте применить современный способ автоматического закрытия файла с помо­ щью оператора try с ресурсами, внеся соответствующие изменения в исходный код данной программы. 11 Продемонстрировать организацию последовательного ввода . 11 В этой програыwе исполь зуется оператор try с ресурсами. 11 Требуе тся установка комплекта JDK , начиная с версии 7 import jav a.io.*; import java .util .*; class Inp utStreamEnume rator implements Enumeration<FileinputStream> private Enumeration<String> files; puЫ ic InputStreamEnumerator (Vector<String> files ) { this . files = files . elements (); puЫ ic boolean hasMoreElements () { return files . hasMoreElements () ; puЫ ic Fi leinputStream nextElement () try { return new Fileinput Stream (files . nextEleme nt () .toString() ); catch (IOException е) { return nul l; class Sequence inp utStreamDemo { puЫ ic static void ma in (St ring args[]) ( int с; Vector<String> files = new Vector<String> () ; files . addElement ("filel .tx t") ; files . addEleme nt ( "file2 . txt" ); files . addElement ("fileЗ . txt " ); Inp utStreamEnume rator ise = new InputStreamEnume rator (files ); InputStream input = new SequenceinputStream(ise) ;
try { Гл ава 20. Пакет java.io для ввода-вывода 7'3 while ((с = input .read()) != -1) System.out . print ((char ) с) ; catch ( NullPointerException е) { System. out . println ( "Owибкa открытия файла.") ; catch (IOException е) ( System.out . println ( "Owибкa ввода- вывода : " + е) ; final ly { try ( input .close () ; catch (IOException е) { System. out . println ( "Оши бка закрытия потока ввода Sequenceinp utStream" ); В данном примере сначала создается объект типа Ve ctor, а затем в него вво­ дятся имена трех файлов. Далее этот вектор с именами файлов передается классу InputStrearnEnume rator, который служит оболочкой для вектора, где его элементы возвращаются не в виде имен файлов, аввиде потоков ввода типа File inp utStream, открытых по этим именам. Поток ввода типа Sеquеnсе inрutStrеаm открывает каж­ дый файл по очереди, чтобы затем вывести получе нное содержимое этих файлов. Обратите внимание на то , что если файл нельзя открыть, то в метод nextEle­ me nt () возвращает пустое значение nu ll. Это приводит к генерированию исклю­ чения ти па Nu llPo interException, перехватываемого в методе ma in (). Класс PrintStream Класс PrintStream предоставляет все возможности для вывода данных по де­ скриптору файла System. out типа System, которые используются в примерах программ с самого начала данной кн иги. Благодаря этому класс PrintStream я вляется одним из наиболее употребительных в Java . Он реализует интерфейсы AppendaЬ le, Au toCloseaЬ le, CloseaЫe и FlushaЫe. В классе PrintStream определяется ряд конструкторов. Рассмотрим сначала следующие формы конструкторов этого класса: PrintStreaш (OUtputStreaш поwож ll lDJ oдa) PrintStreaш ( OUtputStreaш поwож - В51Jlода , boolean авwооvисwжа) Printstreaш (OUtputStreaш поwож - ll lDJO дa , boolean авwооvисwжа , Strinq набор_синво:izов) throws UnsupportedEncodinqExcep tion гд е параметр поток_ вывода обозначает открытый поток вывода типа OutputStream, который будет принимать выводимые данные. Параметр автоочистка определя· ет, будет ли буфер вывода автоматически очищаться при каждой записи последова· тельности символов новой строки (\n) , записи массива байтов или вызове метода println (). Если параметр автоочистка принимает лоmческое значение true, то происходит автоматическая очистка буфера вьmода. А если этот параметр принима· ет логическое значение false, то очистка буфера вывода не производится автомати· чески. Первая форма конструктора данного класса не предполагает автоматическую очистку буфера вывода. Кроме того, можно задать конкретную кодировку символов, передав ее имя в качестве параметра на бор_ симв олов.
''' Ч асть 11. Библиоте ка Java Следующие формы конструкто ров предоставляют простые способы создания объекта класса PrintStream для вывода данных в файл: PrintStream (File фаi i лJl llJil oдa) throws Fi leNotFoundException PrintS tream (File фаi i: л - ашюда , Str inq набор aJrX&oлos) throws FileNotFoundException , Unsupport&dEncocti.ngException PrintS tream (Strinq � фаi i:л а.l llUIO дa ) throws FileNotFoundExcep tion PrintS tream (Strin9 ., ,. - фailлa - JIJUIOдa , Strinq набор СJIН8оло•) throws FileNotFoundExceptl on , UnsupportedEncodingException Они позволяют создавать объект класса PrintStream из объекта типа File или по указанному имени файла. Но в любом случае файл создается автоматиче­ ски . Любой существующий файл с тем же именем уничтожается . Как только пот ок вывода будет создан в виде объекта класса PrintStream, он будет направлять все выводимые данные в указанный файл. Конкретную кодировку символов мо жно за· дать в качестве параметра на бор_ символов. На заметку! При наличии диспетчера защиты некоторые конструкторы класса PrintStream бyrqr генерировать исключение ти па Securit yE xcept i on, если произойдет нарушение защиты. В классе PrintStream поддерживаются методы print () и println () для вы· вода всех типов данных, включая и тип Obj ect. Если аргумент не относится к при· мити вному ти пу, то в методах из класса PrintStream сначала вызывается метод toSt r ing ( ) для выводимого объекта, а затем выводится результат, получаемый из этого метода. Относительно давно (после выпуска версии JDК 5) класс PrintStream был до­ полнен методом print f (). Этот метод позволяет задать то чный формат вывода дан ных. Для форматирования данных в методе printf () используются соответ· ствующие средства класса Formatter, описан ного в гл аве 19, после чего отформа· тированные данные направляются в вызывающий поток вывода. Форматирован ие можно , конечно, выполнить и вручную, обращаясь непосредственно к классу Formatter, но метод printf () значительно упрощает данный процесс . Он я вля· ется ан алогом фун кции printf ( ) в С/С++, упрощая перенос существующего кода из С/С++ нa java. Откровенно говоря , метод printf ( ) стал очень полезным до­ пол нением прикладного программного интерфейса jаvа API, поскольку он значи· тельно упрощает вывод отформати рованных данных на консоль. Метод pr in t f ( ) имеет следующие общие формы: PrintS tream printf (String форнат.р.)'Ulfая строжа , Ob ject PrintS tream printf (Locale per1 1 oнaлwwe настртiюr , String форнат.р,УDlfаR _ строжа , Ob ject apryнeн RZ) В первой форме данного метода заданные аргументы выводятся в стандарт­ ный поток вывода в формате , указанном в качестве параметра форма тирующа я_ строка , с учетом региональных настроек по умолчанию. А во второй форме мож· но указать конкретные региональные_ на стройки. Но в любом случае возвращает­ ся вызывающий поток вывода в виде объекта типа PrintStream. В общем , метод printf () подобен методу forma t (), определенному в классе Fo rma t ter. Параметр форма тирующа я_ строка состоит из элементов двух типов. Элемент первого типа состоит из символов, которые просто копируются в буфер
Гл ава 20. Пакет java.io для ввода -вы вода 7'5 вывода, а элемент второго типа - из спецификато ров формата, определяющих по­ рядок вь1вода указан ных далее аргументов. Подробнее о форматировании выво­ димых дан ных, включая описание спецификаторов формата , см. в описании клас­ са Formatter в главе 19. Стандартный поток вывода System. out относится к типу PrintStream, и по­ этому для него можно вызывать метод print f ().Следовательно , метод printf ( ) можно вызвать вместо метода println (),когда требуется вывести на консоль от­ форматированные данные. Та к, в приведенном ниже примере программы метод print f () вызывается для вывода числовых значений в разных форматах. До вер­ с ии JDК 5 для такого форматирования данных п риходилось немало потрудиться . Но с внедрением метода printf () задача вывода отформатированных данных значительно упростилась. 11 Продемо нс трировать приме нение ме тода printf () class PrintfDemo { puЫ ic static void ma in (String args [] ) { System.out . println ("H ижe при ведены некоторые " + "число вые значения в разных форма тах .\n ") ; System. out .printf ("P aзныe целочисле нные форматы : ") ; System . out .printf("%d %(d %+d %05d\n", 3, -3, 3, 3) ; System.out . println () ; System.out . printf ("Фopмaт чисел с плавающей точ кой " + "по умо лчанию : %f\n", 1234567.123) ; System.out . print f ("Фopмa т чисел с плавающей точ кой , " + "разделяемых запятыми : %, f\n", 1234567 .123) ; System.out . printf (Фopмa т отрицательных чисел с " + "плавающей точкой по умолч анию : %, f\n", -1234567 .123) ; System. out . printf (Дpyгoй Форма т отрицательных чисел с " + "плавающей точкой : %, (f\n" , -1234567 .123) ; System.out . println () ; System. out .printf ( "Bыp aвнивaниe положитель ных и " + "отрица тель ных число вых значений:\n ") ; System. out . printf ("% ,.2f\n% ,.2f\n" , Эта программа выводит следующий резул ьтат: Ниже приведены не которые числовые значения в разных форматах . Раз ные целочисленные форма ты : 3 (3) +3 00003 Формат чисел с ппавающей точ кой по умолчанию : 1234567 . 123000 Формат чисел с плавающей точкой , разделяемых запятыми : 1, 234, 567 . 123000 Формат отрицательных чисел с плавающей точкой по умолч анию : -1, 234, 567 . 123000 Другой Форма т отрицатель ных чисел с пл авающей точкой : (1, 234, 567 . 123000) Выравнивание положитель ных и отрицатель ных число вых значе ний : 1,234, 567 .12 -1,234 , 567 .12 В классе PrintStream о пределяется также метод forma t (). Ниже приведены его общие формы. Он действует таким же образом, как и метод print f ( ) .
71.6 Часть 11. Библиотека Java PrintS tream forшat (Strin9 фopнa7JIPJ'Dl l' a. С!l'рйжа, OЬject . . . apr,YNeнRI') PrintS tream fora a t (Locale регион , Strin9 - фopНВ!l'JIPJ'Dl l aR С!l'рйжа, Ob ject ... apr,YNeн RI') - Классы DataOutputStream и DatainputStream Эти классы позволяют выводить примитивные данные в поток или вводить их из потока. Они реализуют интерфейсы DataOutput и Data!nput соответственно. В этих инте рфейсах определяются методы, выполняющие взаимное преобразова· ние значений примитивных типов и последовательностей байтов. Та кие пото ки ввода-вывода упрощают сохранение в файле данных, представленных в двоичной форме, например, целочисленных значений или числовых значений с плавающей то чкой. Ниже будет показано, каким образом орган изуется ввод-вывод примитив· ных дан ных в двоичной форме. Класс DataOutputStream рас ширяет класс FilterOutput Stream, который, в свою очередь, расширяет класс Ou t pu t S t re am. Помимо интерфейса Dat aOu tput, класс DataOutputStream реализует интерфейсы Au toCloseaЬle, CloseaЫ e и FlushaЫe. В классе Da t aOutputStream oпpeдeляeтcя следующий конструктор : DataOUtputStream ( OUtputStream по�ож _ .1 11П1 ода) где параметр поток_ выв ода обозначает поток, в кото рый будут выводиться дан· ные. Когда поток вывода типа Da t aOutputStream закрывается в результате вы­ зова метода close (),базовый поток, определяемый параметром поток_ вывода , также закрывается автоматически . В классе Dat aOutputStream поддерживаются все методы, определенные в его суперклассах. Но по-настоящему привлекател ьным этот класс делают реализуе­ мые в нем методы из интерфейса DataOutput. В интерфейсе Da t aOutput опре­ деляются методы , сначала преобразующие значения примитивных типов в после· довательности байтов, а затем выводящие их в базовый поток. Ниже приведен ы общие формы этих методов, где параметр зна чение обозначает выводимое в по­ ток значение примитивного типа. final void wri teDouЬle {douЫe .sнav8Нl lr e) throws IOException final void writeВoolean (boolean .sнa v8Нl lr e) throws IOException final void writeint (int .sнa vaюwe) throws IOException Класс Da ta!nputStream служит дополнением класса DataOutputStream для ввода примитивных типов данных в двоичной форме. Он расширяет класс FilterinputStream, который, в свою очередь, расширяет класс InputStream. Помимо инте рфейса Datainput, класс Datainpu tStream реализует интерфейсы AutoC loseaЫe и CloseaЫe. В этом классе определяется следующий единствен· ный конструктор: DatainputBtream { InputStreaш nо7ож_"ода) где параметр поток_ ввода обозначает тот поток, из которого будут вводи'ться дан· ные. Когда поток ввода типа Da tainp utStream закрывается в результате вызова метода close (), базовый поток ввода, определяемый параметром поток_ввода, также закрывается автоматически.
Гл ава 20. Пакет java.io дл я ввода -вы вода 71.7 Как и в классе DataOutputStream, в классе DatainputStream подцерживают­ ся все методы из его суперклассов, а также методы, определенные в интерфейсе Datalnput , что делает его особенно привлекател ьным. Эти методы вводят после­ довател ьность байтов и преобразуют их в значения примитивных типов. Ниже приведены общие формы этих методо в. final douЬle readDouЬle () throws IOException final boolean readВoolean () throws IOException final int readint () throws IOException В следующем примере програм мы демонстрируется применение классов DataOutputStream и Datalnput Stream: 1 1 Продемонс трировать применение кл ассов 1 1 DatainputStreaz и DataOUtputStreaz . 11 В этой програм м е используется оператор try с ресурсами . 11 Требуе тся установка комплекта JDK , начина я с версии 7 irnport java . io . *; clas s DataIODemo { puЬl ic static vo id ma in (String args[] ) 11 сначала вывести данные в файл try ( DataOutputStream dout = new DataOutputStream ( new FileOutputStream ("Test .da t") ) ) dout . writeDouЬle (98.6) ; dout .writeint (lOOO) ; dout . writeBoolean (true) ; } catch (FileNot FoundException е) { System. out . println ( "Heль зя открыть файл вывода") ; return; catch ( IOException е) { System.out . println ( "Owибкa ввода -вывода : "+е) ; 11 а теперь ввести данные из файла try ( DatainputStream din = new Datainput Stream (new Fileinput Stream("Test .dat " )) douЫe d = din. readDouЬle(); int i = din . readlnt (); boolean Ь = din . readBoolean () ; System. out . println ("П oлyчaeмыe значения : " + d +""+i +""+Ь) ; catch (FileNot FoundException е) { System.out . println ( "Heль зя открыт ь файл ввода ") ; return; catch (IOException е) { System.out . println ( "Owибкa ввода- вывода : "+е) ; Ниже приведен результат, выводимый данной программой . получ аемые значения : 98 .6 1000 true
7'8 Ч асть 11. Библиоте ка Java Клacc RandomAcce ssFile Класс RandornAccessFile инкапсулирует файл произвольного доступа. Он не наследуется от класса Inp utStream или Ou tput Stream. Вместо этого он реали· зует интерфейсы Da talnput и Da t aOutput, в которых определяются основные методы ввода-вывода. Этот класс реализует также интерфейсы Au toCloseaЫe и CloseaЫe. Класс RandornAccessFile отличается поддержкой запросов на по­ зиционирование, что позволяет установить указатель файла на любой позиции в пределах этого файла. У этого класса имеются следующие конструкторы: RandoшAccessFile (File объеж� фаiiла , Strinq дос�.УП) throws FileNotFoundE:к:ception RandoшAccessFile ( Strinq 1DUl фаiiла , Strinq дос�.УП) throws FileNotFoundE:к:ception В первой форме конструктора параметр объект_ файла обозначает открывае­ мый файл как объект типа File. А во второй форме имя конкретного файла пере· дается в качестве параметра имя_ файла . Но в любом случае параметр доступ опре­ деляет тип разрешенного доступа. Та к, если параметр доступ принимает значение "r", данные можно прочитать из файла, но не записать в него. Если же параметр доступ принимает значение "rw", файл открывается в режиме чтения и за писи. А если параметр доступ принимает значение "rws ", то файл открывается для вы· полнения операций чтения и записи, и каждое изменение данных или метадан ных в файле немедленно выводится на физическое устройство . Метод seek (),общая форма которого приведена ниже, служит для установки указателя файла на теку· щей позиции в этом файле. void seek ( lonq .новаR_поsяця.1 1 ) throvs IOE:к:ception Здесь параметр новая_ позиция обозначает новую позицию указателя файла, отсчитываемую в байтах от начала файла. После вызова метода see k ( ) следующая операция чтения или записи данных выполняется именно с этой новой позиции в файле. Класс RandornAccessFile реализует стандартные методы ввода-вывода, кото­ рые можно использовать для чтения и записи файлов с произвольным доступом. Кроме того , он содержит ряд дополнительных методов, к числу которых относи'!" ся метод setLength ().Ниже приведена его общая форма. void setLenqth ( lonq дJDIUfa) throvs IOException Метод setLength () устанавливает заданную длину вызывающего файла. С по­ мощью этого метода можно удл инять или укорачивать файл . Если файл удл иняе'Г' ся, добавляемая в него порция не определена. П ото ки ввода-вывода с имвол ов Классы потоков ввода-вывода байтов предоставляют необходимые фун кцио­ нальные возможности для выполнения операций ввода-вывода любого тип а , но они не в состоянии оперировать непосредственно символами в Юникоде. А по­ скольку одн ой из гл авных целей Java является соблюдение принципа "написано
Гл ава 20. Пакет java.io для ввода-вы вода 71+9 однажды, выполняется везде'', то вjava пришлось внедрить поддержку непосред­ ственного ввода-вывода символов. В этом разделе рассматривается ряд классов, предназначенных для ввода-вывода символов . Как пояснялось ранее, на вершине иерархии классов, реализующих потоки ввода-вывода символов, находятся аб­ страктные классы Reade r и Writer. Именно с них и следует начать расс мотрение классов данной категории. Класс Re ader Класс Reader является абстрактным и определяет потоковый ввод символов в Java. Он реализует интерфейсы AutoC loseaЬle, CloseaЫe и ReadaЫe. Все методы этого класса, за исключением метода ma rkSuppo rted (}), генерируют ис­ ключение типа IOException при возникновении ошибок. В табл . 20.4 приведена краткая сводка методов из класса Reader. Табл ица 20.1+. Методы из класса Reader Метод aЬetract void cloee () void 11 1a rk (int 1«1 11 ичество_ символов) Ьo o lean 11 1a rkSupported () int rud () int read (char буфер[]) int read (Char&\dfer буфер) aЬetract int read(char буфер[], int смещение, int КО1 1 ичюпво_симвмов) Ьo o lean ready () void reset () lorщ skip (lonq 1m1 1 ичество_ CWl80JIQ 8 ) Описание Закрывает поток ввода. При последующих попытках чте­ ния данных из этого шлока ввода генерируется исключе­ ние типа IОЕхсерtiоn Размещает на текущей позиции в потоке ввода метку, кото­ рая остается достоверной до тех пор, пока не будет прочи­ тано заданное 1m1 1 ичество_й1М81 11 1О О Возвращает логическое значение true, если в потоке ввода поддерживаются методы 11 1a rk()иreset() Возвращает целочисленное представление следующего символа, доступного в вызывающем потоке ввода. По до­ стижении конца файла возвращается значение -1 Пытается прочитать в указанный буферколичество симво­ лов, равное буфер . длина, возвращая количество успешно прочитанных символов. По достижении конца файла воз­ вращается значение -1 Пытается прочитать символы в указанный буфер. возвра­ щая количество успешно прочитанных символов. По дости­ жении конца файла возвращается значение -1 Пьrrается прочитать в указанный буферзаданное 1«1 11 ичюпво_сwсволов, начиная с позиции буфер [ смещение] и возвращая количество успешно прочитанных символов. По достижении конца файла возвращается значение - 1 Возвращает логическое значение true, если следующий за­ прос на ввод не бу.цет ждать, а иначе - логическое значение false Перемещает указатель ввода на установленную ранее метку Пропускает заданное для ввода 1CQl lUЧeCmвO _cwcвoлoв, возвра­ щая количество фактически пропущенных символов
750 Часть 11. Библиоте ка Java Класс Writer Класс Wr i ter является абстрактным и определяет потоковый вывод символов вjava. Этот класс реализует интерфейсы Au toCloseaЫ e, CloseaЬle, FlushaЫe и Ap pendaЫ e. При возникновении ошибок все методы этого класса генерируют исключение типа IOException. В табл . 20.5 приведена краткая сводка методов из класса Wri ter. Табл ица 20.5. Метод ы мэ класса Wri ter Метод Wr iter append (char сwиюл) Иriter append (CharSequence символы) Иriter append (CharSequence сwиюдь�, int наtШJЮ, int конец) aЬstract void close () aЬs tract void flush () void write (int симвм) void write (char буфер[] ) aЬstract void write (char буфер(], int смещение, int KWIUfU!Cm80_cwcвoлoв) void write (Strinq строка> void write (String строка , int смещение , int н:оличество_сwсволов) Описание Присоединяет указанный символ в конце вызыва· ющего потока вывода. Возвращает ссылку на вы· зывающий поток вывода Присоеди няет указанные сwиюды в конце вы· зы вающего потока вывода. Возвращает ссылку на вызывающий поток вывода Присоединяет указанные символы в заданных пределах от начtl.1 1 0 и до конец-1 в конце вызыва· ющего потока вывода. Возвращает ссылку на вы· зывающий поток вывода Закрывает вызывающий поток вывода. Последующие попытки вывода в этот поток приведут к генерированию исключения типа IOExcep tion Делает конечным состояние вывода, очищая все буфера, в том числе и буфера вывода Записывает еди нственный символ в вызы ва ю­ щий поток вывода. Обратите внимание на то, что параметр символ от носится к типу int, ч то позволяет вызывать метод write О в выраже­ нии, не прибегая к приведению обратно к типу char. Но выводятся только младшие 16 бит ука· занного символа Записывает весь заданный массив символов в вызывающий поток вывода Записывает указанное количество_СWIUЮ.IЮ 8 из за· данного массива буфер в вызывающий поток вы· вода, начиная с позиции буфер [ смещение] Записывает указанную стртсу в вызывающий по­ ток вывода Записывает указанное кoлuчecmвo_ Cl lJIUIO.I Ю8 из заданной строки в вызывающий поток вывода, начиная с позиции, обозначаемой параметром смещение
Класс FileReader Гл ава 20. Пакет java.io для ввода-вы вода 751 Этот класс является производным от класса Reade r и служит д.ля чтения со­ держимого файла. Ниже приведены два наиболее употребительных конструктора этого класса. FileReader (String ny!l.'я. ж фалу) FileReader (File о6ъе.в:!l.':фdла) Здесь параметр путь_ к _ файлу обозначает имя полного пути к файлу, а пара· метр объект_ файла - объект типа Fi le, описывающий файл. Оба конструктора мо гут сгенерировать исключение типа FileNotFoundException. В приведенном ниже примере программы показано, как организовать построч­ ное чтение и запись данных из файла в стандартный поток вывода. Программа читает собственный исходный файл , который должен находиться в текущем ка­ талоге. 11 Продемонстрировать приме нение кл асса FileReader 11 В этой программе исполь зуе тся оператор try с ре сурсами . 11 Требуе тся установка компл екта JDK, начиная с версии 7 import java.io.*; cl ass Fi l eReaderDemo puЬlic static vo id main (String args[] ) { try ( Fi leReader fr = new FileReader ("FileReaderDemo .java" ) { int с; 11 прочитать и вывести содержимое файла while((с = fr. read ()) ! = -1) System.out .print ( (char) с) ; catch (IOException е) { System.out . println ( "Oши бкa ввода- вывода : "+е) ; Класс Fi leWr i ter Этот класс создает поток вывода типа Wri ter для записи данных в файл . Ниже приведе ны наиболее употребительные конструкторы класса FileWr i ter. FileWriter (Strinq ny!l.'ъ .в: фалу) FileWriter (Strinc;i ny!l.'ъ - ж - фалу , boolean дoCSiUl ll l !l'Ъ) FileWriter (File ot»ex!l' - фdла) FileWri ter (File ot»eж!l':фала , Ьoolean nр• соеджюr!l'ъ) Здесь параметр путь_ к _ файлу обозначает имя полного пути к файлу, а пара­ метр объект_ файла - объект типа File, описывающий файл. Если параметр присоединить принимает логическое значение true, то выводимые данные при­ соединяются в конце файла. Все конструкторы данного класса могут генерировать исключение типа IOE xce p tio n . Создание объекта типа Fi leWriter не зависит от того , существует ли файл. Когда создается объект типа FileW riter, то попутно создается и файл , прежде
752 Часть 11. Библ иотека Java чем открыть его для вывода. Если же предпринимается попытка открыть файла, доступный только для чтения, то генерируется исключение типа IOException. В приведенном ниже примере представлена переделанная под ввод-вывод сим· волов версия программы из рассмотренного ранее примера, демонстрировавше­ го применение класса Fil eOutputStream. В этой версии организуется буфер сим· волов для хранения образца текста. С этой целью сначала создается объект типа String, а затем вызывается метод getChars () для извлечения эквивалентного символьного массива. Далее создаются три файла. Первый файл , file . txt , дол· жен содержать каждый второй символ из образца текста, второй файл , fi le2 . txt, - все символы из образца текста, а третий файл , fileЗ . txt, - только послед­ нюю четверть символов из образца текста. 11 Продемонстрировать приме нение класса FileWriter 11 В этой программе используется оператор try с ре сурсами . 11 Требуе тся установка комплекта JDK , начиная с версии 7 import jav a.io.*; class Fi leWri terDemo puЫ ic static void ma in (String args[] ) throws IOException { String source "Now is the time for all good men\n" + " to соте to the aid of their country\n" + " and рау their due taxes ."; char buffer [] = new char [source . length () ]; source .getChars (O, source .length () , buffer , 0) ; try Fi leWri ter fO Fi leWriter fl FileWriter f2 new Fi leW riter ("filel .txt" ); new Fi leWriter ("file2 .txt") ; new Fi leWriter ("fileЗ .txt ") ) 11 выв ести символы в первый файл for (int i=O; i < buffer. length; i += 2) { fO .write (buffer (i] ); 11 выв ести символы во второй файл fl .write (buffer ); 11 вывести символы в тре тий файл f2 .write (buffer, bu ffer . length-buffer . length/4,buffer. length/4 }; catch (IOException е) { System.out . println ("Пpoизoшлa ошибка ввода- вывода") ; Кnacc CharArrayReade r Класс CharAr rayRe ader реализует поток вывода, использующий массив в ка­ честве источника данных. У этого класса имеются два конструктора, каждый из которых принимает массив символов в качестве источника данных. CharArrayReader ( char нacCJUI [ ] ) CharArrayReader (char насс:. . [], int наvало, int 1tOJI1 1r vec!l'so_cянaOJ J oв)
Гл ава 20. Пакет java .io для ввода-вывода 753 Здесь параметр ма ссив обозначает источник ввода данных. Второй ко нструк­ тор созда.ет объект класса, производного от класса Reade r, из подмножества мас­ сива символов, начинающегося с позиции, обозначаемой параметром на чало, и длиной, определяемой п араметром количество_ символов. Метод close (),реализуемый классом CharArrayReader, не генерирует исклю­ чений. Это связано с тем , что его вызов не может завершиться неудачно. В следую­ щем примере применяется пара объектов класса Cha rAr rayReade rs: 11 Пр одемонстрировать применение класса CharArrayReader 11 В этой програм м е исполь зуется оператор try с ресурсами . 11 Тре буе тся установка компле кта JDK , начиная с версии 7 import java.io.*; puЫ ic class CharAr rayReade rDerno puЫic static void rna in (String args []) { String trnp = "abcde fghij klrn n opqrstuvwxyz"; int length = trnp.length () ; char с[] = new char [ length] ; trnp .getChars (O, length , с, 0) ; int i; try (CharAr rayReade r inp utl = new CharArrayReader (c) ) { Systern. out . println ("i nputl :") ; while ((i = input l.read() ) != -1) Systern.out . print ((c har ) i) ; } Systern. o ut . println (); catch (IOExcepti on е) { Systern.out . println ( "Oшибкa ввода-вывода : "+е) ; try (CharAr rayRe ade r input2 = new CharAr rayRe ader (c, О, 5) ) { Systern.out . println ("i nput2 :") ; while ((i = input2 .read() ) != - 1 ) Systern.out . print ((c har) i) ; } Systern.out . println () ; catch (IOException е) { Systern. out . println ( "Owибкa ввода-вывода : " + е) ; Объект input l создается с использованием всего английско го алфавита в н иж­ нем регистре, в то время как объект inp ut2 содержит тол ько первые пять букв. Эта программа выводит следую щий резул ьта т: inputl : abcdefghi jklrn n opqrstuvwx yz input2 : abcde
75' Часть 11. Библ иотека Java Клacc charArrayWr iter Класс CharAr rayWriter реализует поток вывода, использующий массив в ка· честве адресата для выводимых данных. У класса CharArrayWr iter имеются два конструктора: CharArrayWri ter () CharArrayWritar (int жoлirvec�ao_CJDOJo.uoa) В первой форме ко нструктора создается буфер размером, выбираемым по умол· чанию. Во второй форме буфер создается размером, задаваемым параметром количе ство_ симв ол ов. Буфер находится в поле buf класса CharArrayWr iter. Размер буфера будет последовательно увеличиваться по мере надобности. Количество байтов, содержащихся в буфере , находится в поле count того же клас· са. Оба поля , buf и count, являются защищенными. Метод close () не оказывает никакого влияния на класс CharAr rayWr i t er. В приведенном ниже примере представлена переделанная под ввод-вывод симво­ лов версия программы из рассмотренного ранее примера, демонстрировав шеrо применение класса ByteAr rayOutputStream. А в этой версии демонстрируется применение класса CharArrayWriter, хотя выводимый результат оказывается та· ким же , как и в предыдущей версия. 11 Продемонстрировать приме нение класса CharArrayWriter 11 в этой программе исполь зуется оператор try с ресурсами . 11 Требуе тся ус тановка комплекта JDK , начина я с версии 7 import jav a.io.*; class CharAr rayW riterDemo { puЫ ic static vo id main (String arg s[] ) throws IOException { CharArrayWriter f = new CharArrayWriter{) ; String s = " Эти данные должны быть выведены в массив" ; char bu f [] = new char [s.length() ]; s. getChars (O, s.length() , bu f, 0 ) ; try { f.write (bu f) ; catch (IOException е) { System.out . println {"Oшибкa записи в буфе р" ) ; return ; System. out . println ( "Бyфep в виде символьной строки") ; System. o ut . println (f.toString () ); System.out . println ("B массив") ; char с[] = f.toCharAr ray () ; for (int i=O ; i<c . length; i++) System.out . print (c[i] ); System. out . println ("\nB поток выв ода типа Fi leWriter () " ); 11 исполь зовать оператор try с ресур с ами для управления 11 потоком ввода- вывода в файл try ( FileW riter f2 = new Fi leWriter ("test .txt ") ) {
Гл ава 20. Пакет java.io для ввода-вывода 755 f.writeTo (f2) ; catch (IOException е) { System. o ut . println ("Oшибкa ввода- вывода : "+е) ; System.out . println ( "Ycтaнoвкa в исходное состояние") ; f.reset() ; for (int i=O; i<З; i++) f .write ( 'Х'); System.out . println (f.toString () ); Клacc вu��eredReader Класс Bu fferedReader увеличивает производительность благодаря буфериза· ции ввода. У него имеются следующие два конструктора: ВUfferec:IReader (Reader .по!l'ох .&1ЮДа) ВUfferedReader (Reader .пo!l'oJC=.1 11 1 0.r r a , int paSJfeP_cJytepa) В первой форме конструктора создается буферизованный поток ввода симво­ лов, использующий размер буфера по умолчанию. Во второй форме конструктора задается ра эмер_ буфера . Закрытие потока типа BufferedReade r приводит также к закрытию базово­ го потока, определяемого параметром поток_ ввода . Аналогично потоку ввода байтов, буферизованный поток ввода символов также поддерживает механизм перемещения обратно по потоку ввода в пределах доступного буфера. Для этой цели в классе BufferedReader реализуются методы ma rk () и reset (), а метод BufferedReade r .markSuppo rted () возвращает логическое значение true. В вер­ сии JDК 8 класс BufferedReade r дополнен новым методом line s ().Этот метод возвращает ссылку типа Stream на последовательность строк, введенных из пото­ ка чтения. (Класс Stream входит в состав прикладного программного интерфейса API потоков данных, обсуждаемого в гл аве 29.) В приведенном ниже примере представлена переделанная под ввод-вывод сим­ волов версия программы из рассмотренного ранее примера, демонстрировавше­ го применение класса BufferedlnputStream. В новой версии демонстрируется применение класса BufferedReade r для организации потока буферизированно­ го ввода. Как и прежде, для синтаксического анализа с целью обнаружить ссыл­ ку на элемент НТМL-разметки знака авторского права в данной версии програм­ мы используются методы ma rk ( J и reset (). Та кая ссылка начинается со знака амперсанда (&) и оканчивается точкой с запятой (;) без всяких промежуточных пробелов. Образец введенных данных содержит два амперсанда, чтобы наглядно показать, когда происходит установка в исходное состояние с помощью метода r eset () и когда этого не происходит. Эта версия программы выводит такой же результат, как и предыдущая ее версия: 11 Исполь зовать буфери зованный ввод . 11 В этой программе применяется оператор try с ресурсами . 11 Требуе тся установка комплекта JDK , начиная с версии 7
756 Часть 11. Библ иотека Java import jav a.io. *; class BufferedReaderDemo { puЫ ic static vo id ma in ( String args [] ) throws IOException String s = " Это знак авторского права &сору ; " + ", а &сору - нет.\n"; char buf [] = new char [s.length() ]; s.getChars (O, s.length() , bu f, 0) ; CharAr rayReader in = new CharArrayReader (bu f) ; int с; boolean ma rked = false; try ( Bu fferedRe ade r f = new Bu ffe redReader (in) { while ((с = f.read() ) != -1) switch (c) { } case '&': if ( !marked) { f.mark (32) ; marked true; }else{ ma rked = false ; break; case '; ': if (marked ) ma rked = false ; System.out . print (" (c)"); } else System.out . print ((c har) с) ; break; case ' '· if (marked) { marked = false ; f.reset (); System.out . print ("&") ; else System.out .print ((c har ) с) ; break; default: if ( !marked) System. out .print ((char ) с) ; break; catch (IOException е) ( System.out . println ("Omибкa ввода- вывода : "+е) ; Клacc BuEEeredWriter Класс Bu fferedWriter является производным от класса Wr i ter и буферизует выводимые данные. Применяя класс Buffe redWri ter, можно повысить произво­ дительность за счет снижения количества операций физической записи в устрой· ство вывода.
Гл ава 20. Пакет java.io дл я ввода -вы вода 757 У класса BufferedWri ter имеются два конструктора: ВufferedWriter (Writer no'l'oж .вuвода) ВufferedWriter (Wri ter nо!l'ож=.вuвода , int раsиер_буфера) В первой форме конструктора создается буферизованный поток вывода, ис­ пользующий буфер размером, выбираемым по умолчанию. А во второй форме за­ дается ко нкретный ра змер_ буфера. Клacc Pu shЬackReade r Класс PushbackRe ader позволяет возвратить в поток ввода один или больше символов, чтобы просматри вать этот поток, не вводя из него данные. Ниже при­ веде ны два конструктора данного класса. PushЬackReader (Reader no'l'oж ввода) PushЬackReader (Reader nо!l'ож=ввода , int раsнвр_буфера ) В первой форме конструктора создается буферизован ный поток ввода, в кото­ рый можно возвратить один символ , а во второй задается конкретный ра змер_ буфера для возврата символов обратно в цоток ввода. При закрытии потока типа Pu shbackReade r закрывается также базовый поток, определяемый параметром по ток_ ввода . В классе PushbackReader предоставля­ ется метод unread (), возвращающий один или больше символов в вызывающий поток ввода. Ниже приведены три общие формы объя вления этого метода. void unread (int CJIЮIOЛ) throws IOException void unread ( char буфер[] ) throws IOException void unread ( char буфер[] , int снцеюrе , int жолжvес: : !l'ао СJОа10Л<3) throws IOException - В первой форме в поток ввода возвращается указанный симв ол. Это будет сле­ дующий символ , возвращаемый при последующем вызове метода read ( ) . Во вто­ рой форме в поток ввода возвращаются символы из указанного буфера . А в третьей форме в поток ввода возвращается заданное количе ство_ симв олов из указанного буфера , начиная с позиции смеще ние. Исключение типа IOException генерирует­ ся при попытке возвратить символ в поток ввода, когда буфер возврата заполнен. В приведенном ниже примере представлена переделанная версия програм­ м ы и з рассмотренного ранее примера, демонстрировавшего применение клас­ са Pushbac kinput Strearn. В новой верс ии демонстрируется применение класса Pu shba ckReade r, но, как и прежде , данный пример показывает, как возврат дан­ н ьrх (в дан ном случае символов) в поток ввода можно использовать в синтаксиче­ ском анализаторе языка программирования для различения операций сравнения (==) и присваивания (=) . Результат выполнения данной версии программы такой же , как и в прежней ее версии. 11 Продемонстрировать приме нение метода unread() 11 из класса PushЬacltinputstreaш . 11 В этой программе применяется оператор try с ресурсами . /1 Требуется установка компл екта JDK , начиная с версии 7 import jav a.io.*; clas s Pushbac kReaderDemo {
758 Часть 11. Библиотека Java puЫ ic static void ma in (String args[]) String s = "if (а == 4) а=0;\n"; char buf [] = new char (s.length () ]; s.getChars (O, s.length () , buf, 0) ; CharAr rayReade r in = new CharArrayReader (bu f) ; int с; try ( PushbackReade r f = new PushbackReader (in) { while ((c = f.read()) != -1) { switch (c) { 1 case '=': if ((с = f.read()) == '=' ) Syзtem. out . print (".eq. ") ; else { System. out . print ("<- ") ; f.unread(c) ; break; default : System.out . print ((char) с) ; break; catch (IOException е) { System.out . println ( "Omибкa ввода- вывода : "+е) ; Клacc PrintWr iter Класс PrintWr iter, по существу, является символьной версией класса PrintStream. Он реализует интерфейсы AppendaЬle, CloseaЫe и F l ushaЫe. У класса Р r in t Wr i ter имеется несколько конструкторов. Рассмотрим сначала сле­ дующие формы конструкторов это го класса: PrintWriter (OUtputstreaш ПO!lrOll: .81 1В ОД'а) PrintWriter (OUtputstreaa ПO!lrOZ - -ода , Ьoolean aa!l.'OO'irЖC!l'Za) PrintWriter (Vriter по!l'оа: .-а�ода) Print1fri ter (Writer ПО!l'ОХ=.-UЮД'а , Ьo o lean a.!l'OOV•C!l'Za ) где параметр поток_ вывода обозначает открытый поток вывода типа Output Stream, который будет принимать выводимые данные . Параметр автоочистка определяет, будет ли буфер вывода автоматически очищаться всякий раз , когда вы· зывается мeтoд println (),printf ( ) или forma t (). Если параметр автоочистка прин имает логическое значение true, то происходит автоматическая очистка буфера вывода. А есл и этот параметр прин имает логическое значение false, то очистка буфера вывода не производи тся автоматически. Конструкторы , не при· нимающие параметр автоочистка , не производят очистку буфера вывода автома· тически . Следующий ряд конструкторов предоставляет простую возможность создать объект класса PrintWr iter для вывода данных в файл :
Гл ава 20. Пакет java.io для ввода- вывода 759 PrintWriter (File файл Sl lВ Oдa) throws FileNotFoundException PrintWr i ter (File фаiiл - 818ода , String набор СJrНВОлоs) throws FileNotFoundException , Unsuppo rtedEncodingException PrintWriter (String •НR фаi i лаS1 1В ода) throws FileNotFoundException PrintWriter (String .юlфайла - SAZ80дa , String на бор СJrнаолоа) throws FileNotFound:&:xceptlon , UnsupportedEncoding:&:xcep tion Эти конструкторы позволяют создать объект класса PrintWriter из объекта типа File или по имени файла. Но в любом случае файл создается автоматиче­ ски. Любой существующий файл с тем же именем уничтожается. Как только поток вывода будет создан в виде объекта класса PrintWri ter, он будет направлять все выводимые данные в указанный файл . Конкретную кодировку символов можно за­ дать в качестве параметра на бор_ симв ол ов. Класс PrintWr i ter предоставляет методы print () и println () для всех ти­ пов, включая тип ОЬ j е с t. Есл и аргумент не относится к примитивному типу, мето­ ды из класса PrintWri ter вызывают сначала метод toString ( ) такого объекта, а затем выводят результат его выполнения . В классе PrintWri ter поддерживается также метод print f (). Он действует точно так же , как и в описанном ранее классе PrintStream, позволяя задать точ­ ный формат данных. Метод print f () объявляется в классе Pr intWri ter следую­ щи м образом: PrintWri ter printf' (String фopNa !l'JS.PYD/l'aR с!l'рожа , OЬject . . . apryмeн !l'Jol) PrintWri ter printi (Locale per•aнaлwwe нac!l'p�.ltlr , String форна!1'.-ру..-а1 1 _ с!l'р0n , Object ... apr,yнeн!l'l l ) В первой форме данного метода заданные аргументы выводятся в стандарт­ ный поток вывода в формате , указан ном в качестве параметра форма тирующа я_ строка , с учетом региональных настроек по умолчанию. А во второй форме мож­ но указать конкретные региональные настройки. Но в любом случае возвращается вызывающий поток вывода в виде объекта типа PrintWriter. В классе PrintWriter поддерживается также метод forma t ().Ниже приведе­ ны общие формы дан н ого метода. Этот метод действует подобно методу р r in t f ( ) . PrintWri ter f'orшat (String фopN&!l'JS.P.YDlr&l l С!l'рОжа , OЬject PrintWri ter f'orшat (Locale pet'JIOНaл:r. .Nl l 8 нас!l'ро•юr , String фopN&!l'JSp.YDlraR _ C!l'pOA'a , Ob ject Класс Console apryнeн !l'Jol) Класс Console был введен в состав пакета j ava . io в версии JDК 6. Он служит для ввода-вывода данных на консоль, если таковая имеется, и реализует инте рфейс F l ushaЫ e. Класс Console является служебным, поскольку он функционирует главным образом через стандартные потоки ввода-вывода System. in и System. ou t. Те м не менее он упрощает некоторые виды консольных операций, особенно при чтении символьных строк с консоли. Конструкторы в классе Console не предоставляются. Его объект получается в результате вызова метода System. console ( ) , как показано ниже. static System . console О
760 Часть 11. Библ иоте ка Java Если консоль доступна, то возвращается ссылка на нее, в противном слу· чае - пустое значение nu ll. Консоль будет доступна не во всех кл ассах , и если возвращается пустое значение null, то консольные операции ввода-вывода не· возможны. В классе Console определяются методы, перечисленные в табл. 20.6. Следует иметь в виду, что методы ввода, например метод readL ine (), ге нерируют ис· ключение типа IOException, когда возникают ошибки ввода. Класс исключен ия IOError является производным от класса Error и обозначает фатальную ошибку ввода-вывода, которая не поддается контролю в прикладной программе. Это озна· чает, что исключение типа IOError обычно не перехватывается. Откровенно го­ воря , если исключение типа IOError возникает при обращении к консоли , обыч· но это свидетельствует об аварийном сбое системы. Следует также иметь в виду, что методы типа readPas sword () позволяют считывать пароль, не выводя его на экран . Читая пароли, следует "обнулять" как массив, содержащий символьную строку, введенную пользователем, так и мае· сив, содержащий правильный пароль, с которым требуется сравнить эту строку. Благодаря этому уменьшается вероятность того , что вредоносная программа полу­ чит пароль, просмотрев оперативную память. Табл ица 20.6. Методь1 из кnасса console Метод void flush () Console for1 11a t(Strin9 форматирующая_строха , OЬjeot ... аргументы) Console print� (String форматирующая_стрtжа , OЬjeot. . . аргументы) Reader read8r () String r•adLin• () String readLine (Strin9 форматирующая_строrш , OЬjeot. . . арzументы) Описание Выполняет физический вывод буферизованных данных на консоль Выводит на консоль указанные арrумен ты, используя фор­ мат, определяемый параметром форматирующая_строка Выводит на консоль указан ные арrументы, используя фор­ мат, определяемый параметром форматирующая_строка Возвращает ссылку на поток чтения типа Reader, связан· ный с консолью Читает и возвращает символьную строку, введенную с клави· атуры . Ввод прекращается нажатием клавиши <Enter>. Если достигнут конец потока ввода с консоли, то возвращается пустое значение null. А если происходит фатальная ошибка ввода, то генерируется исключение типа IOZrror Выводит строку приглашения, используя формат, опреде­ ляемый параметром форматирующая_строrш, а также ука· занные аргументы, затем читает и возвращает символьную строку. введенную с клавиатуры. Ввод прекращается нажа· тием клавиши <Enter>. Если достигнут конец потока ввода с консоли, то возвращается пустое значение null. А если происходит фатальная ошибка ввода, то генерируется ис· ключение типа IOError
Метод char [] readPassword () char [] readPa ssword (Strinq форматирующая_строка , ОЬ)есt. . . арг-ументы) PrintWriter writer () Гл ава 20. Пакет java.io дл я ввода -вывода 761 ОкtтЧание та6а. 20. 6 Описание Читает и возвращает символьную строку, введенную с кла· виатуры. Ввод прекращается нажатием клавиши <Enter>. Введенная строка не выводится на экран. Если достиrнуr конец потока ввода с консоли, то возвращается пустое зна­ чение nul l. А если происходит неустранимая ошибка ввода, то генерируется исключение типа IOError Выводит строку приrлашения, используя формат, опре­ деляемый параметром фор.мптирующая_ стрт т ,атакже указанные аргумен ты, затем читает и возвращает символь­ ную строку, введенную с клавиатуры. Ввод прекращается нажатием клавиши <Enter>. Введенная строка не выводится на экран. Если достигнут конец потока ввода с консоли, то возвращается пустое значение null. А если происходит фатальная ошибка ввода, то генерируется исключение типа IOError Возвращает ссылку на поток записи тип а Writer, связанный с консолью В следующем примере программы демонстрируется применение класса Console: 11 Продемонс трировать применение кл асса Console import java.io.*; clas s ConsoleDemo { puЫ ic static vo id rna in (String args[] ) { String str; Console con; 11 получи ть ссылку на консоль con = Systern. console () ; 11 выйти из программы, если консоль недоступна if (con == null) return; 11 прочи тать строку и выв ести ее str = con . readLine ("B вeдитe строку : ") ; соn .рrintf ("Б веденная вами строка : %s\n", str) ; Ниже приведен пример выполнения данной программы. Введите строку : Это тест . введе нная вами строка : Это тест . Сериалиэация Сериализация - это процесс записи состояния объектов в поток вывода байтов. Она оказывается удо бной в том случае, когда требуется сохранить состоя ние при-
762 Часть 11. Библиоте ка Java кладной программы в таком месте постоянного хранения, как файл . В дальней· шем эти объекты можно восстановить в процессе десериализации. Сериализация также требуется для реализации удалсниого въ�зова методов (RМI - Remote Method Invocation) . Механизм RМI позволяет объекту Java на одной маши· не обращаться к методу объектаjаvа на другой машине. Объект может быть предо­ ставлен в виде аргумента этого удал енного метода. Передающая машина сериали· зирует и посылает объект, а принимающая машина десериализует его. (Подробнее механизм RМI рассматривается в гл аве 30.) Допустим, сериализируемый объект ссылается на объекты , которые, в с вою очередь, ссылаются на какие-ни будь другие объекты. Та кой ряд объектов и отно­ шений между ними образует направленный граф , где могут присутствовать и ци· клические ссылки. Иными словами, объект Х может содержать ссылку на объект У, а объект У - обратную ссылку на объект Х. Объекты могут также содержать ссьшки на самих себя. Для правильного разрешения подобных ситуаций и пред· назначены средства сериализации и десериализации объектов. Если попытаться сериализировать объект, находящийся на вершине направленного графа, то все прочие объекты , на которые делаются ссьшки , также будут рекурсивно найдены и сериализированы. Ан алогичным образом все эти объекты и их ссьшки правиль· но восстанавливаются в процессе десериализации. Ниже делается краткий обзор интерфейсов и классов , поддерживающих сериализацию. И нтерфей с SerializaЫe Средствами сериализации может быть сохранен и восстановлен только объект класса, реализующего интерфейс SerializaЫe. В интерфейсе SerializaЫe не определяется никаких членов. Он служит лишь для того , чтобы указать, что класс может быть сериализирован . Если класс сериализируется, то сериализируются и все его подклассы. Переменные, объявленные как transient, не сохраняются средствами с ериа· лизации. Не сохраняются и статические переменные. И нтерфей с ExternalizaЬle Средства Java для сериализации и десериализации разработаны таким обра· зом , чтобы большая часть операций сохранения и восстановления состояния о бъ· екта выполнялась автоматически . Но иногда требуется управлять этим процессом вручную, например, чтобы воспользоваться алгоритмами сжатия и шифров ания данных. Именно для таких случаев и предназначен инте рфейс Extern alizaЫe. В инте рфейсе Extern alizaЫe определяются следующие методы: void readExternal (Objectinput потох "ода) throws IOException , ClassNotFoundException void writeExternal (ObjectOUtput потох .вuвода) throws IOException - В этих методах параметр поток_ ввода обозначает поток байтов, из которого может быть введен объект, а параметр поток_ выв ода - поток байтов, куда это объ­ ект может быть выведен.
Гл ава 20. Пакет java.io для ввода - в ы вода 763 Интерфей с Ob jectOutput Интерфейс Obj ectOutput расширяет интерфейсы Au toClo seaЫe и Da ta Outpit, поддерживая сериализацию объектов. В нем определяются методы, пере­ численные в табл. 20.7 . Следует особо отметить метод wr iteObj ect (), который вы­ зывается для сериализации объекта. При возникновении ошибок все методы этого интерфейса генерируют исключение типа IOExcept ion. Табл ица 20.7. Методы иэ инте рфейса Ob jectOutput Метод void cloae () void flush () void write (byte буфер [ ] ) void write (byte буфер[) , int смещение , int количество_ баUтов> void write (int Ь) void writeOЬject (OЬject об&екm) Описание Закрывает вызывающий поток вывода. Последующие попытки вывести данные в этот поток приведуr к ге­ нерированию исJUJючения типа IOException Делает конечным состояние вывода, чтобы очистить все буфера, в том числе и буфера вывода Записывает массив байтов в вызывающий поток вы· вода Записывает заданное ко.личество_байтов из указанного массива буфер, начиная с позиции буфер [ смещение] Записывает одиночный байт в вызывающий поток вывода. Из указанного аргумента Ь выводится только младший байт Записывает заданный объект в вызывающий поток вывода Клacc Ob jectOu tputStream Класс Obj ectOutputStream расширяет класс OutputStream и реализует ин­ терфейс Obj ectOutpu t. Этот класс отвечает за вывод объекта в поток. Ниже при­ веден конструктор этого класса. OЬjectoutputStreaш (OutputStreaш по�ож _ аusода ) throws IOException Аргумент поток_ выв ода обозначает поток, в который могут быть выведены сериализируемые объекты. Закрытие потока вывода типа Ob j ectOutputStream приводит также к закрытию базового потока , определяемого аргументом поток_ вывода. Некоторые наиболее употребительные методы из класса Ob j ectOutputStream перечислены в табл. 20.8. При возникновении ошибки все они генерируют исклю­ чение типа IOException. Имеется также внутренний класс PutField, вложенный в JUJacc Ob j ectOutputStream. Он упрощает запись постоянных полей, но описа­ ние его применения выходит за рамки данной книги.
761+ Ч асть 11. Библиоте ка Java Табл ица 20.8 . Наиболее употребител ьные методы иэ класса Ob jectOutputStream. М етод void close () void flush () void write (byte буфер[]) void write (byte буфер[] , int смещение , int кмичество_байтов) void write (int Ь) void writeВoolean (boolean Ь) Описание Закрывает вызывающий поток вывода. Последующие попытки вывода данных в этот по­ ток приведут к генерированию исключения типа IOException. Базовы й поток выводатакже закры· вается Делает конечным состояние вывода, очищая все буфера, в том числе и буфера вывода Записывает массив байтов в вызывающий поток вывода Записывает в вызывающий поток вывода заданное 1WJ1ичество_байтов из указанного массива буфер. на· чиная с позиции буфер [ смещение] Записывает одиночный байт в вызывающий поток вывода. Из указанного аргумента Ь зап исывается только младш ий байт Записывает логическое значение типа Ьoolean в вызывающий поток вывода void wri teВyte (int Ь) Записывает значение типа Ьуtе в вызывающий по­ ток вывода. Выводимый байт является младшим из указанного аргумента Ь void writ.Вytes (String строка) Записывает байты , составляющие заданную строку, в вызывающий поток вывода void writeChar (int с) Записывает заданное значение с типа char в вызы· вающий поток вывода void writeChars (String стр ока) Записывает символ ы, составляющие заданную строку, в вызывающий поток вывода void vriteDouЫe (douЬle d) Записывает заданное значение dтипа douЬle в вы­ зывающий поток вывода void wri teFloat (float j) Записывает заданное значение /типа f'loat в вызы­ вающий поток вывода void write int (int i) Записывает заданное значение i типа int в вызыва­ ющий поток вывода void writeLong (lonq � final void writeObject (Object обr.екm) void wr iteShort (int i) Записывает заданное значение lтипа long в вызы· вающий поток вывода Записывает заданный обr.екm в вызывающий поток вывода Записывает заданное значение i типа short в вызы· вающий поток вывода Интерфейс Objectinput Интe pфeйc Ob j ectinput рас ширяет интерфейсыАutоСlоsеаЫе и Da tainput. В нем определяются методы , переч исленные в табл . 20.9, и поддерживается
Гл ава 20. Пакет java.io для ввода-вывода 765 сериализация объектов. Следует особо отметить метод readOb j ect ( ) , который вызывается для десериализации объекта. При возникновении ошибок все мето­ ды данного интерфейса генерируют исключение -mпа IOException. Метод read Ob ject ( ) может также сгенерировать исключение -mпа ClassNotFoundExcept ion. Табл ица 20.9. Методы из инте рфейса Ob jectinput Метод int availaЬle () void cloae () int read () int read (byte буфер[]) int read(Ьуtе буфер[] , int смещение , in t количество_байтов) OЬject reada)ject О Lonq skip (lon9 количество_ байтов) Описание Возвращает количество байтов, доступ ных на дан­ ный момент в буфере ввода Закрывает вызывающий поток ввода. Последующие попытки ввода дан ных из этого потока приведуr к ге нерированию исключения типа IOJ:xception. Базовый поток ввода также закрывается Возвращает целочисленное представление следую­ щего байта, доступ ного для ввода. По достижении конца файла возвращается значение -1 Пытается прочитать в указанный буфер количество байтов, равное буфер . длина, возвращая количество ус пешно прочитанных байтов. По достижении кон­ ца файла возвращается значение -1 Пытается прочитать в указанный буфер за- дан ное количество_байmов, начиная с позиции буфер [ смещение] и возвращая количество ус пешно прочитанных байтов. По достижении конца файла возвращается значение -1 Читает объект из вызывающего потока ввода Игнорирует (т.е . пропускает) заданное для ввода количество_байтов, возвращая количество фактиче­ ски п роигнорированных байтов Класс Ob jectinpu tS tream Этот класс расширяет класс InputStream и реализует интерфейс Obj ectinput. Класс Ob j ectinput Stream отвечает за ввод объектов из потока. Ниже приведен конструктор этого класса. OЬjectinputStream (InputStreaш по�ож_ввода) throws IOException Аргумент по ток_ ввода обозначает поток, из которого должен быть введен сери­ ализированный объект. Закрытие потока ввода типа Ob j ectinpu tS tream приводит также к закрытию базового потока ввода, определяемого аргументом поток_ввода . Некоторые наиболее употребительные методы из класса Obj ect input St ream перечислены в табл . 20.10. При возникновении ошибки все они генерируют ис­ ключение типа IOException. Метод readObj ect ( ) может также сгенерировать исключение типа Clas sNotFoundException. Имеется также внутренний класс Ge tField, вложенный в класс Obj ectinput St ream. Он упрощает чтение постоя н­ н ьtх полей, но описание его применения выходит за рамки данной книги.
766 Часть 11. Библ иоте ка Java Табл ица 20. 1 О. Наиболее употребительные методы из класса Ob jectinputStream Метод int availaЬle () void close ( ) int read () int read (byte буфер[] , int смещение , int каличество_байтов) Вoolean readВoolean () byte readВyte () char readChar () douЫe readDouЫe () douЬle readFloat () void readFully (Ьуtе буфер [] ) void readFully (Ьуtе буфер [] , int смещение, int ко.1 1 ичество_ байтов) int readint () int readLong () final Ob ject readOЬject () short readShort () int readUnsignedВyte () int readUnsignedShort () Описание Возвращает количество байтов, доступных в дан· ный момент в буфере ввода Закрывает вызывающий поток ввода. Последующие попытки ввода данных из этого потока приведут к ге нерированию исключения типа IOZxception. Базовый поток ввода также закрывается Возвращает целочисленное представление следую­ щего байта, доступного для ввода. По достижении конца файла возвращается значение -1 Пытается прочитать заданное КОJ1ичество_ байтов в указанный буфер, начиная с позиции буфер [ смещение] и возвращая количество байтов, которые уд алось прочитать. По достижении конца файла возвращается значение -1 Читает и возвращает логическое значение типа Ьoolean из вызывающего потока ввода Читает и возвращает значение типа byte из вызы· вающего потока ввода Читает и возвращает значение типа char из вызы­ вающего потока ввода Читает и возвращает значение типа douЬle из вы­ зывающего потока ввода Читает и возвращает значение типа float из вызы­ вающего потока ввода Читает в указан ный буфер количество байтов, рав· ное буфер . длина. Возвращает управление только тогда, когда прочитаны все байты Читает заданное ко.1 1 ичество_байтов в указан- ный буфер, начиная с позиции буфер [ смещение]. Возвращает управление только тогда, когда прочи­ тано заданное каличество_байтов Читает и возвращает значение типа int из вызыва· ющего потока ввода Читает и возвращает значение типа long из вызы­ вающего потока ввода Читает и возвращает объект из вызывающего по­ тока ввода Читает и возвращает значение типа short из вызы­ вающего потока ввода Читает и возвращает значение типа unsigned byte из вызывающего потока ввода Читает и возвращает значение типа unsigned ehort из вызывающего потока ввода
Гл ава 20. Пакет java.io для ввода-вывода 767 Пример сериализации В приведенном ниже примере программы демонстрируется применение сериа­ лизации и десериализации объектов. Эта программа начинается с создания экзем­ пляра объекта типа MyClass. У этого объекта имеются три переменные экземпля­ ра типа String, int и douЫe. Данные, хранящиеся именно в этих переменных, требуется сохранять и восстанавливать. С этой целью в данной программе создается поток вывода типа FileOutput­ Stream, ссылающийся на файл по имени "serial ", а для него - поток вывода ти па Obj ectOutputStream. Затем для сериализации объекта вызывается метод wr iteObj ect () из класса Ob jec tOutputStream. По завершении данного процес­ са очищается и закрывается поток вывода объектов. Далее создается поток ввода типа FileinputStream, ссылающийся на файл по имени "serial ", а для него - поток ввода типа Ob j ectinput Stream. Для по­ следующей дес ериализации объекта вызывается метод readObject () из класса Ob ject inputStream. По завершении данного процесса очищается и закрывается поток ввода объектов. Обратите внимание на то, что объект типа MyClass определяется для реали- 3ации интерфейса SerializaЫ e. Если не сделать этого , будет сгенерировано исключение типа NotSe rializaЫeException. Поэкспериментируйте с этой программой , объявляя как transient некоторые переменные экземпляра класса MyClas s. Хранящиеся в них данные не будут сохраняться при сериализации. 11 Пр одемон стрировать применение сериализации и десериализации 11 В этой программе исполь зуе тся оператор try с ресурсами . 11 Требуется установка компле кта JDK , начиная с версии 7 import java.io.*; puЫic class SerializationDemo { puЫic static void main (String args[J ) { 11 произвести сериализацию объе кта try ( Ob jectOutputStream ob jOStrm = } new Ob jectOutputSt ream (new Fi leOutputStream("s erial") ) MyClass objectl = new MyClass ( "Hello", -7 , 2.7 е10) ; System. o ut . println ("obj ectl : "+obj ectl) ; obj OStrm. writeObj ect (objectl ); catch (IOException е) { System. o ut . println ( "Исключение при сериализации : "+е) ; 11 произвести десериализ ацию объе кта try ( Ob jectinpu tStream obj IStrm = ) new Ob jectinputStream (new Fi leinputStream("serial") ) MyCl ass object2 = (MyClas s) obj IStrm. readObj ect (); System. o ut . println ("obj ect2 : "+obj ect2 ); catch (Exception е) {
768 Часть 11. Библ иоте ка Java System.out . println ( "Исключение при десериализ ации : "+е) ; System. exit (O) ; cl ass MyClass implements Seriali zaЫe { String s; int i; douЫ e d; puЫic MyClass ( String s, int i, douЫ e d) { this .s s; this .i i; this .d d; puЫ ic String toString () return"s="+s+11;i="+i+";d=11+d; Эта программа демонстрирует идентичность переменных экземпляра obj ееt 1 и obj ect2 . Ниже приведен результат ее выполнения. ob jec tl : s=Hello; i=-7; d=2 .7E10 object2 : s=Hello; i=-7; d=2 .7E10 Преимуще ства пото ко в ввода- вывода Потоковый интерфейс ввода-вывода в Java предоставляет чистую абстракцию для решения сложных и зачастую обременительных задач . Структура классов фильтрующих потоков позволяет динамически строить собственные настраи вае­ мые потоковые интерфейсы, отвечающие требованиям передачи данных. Те п ро­ граммы нajava, где применяются эти классы с высоким уровнем абстракции , в том числе InputStream, OutputStream, Reade r и Writer, будут правильно функцио­ нировать и впредь - даже в том случае, если появятся новые и усовершенствован· ные конкретные классы потоков ввода-вывода. Как будет показано в гл аве 22, эта модель оказывается вполне работос пособной при переходе от ряда потоков ввода· вывода в файлы к потокам ввода-вывода через сеть и сокеты . И наконец, сериал и· зация объектов играет важную роль в самых разных программах на Java. Классы сериализации ввода-вывода в Java обеспечивают переносимое решение этой по­ рой не совсем простой задачи.
21 Система ввода-вывода NIO Начиная с версии 1.4 вjava предоставляется вторая система ввода-вывода под на­ званием NIO (сокращение от New 1/0 - новый ввод-вывод) . В этой системе поддер­ живается канальный подход к операциям ввода-вывода, ориентированный на при­ менение буферов. А в версии JDK 7 система ввода-вывода NIO была существенно расширена, и теперь она оказывает улучшенную поддержку средств обработки фай­ лов и файловых систем. На самом деле изменения в этой системе настолько значи­ тельны, что она нередко обозначается термином NI0. 2. Благодаря возможностям, предоставляемым новыми классами файлов из системы ввода-вывода NIO, ожидает­ ся, что значение этой системы в обработке файлов будет только возрастать. В этой главе рассматривается ряд основных характеристик системы ввода-вывода NIO. Классы с и стем ы ввода- вывода NIO В табл. 21.1 перечислены пакеты , в которых содержатся классы системы ввода­ вывода NIО. Таблица 21.1. Пакет ы , содержа щие классы систе м ы ввода-вывода NIO Пакет На3 начение java . nio Это пакет верхнего уровня в системе ввода-вывода NIO. Он инкапсулирует разл ичные типы буферов, содержащих дан­ ные, которыми оперирует система ввода-вывода NIO java.nio.chan n els Поддерживает каналы, открывающие соединения для вво­ да-вывода java.nio . channel s . spi Поддерживает поставщики услуг для каналов java . nio . charset Инкапсулирует наборы символов. Поддерживает также функционирование кодеров и декодеров для взаимного преобразования символов и байтов java . nio . charset . spi Поддерживает поставщики услуг для наборов символов java . nio . file Поддерживает ввод-вывод в файлы java . nio . file . attriЬute Поддерживает атрибуты файлов java.nio.file.spi Поддерживает поставщики услуг для файловых систем
770 Часть 11. Библ иоте ка Java Прежде чем приступить к рассмотрению системы ввода-вывода NIO, следует за· метить, что эта система не предназначена для замены классов ввода-вывода, входя· щих в состав пакета j а vа . i о и представленных в гл аве 20. Напротив, практические знания о классах из этого пакета помогают легче усвоить систему ввода-вывода NIO. На заметку! В этой гл аве предполагается , что вы уже проработали материал гл ав 13 и 20, посвя­ щен ный принципам ввода-вывода вообще и потокового ввода-вывода в частности. О с новные пол ожения о систе ме ввода - вывода NIO Система ввода-вывода NIO построена на двух основополагающих элеме нтах: буферах и каналах. В буфере хранятся данные, а канал предоставляет открытое со· единение с устройством ввода-вывода, например файлом или сокетом. В об щем , для применения системы ввода-вывода NIO требуется получить канал для устрой· ства ввода-вывода и буфер для хранения данных. После этого можно обращаться с буфером, вводя или выводя данные по мере необходимости. Поэтому в последу· ющих разделах буфера и каналы будуг рассмотрены подробно. Буфера Буфера определяются в пакете j а va . ni о. Все буфера являются подклассами, про­ изводными от класса Buffer, в котором определяются основные функциональные возможности , характерные для каждого буфера, в том числе текущая позиция, пре­ дел и емкость. Тек ущая позиция определяет индекс в буфере, с которого в следующи й раз начнется операция чтения или записи данных. Те кущая позиция перемещается после выполнения большинства операций чтения или записи. Пре дел определяет значение индекса за позицией последней доступной ячейки в буфере. Емкостъ опре­ деляет количество элементов, которые можно хранить в буфере. Зачастую предел равен емкости буфера. В классе Bu ffer поддерживается также отметка и очистка буфера. В нем определяется ряд методов, перечисленных в табл. 21.2. Табл ица 21.2. Методы из классы Bu ffer Метод aЬs tract Ob ject array () aЬs tract int arrayOffset () Описание Возвращает ссылку на массив, если вызывающий буфер под· держивается массивом, иначе генерирует исключение типа UnsupportedOperationException. Если же массив досту­ пен только для чтения, то генерируется искл ючение типа ReadOnlyBufferException Возвращает индекс первого элемента массива, если вызываю· щий буфер поддерживается массивом , а иначе ге нерируется исключение типа Unsuppor tedOpe rationException. Если же массив доступен только для чтения, то генерируется исключе· ниe типa ReadOnlyBufferException
Метод final int capacity () final Buffer clear () final Вuffer flip () aЬstract Ьo o lean has Array () final Ьoolean hasRulaining () aЬstract Ьo o lean iвDirect () aЬвtract Ьoolean isReadOnly () final int liai.t О final Buffer li.иlit (int n) final Buffer 11 1a rk () final int position () final Вuffer ровi tion (int n) int rel ll&i ning () final Buffer reaet () Гл ава 21. Систе ма ввода-вывода NIO 771 Окончание та6.4. 21.2 Описание Возвращает количество элементов, которые можно хранить в вызывающем буфере Очищает вызывающий буфер и возвращает ссылку на него Задает текущую позицию в качестве предела для вызываю­ щего буфера и затем устанавливает текущую позицию в нуль. Возвращает ссылку на буфер Возвращает логическое значение true, если вызывающий буфер поддерживается массивом, доступным для чтения и за­ писи, а иначе - логическое значение false Возвращает логическое значение true, если в вызывающем буфере еще остались какие-нибудь элементы, а иначе - логиче - ское значение false Возвращает логическое значение true, если вызывающий буфер оказывается прямым. Иными словами, операции ввода­ вывода выполняются над ним напрямую. В противном случае возвращается логическое значение false Возвращает логическое значение true, если вызывающий буфер является буфером только для чтения, а иначе - логиче- ское значение false Возвращает предел для вызывающего буфера Задает предел п для вызывающего буфера. Возвращает ссылку на буфер Уста навливает метку и во3Вращает ссылку на вызывающий буфер Возвращает текущую позицию Задает текущую позицию буфера равной n. Возвращает ссылку на буфер Возвращает количество элементов, доступных до того, как будет достигнут предел. Иными словами, возвращается предел минус текущая позиция Уст анавливает текущую позицию в вызывающем буфере на установленной ранее метке. Возвращает ссылку на буфер final Buffer rewind () Уст анавливает текущую позицию в вызывающем буфере в нуль. Возвращает ссылку на буфер Оr класса Buf f е r происходят приведенные ниже классы конкре-mых буферов, rде rnп хранимых данных можно определить по их именам. Класс MappedBy teBuffer является производным от класса ByteBuffer и используется для сопоставления файла с буфером. ВyteВuffer CharBuffer DouЫ.Вuffer FloatВuffer Intвuffer LongBuffer uffer ShortВuffer Все упомянугые выше буфера предоставляют различные методы ge t ( ) и put ( ) , которые позволяют получать данные из буфера или вносить их в неrо. (Разумеется, метод pu t ( ) недоступен, если буфер предназначен только для чтения .) В табл . 21.3 перечислены методы get ( ) и put (),определенные в классе ByteBuffer. Другие классы буферов имеют похожие методы. Во всех классах буферов поддерживают-
772 Часть 11. Библиоте ка Java ся rакже методы, выполняющие различные операции с буфером. Н апример , с по­ мощью метода al loca te () можно вручную выделить оперативную память под бу· фер, с помощью метода wrap () - организовать массив в пределах буфера, а с по­ мощью метода slice () -создать подпоследовательность в буфере. Табл ица 21.Э. Метод ы qet () и put () из класса ByteBuffer Метод aЬstract Ьуtе qet () ВyteBuffer qet (Ьyte значения[]) ВyteВuffer qet (byte значения[], int начало, int количество) aЬstract byte qet (int индекс) aЬstract ByteВufferput (byte Ь) final Byteвufferput (byte значения[]) ВyteBuffer put (Ьyte эначения[], int начало, int количество) ВyteВufferput (ByteВuffer ЬЬ) aЬstract ByteВufferput (int индекс , byte Ь) Каналы Описание Возвращает байт на текущей позиции Копирует вызывающий буфер в заданный масс ив эначения. Возвращает ссылку на буфер. Если же в бу· фере не осталос ь бол ьше элементов, количество которых равно значения . lenqth, то генерируется ис· ключeниe типa BufferUnderflowException Копирует заданное JCOJJИЧ8C'l'BO элементов и з вызыва· ющего буфера в указанный массив значения, начиная с позиции по индексу нача.ло. Возвращает сс ылку на буфер. Если в буфере бол ьше не осталось задан ное количество элементов, то ге нерируется исключение типa BufferUnderflowException Возвращает из вызывающего буфера байт по указан· ному индексу Копирует заданный байт Ь на текущую позицию в вы· зывающем буфере. Возвращает ссылку на буфер. Если буфер заполнен, то генерируется искл ючение типа BufferOverflowBxception Копирует все элементы из указанного масс ива эначения в вызывающий буфер, начиная с текущей позиции. Возвращает ссылку на буфер. Есл и буфер не может вместить все элементы, то генерируется ис· ключение типа BufferOverflowException Копирует в вызывающий буфер заданное кол ичество элементов из указанного массива значения, на­ чиная с указанной позиции нача.ло. Возвращает ссылку на буфер. Есл и буфер не может хранить все эл ементы, то ге нерируется исключение типа BufferOverflowException Копирует элементы из заданного буфера ЬЬ в вы· зывающий буфер, начиная с текущей позиции. Если буфер не может хранить все элементы , то генериру· ется искл ючение типа BufferOverflowException. Возвращает ссылку на буфер Копирует байт Ь на позицию по указанному индексу в вызывающем буфере. Возвращает ссылку на буфер Каналы определены в пакете j ava . nio . channels. Kaн(J JI, представляет от крытое соединение с источником или адресатом ввода-вывода. Классы каналов реализуют ин·
Гл а ва 21 . С истема ввода-вывода NIO 773 терфейс Channe l, расширяющий интерфейс CloseaЬle, а начиная cJDK 7-интер­ фейс Au toCloseaЫ e. При реализации интерфейса AutoCloseaЫe каналами можно упраалять в блоке оператора try с ресурсами, где канал закрывается автоматически, когда он больше не нужен. (Подробнее об операторе try с ресурсами см. в гл аве 13.) Один из способов получения канала подразумевает вызов метода getChanne l ( ) для объекта, поддерживающего каналы. Например, метод ge tChannel ( ) поддержи­ вается в следующих классах ввода-вывода: DatagramSocket FileinputS tream Fi l eOutpu tS tream RandomAccessFile ServerSocket Socket Конкретный тип возвращаемого канала зависит от ти па объекта, для которого вызывается метод getChanne l ().Например, когда метод getChanne l () вызываеТ'­ ся д.ля объекта типа FileinputStrearn, FileOutputStrearn или RandornAccessFile, он возвращает канал типа FileChannel. А если этот метод вызывается для объекта типа Soc ket, то он возвращает канал типа Socke tChanne l. Еще один способ получения канала подразумевает использование одного изста­ тических методов, определенных в классе F i 1 е s, который был введен в версииJDK 7. Например, используя класс Fi les, можно получить байтовый канал при вызове метода newByteChanne l ().Он возвращает канал типа See kaЬ leByteChanne l, т. е . и нтерфейса, реализуемого классом FileChanne l. (Более подробно класс Files рассматривается далее в этой гл аве.) В каналах типа FileChanne l и SocketChanne l поддерживаются различные ме­ тоды read () и wri te (), которые позволяют выполнять операции ввода-вывода через канал. Например, в табл. 21.4 перечислен ряд методов read ( ) и wri te ( ) , определенных в классе Fi l eChanne l. Табл ица 21.,. Методы read () и write ( ) иэ класса FileChannel Метод aЬstract int read (Byt.Вuffer ЬЬ> throws IOBxception aЬstract int read (ByteВuffer ЬЬ , lonq ШJ1Ю.1 1 0) throws IOlxception aЬstract int write (ByteBuffer ЬЬ) throws IOBxception aЬstract int write (ByteВuffer ЬЬ , lonq НQЧ41Ю) throws IOException Описание Считывает байты из вызывающего канала в указанный буфер ЬЬ до тех пор, пока буфер не будет заполнен или же не исчерпа­ ются вводимые данные. Возвращает количество прочитанных байтов Считывает байты из вызывающего канала в указанный буфер ЬЬ, начиная с позиции на14 4!1 о и до тех пор, пока буфер не будет заполнен или же не исчерпаются вводимые данные. Те кущая позиция не изменяется. Возвращает количество прочитанных байтов или значение -1, если позиция НQЧ41 1О окажется за пределами файла Записывает содержимое байтового буфера в вызывающий канал, начиная с текущей позиции. Возвращает кол ичество за· писанных байтов Записывает содержимое байтового буфера в вызывающий канал, начиная с позиции НQtUJ JI O в файле. Те кущая позиция не изменяется . Возвращает количество записанных байтов
771+ Часть 11. Библ иотека Java Все каналы поддерживают дополнительные методы, предостамяющие доступ к каналу и позволяющие управлять им. Например, канал типа Fil eChannel подцер­ живает среди прочего методы для получения и установки текущей позиции , пере­ дачи данных между файловыми каналами, получения текущего размера канала и его блокировки. В классе Fil eChannel предостамяется статический метод open (),ко­ торый открывает файл и возвращает для него канал. Та кой результат достигается другим способом получения канала. В классе FileChannel предостамяется также метод map ( ) , с помощью которого можно сопоставить файл с буфером. Наборы символов и селекторы В системе ввода-вывода NIO применяются наборы символов и селекторы. Набор символов определяет способ сопостамения байтов с символами. С п омощью кодера можно закодировать последовательность символов в виде байтов. Процесс декодирования производится с помощью декодера. Наборы символов, кодеры и де­ кодеры поддерживаются в классах, определяемых в пакете jav a.nio .cha r set. Кодеры и декодеры предостамяются по умолчанию, и поэтому обращаться непо­ средственно к наборам символов приходится крайне редко. Селектор обеспечивает возможность многоканального ввода-вывода по клю­ чам , не прибегая к блокировке . Иными словами, с помощью селекторов можно выполнять операции ввода-вывода через несколько каналов. Селекторы подцер­ живаются классами, определяемыми в пакете j а va . nio . channels. Они чаще все­ го применяются в каналах, опирающихся на сокеты. В примерах, представленных в этой гл аве , наборы символов и селекторы не применяются, тем не мен ее они могут оказаться полезными в ряде приложений. Усоверш е нствования в систе м е NIO, начиная с версииJDK7 В версии JDK 7 система ввода-вывода NIO бьта значительно расширена и усо­ вершенствована. Помимо поддержки оператора try с ресурсами , который обе­ спечивает автоматическое упрамение ресурсами, усовершенствования включают три новых пакета (java .nio . file, java . nio . file . attribute и java .nio . file . spi), несколько новых классов, интерфейсов и методо в, а также прямую поддержку потокового ввода-вывода. Эти усовершенствования существе нно расширили воз­ можности для применения системы ввода-вывода NIO, особенно в файлы. В пОСJiе­ дующих разделах описывается ряд ключевых дополнений данной системы. Интерфейс Path Возможно, одним из наиболее важных дополнений системы ввода-вывода NIO ямяется интерфейс Path, поскольку он инкапсулирует путь к файлу. Как будет показано далее, интерфейс Path служит связующим звеном для большинства но­ вых файловых средств в системе ввода-вывода NI0.2 . Он описывает расположе-
Гл ава 21 . Систе ма ввода-вь1вода NIO 775 ние файла в структуре каталогов. Интерфейс Path находится в пакете java . nio . file и наследует интерфейсы WatchaЬle, IteraЫe<Path> и Cornpa raЬle<Path>. Инте рфейс Wa tchaЫe описывает объект, который можно наблюдать и изменять. Интерфейсы IteraЫe и CornparaЫe были представлены ранее в данной кн иге. В интерфейсе Раth объявляется немало методов для манипулирования путя ми к файлам. Некоторые из них приведены в табл. 21.5 . Обратите особое внимание на метод getNarne ().Он служит для получения элемента пути. С этой целью в дан­ ном методе применяется индекс . Нулевому значению индекса соответствует бли­ жайшая к корневому каталогу часть пути , являющаяся его крайним слева элемен­ том. Последующие индексы определяют элементы вправо от корневого каталога. Количество элементов в пути может быть получено в результате вызова метода getNarneCount ().Если же требуется получить строковое представление всего пути, достаточно вызвать метод toString (). Следует также заметить, что для распозна­ вания относительного и абсолютного пути достаточно вызвать метод resolve (). Табл ица 21 .5. Избранные методы из инте рфейса Path Метод Ьo o lean end8With (Strinq путь) Ьo o lean endsWith (Path путь) Path qetFileNU18 () Path qetNU18 (int индекс) int qetNameCount () Path qetparent () Path qetRoot () Ьo o lean isAЬsolute () Описание Возвращает логическое значение true , если вызыва­ ющий объект типа Path оканчивается пугем, опре­ деляемым параметром путь. а иначе - логическое значение falae Возвращает логическое значение true, если вызыва­ ющий объект типа Path оканчивается пугем, опре­ деляемым параметром путь. а иначе - логическое значение falae Возвращает имя файла, связанное с вызывающим объектом типа Path Возвращает объект типа Path, содержащий имя элемента пуги по указанному � в вызывающем объекте. Крайний слева элемент имеет нулевой ин­ декс и находится ближе всего к корневому каталоrу. А крайний справа элемент имеет индекс qetNaш­ eCount() - 1 Возвращает количество элементов (кроме корневого) в вызывающем объекте типа Path Возвращает объект типа Path, который содержит весь пугь, кроме имени файла, определяемого вызы­ вающим объектом типа Path Возвращает корневой каталог из вызывающего объ­ екта типа Path Возвращает логическое значение true, если вызы­ вающий объект типа Path обозначает абсолютный путь, а иначе - логическое значение falae
776 Часть 11. Библиоте ка Java Метод Path resolve (Path путь) Path resolve (Stri119 путь) Описание Если указанный путь является абсолютным, то возвра· щается именно он. А если указанный путь не содер­ жит корневой каталог, то этот путь предваряется кор­ невым каталогом из вызывающего объекта типа Path. а затем возвращается полученный результат. Если же указанный путь пуст, то возвращается вызывающий объект типа Path. В противном случае поведение дан· ного метода не определено Если указанный путь является абсолютным, возвраща­ ется именно этот путь. А если указанный путь не со­ держит корневой каталог, то этот путь предваряется корневым каталогом из вызывающего объекта типа Path, а эатем возвращается полученный результат. Если же указанный путь пуст, то возвращается вызы· вающий объект типа Path. В противном случае пове­ дение данного метода не определено Ьo o lean starts1fith (Strinq путь) Возвращает логическое значение true, если вызы· вающий объект типа Path начинается с указанного nymu, а иначе - логическое значение falи Ьo o lean starts1fi th (Path путь) Возвращает логическое значение true, если вызы­ вающий объект типа Path начинается с указанного пути. а иначе - логическое значение false Path toAЬsolutePath () Возвращает вызывающий объект типа Path в виде абсолютного пути Strinq toString ( ) Возвращает строковое представление вызывающего объекта типа Path Следует также иметь в виду, что при обновлении унаследованного кода, в кото­ ром используется класс Fi le, определенный в пакете j ava . io, экземпляр класса Fi le можно преобразовать в экземпляр интерфейса Ра th, вызвав метод toPath () для объекта типа File. Этот метод был введен в класс File в версииJDК 7. Кроме то го , экземпляр класса File можно получить , вызвав метод toFile () , определяе­ мый в инте рфейсе Path. Класс Files Большинство действий, которые выполняются над файлами , предоставляются статическими методами из класса Files. Пугь к файлу, над которым выполняются определенные действия , задает объект ти па Ра th. Та ким образом, методы из клас· са Files используют объект типа Path, чтобы указать используемый файл. Класс F i 1 е s обладает обширным рядом функциональных возможностей. Та к, в нем име­ ются методы, позволяющие открывать или создавать файл по указанному пуrи. Кроме того, из объекта типа Path можно получить следующие сведения о файле: является ли он исполняемым, скрытым или доступным только для чтения . В классе
Гл ава 21 . С исте м а ввода-вывода NIO 777 F iles предоставляются также методы, позволяющие копировать или перемещать файлы. Некоторые методы, определенные в этом классе, перечислены в табл. 2 1 .б . Помимо исключения типа IOException, возможны и другие исключения. В версии jDК 8 класс Files дополнен следующими четырьмя методами: list ( ) , wa lk ( ) , lines ( ) и find ().Все эти методы возвращают объект типа Strearn. Они с пособствуют интеграции системы ввода-вывода NIO с новым прикладным про­ граммным интерфейсом API потоков ввода-вывода, определенным в версии JDK 8 и описываемым в гл аве 29. Та бл ица 21.6. Избранные методы мэ класса Files Метод static Path copy (Path источних, Path адрео о т CopyOption ... споrоб) throws IOException static Path createDirectory (Path путь , FileAttriЬu te<?> .. . ampui iymьl ) throws IOВ:xception static Path createFile (Path путь, FileAttriЬute<?> . . . аmрибуты) throws IOException static void delete (Path путь) throws IOException static Ьoolean exi sta (Path путь, LinkOptions . . . параметр ы > static Ьoolean il!IDirectory (Path путь, LinkOptiona ... по.рамет ры > Описание Копирует файл из ш:точншr а по указанному адреmту заданным спосо6ом Создает каталог по указанному nymu. Атрибуты ката­ лога определяются параметром � Создает файл по указанному nymu. Атрибуты файла определ яются параметром ampuбymы Удаляет файл по указанному nymu Возвращает логическое значение t.rue, если файл существует по указанномуnymu, а иначе -логиче­ ское значение false. Если же арrумеIП � не определен, то используются символические ссылки. С целью п редотвратить следование по сим­ волическим ссылкам арrумеIП �должен принимать значение NОFОLLОlf_LINКS Возвращает логическое значение t.rue , если пара­ метр путь определяет каталог, а и наче - логическое значение false. Если же арrумеJП пораметр ы не определен , то используются символические ссыл­ ки. С целью предотвратить следование по симво­ лическим ссьu�кам заданный аргумент параметры должен п ринимать значение NOFOLLOlf_LINКS static Ьoolean isВ:xecutaЬle (Path Возвращает логическое значение true, если файл путь) по указанному nymu является исполняемым, а ина­ че - логическое значение false static Ьoolean iaHidden (Path путь) throws IOException static Ьoolean iaReadaЬle (Path путь) Возвращает логическое значение true, если файл по указанному nymu является скрытым, а иначе - логическое значение false Возвращает логическое значение true, если файл по указанному nymu доступен для чтения, а иначе - логическое значение fal••
778 Часть 11. Библиотека Java Метод static Ьoolean isRegularFile (Path путь, LinltOptions ... параметр ы ) atatic Ьoolean iaWrit.aЬle (Path путь) static Path J1 10 ve(Path uсточник, Path aдpemm, Copy()pticn ... способ) throws IOException static Se ekaЬ leВyteChan n el newВyteChan n el (Path путь, OpenOption ... способ)throwa IOException static DirectoryStreui<Path> newDirectoryStreaJD. (Path путь) throwa IOException atatic InputStreaa newinputStream (Path путь, OpenOption ... cnoro6)throwa IOException static OUtputStr8&1 11 ne.. .OU tputstream (Раth путь, OpenOption ... спосо6)throws IOException static Ьoolean not:Exi sts (Path путь,LinltOption ... параметр ы ) atatic <А extends 8asicFileAttriЬutes> А readAttriЬu taa (Path путь, Class<A> mun_ampu.6ym a , LinltOption .. . пораметр ы ) throws IOExceptioыn atatic long size (Path путь) throws IOException 0кОlf'ШнШ таlИ. 21 .6 Описание Возвращает логическое значение true, если пара· метр путь оп ределяет файл , а иначе - логическое значение false. Если же аргумент параметр ы не оп ределен , то используются символические сс ыл­ ки. С целью предотвратить следование по символи­ ческим ссылкам аргумент nпра.метры должен при­ нимать значение Ж>FOLLOW LINКS Возвращает логическое значение true, если файл по указанному путидоступен для записи, а иначе - логическое значение falae Коп ирует файл из исmочник а п о указанному адреаzтузаданным сnособом Оrкрывает файл по указанному пути заданным способом. Возвращает для файла байтовый канал типа SeekaЬleВyteChan n el. Те кущая позиция в этом канале может быть изменена. Интерфейс SeekaЬleВyteChan ne l реализуется классом FileChan n el Оrкрывает каталог по указанному пути. Возвращает п оток ввода каталога типа DirectoryStreaш, свя­ занный с каталогом Оrкрывает файл по указанному пути задан­ ным аwсобом. Возвращает поток ввода типа InputStream, связанный с файлом Оrкрывает файл по укааанному пути задан­ ным сnособом . Возвращает поток вывода типа OUtputStrea1 11, свяэанный с файлом Возвращает логическое значение true, если файл по указанному пути не существует, а иначе - логиче­ ское значение falae. Если же аргумент пораметр ы не оп ределен, то используются сим волические ссылки. С целью п редотвратить следование по сим­ волическим ссылкам аргумент пара.мет ры должен п ринимать значение NОFОLLОК_LINКS Получает атрибуты , связанные с файлом . Тип пере­ даваемых атрибутов о пределяется параметром mun_ampuliym tL Если же аргумент mраметры не определен , то используются символические сс ыл­ ки. С целью п редотвратить следование по символи­ ческим ссылкам аргумент nпра.метры должен при­ нимать аначение Ж>FОLLОW LINКS Возвращает размер файла по указанному пути
Гл ава 21 . С истема ввода-вывода NIO 779 Обратите внимание на то , что некоторые методы, перечисленные в табл . 21.6, получают аргумент типа Op enOpt ion. Это интерфейс, описывающий способ О'Г­ крытия файла. Он реализуется классом StandardOpenOption, где определяется перечисление, значения которого представлены в табл . 2 1.7 . Табл ица 21.7. Ста нда ртн ые значения параметров открытия файлов Значение APPEND CRDТZ CRZAТENE1f D!LZТE_ON _CLOSB DSYNC RIAD SPARSB SYNC ТRONCAТE_В.XISTING 11RIТВ Класс Paths Н азначение Присоединить выводимые данные в конце файла Соэдать файл, если он еще не существует Создать файл только в том случае, если он еще не существует Удалит ь файл, когда он закры вается Н емедле нно записать в носимые измене н ия в физический файл. Как правило , для повыше н и я производительности измене н ия в фа йле буферизируются файловой системой и записы ваются только по мере надобности Открыть файл для операций ввода Указать файловой системе, что файл разрежен , а следовательн о, н е может б ыть полностью заполне н данными. Если файловая система н е поддерживает разреже нные файл ы , это значение па­ раметра игнорируется Немедленно записать вносимые изменения в файл или его мета­ данные в физический файл. Как правило , для повыше н и я про­ изводител ьност и изме не н ия в файле буферизируются файловой системой и записываются только по мере надобности Укоротить до нул я длину уже сущест вующего файла, от кры вае мо­ го для вы вода Открыть файл для операций вывода Экземпляр типа Path нельзя создать непосредственно с помощью конструк­ тора, поскольку это интерфейс, а не класс. Вместо этого можно получить объект типа Path, вызвав метод, который возвращает этот объект. Как правило, для этой цели служит метод get (), определяемый в классе Paths. Существуют две формы метода get (). Ниже приведена та его форма, которая употребляется в примерах этой гл авы. static Path qet (Strinq JU U1 _ nY'l!• , Strinq ... vac!l'Jr) Этот метод возвращает объект, инкапсулирующий определенный путь. Путь мо­ жет быть задан двумя способами. Если параметр ча сти не указан, то путь должен полностью определяться параметром имя_ пути. В качестве альтернативы путь можно передать по частям, причем первую часть в качестве параметра имя_ пути, а остал ьные части - в качестве параметра ча сти переменной дл ины. Но в любом случае метод get () сгенерирует исключение типа Inval idPathException, если указанный путь синтакс ически недостоверен.
780 Ч асть 11. Библиоте ка Java Во второй форме метода get ( ) объект типа Ра th создается из URI. Эта форма выглядит следующим образом: static Path qet (URI uri ) В итоге возвращается объект типа Path, соответствующий заданному параме­ тру uri. Следует, однако , иметь в виду, что создание объекта типа Path не приво· дит к открытию или создан ию файла. Вместо этого лишь создается объект, инкап· сулирующий путь к каталогу, в котором находится файл. Интерфейсы атрибутов файлов С файлами связан ряд атрибутов, обозначающих время создания файла, вре· мя его последней модифика ции, размер файла или каталог. Система ввода-выво­ да NIO организует атрибуты файлов в виде иерархии различных интерфейсов, определенных в пакете j ava . nio . file . attribute. На вершине этой иерархии находится инте рфейс Bas icFi leAt tributes, инкапсулирующий ряд атрибутов, которые обычно применяются в больц�инстве файловых систе м. Методы, опреде· ленные в интерфейсе BasicFileAttributes, перечислены в табл. 21.8 . Табл ица 21 .8. Методы из инте рфейса BasicFi leAttriЬutes Метод FileTiшe creationTiшe () CЬject fildey () Ьo o lean isDirectory () Ьo o lean isOther () Ьo o lean isReqularFile () Ьoolean isSyшЬolicLi.nk () FileTiшe lastAcoessTiшe () FileTiшe lastмodified­ Tiшe () long size () Описание Возвращает время создания файла. Если этот атрибуг не подцерживается файловой системой, то возвращается зна· чение, зависящее от конкретной реализации Возвращает файловый JUIIOЧ. Если этот атрибуr не подцер­ живается файловой системой, возвращается пустое значе­ ние null Возвращает логическое значение true, если файл является каталогом Возвращает логическое значение true, если файл является символической ссьшкой или каталогом, а не файлом Возвращает логическое значение true, ее.ли файл являет· ся обычным файлом, а не каталогом или сим волической ссылкой Возвращает логическое значение true, если файл является символической ссьшкой Возвращает время последнего обращения к файлу. Если этаr атрибуr не поддерживается файловой системой , то возвра· щается значение, зависящее от конкретной реализации Возвращает время п оследней м одиф икации файла. Если этот ат рибут не поддерживается файловой системой , то возвращается значение, завися щее от конкретной реали· зации Возвращает размер файла
Гл ава 21 . Система ввода -вывода NIO 781 Производными от интерфейса BasicFi leAt tributes являются следующие два и нтерфейса: Do sFileA ttributes и PosixFileAt tribute s. В частности, интер­ фейс Do sFi leAt tributes описывает атрибугы, связанные с файловой системой FAT, которые были первоначально определены в файловой системе DOS. В этом интерфейсе определяются методы, перечисленные в табл. 21.9 . Табл ица 21 .9. Методы из инте рфейса DosFileAttriЬute s Метод Описание Ьoolean isArchive О Возвращает логическое значение true, если файл помечен как архи вный , а иначе - логическое значение fal se Ьoolean isBidden () Возвращает логическое значение true, если файл помечен как скрытый , а иначе - логическое значение false Ьoolean isReadOnly ( ) Возвращает логическое значение true, если файл пом ечен как доступ ный тол ько для чтения, а иначе - логическое значение fal se Ьoolean isSy•tea O Возвращает логическое значе ние true, если файл помечается как системный, а иначе - логическое значение falee Инте рфейс PosixFileAttributes инкапсулирует атрибугы , определенные no стандартам POSIX (PortaЫe Operat ing Sys tem Interface - переносимый интер­ фейс операционных систем ). В этом интерфейсе определяются методы , перечис­ ленные в табл. 21.10. Табл ица 21.10. Методы из инте рфейса PosixFileAttriЬute s Метод Gro upPrincipal group () UserPrincipal owner О Set<Po sixFileP8r1 11i ssion> per1 11i ssions () Описание Возвращает группового владельца файла Возвращает отдельного владельца файла Возвращает полномочия доступа к файлу Имеются разные способы доступа к атрибугам файлов. В частности , вызвав ста­ тический метод readAttributes (), определенный в классе Files, можно полу­ ч и ть объект, инкапсулирующий атрибугы файла. Ниже приведена одна из общих форм объявления этого метода. static <А extends BasicFileAttributes> А readAttributes (Path nyritJ. , Class<A> !l'IOJ а�•бу!l'а , LinkOption . . . napaнe!l'p5f) throws IOExceptTon Этот метод возвращает ссылку на объект, обозначающий атрибугы файла no указанному пути. Конкретный тип атрибутов указывается в виде объекта типа C l ass с помощью параметра тип_ атрибута . Например, для получения основ­ н ых атрибугов файла следует передать в качестве параметра тип_ атрибута объ­ е кт ти па Ba sicFileAttribu tes . clas s, для получения атрибугов DOS - объект типа Do sFileAt tributes . class, а для получения атрибугов РОSIХ - объект типа PosixFileAttribute s.class. Дополнительные, но необязательные параметры ссьuюк передаются как аргумент параме тры. Если же аргумент параме тры не опре-
782 Часть 11. Библиотека Java делен, то следуют символические ссылки. Метод readAt tributes ( ) возвращает ссылку на требуемый атрибут. Если же тип требуемого атр ибута недоступен , то re нерируется исключение типа UnsupportedOpe rationExcept ion. Используя объ­ ект, возвращаемый этим методом, можно обратиться к атрибутам файла. Еще один способ доступа к атрибутам файла состоит в то м, чтобы вызвать ме­ тод getFileAt tributeView (), определенный в классе Files. В системе ввода· вывода NIO определяется несколько интерфейсов для представлений атрибугов, в том числе Attr ibuteView, Ba sicFileAt tributeView, Do sFileAt tributeView и PosixFi leAt tributeView. В примерах из этой гл авы представле ния атрибуrов не употребляются, но в некоторых случаях это средство может оказаться полезным. Иногда можно и не прибегать непосредственно к интерфейсам атрибутов фай· лов, поскольку в классе Files предоставляются служебные статические методы, позволяющие обращаться к некоторым атрибутам. Для этой цел и в классе Files имеются такие методы , как isHidden ( ) и isWritаЫе (). Следует, однако , иметь в виду, что все допустимые атрибуты файлов поддержи· ваются не во всех файловых системах. Например, атрибуты файлов DOS относят­ ся к файловой системе FAT, хотя первоначально они бьmи определены в файловой системе DOS. Те атрибуты, которые применяются в обширном ряде файловых си· стем, описаны в инте рфейсе Ba sicFileAttributes. Поэтому именно они и упо­ требляются в примерах из этой гл авы. Классы FileSystem, FileSystems и FileStore Для упрощения доступа к файловой системе в пакете j а va . nio . f i le предо­ ставляются классы FileSystem и FileSystems. В действительности, используя метод newFileSys tem (),определенный в классе FileSystems , можно даже полу· чить новую файловую систему. А класс FileStore инкапсулирует систему хране­ ния файлов. И хотя упоминаемые здесь классы не употребляются в примерах из этой гл авы , им можно найти применение в своих прикладных программах. П рименение с и стемы ввода - вывода NIO В этом разделе демонстрируется применение системы ввода-вывода NIO для решения самых разных задач. Но прежде следует подчеркнуть, что в версии JDK 7 бьmа значител ьно расширена как сама система ввода-вывода NIO, так и об­ ласть ее применения. Как упоминалось ранее, усовершенствованную версию этой системы иногда называют NI0.2. Вследствие столь значительных усовершенство­ ва ний в системе ввода-вывода NI0.2 изменился и способ написания кода н а ее ос· паве , а также расширился крут задач, для решения которых можно ее применять. В силу этого обстоятельства в большей части примеров из этой гл авы применяют­ ся средства из системы ввода-вывода NI0.2, и поэтому для проработки этих при· меров потребуется комплект JDK 7, JDK 8 или более поздняя его версия. Те м не менее в ко нце этой гл авы дается краткое описание кода , написанного до версии JDK 7, в помощь тем программистам, которые пользуются системой ввода-вывода до верс ииJDК 7 или сопровождают унаследованный код.
Гл ава 21 . С исте ма ввода-вывода NIO 783 Помните! Дnя ко мпиляции большинства примеров из этой гл авы требуется комплект JDK 7, JDK 8 или более поздняя его версия. Раньше система NIO предназначалась в основном для канального ввода-выво­ да, и эта разновидность ввода-вывода по-прежнему остается важнейшей областью ее применения. Но теперь систему ввода-вывода NIO можно также использовать для потокового ввода-вывода и выполнения операций в файловой системе. Та ким образом, обсуждение областей применения системы ввода-вывода NIO можно раз­ делить на три части: • канальный ввод-вывод; • потоковый ввод-вывод; • операции в файловой системе. Самым распространенным средством ввода-вывода является файл на диске , поэтому в примерах, представленных далее в этой гл аве , употребляются файлы на диске. А поскольку все канальные операции ввода-вывода в файлы основыва­ ются на передаче байтов, то для их выполнения будут использоваться буфера типа ByteBuffer. Прежде чем открыть файл для доступа к нему средствами системы ввода-выво­ да NIO, следует получить объект типа Path, описывающий этот файл. Это мож­ но, в частности , сделать, вызвав упоминавшийся ранее фабричный метод Paths . ge t (). В приведенных далее примерах употребляется следующая общая форма объявления метода get ( ) : static Path qet (Strinq llНR_ny'l'l l , Strinq . .. vac!1'11) Напомним, что путь к файлу можно указать двумя способами. Во-первых, пере­ дать первую его часть в качестве параметра имя_ пути, а остальные части - в ка­ честве параметра ча сти переменной длины. И во-вторых, указать весь путь в ка­ честве параметра имя_ пути, а параметр ча сти опустить. Именно такой способ и применяется в рассматр иваемых далее примерах. Применение с и сте мы NIO для канального ввода-вывода Важнейшей областью применения системы ввода-вывода NIO является получе­ ние доступа к файлу через каналы и буфера. В последующих разделах демонстриру­ ются некоторые способы применения канала для чтения и записи данных в файл . Чтение файла через канал Имеется несколько способов чтения да нных из файла через канал. Наиболее распространенный из них, вероятно , состоит в том, чтобы сначала выделить опе­ ративную память под буфер вручную , а затем выполнить явным образом опера­ цию чтения для загрузки этого буфера данными из файла. Поэтому рассмотрим этот способ в первую очередь.
78' Часть 11. Библ иоте ка Java Прежде чем прочитать данные из файла, его нужно открыть. Для этого сначала создается объект типа Ра th, описывающий файл , а затем он используется для О'Г" крытия файла. Имеются разные способы открыть файл в зависимости от того, как он будет использоваться. В рассматриваемом здесь примере файл будет О'Г" крыт для выполнения явных операций байтового ввода. Поэтому в данном при· мере для открытия файла и установления канала доступа к нему вызывается метод Fi les . newByt eChannel ( ) . Метод newByteChanne l ( ) имеет следующую общую форму: static SeekaЬleВyteChan n el newВyteChan n el( Path ny!l'ь , OpenOption . . . способ) throws IOException Этот метод возвращает объект типа SeekaЬleByteChanne l, инкапсулирующий канал для файловых операций. Объект типа Ра th, описывающий файл , передает­ ся в качестве параметра путь. А параметр спо соб определяет порядок открытия файла . Это параметр переменной длины, и поэтому в качестве его можно указать любое кол ичество аргументов через запятую. (Допусти мые значения параметров дан ного метода обсуждались ран ее и приведены в табл . 21. 7.) Если же ни каких аргументов не указано, то файл открывается для операций ввода. Инте рфейс SeekaЫeByteChannel описывает канал, применяемый для файловых операци й. Он реализуется классом FileChannel. Когда используется выбираемая по умол· чанию файловая система, возвращаемый объект может быть приведен к типу FileChanne l. Завершив работу с каналом, следует закрыть его. Классы всех ка· налов, включая и класс FileChannel, реализуют интерфейс Au toCloseaЫe, по­ этому для автомати ческого закрытия файла вместо явного вызова метода close () можно воспользоваться оператором try с ресурсами. Именно такой подход и при· меняется в примерах из этой гл авы. Затем следует получить буфер , который будет использоваться каналом , за· ключив буфер в оболочку существующего массива или динамически выделив опе­ ративную память под буфер. В примерах из этой гл авы применяется выделение оперативной памяти под буфер, но вы вольны выбрать любой из этих двух спосо­ бов. Файловые каналы оперируют буферами байтов, и поэтому для их получения в примерах из этой гл авы вызывается метод allocate (), определенный в классе ByteBuffer. Ниже приведена его общая форма, где емко сть обозначает конкрет­ ную емкость буфера, а в итоге возвращается ссылка на буфер. static ВyteВuffer allocate (int енжос!l'ь) После создания буфера для канала вызывается метод read ( ) , которому пере­ дается ссылка на буфер. Ниже приведена общая форма метода read ( ) , кото рая употребляется в примерах, представленных далее в гл аве. int read (ВyteВUffer буфер) throws IOException При каждом вызове метода read ( ) указанный буфер заполняется данными из файла. Чтение осуществляется последовател ьно, а следовательно, при каждом вы· зове метода read ( ) из файла в буфер читается следующая порция байтов. Метод read ( ) возвращает количество фактически прочитанных байтов. При попытке прочитать дан ные по достижении конца файла возвращается значение -1 .
Гл ава 21. С истема ввода-вывода NIO 785 В приведенном ниже примере программы все изложенное выше демонстриру­ ется на практике. В этой программе данные из файла test . txt читаются через канал посредством явных операций ввода. 1 1 Исполь зовать канал ввода- вывода для чтения файла . 11 Тре оуе тся установка комплекта JDK , начиная с версии 7 import java.io.*; import java.ni o.*; import java.nio. channe ls .*; import java.nio .file.*; puЫ ic class ExplicitChannelRead { puЫ ic static void ma in (String args [] ) { int count ; Path filepath z null; 11 сначала получить путь к файлу try { filepath = Paths .get ( "test .txt" ); catch (InvalidPathException е) { System. out . println ("P ath Error "+е) ; return; 11 затем получить канал к этому файлу в 11 Олоке оператора try с ресурсами try (See kaЬleByteChannel fChan = Files . newByteChannel (filepath) ) { 11 выделить память под Оуфер ByteBuffer mВuf = ByteBuffer . allocate (l2 8) ; do{ 11 читать данные из файла в Оуфер count = fChan .read (mВuf) ; 11 пре кратить чтение по достижении конца файла if(count != -1) { 11 подготовить Оуфер к чтению из него данных mВuf. rewind(); 11 читать Оайты данных из Оуфера и 11 выводить их на экран как символы for (int i=O; i < count; i++) System.out . print ((c har )mВuf .ge t() ); while ( count != -1) ; System. o ut . println () ; catch (IOException е) { System. o ut . println ("OшиOкa ввода-вывода "+е) ; Эта программа действует следующим образом. Сначала создается объект типа Path, содержащий относительный пуrь к файлу test . txt. Ссылка на этот объект присваивается переменной filepath. Затем для создания канала, связанного с фай-
786 Ч асть 11. Библиотека Java лом, вызывается метод newByteChannel (), которому передается ссылка на файл в переменной f i lepath. А поскольку никаких параметров открытия файла не ука· зано, то файл по умолчанию открывается для чтения. Обратите внимание на то, что создан ный в итоге канал является объектом, управляемым оператором t r y с ресурсами. Та ким образом, канал автоматически закрывается в конце блока это­ го оператора. Далее в данной программе вызывается метод allocate ( ) из класса ByteBuffer, чтобы выделить оперативную память под буфер для хранения содер­ жимого файла во время чтения. Ссьтка на этот буфер хранится в переменной экзем· пляра mВuf. После этого вызывается метод read (),исодержимое файла читается по очереди в буфер mВuf. Количество прочитанных байтов сохраняется в перемен· ной count. Затем вызывается метод rewind (), чтобы подготовить буфер к чтен ию из него данных. Этот метод нужно вызвать потому, что после вызова метода r e a d () текущая позиция находится в конце буфера. Ее следует возвратить в начало буфера, чтобы при вызове метода get () можно бьто прочитать байты данных из буфера mВuf. (Напомним, что метод get () определяется в классе ByteBuffer.) Буфер mВuf может содержать только байты данных, поэтому из метода ge t ( ) возвращаются байты. Они приводятся к типу char, чтобы выводить на экран содержимое файла в текстовом виде. (В качестве альтернативы можно создать буфер, автоматически преобразующий байты в символы, а затем прочитать их из этого буфера.) По до­ стижении конца файла метод read ( ) возвращает значение -1. В таком случ ае про­ грамма завершается и канал автоматически закрывается. Обратите вни мание на следующий интереснь1й момент: программа получает объект типа Ра th в пределах одного блока оператора try, а затем использует его в другом блоке оператора try для получения и манипулирования каналом, свя· занным с файлом по пути , который задается объектом типа Path. И хотя в таком подходе нет ничего плохого, во многих случаях его можно упростить, чтобы opra· низовать ввод-вывод данных только в одном блоке try. В этом случае вызовы ме­ тодов Paths . ge t () и newByteChannel () следуют друг за друго м. В качестве при· мера ниже приведена переделанная версия программы из предыдущего примера, где применяется данный подход. 11 Боле е компактный способ открытия канала . 11 Требуется ус тановка комплекта JDK , начиная с версии 7 import jav a.io. *; import java . nio .•; import jav a.nio . channe ls .*; import java .ni o.file.*; puЫ ic class Expl icitChannelRead { puЫic static vo id ma in (String args[] ) { int count ; 11 Здесь канал открыв ается по пути , возвращаемому 11 методом Patha .qet () в виде объе кта типа Path . 11 Переменная filepath больше не нужна try (SeekaЫeByteChannel fChan = Fi les . newByteChannel (Paths .get ( "test .txt" )) 11 выдели ть памя ть под буфер ByteBuffer mВuf = ByteBuffer . allocate (l28) ; do(
Глава 21. Система ввода-вывода NIO 787 11 читать данные из файла в буфер count = fChan .read (mB uf) ; 11 пре кратить чтение по дос тиже нии конца файла if(count 1= -1) { 1 11 подготовить буфер к чтению из него данных mBuf. rewind(); 11 читать байты данных из буфера и 11 выводить их на экран как символы for (int i=O; i < count; i++) System.out . print ((c har )mB uf .get () ); 1 while(count != -1); System.out .print ln (); catch (In val idPathException е) ( System. out .println l "Oшибкa указания пути " + е ); cat ch (IOException е) { System. out .println ( "Oшибкa ввода -вы вода"+ е ) ; В этой версии программы переменная filepath больше не требуется . и оба исключения о браба тыва ются в одном и то м же блоке оператора try. Та кой под­ ход компактн ее , поэтому именно оп и применяется в остальных примерах из это й гла вы. Разумеется, при написании прикладного кода возможны случаи, когда соз­ дан ие объекта типа Ра th должно бьпъ отделено от получения канала. В подобных случ ая х п р име н яется прею,щущий подход. Другой способ чтения файла подразумевает его сопоставление с буфером . Пре имущество та коl'О способа <· остоит в том, что буфер автоматически получает соде рж имое файла. Никаких явных операци й •пен ия н е требуется . Со1юставление и чте ние содержимого файла осуще<·твлястся в ходе следую ще й общей про цедуры. Сначала п олуч аетс я объект типа Path, инкапсулирующий файл , как описано ра­ нее. Эате м получается канал к этому файлу. С этой целью вызывается метод Fi les . newByteChanne1 (), которому передастся объект типа Ра th, а тип возвращае11ю1·0 из него объ(� кта приво;�ится к ти пу Fi leChannel. Как упоминалось ранее, м етод newByteChanne 1 () возв ращает объект типа See kaЫeByteChanne 1. Еели исполь­ зуется выбираемая по ум ол ча н и ю файл овая система, этот объект может быть при­ веден к типу FileChannel. Эатем для канала вызывается метод mар ( ) , чтобы сопо­ стави1ъ этот канал с буфером . Метод map () определяется в классе Fil eChannel, поэтому и требуется приведе11ие к типу FileChannel. Ниже 11риведена общая форма объя вления метода map (). мappedВyteBuffer map ( FileChan n el . НapМode способ , long лоэицяя , long раэнер) throws IOException В методе ma p () ;�а�шыс из файла сопоставляются с буфером в оперативной па­ мяти . Значение параметр а спо со б онрсделяет вид ра зре шенно й операции. Этот параметр может принимать одно из следующих допу(·ти мых :ш а•1ений: 1МАрМоdе . READ_ONLY jМapМode .READ _ WRIТE lмapМode . PRIVAТE
788 Часть 11. Библ иотека Java Для чтения данных из файла следуетуказать значени� MapMode . READ_ONL У, а для чтения и записи данных в файл - значение MapMode . READ_WR ITE. Выбор значения MapMode . PRIVAT E приводит к созданию закрытой копии файла, чтобы внесенные в буфере изменения не повлияли на основной файл. Место для начала сопоставле­ ния в пределах файла определяется параметром позиция, а количество сопоставля· емых байтов - параметром ра змер. Ссылка на буфер возвращается в виде объекта класса MappedByteBuffer, производного от класса ByteBuffer. Как только файл будет сопоставлен с буфером , его содержимое можно прочитать в файл из буфера. Та кой способ демонстрируется в следующем примере программы: 11 Исполь зовать сопоставление для чтения данных из файла . 11 Требуе тся установка комплекта JDK , начиная с версии 7 import java.io. *; import java .nio .*; import java .nio . channe ls .*; import java .ni o. file.*; puЫ ic class MappedChanne !Read ( puЫic static void main (String args []) ( 11 получить канал к файлу в блоке оператора try с ресурсами try ( FileChannel fChan = (FileChanne l) Fi les . newByteChannel (Paths .get ( "test . txt " )) // получить размер файла long fSize = fChan. size(); 11 а теперь сопоставить файл с буфером MappedByteBuffer mВuf = fChan .map ( FileChannel . MapMode .READ _ ONLY, О, fSize) ; 11 читать байты из буфера и выводить их на экран for (int i=O; i < fSize; i++) System.out . print ((c har)mВu f .get () ); System.out . println () ; catch (InvalidPathException е) { System.out . println ( "Oшибкa указания пути " + е) ; catch (IOException е) ( System.out . println ("Oшибкa ввода- вывода "+е) ; В данной программе сначала создается путь к файлу, обозначаемый объектом типа Path, а затем открывается файл с помощью метода newByteChannel (). Получаемый в итоге канал приводится к типу FileChanne l и сохраняется в пере­ менной экземпляра fChan. Затем в результате вызова метода size ( ) для канала получается размер файла. Далее вызывается метод map ( ) по ссылке fChan на объ· ект канала, чтобы сопоставить весь файл с областью в памяти , выделяемой под бу· фер, а ссылка на буфер сохраняется в переменной экземпляра mВ uf. Обратите внимание на то , что переменная mBuf объявляется как ссылка на объект типа MappedByteBuffer. Байты из буфера в переменной mBu f читаются непосред· стве нно методом ge t ().
Гл ава 21 . С истема ввода-вывода NIO 789 Запись данных в файл через канал Как и при чтении данных из файла, для записи данных в файл через канал имеется несколько способов. Рассмотрим сначала один из наиболее распростра­ ненных способов. Он предполагает выделение оперативной памяти под буфер вручную, запись в него данных, а затем выполнение явной операции записи этих данных в файл . Прежде чем записать данные в файл , его следует открыть. Для этого нужно по­ лучить сначала объект типа Path, обозначающий пугь к файлу, а затем использо­ вать этот пугь, чтобы открыть файл . В рассматриваемом здесь примере файл будет открыт для выполнения явных операций байтового ввода. Поэтому в данном при­ мере для открытия файла и установления канала доступа к нему вызывается метод Files . newByteChannel ().Как показано в предыдущем разделе, общая форма ме­ тода newByt eChanne l ( ) такова: static SeekaЬleВyteChannel newВyteChan n el( Path пу�ъ , OpenOption ... способ) throws IOException Этот метод возвращает объект типа See kaЫeByteChannel, инкапсулирую­ щий канал для файловых операций. Чтобы открыть файл для вывода , в качестве параметра спо соб следует передать значение StandardOp enOpt ion . WR ITE. Если файл еще не существует и его нужно создать, то следует указать также значение S tanda rdOpenOption . CREATE. (Другие доступные значения стандартных пара­ метров открытия файлов перечислены в табл. 21.7 .) Как пояснялось в предыду­ щем разделе, инте рфейс See kaЫ eByteChannel описывает канал , применяемый для файловых операций. Его реализует класс FileChannel. Когда используется в ыбираемая по умолчанию файловая система, возвращаемый объект может быть пр иведен к типу FileChannel. Завершив работу с каналом, следует закрыть его. Один из способов записи данных в файл через канал подразумевает явные вы­ зовы метода wri te ().Сначала получается объект типа Path, обозначающий пугь к файлу, а затем для открытия этого файла вызывается метод newByteChannel () и возвращаемый результат приводится к типу F i 1 eChanne1.Далее выделяется опе­ ративная память под буфер байтов, в который записываются выводимые в файл дан ные. Прежде чем данные будут записаны в файл , для буфера следует вызвать метод rewind (), чтобы обнулить его текущую позицию. (Каждая операция выво­ да в буфер увеличивает его текущую позицию. Поэтому перед записью в файл ее следует возвратить в исходное положение.) Далее для канала вызывается метод w r ite (), которому передается буфер. Вся эта процедура демонстрируется в сле­ дующем примере программы, где весь английский алфавит записывается в файл t est.txt: 11 Записать данные в файл средствами системы ввода -вывода NIO . 11 Требуется ус тановка комплекта JDK , начиная с версии 7 import jav a.io. *; import java .nio .*; import jav a.nio . channels .*; import java .nio .file.*; puЫic class Expl icitChannelWrite
790 Часть 11. Библиотека Java puЫ ic static void maiп (St riпg args[]) { 11 получить канал к файлу в блоке о ператора try с ресурсами try ( Fi l eChaппel fChaп = (FileChaппel) Files . пewByteChaппel ( Paths .get ( "test .tx t") , Staпda rdOpeпOptioп . WRI TE, StaпdardOpeпOptioп .CREATE) 11 создать буфер ByteBuffer mБu f = ByteBuffer . allocate (26) ; 11 записать не которое количество байтов в буфер for ( iпt i=O; i<26; i++) mВuf.put((byte)('A' + i)); // п одготовить буфер к записи данных mВuf . rewiпd () ; 11 записать данные из буфера в вых одной файл fChaп .write (mВu f) ; catch (InvalidPathException е) { System. out .priпtlп ("Omибкa указания пути " + е) ; catch (IOExceptioп е) { System. o ut . priпtlп (wOmибкa ввода-вывода : "+е) ; System. exit (l) ; Следует отметить одну важную особенность данной программы. Как упомина· лос ь ранее , после записи данных в буфер байтов mВuf, но перед их записью в файл для буфера mВuf вызывается метод rewi nd (). Это требуется для обнуления те­ кущей позиции после записи данных в буфер mВuf. Не следует забывать, что по­ сле каждого вызова метода put () для буфера mВuf текущая позиция смещается. Поэтому текущую позицию необходимо возвратить в начало буфера, прежде чем вызывать метод wri te ( ) . Если не сделать этого, метод wr i te ( ) не сумеет обнару­ жить в буфере никаких данных, посчитав, что их там вообще нет. Еще один способ обнуления буфера между операциями ввода и вывода подраз­ умевает вызов метода fli p () вместо метода rewi nd (). Метод fli p () устанамива­ ет для текущей позиции нулевое значение, а для предела - значение предыдущей текущей позиции. В приведенном выше примере емкость буфера совпадает с его пределом , поэтому метод fli p() можно использовать вместо метода rewind ( ). Но эти два метода взаимозаменяемы далеко не всегда. Как правило , буфер следует обнулять между любыми операциями чтения и за­ писи. Например, в результате выполнения приведенного ниже цикла, состамен· ного на основе предыдущего примера, ан глийский алфавит будет записан в файл три раза. Обратите особое внимание на то , что метод rewi nd () вызывается каж· дый раз в промежутке между операциям чтения и записи. iпt h=O; h<З; h++) { // записать заданное количество байтов в буфер for ( iпt i=O; i<26; i++) mВu f.put ((byte) ('A' + i) );
Гл ава 21 . С исте м а ввода-вь1вода NIO 791 11 подготовить буфер к записи данных mBuf. rewind(); 11 записать данные из буфера в выходной файл fChan .write (mВu f) ; 11 снова подготовить буфер к записи данных mBuf. rewind (); В отношении рассматриваемой здесь программы следует также иметь в виду, что в процессе записи данных из буфера в файл первые 26 байт в файле будут со­ держать выводимые данные. Если файл test . txt существовал ранее, то после вы­ полнения программы первые 26 байт в файле test . txt будут содержать алфавит, а ос тальная часть файла останется без изменения. Еще один способ записи данных в файл подразумевает его сопоставление с бу­ фером. Преимущество такого подхода заключается в том, что занесенные в буфер дан ные будут автоматически записаны в файл. Никаких явных операций записи не требуется . Для сопоставления и записи содержимого буфера в файла необходимо придерживаться следующей общей процедуры. Сначала получается объект типа Path, инкапсулирующий файл , а затем создается канал к этому файлу, для чего вызывается метод Files .newByteChanne l (), которому передается объект типа P a th. Ссылку, возвращаемую методом newByteChannel (), следует привести к типу FileChanne l. Затем для канала вызывается метод map (), чтобы сопоставить ка­ нал с буфером. Метод map ( ) был подробно описан в предыдущем разделе, а здесь он упоминается ради уд обства изложения. Ниже приведе на его общая форма. мappedВyteВUffer map ( FileChannel .МapМode способ , long nOSJrru u , lonq ра.зиер) throws IOException Метод map ( ) сопоставляет данные из файла с буфером в памяти . Значение пара­ метра спо соб определяет разрешенные операции. Чтобы записать данные в файл , в качестве параметра спо соб следует указать значение MapMode . READ_WR ITE. Место для начала сопоставления в файле определяется параметром позиция, а количество сопоставляемых байтов - параметром ра змер. В итоге возвращается ссылка на бу­ фер. Как только файл будет сопоставлен с буфером, в буфер можно вывести данные, которые будут автоматически записываться в файл. Поэтому никаких явных опера­ ций записи в канал не требуется. Ниже приведена новая версия програм мы из предыдущего примера, переделан­ ная таким образом, чтобы использовать сопоставление для записи данных в файл . Обратите внимание на то , что при вызове метода newByteChanne l ( ) в качестве параметра указывается значение Standa rdOpenOption . READ. Дело в том, что со­ поставля емый буфер может использоваться только для чтения или же для чтения и записи. Та ким образом, для записи в сопоставляемый буфер канал должен быть открыт как для чтения , так и для записи. // Записать данные в сопоставляемый файл // Требуе тся ус тановка комплекта JDK , начиная с версии 7 impo rt jav a.io. *; import java .nio . *;
792 Ч асть 11. Б ибл иотека Java import java.nio . chann els .*; import java .nio .file.*; puЫ ic class MappedChannelWrite { puЫ ic static void main (String args[]) { 11 получить канал к файлу в блоке try с ресурсами try ( Fil eChannel fChan = (FileChannel) Files . newByteChannel (Paths .get ( "test. txt " ), StandardOpenOption . WRITE , StandardOpenOption . READ , StandardOpenOption.CREATEJ 11 затем сопоставить файл с буфером MappedByteBuffer mBu f = fChan .map ( Fi leChannel . MapMode .READ_WRI TE, О, 26) ; 11 записать заданное колич ество байтов в буфер for (int i=O ; 1<26; i++) mВuf.put( (byte) ('А' + i)); catch (InvalidPathException е) { System. o ut . println ("Oшибкa указания пути " + е) ; catch (IOException е) { System.out .println ( "Oшибкa ввода - вывода "+е) ; Как видите , в данном примере отсутствуют явные операции записи непосред· ственно в канал. Буфер mВuf сопоставляется с файлом, поэтому изменения в буфе­ ре автоматически отражаются в основном файле. Копирование файлов средствам и систем ы ввода-вывода NIO Система ввода-вывода NIO упрощает несколько видов файловых операций. Хотя здесь недостаточно места, чтобы рассмотреть все эти операции, приведе н· ный ниже пример программы дает общее представление о доступных средствах. В этой программе файл копируется единственным методом сору ( ) - статичес ким методом из класса Files в системе ввода-вывода NIO. У этого метода имеется не­ сколько общих форм. Ниже приведена та общая форма, которая будет использо­ ваться в примерах, представленных далее . atatic Path copy (Path XC'1'0'ilНl l Jt, Path адреса!l', CopyOption ... способ) throws IOException Файл , определяемый параметром источник, копируется в файл, обозначаемый параметром адреса т. А порядок копирования определяется параметром сп особ. Это параметр переменной длины, поэтому он может отсутствовать. Если же он определен , то позволяет передать одно или несколько приведенных ниже значе­ ний, допустимых для всех файловых систем. В зависимости от конкретной реали· зации могут поддерживаться и другие значения. StandardCopyOption .COPY_AT'l'RIBOТZS Зап росить коп ирова ние атрибутов файла StandardLink0ption .NOFOLL01f_LINltS Н е сл едоват ь по символ ическим ссылкам StandardCopyOption .REPLACE_ВXISTING П ерезап исат ь п режний фа йл
Гл ава 21 . С истема ввода-вывода NIO 793 В приведенном ниже примере программы демонстрируется применение мето­ да сору ( ) . Исходный и резул ьт ирующие файлы указываются в командной строке , причем исходный файл указывается первым . О братите внимание на краткость про граммы. Есл и сравнить эту версию программы копирования файла с ее анало­ гом из гл авы 13, то окажется, что та часть программы, которая фактически копи­ рует файл , существенно короче в представленной здесь версии на основе системы ввода-вывода NIO. 11 Скопировать файл средствами системы ввода-вывода NIO . 11 Требуе тся установка комплекта JDK , начиная с версии 7 irnpo rt java.io.*; irnpo rt jav a.nio . *; irnpo rt jav a.nio . channels .*; impo rt java.ni o. file.*; puЫ ic class NIOCopy { puЬlic static vo id ma in (String args[] ) { if(args . length ! = 2) { System.out . println ("Пpимeнeниe : откуда и куда копировать ") ; return; try { Path source = Paths .get ( args [O]}; Path target = Paths .get (args [l]); 11 скопировать файл Fi les . copy (source , targ et, StandardCopyOpt ion . RE PLACE_EX ISTING} ; catch (Inva lidPathException е} { System. out . println ( "Owибкa указания пути " + е} ; catch (IOExc eption е} { System.out . println ( "Owи бкa ввода- вывода "+е} ; П рименение с и сте мы NIO дл я потокового ввода - вывода Начин ая с версии NI0.2, систему NIO можно испол ьзовать для открытия пото­ ка ввода-вывода. Получив объект типа Ра th, следует открыть файл , вызвав стати­ чес кий метод newinput Stream () или newOutputStream (),определенный в клас­ се Files. Эти методы возвращают поток ввода-вывода, связанный с указан ным файло м. В любом случае поток ввода-вывода может быть затем использован так, как описано в гл аве 20, и для этого пригодны те же самые способы. Преимущество использования объекта типа Path для открытия файла заключается в том, что до­ ступны все средства системы ввода-вывода NIO. Для открытия файла с целью потокового ввода служит метод Files . n e winp utStream ().О н имеет следующую общую форму: static InputStream newinputStream (Path пу�ъ , OpenOption .. . способ) throws IOException
791+ Часть 11. Библ иотека Java где параметр путь обозначает открываемый файл , а параметр спо соб - порядок открытия файла. Это параметр переменной дл ины, и поэтому он должен при· нимать одно или несколько значений, определенных в упомянугом ранее классе Standa rdOpenOption. (Безусловно , в данном случае применимы только те значе· ния , которые относятся к потоку ввода.) Если же параметр спо соб не определен, то файл открывается так, как будто в качестве этого параметра передано значение Standa rdOpenOption . READ. После открытия файла можно использовать любой из методов, определенных в классе InputStrearn. Например, метод read ( ) можно использовать для чтения байтов из файла. В приведенном ниже примере программы демонстрируется применение потоко­ вого ввода-вывода на основе системы NIO. Этот пример содержит версию програм· мы ShowFi le из гл авы 13, переделанную таким образом, чтобы для открытия файла и получения потока ввода-вывода использовались средства системы NIO. Нетрудно заметить, что эта версия программы очень похожа на первоначальный ее вариант, за исключением используемого интерфейса Р а th и метода newInput Strearn ( ) . /* Эта пр ограмма выводит те кстовый файл , используя код */ потокового ввода- вывода на основе систеыы NIO . Требуе тся установка комплекта JDK , начиная с версии 7 Чтобы восполь зоваться этой программой , укажите имя файла , который тре буе тся просмотре ть . Например, чтобы просмотреть файл ТISТ .ТХТ, введите в режиме кома ндной строки следующую команду : java ShowFile ТЕSТ.ТХТ import java.io.*; import java.nio .file.*; class ShowFi le { puЫic static void ma in (String args[J ) { int i; 11 сначала удостоверит ься, что указано имя файла if (args . length != 1) { System. out . println ( "Пpимe нeниe : ShowFile имя _ файла") ; return ; // о ткрыт ь файл и получить связанный с ним поток ввода-вывода try (InputStream fin = Fi les . newinputStream (Paths .get (args[O ]))) { do{ i = fin.read(); if(i != -1) System.out . print ((char) i) ; ) while(i != -1); catch (Inva lidPathException е) { System. out .println ( "Oшибкa указания пути " + е) ; catch (IOException е) { System. out .println ( "Oшибкa ввода-вывода "+е) ;
Гл ава 21 . С истема ввода-выв ода NIO 795 Поток ввода-вывода, возвращаемый методом newinp utStream (}, является обычным, и поэтому он применяется как и любой другой поток ввода-вывода. Например, оди н поток ввода можно заключить в оболочку другого , буферизован­ ного потока ввода, например , типа Bu fferedinput Stream, чтобы обеспечить бу­ феризацию так, как показано ниже. В итоге все операции чтения будут автомати­ чески буферизованы. new BufferedinputStream (Files . newinputStream (Pathз .get (args[O J))) Для открытия файла с целью вывода служит метод Files . newOutputStream ( ) , который имеет следующую общую форму: static OUtputStreaa newOutputStreaш ( Path пут1о , OpenOption ... способ) throwa IOException где параметр путь обозначает открываемый файл, а параметр способ - порядок открытия файла. Это параметр переменной дл ины, и поэтому он должен при­ н имать одно или несколько значений, определенных в упомянутом ранее классе Standa rdOpenOption. (Безусловно, в данном случае применимы только те значе­ н ия, которые относятся к потоку вывода.) Если же параметр спо соб не опреде­ лен, то файл открывается так, как будто в качестве этого параметра переданы зна­ чения StandardOp enOption .WRITE, StandardOpenOption . CREATE и Standa rd OpenOption . TRUNCATE_EX IST ING. Метод newOutputStream (} применяется таким же способом, как и описанный ранее метод newinp utStream {). После открытия файла можно вызвать любой метод, определенный в классе Ou tput Stream. Например, вызвать метод write {} для записи байтов в файл . Кроме того, поток вывода можно заключить в поток вы­ вода байтов типа BufferedOutputStream, чтобы буферизовать его. В приведенном ниже примере программы демонстрируется применение метода newOutputStream (}. В этой программе английский алфавит записывается в файл t e st . txt. Обратите внимание на использование буферизованного ввода-вывода. 11 Продемонс трировать потоковый вывод на основе системы NIO 11 Требуе тся ус тановка комплекта JDK , начиная с версии 7 irnport java.io. *; irnpo rt java .nio .file .*; clas з NIOStreamW rite { puЫic static void ma in (String args[] ) { 11 открыть файл и получить связанный с ним поток вывода try (Output Stream fout = new Bu fferedOutputStream ( Files . newOutputStream (Paths .get ( "test .txt " ))) 11 вывести в поток заданное количество байтов for(int i=O; i < 26; i++) fout .write ((byte) ( ' А' + i) ); catch (Inva lidPathException е) System.out . println ("Oшибкa указания пути "+е) ; catch ( IOException е) { System. out . println ( "Oшибкa ввода- вывода : "+е) ;
796 Часть 11. Библиоте ка Java Применение системы ввода-вывода NIO для операций в файловой системе В начале гл авы 20 был представлен класс File, входящий в пакет j ava . io. Как упоминалось ранее, класс File обращается к файловой системе и оперирует раз­ личными атрибугами файлов, обозначающими , например , доступ только для чте­ ния, скрытый файл и т.д . Он служит также для получения сведений о пути к файлу. Начиная с версии JDK 7 интерфейсы и классы, определенные в системе ввода· вывода NI0.2, предоставляют лучший способ выполнения этих операций. К их преимуществам относятся улучшенная поддержка символ ических ссылок, обхода дерева каталогов, усовершенствованная обработка метадан ных и многое другое. В последующих подразделах приведены примеры двух наиболее распространен· пых операций в файловой системе: получения сведений о пути к файлу и самом файле, а также сведений о содержимом каталога. Помните! Есл и требуется обновить устаревший код, в котором применяется класс j ava . io . File, новым кодо м, в кото ром применяется инте рфейс Ра th, воспол ьзуйтесь методом toPath () , чтобы получ ить экземпляр инте рфейса Path из экземпляра класса Fi le. Получение сведений о пути к файnу и самом файле Сведения о пути к файлу могут быть получены методами, определе нными в интерфейсе Ра th. Некоторые атрибуты файлов, описываемые в интерфейсе Path (например, скрытый файл), получаются методами, определенными в классе Files. В рассматриваемом здесь примере употребляются такие методы из интер­ фейса Path, как getName (), ge tParent () и toAЬsolutePath (),атакже методы isExecutaЫe (),isHidden (),isReadaЫ e (), isWritаЫе () и exists ( ) из клас· са Files (они представлены в табл . 21.5 и 21.6) . Внимание! Та кими метода ми, ка к isExecutaЫe (), isReadaЫe (), isWritaЫe (1 и exi sts (), следует пол ьзо ваться осторожно, поскол ьку состояние файловой системы после их вызова может измениться, что может привести к наруш ению нормальной рабо· ты программы и отри цател ьно сказаться на состоя нии защиты системы. Другие атрибуты файлов получаются по запросу из списюi , создаваемого при вызове метода Files . readAttribu tes ().Врассматриваемом здесь примере про­ граммы этот метод вызывается для получения связанного с файлом объекта типа BasicFi leAttribu tes, но аналогичный общий подход можно применить и кдру· гим типам атрибутов. В приведенном ниже примере программы демонстр ируется применение не­ которых методов из интерфейса Path и класса Files наряду с методами из ин· терфейса Ba sicFileAttribu tes. В этой программе подразумевается, что файл test . txt находится в каталоге examples, входящем в текущий каталог. 11 Получить сведения о пути к файлу и самом файле 11 ТреОуе тся установ ка комплекта JDK , начиная с версии 7 impo rt java.io.*;
Гл ава 21 . С истема ввода-вы вода NIO 797 import java .пio .file . *; import java . пio .file . attribute .*; class PathDemo { puЫ ic static void maiп (Striпg args[] ) { Path filepath = Paths .get ("e xamples \\te st .txt " ); Syзtem. o ut . priпtlп ( "Имя файла : "+filepath . getName (l)); System. out . priпtlп ( "Пyть к файлу : " + filepath) ; System.out . priпtlп ( "AOcoлютный путь к файлу : " + filepath .toAb solutePath () ); Syзtem. o ut . priпtlп ( "Родитель ский катал ог : "+filepath . getPareпt () ); if (Fileз . exi зtз ( filepath) ) Syзtem. o ut . priпtlп ( "Фaйл суще ству ет") ; else System.out . priпtlп ("Фaйл не суще ствует") ; try { if (Files . isHiddeп (filepath) ) Syзtem.out . priпtlп ( "Фaйл скрыт") ; еlзе Syзtem.out . priпtlп ( "Фaйл не скрыт") ; catch ( IOException е) { System. o ut . priпtlп ("Owибкa ввода- выв ода : " + е) ; Files .iз WritaЬle ( filepath) ; System. o ut . priпtlп ( "Фaйл доступен дл я записи" ); Fi leз .iзReadaЬle (filepath) ; Syзtem. out . priпtlп ( "Фaйл доступен для чтения" ); try BasicFileAtt ribut es att ribs = Files . readAt tributes(filepath , Ba sicFileAttributes .class) ; if (attribз . isDi rectory ()) System.out . priпtlп ("Этo каталог" ); еlзе Syзtem.out . printlп ("Этo не каталог" ) ; if (attribs .is Re gularFile () ) System.out . priпtlп ("Этo оОычный файл" ); else Syзtem.out . priпtln ("Этo не обычный файл" ); if(attribs .isSymЬolicLiпk () ) Syзtem.out . priпtlп ("Этo симв олическая ссылка" ) ; else Syзtem.out . priпtlп ("Этo не символич еская ссылка" ); System. o ut . priпtlп ("Bpeмя последней модификации файла : " + attribз .lastModi fiedTirne () ); System. o ut : priпtlп ("P aэмep файла : " + attribs .size () + " байтов" ); catch ( IOExceptioп е) ( System. o ut . priпtlп ( "Oшибкa чтения атрибутов : " + е) ;
798 Ч асть 11. Библиотека Java Если запустить :эту программу на выполнение из каталога MyDi r, в котором име­ ется каталог examples, содержащий файл te st . txt , то в конечном итоге будет выведен результат, аналогичный приведенному ниже. (Разум еется , размер файла и его временные характеристики будут иными.) Имя файла : test . txt Пу�ь к файлу : examp les\test . txt Абсолютный путь к файлу : C:\MyDi r\examp les \test .txt Родитель с кий каталог : examples Файл существует Файл не скрыт Файл доступен для записи Файл доступен для чтения Это не каталог Это обычный файл Это не символическая ссылка Время последней модификации файла : 2014-01-01T18 :20:46.380445Z Размер файла : 18 байтов Если вы пользуетесь компьютером с файловой системой FAT (т.е. файловой системой DOS) , то попытайтесь применить методы , определенные в интерфейсе Do s F i l еА t t r ibute s.А если вы пользуетесь системой, совместимой с POSIX, то по­ пробуйте применить методы, определенные в интерфейсе Роs iхFi lеА t triЬu tеs. Получение содержи м ого катал ога Если путь описывает каталог, можно прочитать содержимое :этого каталога, используя статические методы, определенные в классе Files. Для :этого следует сначала получить поток ввода из каталога, вызвав метод newDirectorySt ream () и передав ему объект типа Path, обозначающий каталог. Ниже приведена одна из форм метода newDirectorySt ream (). stati c DirectoryStream<Path> newDirectoryS tream (Path пуwъ х ха wалогу) throws IOException - - Здесь параметр путь_ к _ ка талогу инкапсулирует путь к конкретному ката· логу. Этот метод возвращает объект типа DirectoryStream<Path>, прим еня· емый для получения содержимого каталога. Он генерирует исключение типа IOException при возникновении ошибки ввода-вывода, а также исключение типа Not DirectoryException (его класс является производным от класса IOExcep tion) , если указанный путь не приводит к каталогу. Кроме того , может быть сгенерирова· но исключение типа SecurityException, если доступ к каталогу запрещен. Класс DirectoryStream< Path> реализует интерфейс Au toCloseaЫ e, поэтому объектом :этого класса можно управлять в блоке оператора try с ресурсами. Этот класс реализует также интерфейс I t е r аЫ е < Р а th>. Это означает, что содержимое каталога можно получить, перебрав содержимое объекта типа DirectoryStream. При переборе каждая запись каталога представлена :экземпляром интерфейса Path. Простейший способ перебрать объект типа Di rectoryStream - органи зо­ вать цикл for в стиле for each. Следует, однако , иметь в виду, что итератор, реа· лизуемый в классе DirectoryStream<Path>, может быть получен только один раз для каждого :экземпляра. Следовательно, метод i terator () может быть вызван , а цикл for в стиле for еасh - выполнен только один раз.
Гл ава 21 . С истема ввода-вывода NIO 799 В следующем примере программы выводится содержимое каталога MyDir: 11 Вывести содержимое каталога . Тре буется установка комплекта JDK , 11 начиная с версии 7 import java.io. *; impo rt java.ni o. file.*; import java.nio . file . attribute . *; class DirList { puЫ ic static void main (String args[]) String di rname = "\\MyDir"; 11 получить и обработать поток ввода каталога 11 в блоке оператора try try ( Di rectoryStream< Path> dirstrm = Fi les . newDirectoryStream (Paths .get (dirname) ) System. o ut . println ("Kaтaлoг " + dirname) ; 11 Класс DirectoryStreaш ре ализуе т интерфейс IteraЫe , 11 поэтому для вывода содержимо го каталога можно 11 организовать цикл for в стиле for each for (Path entry : dirstrm) { BasicFileAt tributes attribs = Files . readAt tribute s(e ntry, BasicFileAttributes . class) ; if (attribs . isDirectory () ) System. o ut . print ( "<DIR> ") ; else System. o ut . print (" "); System.out . println (entry . getName (l) ); catch (Inva lidPathException е) { System.out . println ("OшиOкa указания пути " + е) ; catch ( NotDirectoryException е) { System.out . println ( dirname +"не является каталогом. ") ; catch (IOException е) { System. o ut . println ("Owи Oкa ввода- вывода : "+е) ; Эта программа выводит следующий результат: Каталог \MyDi r Di rList .class DirLi st . java <DIR> examples Test .txt Содержимое каталога можно отфильтровать двумя способами. Самый про­ стой из них - воспользоваться следующей общей формой метода newDirectory Stream(): · static DirectoryS tream<Path> newDirectoryStreaa ( Path пут• ж жатапо�у, Strinq •абпон) throws IOException
800 Часть 11. Библ иотека Java В этой форме получаются только те файлы, имена которых совпадают с задан· ным ша блоном. В качестве параметра ша блон можно указать полное имя файла или маску. Маска - это символьная строка, определяющая гл обальный или общий шаблон, с которым будет совпадать один или несколько файлов, и содержащая общеупотребительные метасимволы * и ? . Они соответствуют любому количеству символов и любому одиночному символу соответственно. Ниже приведены другие шаблоны для сопоставления с мас кой. ** [alМ80./lf>I] Совпадает с любым кол ичеством ра3Личных символов в каталогах Совпадает с любым из указанных СUМ80ЛО8. Символ ы * и ? среди ука­ занных символов будуr рассматриваться как обычные символы, а не как метасимвол ы. Через дефис можно указать пределы сопоставления с ша· блоном, например, [x-z] Совпадает с любой из масок, задаваемых в mиске масок через запятую Метасимволы *и?можно указать, используя последовательности символов \* и \?, а для того чтобы указать знак \ - последовательность символов \ \. Можете поэкспериментировать с маской, подставив ее в вызов метода newDirectory­ Stream () из предыдущего примера программы следующим образом: Files . newDirectoryStream (Paths .get (dirname) , "{Path, Dir ) *. {java ,class ) ") Еще один способ отфильтровать каталог - воспользоваться приведенной ниже общей формой метода newDirectoryStream (). static DirectoryStream<Path> newDirectoryS tream (Path пу�ь ж ха �алоrу , DirectoryStreaa .Filter<? super Path> Ф•ЛЪ !!'р файло.8) - throws IOException - Здесь DirectoryStream. Filter - это интерфейс, в котором определяете.я следующий метод: Ьoolean accept (T эленен�) throws IOException В данном случае типом Т будет Path. Если требуется включить указанный элемент в список, возвращается логическое значение true , а иначе - логиче­ ское значение false. Эта форма метода newDi rectoryStream () предоставляет возможность отфильтровать каталог по другому критерию, кроме имени файла. В частности , каталог можно отфильтровать по размеру. дате создания , дате моди­ фикации или атрибуту. Этот процесс демонстрируется в приведенном ниже примере программы. В этой программе перечисляются только те файлы, которые доступны для записи. 11 Вывести только те файлы из ка талога , 11 которые доступны для записи import jav a.io.*; import jav a.nio . file .*; import jav a.nio . file . attribute .*; class Di rList { puЫic static void main (String args[] ) String di rname = "\\MyDir" ; 11 создать фильтр, возвращающий логическое значение true
Гл ава 21 . С истема ввода-вы вода NIO 801 11 только в отношении доступных дл я записи файлов DirectoryStream. Fi lter<Path> how = }; new DirectorySt ream. Fi lter<Path> () { puhl ic boolean accept (Path filename ) throws IOException if(Files . isWritaЬ le ( filenam e) ) return true ; return false ; 11 получить и исполь зовать поток ввода из каталога 11 только доступных дл я записи файлов try (DirectoryStream< Path> dirstrm = Fi l es . newDi rectoryStream (Paths .get (dirname) , how ) System. o ut . println ("K aтaлoг " + di rname) ; for ( Path entry : dirstrm) ( BasicFileAttribu tes attribs } Files . readAttributes (entry, BasicFileAttributes . class ); if (attribs . isDirectory () ) System.out . print ( "<DIR> ") ; else System.out . print (" "); System.out . println (entry . getName (l) ); catch (InvalidPathExc eption е) ( System.out . println ( "Oшибкa указания пути " + е) ; catch (NotDirectoryException е) { System.out . println (dirname +"не является каталогом. ") ; catch (IOException е) { System. o ut . println ("Omибкa ввода-вывода : "+е) ; Обход дерева каталогов с помощью м етода walkFileTree () В предыдущих примерах получалось содержимое только одного каталога. Но иногд а требуется получить список файлов из дерева каталогов. В прошлом решить подобную задачу бьvю нелегко , но система ввода-вывода NI0.2 значительно упро­ щает ее решение, поскольку теперь из класса Files можно вызвать метод wa lk FileTree (), способный обработать дерево каталогов. Этот метод имеет две об­ щие формы объявления. В примерах, приведенных в этой гл аве , употребляется следующая форма этого метода: static Path walkrileTree (Path жорена. , FileVisitor<? extends Path> fv) throws IOException Исходная точка обхода дерева каталогов передается в качестве параметра корень. Экземпляр интерфейса Fi leV isitor передается в качестве параметра fv. Реализация инте рфейса Fi leV isitor определяет способ обхода дерева ката­ лого в, а также позволяет обращаться к сведениям о каталоге. При возникновении ошибки ввода-вывода ге нерируется исключение типа IOException. Кроме того , может быть сгенерировано исключение типа Securi tyException.
802 Часть 11. Библиоте ка Java В интерфейсе FileVisitor определяется, каким образом посещаются файлы при обходе дерева каталогов. Этот обобщенный интерфейс объявляется следую­ щим образом: interface FileVisitor<T> При вызове метода wa lkFileTree () в качестве параметра ти па Т указыва­ ется интерфейс Path (или любой производный от него ти п). В интерфейсе FileV isitor определены методы, перечисленные в табл . 2 1.11 . Табл ица 21.1 1. Методы из инте рфейса FileVisitor Метод FileVisitResult postVisitDirectory (Т катш�ог, IOException UСК1 1 ючение) throws IOException FileVisitResult preVi aitDirectory (Т катш�ог , В&aicFileAttriЬutes атрибуты) throwa IOException FileVisit:Result vi sitFile (T файл , В&sicFileAttriЬutes атри6уты) throws IOException FileVisit:Result vi sitFileFailed (T файл , IOException UСК1 1 ючение) throws IOException Описание Вызывается после посещения каталога. Каталог передается в качестве параметра каталог, а лю­ бое исключение типа IOException - в качестве параметра шж.лючение. Если параметр исключение принимает пустое значение nul l, то никакого исключения не происходит. Возвращает получен­ ный резул ьтат Вызывается перед посещением каталога. Каталог передается в качестве параметра каталог, а свя­ занные с ним атрибуты - в качестве параметра атрибуты. Возвращает полученный резул ьтат. Чтобы исследовать каталог, следует возвратить значение FileVi sitResul t.CONТINUE Вызывается при посещении файла. Файл пере­ дается в качестве параметра файл, а связан- ные с ним атрибуты - в качестве параметра атрибуты. Возвращает полученный резул ьтат Вызывается при неудачной попытке посетить файл. Файл , который не удалось п осетить, пере­ дается в качестве параметра файл, а исключе­ ние типа IOException - в качестве параметра исключение. Возвращает полученный резул ьтат Обратите внимание на то , что каждый метод возвращает значение из перечне· ления Fi leVi sitResul t. В этом перечислении определяются следующие значения: 1 CONТINUE 1 SKIP SIBLINGS 1 SKIP SОВТRЕ Е 1 ТUМINАТЕ В общем, для продолжения обхода каталога и находящихся в нем каталогов ме­ тод должен возвратить значение CONTINUE. Для того чтобы пропустить каталог и его содержимое, а также предотвратить вызов мeтoдa po s tVi sitDirectory (),из метода preVi sitDirectory () должно быть возвращено значение SKI P_ SIBLINGS, чтобы пропустить только каталог и подкаталоги - значение SKI Р_SUBTREE, а для того чтобы остановить обход каталога - значение TERMINATE. Безусловно, можно создать собственный класс для обхода каталогов и реа­ лизовать в нем методы, определе нные в интерфейсе FileV isitor, но об ычно так не поступают, поскольку предоставляется простая их реализация в классе SimpleFileVi sitor. В этом случае достаточно переопределить реализацию
Гл ава 21. Система ввода-вывода NIO 803 по умолчанию одного или нескольких нужных методов. Ниже приведен краткий пример программы, демонстрирующий этот процесс. В этой программе выводят­ ся все файлы из дерева каталогов, в корне которого находится каталог \MyDir. Обратите внимание на краткость этой программы. 11 Простой пример приме нения метода valkPileTree () 11 для вывода дерева ка талогов . Тре буе тся установка 11 комплекта JDK, начиная с версии 7 import java.io.*; import java.nio. file .*; import java.nio. file . attribute .*; 11 создать специаль ную версию класса Si.JapleFileVieitor, 11 в которой переопределяется метод vieitFile () class MyFileVi sitor extends SimpleFileVisitor<Path> { puЫ ic Fi leVi sitResult visitFile ( Path path , BasicFileAttributes attribs ) throws IOException System.out . println (path) ; return FileVi sitResult . CONT INUE; class DirTreeList { puЫ ic static void ma in (St ring args[)) { String di rname = "\\MyDi r"; System.out . println ( "Дepeвo каталогов, начиная с каталога " + dirname + ":\n") ; try { Files . walkFi leTree (Paths .get (dirname) , new MyFileVisitor () ); catch (IOException ехс ) { System. out . println ( "Oшибкa ввода-вывода") ; Ниже приведен примерный результат, выводимый данной программой для обхода того же каталога My Dir, что и прежде. В дан ном примере подкаталог examples соде ржит только один файл MyProgram . j ava. Дере во каталогов , начиная с каталога \MyDi r: \MyDi r\DirL ist .class \MyDi r\Di rLi st .java \MyDi r\exampl es\MyProgram.java \MyDi r\Test . txt В данной программе класс MyFi leVi s i tor расширяет класс SimpleFi leVi sitor, переопределяя только метод v i sitFi le () , который просто выводит их, хотя совсем не трудно достичь и более сложных функциональных возможностей. Например, можно бьто бы отфильтровать файлы или выполнить над ними такие действия , как копирование на резервное устройство. Ради простоты в данном при­ мере для переопределения метода v i sitFi le () выбран именованный класс , но вместо него ничто не мешает воспользоваться анон имным внутренним классом. И последнее замечание: средствами класса java .ni o. f i le . WatchService можно отследить изменения в каталоге.
804 Ч асть 11. Б иблиоте ка Java Пример ы орга низац ии ка наль ного ввода - вывода до верс ии JDK 7 Прежде чем завершить эту главу, следует рассмотреть еще одну особенность системы ввода-вывода NIO. В приведенных выше примерах употреблялись неко­ торые из новых средств , внедренных в систему ввода-вывода NIO, начиная с вер­ сии JDК 7. Но ведь имеется еще немало кода , который написан до версии JDК 7 и требует надлежащего сопровождения, а возможно, и преобразования для приме­ нения новых средств. Поэтому в последующих разделах будет показано, ка к opra· низовать чтение и запись в файлы средствами системы ввода-вывода NIO, доступ· ными до версии JDK 7. Некоторые из приведенных выше примеров переделаны таким образом, чтобы использовать предыдущие средства системы ввода-вывода NIO, а не новые средства, поддерживаемые в системе ввода-вывода NI0.2 . Это оз· начает, что приведенные далее примеры пригодны для версийjаvа , выпущенных до версииJDК 7. Гл авное отличие прежнего кода от нового , в котором применяется система вво­ да-вывода NIO, заключается в интерфейсе Path, который был внедрен в версии JDK 7. Следовательно, для описания файла или открытия канала к нему в прежнем коде не применяется интерфейс Раth. Кроме того , в прежнем коде не примен яют­ ся операторы try с ресурсами, поскольку автоматическое управление ресурс ами также было внедрено только в версииJDК 7. Помните! В примерах програ мм из этого раздела описывается действие унаследованного кода, в кото ром применяется систе ма ввода-вывода NIO. Материал этого раздела предн азначен для тех программисто в, которые продолжают работать с унаследованным кодом ил и nonь· зуются компилято ром, выпущенным до версии JDK 7. В новом коде должны применяться средства системы ввода-вывода NIO, внедренные в версии JDK 7. Чтение иэ файла до версии JDK7 В этом разделе рассматриваются два предыдущих примера канального ввод а из файла, переделан ных для использования средств систе мы ввода-вывода NIO, доступных только до версииJDК 7. В первом примере для чтения данных из фай· ла сначала выделяется вручную оперативная память под буфер, а затем выполня· ется явным образом операция чтения . Во втором примере производится с опо­ ставление файла с буфером , автоматизирующее весь процесс чтения данных из файла. Если для чтения данных из файла через канал и выделяемый вручную буфер используются версии jаvа, выпущенные до версии JDК 7, то сначала файл откры· вается с помощью потока ввода типа FilelnputStream таким же способом, как описано в гл аве 20. Затем вызывается метод getChanne l () для потока ввода ти па Fi lelnput Stream, чтобы получить канал к открытому файлу. Ниже приведена об­ щая форма этого метода. FileChan n el qetChan n el ()
Гл ава 21 . С истема ввода-вы вода NIO 805 В этой форме возвращается объект типа FileChannel, инкапсулирующий ка­ нал для файловых операций. Далее вызывается метод allocate () для выделения оперативной памяти под буфер. Файловые каналы оперируют буферами байто в, и поэтому для их получения в примерах этой гл авы вызывается метод а l locat е ( ) , определенный в классе ByteBuffer, как пояснялось ранее. В следую щем примере программы демонстрируется чтение и вывод содер­ жимого файла test . txt через канал с использованием явных операций ввода для версийJava, выпущенных до JDK 7: // Использовать каналы для чтения данных из файла . // Версия до JDK 7 impo rt java.io.*; impo rt java .nio .*; impo rt jav a.nio . chaпnel s.*; puЫic class ExplicitChaппelRead { puЫ ic static void ma in (String args[] ) FilelnputStream fln = null; Fi leChannel fChan = null; ByteBu ffer mвu f; int count ; try { 11 сначала открыт ь файл для ввода fin = new Filelnput Stream("test .txt " ); 11 затем получить канал к этому файлу fChan = fln . ge tChannel () ; // выдели ть оперативную память под буфер mB uf ByteBuffer . allocate (l2 8) ; do{ 11 читать данные в буфер count = fChan .read (mБ uf) ; 11 пре кратить чтение по достижении конца файла if(count != -1) { ) 11 подготовить буфер к чтению из него данных mвuf. rewind(); 11 читать байты данных из буфера и // выводить их на экран как символы for ( int i=O; i < count; i++) System . out .print ((char )mвuf .get () ); while ( count != -1) ; System. o ut . println () ; catch (IOException е) System.out . println ( "Owибкa ввода- вывода "+е) ; final ly { try { if ( fChan != null) fChan .close () ; // закрыт ь канал catch (IOException е) { System. o ut . println ( "Owибкa закрытия канала.") ;
806 Часть 11. Библиотека Java try { if(fln != null) fln . close () ; // закрыть файл catch ( IOException е) { Systern.out . println ( "Omибкa закрытия файла.") ; Обратите внимание на то , что в данной программе файл открывается с помо­ щью конструктора класса Filelnput Stream, а ссылка на создаваемый объект это­ го класса присваивается переменной экземпляра f l n . А когда вызывается метод ge tChanne1 ( ) по ссылке на этот объект в переменной экземпляра f In, то создает­ ся канал, подключаемый к открытому файлу. После этого программа работает та· ким же образом, как и в рассмотренной ранее версии для JDК 7. Напомним вкрат­ це, что сначала вызывается метод allocate ( ) из класса B y teBu f fer, чтобы выде­ лить оперативную память под буфер для хранения содержимого, прочитанного из файла. Для этого создается буфер байтов, поскольку класс FileChanne 1 оперирует байтами. Ссылка на этот буфер хранится в переменной экземпляра rnВuf. Затем со­ держимое файла читается по очереди в буфер mBuf методом read ().Колич ество прочитанных байтов хранится в переменной coun t. Далее вызывается метод rewind ( ) , чтобы подготовить буфер к чтению данных. Этот метод приходится вы· зывать потому, что после вызова метода read ( ) текущая позиция находится в кон· це буфера, а ее следует возвратить в начало буфера, чтобы все байты данных могли быть прочитаны из буфера mВuf при вызове метода get ().По достижении конца файла метод read ( ) возвратит значение -1. Когда это произойдет, программа за· вершится , закрыв канал и файл явным образом. Другой способ чтения данных из файла состоит в его сопоставлении с буфе­ ром. Как пояснялось ранее, основное преимущество такого подхода заключается в том, что буфер автоматически получает содержимое файла. Никаких явных опе· раций чтения для этого не требуется. Чтобы сопоставить и прочитать соде ржимое файла, используя средства системы ввода-вывода NIO, доступные до ве рсии JDК 7, следует открыть сначала файл с помощью потока ввода типа FilelnputStream. а затем получить канал к этому файлу, вызвав метод getChannel ( ) для файлового объекта. После этого вызывается метод mар ( ) для объекта типа FileChannel, что­ бы сопоставить полученный канал с буфером , как описано ранее. Ниже приведена версия програм м ы из предыдущего примера, переделанная таким образом, чтобы для сопоставления файла с буфером использовались только средства системы ввода-вывода NIO, доступные до версииJDК 7. // Использовать сопоставление дл я чтения данных из файла . 11 Версия до JDK 7 irnport java.io.*; irnport java.ni o.*; irnport java.nio. channels .*; puЫ ic class MappedChannelRead puЫ ic static void rnain (String args[] ) Fi leinputSt ream f!n = null;
Глава 21. Система ввода-вывода NIO 807 Fi leChannel fChan = nul l; long fSize ; Mappe d ByteBuffer mBuf; try // сначала открыт ь файл для в в о да fln = new Fi lelnputStream ("test . txt " ); !/ затем получить канал к этому файлу fChan � fin. get Channel (); // получить ра змер файла fSi ze = fChan.size() ; // а теперь сопоставить файл с буфером mBu f = fChan .map ( FileChanne1 . MapMode . P. EAD _ONLY , О, fSize) ; // читать байты из буфера и выводить их на экран for lint i=O; i < fSize; i++) System.out .print ((c har )mB uf.get () ); ca tch (IO Except ion е) { System.out .println ( "Oшибкa ввода - вывода " + е) ; fшal ly { try { if(fС!ып 1 = nt111) fCi1an.close(); // закрыть канал catch (IO Excepr.ion е) { System. out .println ("O шибкa за крытия канал а.") ; try if (fin 1= г1u 1li fln.close (); // за крыть файл catch (IO F: xception е) { syst e m. out. .priпt 111 ( "Ошибка за крыти я файла.") ; В ;1а111юй 11рограммс ф а й л откр ывается с помощ1,ю ко11стру1по р а кл асса Fi leinp11tStrearп, а пъ1л ю1 на со:щ а ваем ы й объект это1·0 ю1 асса нрисваи ваетс я псрсмешюii :'l к:1е:1-111ляр а fin. З ате м ВЫ:-\ЫВается метод getChannel ( ) 1ю ссылке на :пот о бъе кт ri 11сре:1-1е111юй эк:1ем11ляр а fin, чтоб ы создить канал , но;(ю1 10•1ае­ мый к откр ытому файлу. Лал се 011 рс;1еляется ра:1мер файла. П осле эт01·0 вызыва­ ется �1 стщ( rпар ().•1тоб ы со1юста r�и·1ъ весь файл с областью в памяти , выделяемой нод буфер, а ссылка на :пот буфер сохраняется в переменной эк:Jемш1яра гоВu f. И наконец, 11ы:1ыr�аетп1 мсто;( qet� ( ) , чтобы нрочитать байты 11:1 буфера rпBuf. Запись в файл до версии JDK7 В это:1-1 ра:щсле 11ре;(ст;:шлс11ы /(Ва 11р е;(ыду щих н р имср а канал ьного uывода да нных в файл , 11с�><.'дел а11 11ые та 1<и'\1 о бра:Jом, чтоб ы испол ь:зовать тол 1,ко сред­ ства п1 стс:1-1ы ввода-выво;(а NIO, /(осту 1111ые до вер сии JПК 7. В нервом нриме­ рс ;uш :1а1111си J(а1 111ых 11 ф айл сначала выделя ется в ручну ю опср <iти�шая память тщ буфер, а :1атем вы1юл11ястся нвным о бра:юм 011ер ация :1а11иси. Л во вто р о м п р имере 111ю1пвщ(итсн с0110гп1 11 лt>11ис ф а й ла с буфе р ом, автомати:111 р у ю111ее вс<ъ
808 Часть 11. Библиотека Java процесс записи данных в файл. Но в обоих случаях ни интерфейс Path, ни опе· ратор try с ресурсами не применяются, поскольку они доступны лишь с версии JDK 7. Если для записи данных в файл через канал и выделяемый вручную буфер нс· пользуются версии java, выпущенные до версии JDK 7, то сначала файл откры· вается с помощью потока вывода типа FileOutputStrearn таким же способом, как описано в гл аве 20. Затем вызывается метод ge tChannel () для потока выво­ да типа FileOutputStrearn, чтобы получить канал к открытому файлу, а после этого - метод allocate (), чтобы выделить оперативную память под буфер , как описано в предыдущем разделе. Далее записываемые в файл дан ные размещаются в этом буфере и вызывается метод wr i te ( ) для открытого канала. Вся эта процеду· ра демонстрируется в приведенном ниже примере программы. В этой программе английский алфавит записывается в файл test . txt. // З аписать данные в файл средствами системы ввода-вывода NIO. 11 Версия до JDK 7. import java.io.*; import java.ni o. *; import java.nio . channe ls .*; puЫ ic class ExplicitChannelWrite { puЫ ic static void ma in (String args[] ) Fi leOutputStream fOut = null; Fi leChannel fChan = null ; ByteBu ffer mвu f; try { 11 сначала открыт ь файл для вывода данных fOut = new FileOutputStream( "test . txt " ); // затеи получи ть канал к файлу для вывода данных fChan = fOut . getChannel () ; 11 создать буфер mБuf = ByteBuffer. allocate (26) ; 11 записать некоторое количество байтов в буфер for (int i=O; i<2 6; i++) mВuf.put((byte)( 'A ' + i)); 11 подготовить буфер к записи данных mвuf . rewind(); 11 записать данные из буфера в выходной файл fChan .write (mБuf) ; catch (IOException е) { System.out .pr intln ("Oшибкa ввода-вывода "+е) ; finally { try { if ( fChan != null ) fChan .close () ; // закрыть канал catch (IO Exception е) { System.out . println ( "Owибкa закрытия канала. ") ; try { if ( fOut != null) fOut .close (); // закрыт ь файл
catch (IOException е) Глава 21. Систем а ввода-вывода NIO 809 System.out . println ("Oши бкa закрытия файла.") ; Вызов метода rewind ( ) по ссылке на буфер в переменной экземпляра mВ uf тре­ буется для возврата текущей позиции в начало буфера mВ uf после записи данных. Напомним, что при каждом вызове метода put ( ) текущая позиция продвигается к концу буфера. Поэтому, прежде чем вызвать метод write (),следует установить текущую позицию в начало буфера. В противном случае метод write ( ) не сумеет обнаружить в буфере никаких данных, посчитав , что их там вообще нет. Если для записи данных в файл производится сопоставление файла с буфе­ ром и для этой цели используются версии Java, выпущенные до версии JDK 7, то сначала для выполнения операций чтения и зап иси создается объект класса RandornAccessFile, чтобы открыть файл. Для открываемого файла требуется раз­ решение на чтение и запись. Затем для данного объекта вызывается метод map ( ) , чтобы сопоставить открытый файл с буфером. Далее записываемые данные разме­ щаются в буфере. А поскольку буфер сопоставлен с файлом, то любые изменения в нем автоматически отражаются в файле. Та ким образом, никаких явных опера­ ций записи в канал не требуется. Ниже приведена версия программы из предыдущего примера, переделан ная для сопоставления файла с буфером. 11 Записать данные в сопоставленный файл . Версия до JDK 7. import java.io.*; import java .nio .*; import java.nio . channe ls .*; puЫ ic class MappedChanne lWrite { puЫic static void ma in (String args[J ) RandomAccessFile fOut = null; FileChannel fChan = null; ByteBuffer mBu f; try fOut = new RandomAccessFile ("test . txt", "rw ") ; // получить канал к открыт ому файлу fChan = fOut . getChannel () ; // затем сопоставить файл с буфером mBu f = fChan .map ( Fi leChannel . MapMode .READ_WRI TE, О, 26) ; 11 записать не которое количество байтов в буфер for( int i=O; i<26; i++) mBuf.put( (byte) ('А' + i)); catch (IOException е) { System.out . println ( "Oшибкa ввода- вывода "+е) ; finally { try { if ( fChan != null) fChan .close () ; // закрыт ь канал ) catch (IOExcept ion е) {
810 Часть 11. Библ иотека Java System.out . println ( "Oшибкa закрытия канала.") ; try { if ( fOut != null) fOut .close (); // закрыть файл catch (IOException е) { System.out . println ("Omибкa закрыт ия файла.") ; Как видите, в данном примере нет никаких явных операций записи непосред· ственно в канал. Благодаря тому что буфер rnBu f сопоставляется с файлом, измене­ ния в буфере автоматически отражаются в базовом файле.
22 Работа в сети Как известно, язык Java служит практически синонимом программирования для Интернета. На то имеется немало причин, и далеко не самой последней из них является способность создавать безопасный код, переносимый между платформа­ ми. Но одна из наиболее важных причин, по которым языкjаvа отлично подходит для сетевого программирования, кроется в классах, определенных в пакете j ava . net. Эти классы обеспечивают простые в употреблении средства, с помощью ко­ тор ых программисты всех уровней квалификации могут обращаться к сетевым ресурсам. Эта гл ава посвящена пакету java .net. Следует особо подчеркнуть, что работа в сети - очень обширная и сложная те ма. В данной книге недостаточно места, что­ бы полностью описать все средства, входя щие в пакет j ava . net. Поэтому в этой гл аве основное внимание уд еляется лишь самым основным классам и инте рфей­ сам для работы в сети . Осн овы работы в сети Прежде всего полезно дать хотя бы самое общее представление о ключевых понятиях и терминах, связанных с работой в сети. В основу работы в сети , под­ держиваемой в Java, положено понятие сакета, обозначающего конечную точку в сети. Понятие сокета стало употребляться в ОС UNIX, начиная с версии 4.2 BSD Berkley еще в начале 1980-х годов. По этой причине употребляется также те рмин сикст Беjжли. Сокеты составляют ос нову современных способов работы в сети , поскольку сокет позволяет отдельному компьютеру од новременно обслуживать много разных клиентов, предоставляя разные виды информации. Эта цель дости­ гается благодаря применению пс;рта - нумерованного сокета на отдел ьной маши­ не . Го ворят, что серверный процесс "прослушивает" порт до тех пор, пока клиент не соединится с ним. Сервер в состоя нии принять запросы от многих клиентов, подключаемых к порту с одним и тем же номером, хотя каждый сеанс связи инди­ видуален. Для уп равления соединениями со многими кл иентами серверный про­ цесс должен быть многопоточным или располагать какими-то другими средствами для мультиплексирования одновременного ввода-вывода. Связь между сокетами устанавливается и поддерживается по определенному сетевому прото колу. Пр отакол Ин тернета (IP) является низкоуровневым маршру-
812 Часть 11. Библиотека Java ти зирующим сетевым протоколом , разбивающим данные на небольшие пакеты и посылающим их через сеть по определенному адресу, что не гарантирует достав· ки всех этих пакетов по этому адресу. Пр отокол управления переда'Чilй (ТСР) является сетевым протоколом более высокого уровня , обеспечивающим связывание, со­ ртировку и повторную передачу пакетов, чтобы обеспечить надежную доставку дан ных. Еще одним сетевым протоколом более низкого уровня, чем ТСР, является протокол полъзовате.лъских дейтаграм м (UDP). Этот сетевой протокол может быть использован непосредственно для поддержки быстрой, не требующей постоянно­ го соединения и ненадежной транспортировки пакетов. Как только соединение будет установлено, в действие вступает высокоуро в· невый протокол , тип кото рого зависит от используемого порта. Протокол ТСР/ IP резервирует первые 1024 порта для отдел ьных протоколов. Многие из них по­ кажутся вам знакомыми, если вам довелось потратить хотя бы немного времени на блужда ние по Интернету. В частности , порт 21 выделен для протокола FГР. порт 23 - для протокола Te lnet , порт 25 - для эл ектронной почты, порт 43 - для прото­ кола whois , порт 80 - для протокола НТТР, порт 119 - для протокола netnews и т.д. Каждый сетевой протокол определяет порядок взаимодействия клиента с портом. Например, протокол НТТР используется серверами и веб-браузерами для пе­ редачи гипертекста и графических изображений. Это довольно простой прото­ кол для базо вого постраничного просмотра информации, предоставляемой веб­ серверами. Рассмотрим принцип его действия. Когда клиент запрашивает файл у НТТР-сервера, это действие называется обращением и состоит в то м, чтобы ompa· вить имя файла в специальном формате в предо пределенный порт и затем прочи· тать содержимое этого файла. Сервер также сообщает код состояния, чтобы изве· стить клиента, был ли запрос обслужен , а также причину, по которой он не может быть обслужен. Гл авной составляющей Инте рнета является адрес. Каждый компьютер в Интернете обладает своим адрес ом. Адрес Интернета представляет собой число, однозначно обозначающее каждый компьютер в Интернете. Изначально все адре­ са Интернета состояли из 32-разрядных значений, организованных по четыре 8-разрядных значения. Адрес такого типа определен в протоколе 1Pv4 (Протокол Интернета версии 4). Но в последнее время вступила в действие новая схема адре­ сации, называемая IPvб и предназначенная для поддержки намного большего адресного пространства. Правда, для сетевого программирования нa java обычно не приходится беспокоиться , какого типа адрес используется: 1Pv4 или IPvб, по­ скольку эта задача решается в Java автоматически. Подо бно тому, как IР-адрес описывает сетевую иерархию, имя адреса Инте рнета, называемое доменнъш именем, обозначает местонахождение машины в пространстве имен. Например, адрес www . HerbSchildt . com относится к верх· нему домену com, зарезервированному для коммерческих веб-сайтов в CII IA и име­ ющему имя HerbSchildt (по названию компании), а префикс www обозначает веб-сервер, обрабатывающий запросы. Доменное имя Интернета сопоставляется с IР-адресом с помощью службы доменнъ�х имен (Domain Name Service - DNS) . Это дает пользователям возможность обращаться с доменными именами, тогда как Инте рнет оперирует IР-адресами.
Гл ава 22. Работа в сети 813 Сете вые классы и инте рфейс ы Сетевой протокол ТСР/IP поддерживается в Java благодаря расширению уже имеющихся интерфейсов потокового ввода-вывода, представленных в гл аве 20, а также внедрению новых средств , необходимых для построения объектов ввода­ вывода через сеть. В Java поддерживаются оба семейства протоколов - ТСР и UDP. Протокол ТСР применяется для надежного потокового ввода-вывода через сеть. А протокол UDP поддерживает более простую, а следовательно , и быструю модель передачи дейтаграмм от одной точки сети к другой. Ниже перечислены классы , входящие в пакет j ava . net. Authenticator Inet:Ad dre ss SocketAd dre ss C&chd8que st Inetsocke tAd dre ss SocketIJapl C&cheResponse InterfaceAd dre ss Socke�rl lli ssion Contentвandler JartJRLConnection StandardSocketOption CookieВandler ЪAilticastSocket URI CookieNanaqer NetPerl lli ssion ORL DataorllJDPacltet Networkinter�aoe tJRLClassLoader Datagr&l llS ocltet PasswordAuthentication tJRLConnection Dataqr&l llS oclcetIJapl Proxy URLDecoder BttpCookie ProxySelector ORLEncoder Btt:pURLConnection ResponseCac::he ORLPerl lli ssion (добавлен в версииJDК 8) IDN SecureCacheЬsponse ORLStreaDIВa n dler Inet4Ad dre ss ServerSocket Inat6Ad dre ss Socket Ниже перечислены интерфейсы из пакета j ava . net. Contentвandle rl!'actory l!'ileNaшeМap SocketOptions CookiePolic:y ProtocolF&JDi.ly URLS tre&Jl lВ andlerFactory CookieStore SocketiшplFactory Dataqr&DISocketiшpll!'actory SocketOption В последующих разделах рассматриваются основные сетевые классы и пред­ ставлен ряд примеров их применения. Как только вам станет понятно внугреннее устройство сетевых классов, вы сможете строить на их основе свои классы. Кла сс InetAddre ss Этот класс служит для инкапсуля ции как числового IР-адреса, так и его домен­ ного имени. Для взаимодействия с этим классом используется имя IР-хоста, т. е. узла сети , которое намного уд обнее и понятнее, чем IР-адрес. Числовое значение IР-адреса скрывается в классе Ine tAdd re ss. Этот класс может опериро вать адре­ сами как по протоколу 1Pv4, так и по протоколу IPvб.
81' Часть 11. Библ иоте ка Java Фабричные м етоды В классе I ne tAddre s s отсугствуют досrупные конструкторы. Чтобы создать объ· ект класса InetAddre ss, следует использовать один из досrупных в нем фабричных методов. Фабри'Чнъ�е метоuы просто обозначают соглашение, по которому статиче­ ские методы класса возвращают экземпляр этого класса. Это делается вместо пере­ грузки конструктора с различными списками параметров, когда наличие одн означ· пых имен методов проясняет результат. Ниже приведены общие формы трех наи· более употребител ьных фабричных методов из класса InetAdd re ss. static Ine tAd dr ess ge tLocalHost () throws UnknownBos tException stati c Ine tAd dr ess getвyName (String ЯНJr_х ос�а) throws UnknownHos tException static Ine tAd dr ess [] getAllВyNaшe ( String 1l lN1I хос�а) throws UnknownHos tException - Метод getLocalHost ( ) возвращает объект типа Ine tAddre ss, представля ю­ щий локальный хост, а метод getByName ( ) - объект типа Ine tAddre ss, представ· ляющий хост, имя которого передается в качестве параметра имя_ хоста . Если эти методы оказываются не в состоянии получить имя хоста, они генерируют исклю­ чение типа Un knownHo stException. В Инте рнете считается обычным явлением, когда одно имя используется для обозначения нескольких машин. Это единственный путь обеспечить в какой· то степени масштабируе мость веб-серверов. Фабричный метод getAl lByName () возвращает массив объектов типа I ne tAdd re s s, представля ющих все адреса, в ко­ то рые преобразуется конкретное имя. Этот метод генерирует также исключение ти па Un knownHo stException в том случае, если он не в состоянии преобразовать имя хотя бы в один ад рес. В состав класса I ne tAdd re s s входит также фабричный метод ge t DyAddre s s (), который принимает IР-адрес и возвращает объект типа InetAddre ss. Причем мо­ гут использоваться адреса как по протоколу IPv4, так и по протоколу IPvб. В следующем примере программы выводятся адреса и имена локальной маши· ны, а также двух веб-сайтов в Интернете : 11 Продемонстрировать применение класса InetAd dr ess import java .net .*; class InetAddre ssTest { puЫic static void main (String args[]) throws UnknownHostException InetAddress Address = InetAdd re ss . getLocalHost () ; System. out . println (Addres s) ; Add re ss = InetAddress . getByName ("www . HerbSchildt .com ") ; System. out . println (Address) ; Ine tAddr ess SW [] = InetAddr ess .getAllByName ("www .nba . com" ); for (int i=O ; i<SW . length; i++) System.out . println(SW [i] ); Ниже приведен результат, выводимый этой программой. Безусловно, код, который вы увидите на своей машине, может несколько отличаться от приведенного ниже.
de fault/166 . 203 .115 . 212 ww w .HerbSchildt . com/2 16.92.65.4 ww w .nba . com/216.66 . 31. 161 ww w . nba . com/216.66.31.179 Методы экземпляра Гл ава 22. Работа в сети 815 В классе InetAddress имеется также ряд других методов, которые могуг вызы­ ваться для объектов, возвращаемых упомянугыми выше методами. Некоторые из наиболее употребительных методов перечислены в табл . 22 . 1. Табл ица 22.1. Наиболее употребител ьные методы из класса InetAddress Метод Описание Ьoolean equal a (OЬject другое) Возвращает логическое значение true, если объект имеет тот же адрес Интернета, что и объект, обозна­ чае мый параметром другое byte [] qet:Ad dr eas() Возвращает массив байтов, представляющий IР­ адрес в порядке следования байтов в сети Strinq getвos tAd dr esa О Возвращает символьную строку, представляю­ щую адрес хоста, связанного с объектом типа InetAd dr ess Strinq qetвoatN&JDe () Возвращает символьную строку, представляющую и м я хоста, связанного с объектом типа InetAd dr eas Ьoolean isМultioastAd dr ess ( ) Возвращает логическое значение true, если ад рес является груп повым , а иначе - возвращает логиче­ ское значение f'al se Strinq to Strinq ( ) Возвращает сим вольную строку, перечисляющую для удобства имя и IР-адрес хоста Поиск адресов Интернета осуществляется в последовательном ряде иерархически кешированных серверов. Это означает, что локальный компьютер может автомати­ чески сопоставить конкре·пюе имя с его IР-адресом, как в отношении себя, так и в О'I'­ ношении ближайших серверов. А в отношении всех прочих имен он может обращать­ сякDNS-Cepвepaм, откуда получаются сведения об IР-адресах. Если на таком сервере отсугствуют сведения об определенном адресе, он может обратиться к следующему удаленному сайту и запросить у него эти сведения. Этот процесс может продолжать­ ся вплоть до корневого сервера и потребовать немало времени. Поэтому структуру прикладного кода следует построить таким образом, чтобы сведения об IР-адресах локально кешировались и их не приходилось искать каждый раз заново. Кл ассы Inet4Addre ss и InetбAddress В состав Java включена поддержка адресов как по протоколу 1Pv4, так и по про­ токолу IPvб. В связи с этим были созданы следующие два подкласса, производных от класса InetAdd ress: Inet4Addre ss и Inet бAddre ss. Класс Inet 4Addre ss
816 Часть 11. Библ иотека Java представляет традиционные адреса по протоколу 1Pv4, а класс Inet бAddre s s ин· капсулирует адреса нового стиля по протоколу IPvб. А поскольку оба эти класса являются производными от класса InetAddress, то ссылки на класс Ine tAddress могут указывать и на них. Это единственный способ, с помощью которого уда ется внедрить в Java функциональные возможности протокола IPvб , не нарушая суще­ ствующий код и не вводя большое количество новых классов. Как правило, класс InetAddress можно применять напрямую, чтобы оперировать IР-адресами , по­ скольку этот класс приспособлен для обеих разновидностей адресов. Клиентские сокеты по протоколу TCP/IP Сокеты по протоколу ТСР/IP служат для реализации надежных двунап рав· ленных, постоянных, двухточечных, потоковых соединений между хостами в Интернете. Сокет может служить для подключения системы ввода-вывода в Java к другим программам, которые могут находиться как на локальной машине, так и на любой другой машине в Интернете. на эамеnсуl Как правило, аплеты могут устанавливать сокетные соединения только с тем хосто м, с ко· торого они были загружены. Это ограничение введено в связи с тем, что аnлетам, загружаемым через брандмауэр, было бы опасно предоставлять доступ к любой произвольной машине. В Java поддерживаются две разновидности сокетов по протоколу ТСР/IP: оди н - для серверов, другой - для клиентов. Класс ServerSocket служит "приемником", ожидая подключения клиентов прежде, чем предпринять какие-нибудь действия. Иными словами, класс ServerSocket предназначен для серверов, тогда как класс Socket - для клиентов. Он служит для подключения к серверным сокетам и иниции· рования обмена данными по сетевому протоколу. Клиентские сокеты чаще всего при· меня ются в прикладных программах наJava, поэтому они здесь и рассматриваются. При создании объекта типа Socket неявно устанавливается соединение клиен· та с сервером. Выявить это соединение нельзя никакими методами или конструк· торами. В табл . 22 .2 перечислены два конструктора класса Socket, предназначен· ные для создания клиентских сокетов. Табл ица 22.2. Ко н структо ры класса Socket Конструктор Socltet (String wся_хоста , int rюpm) throws UnknownВostException , IOException Socltet (Inet.Ad dr ess IР-адрес , int rюpm) throws IOВxcep tion Описание Создает сокет, подключае мый к указанному имени_ хоста и порту_ Создает сокет, испол ьзу я уже существу ющий объект типа Inet.Ad dr ess и указанный порт В классе Socket определяется ряд методов экземпляра. Например, объект типа Socket может быть просмотрен в любой момент для извлечения сведений о свя· занных с ним адресе и порте. Для этого применяются методы , перечисленные в табл. 22 .3.
Гл ава 22. Работа в сети 817 Табл ица 22.3. Методы из класса socke t Метод InetAd dr e88 qetine tAd dre es () int getPort () Описание Возвращает объект тип а InetAd dr •••. связанный с объектом типа Socltet. Если же сокет не п одключен, возвращается пустое знаtlение null Возвращает удаленный порт, к которому привязан вызывающий объект типа Socket. Если же сокет не п ривязан, возвращается нулевое значение int getLocalPort О Возвращает локальный порт, к которому п ривязан вызывающий объект тип а Socltet. Если же сокет н е п ривязан, возвращается значение -1 Для доступа к потокам ввода-вывода, связанным с классом Socket, можно вос­ пользоваться методами ge tlnputStream () и getOuptutStream (), перечислен­ ными в табл. 22 .4 . Каждый из этих методов может сгенерировать исключение типа IOException, если сокет оказался недействительным из-за потери соединения. Эти потоки ввода-вывода используются для передачи и приема данных таким же образом, как и потоки ввода-вывода, рассмотренные в гл аве 20. Таблица 22.4. Методы доступа к пото ка м ввода-вывода Метод Описание InputStreaa qetinputStreaa () Возвращает объект тип а InetAd dr ess, связанный throws IOException с вызывающим сокетом OutputStreaa getOutputStreaa () Возвращает объект типа Outputstreaa, cвязaн- throws IOException ный с вызывающим сокетом Имеется и ряд других методов, в том числе метод conne ct () , позволя ющий указать новое соединение; метод isConnected (),возвращающий логическое зна­ чени е true, если сокет подключен к серверу; метод isBound (), возвращающий ло­ гическое значение true, если сокет привязан к адресу; а также метод isClosed (), возв ращающий логическое значение true, если сокет закрыт. Чтобы закрыть со­ кет, достаточно вызвать метод close (). Закрытие сокета приводит также к закры­ тию связанных с ним потоков ввода-вывода. Начиная с версииJDК 7 класс Socket реал и зует также интерфейс Au toCloseaЫe. Это означает, что управление соке­ том можно орган изовать в блоке оператора try с ресурсами. Приведенная ниже программа служит простым примером применения класса Socket. В этой программе устанавливается соединение с портом "whois" (номер 43) на lnterNIC-cepвepe, посылается аргумент командной строки через сокет, а затем вы­ водятся возвращаемые данные. InterNIC-cepвep пытается интерпретировать аргу­ мент как зарегистрированное доменное имя Интернета, а затем возвращает IР-адрес и контактную информацию из веб-сайта, найденного по этому доменному имени. 11 Продемо нстрировать обращение с соке тами import java.net. *; import java.io.*; class Whois {
818 Часть 11. Библ иотека Java puЫ ic static vo id ma in ( String args[]) throws Exception { int с; 11 создать сокетное соединение с вес-сайтом internic .net 11 через порт 43 Socket s = new Socket ( "whois . internic .net ", 43) ; 11 получить потоки ввода- вывода Inp utStream in = s.getinputStream() ; OutputStream out = s.ge tOutputStream(J ; 11 сформировать строку запроса St ring str = (args . length == О ? "MHProfession al . com" + "\n"; 11 преобразовать строку в Сайты byte buf [J = str . getBytes (); 11 послать запр ос out .write (bu f) ; 11 прочитать ответ и вывести его на экран while ((с=in . read() ) != -1) ( Syзtem.out . print ((char) с) ; } s.close (); args [OJ ) Если запросить, например, сведения об адресе MH Pro fe ssional . сот, то будет получен результат, аналогичный следующему: Whoi s Server Ve rsion 2.0 Doma in name s in the .com and .net domains can now Ье regi stered with ma ny di fferent competing registrars . Go to http :// www.internic.net for de tailed information . Domain Name : MHPROFESSIONAL .COM Re g istrar : MELBOURNE IT, LTD . D/B/A INTERNET NAМES WORLDWIDE Whois Serv er : whois .melbourneit.com Re ferral URL : http:// www .melbourneit . com Name Server : NSl .MHEDU . COM Name Server : NS2 .MHEDU . COM Эта программа действует следующим обраэом. Сначала в ней создается объ· ект типа Socket, обозначающий сокет и задающий имя хоста " whois . internic. net " и номер порта 43 ( internic.net - это сайт веб-службы InterNIC, обраба· тывающей запросы по протоколу whois; а порт 4;3 предназначен именно для этой службы). Затем в сокете открываются потоки ввода-вывода. Далее формируется символьная строка, содержащая имя веб-сайта, сведения о котором требуется получить. Если веб-сайт не указан в командной строке , то выбирается имя хоста "MHPro fe ssional .com ". Эта символьная строка преобразуется в массив байто11 и направляется в сеть через сокет. После этого ответ читается из сокета, а резуль­ тат выводится на экран . И наконец, сокет закрывается, а вместе с ним и потоки ввода-вывода. В дан ном примере сокет закрывается вручную в результате вызова
Гл ава 22. Работа в сети 819 метода close (). Если же используется комплект версии JDК 7, то для автомати­ ческого закрытия сокета можно орmнизовать блок оператора try с ресурсами. В качестве примера ниже приведен другой способ написать метод ma in ( ) из рас­ сматриваемой здесь программы, чтобы закрывать сокет автоматически . 11 Использовать блок оператора try с ресурсами дл я закрытия сокета puЫ ic static vo id ma in (String args [] ) throws Exception { int с; 11 создать сокетное соединение с веб-сайтом internic .net 11 ч ерез порт 43 . Этим соке том управляет блок оператора 11 try с ресурсами try ( Socket s = new Socket( "whois . internic. net", 43) ) { 11 получ ить потоки ввода- вывода IпputStream iп = s.get iпput Stream () ; OutputStream out = s.getOutput Stream() ; 11 сформировать строку запроса String str = (args . length == О ? "MHProfe ssional . com" + "\п"; 11 пре образовать строку в байты byte buf [] = str . getBytes () ; 11 послать запрос out .write (buf) ; 11 проч итать ответ и вывести его на экран while ((с = in. read()) != -1) { System. out .print ((cha r) с) ; 11 Теперь сокет закрыт args [OJ ) В приведенных далее примерах для демонстрации явного закрытия сетево­ го ресурса и совместимости кода этих примеров с версиями Java, выпущенными до версии jDК 7 , будет вызываться метод close ().Н о в своем прикладном коде следует уд елить вниман ие автоматическому управлению сетевыми ресурсами, по­ скольку это более рациональный и гибкий подход. Следует также иметь в виду, что в данной версии программы исключения все еще генерируются в теле метода ma in ( ) , но их можно было бы перехватывать и обрабатывать, добавив операторы catch в конце блока оператора try с ресурсами. На заметку! Ради простоты в примерах из этой главы все исключен ия генерируются в теле метода ma in ().Это позволяет яснее показать логику выполнения сетевого кода. Но в реал ьном коде исключения должны обрабатываться надлежа щим образом. Кnacc URL Предыдущий пример не совсем нагляден, поскольку в настоящее время Интернет не ассоциируется с такими старыми протоколами, как whois, finger или fГР. Здесь царствует Всемирная паутина (World Wide Web ), или просто веб , - ела-
820 Часть 11. Библиоте ка Java бо связан ная совокупность высокоуровневых сетевых протоколов и форматов файлов, ун ифицированным образом применяемых в веб-браузерах. Одна из наи· более важных особенностей веб состоит в том , что ее создатель Тим Бернерс-Ли (Тim Berners-Lea) предложил масштабируемый способ нахождения всех ресурсов в Интернете. Как только уд ается однозначно и надежно что-нибудь именовать, это становится очень эффективным принципом. Именно это и делает URL - унифици­ рованный указатель ресурса. URL обеспечивает довольно ясную форму однозначной идентификации адрес· ной информации в веб, и для этой цел и они широко применяются практически во всех браузерах. Класс URL из библиотеки сетевых классов вJava предоставляет простой и краткий прикладной программный инте рфейс API для доступа к ин· формации в Интернете с помощью URL. Все URL совместно используют один и тот же основной формат, хотя и с не­ которыми возможн ыми вариациями. Расс мотрим следующие два примера URL: http:/ / www . MH Professional . corn/ и http : //www . MH Pro f essional . com: 80/ i ndex . h trn. Спецификация URL основывается на четырех составляющих. Первая составляющая обозначает используемый сетевой протокол , отделяемый двоето­ чием ( : ) от остальной части URL. К числу наиболее распространенных сетевых протоколов относятся НТТР, FГР, gopher и file, хотя ныне почти весь обмен дан· ными через Инте рнет осуществляется по протоколу НТТР (на самом деле боль­ шинство браузеров правильно интерпретируют URL, даже если исключить из его спецификации составля ющую сетевого протокола 11 http : //" ). Вторая составля· ющая обозначает имя хоста или IР-адрес , используемый хостом. Она отделяется слева двойным знаком косой черты (//), а справа - одним знаком косой черты (/) или двоеточием (:), хотя это и необязательно. Тр етья составляющая обознача· ет номер порта. Это необязательный параметр , отделяемый слева от имени хоста двоеточием, а справа - одним знаком косой черты. (Так, если порт 80 выби рается для сетевого протокола НТТР по умолчанию, то указывать параметр 11 : 8 О 11 в URL излишне.) Четвертая составляющая обозначает действительный путь к файлу. Большинство НТТР-серверов присоединяют имя файла index . h trnl или index . h trn к URL для непосредственного указания какого-нибудь ресурса в каталоге. Та ким образом, URL ти па http:// www . MHProf essional . corn/ указывает на тот же самый адрес , что и URL типа http : //www . MHPro fe ssional . com/ inde x .htrn. У класса URL имеется ряд конструкторов, каждый из которых может сrе нери· ровать исключение типа Ma lforrne dURLException. В одной из наиболее уп отре­ бительных форм конструктора этого класса URL определяется в виде символьной строки, похожей на ту, что можно видеть в поле адреса, расположенном в верхней части окна браузера. Эта общая форма конструктора класса URL выглядит след:f!О" щим образом: URL(Strinq спец.фжжаrор_С7RL) throws МalforшedURLException Две приведен ные ниже формы конструктора данного класса позволяют разде­ лить URL на отдельные составляющие. URL(Strinq ЖНJI проrожола , Strinq •НJ1 xocra , int nopr , String nyr�) throws МalforaedURLException - URL ( Strinq ЖНJI проrожола , Strinq •- xocra , Strinq nopr) throwa МalforaedURLException -
Гл ава 22. Работа в сети 821 Другой часто употребляемый конструктор данного класса позволяет указать су­ ществующий URL в качестве ссылочного контекста, а затем создать из этого кон­ текста новый URL. И хотя это, на первый взгляд, сл ишком сложно , на самом деле очень просто и уд обно. Общая форма такого конструктора выглядит следующим образом: URL (URL oб'5e.lt'!1' URL, Strinq cneфlfp.lt'a!l'op URL) throws мalforl l&d URLException - В следующем примере программы формируется URL, указывающий на страни­ цу веб-сайта HerbSchildt . сот, а затем просматри ваются его свойства: 11 Продемонстрировать приме нение класса URL impo rt java .пet .*; class URLDemo {* puЫic static vo id ma in ( String args[] ) throws Mal formedURLException URL hp = new URL ( 11 http :// www . He rbSchildt . com/Articles 11 ); System.out . println ( 11 Пpoтoкoл : 11 + hp . ge tProtocol () ); System. out .println ( 11 Пopт : 11 + hp . getPort () ); System. out .println ( 11Xocт : 11 + hp . getHost ()); System. out .println ( 11 Фaйл : "+hp . getFile () ); System. o ut . println ( 11 Пoлнaя форма : "+hp . toExternalForm() ); Эта программа выводит следующий результат: Протокол : http Порт : -1 Хост : www. HerbSchildt .com Фа йл : /Art icles Полная форма : http :// www .HerbSchildt . com/Articles Обратите внимание на то , что порт имеет номер -1, а это означает, что он явно не установлен. Имея объект типа URL, можно извлечь из него связанные с ним данные. Чтобы получить доступ к конкретн ым битам данных или инфор­ мационному наполнению объекта типа URL, следует создать из него объект типа URLConnect ion, вызвав его метод openConne ction (),как показано ниже. urlc = url . openConnection () Метод openaConnection () имеет следующую общую форму: URLConnection openConne ction () throws IOException Этот метод возвращает объект типа URLConne ct ion, связанный с вызывающим объектом типа URL. Следует, однако , иметь в виду, что метод openaConne ction () может сгенерировать исключение типа IOException. Класс URLConnection Класс URLConne ct ion является классом общего назначения и предназначен для доступа к атрибутам удаленного ресурса. Как только будет установлено соеди­ не ние с уд аленным сервером, класс URLConne ction можно использовать для про-
822 Ч асть 11. Библиотека Java смотра свойств удал енного объекта, прежде чем переносить его локально. Эти атрибуты раскрываются в спецификации сетевого протокола НТГР и как таковые имеют смысл только для объектов типа URL , использующих протокол НТГР. В классе URLConnection определяется несколько методов. Некоторые из н их перечислены в табл. 22.5. Табл ица 22.5. Избранные методы иэ кпасса URLConnec:tion Метод int getCont:entLength () long getContentLengthLonq () String getContentТype () long getDat:e () long getExpiration () String getвeaderField (int uндеи) String getвeaderField (String WUl_noAЯ) String getвeaderFieldК8y (int uндекс) Мap<String , List<String>> getвeaderFields () long getLastмod.i.Eied () InputStreua getinputStreua () throws IOException Описание Возвращает длину в байтах содержимого, связанного с ресурсом. Если длина недоступна, возвращается значение -1 Возвращает длину в байтах содержимого, связанного с ресурсом. Если длина недоступ на, возвращается значение -1 Возвращает тип содержимого, обнаруженного в ре­ сурсе. Это значение поля заголовка content-type. Возвращает пусгое значение nul l, если тип содер­ жимого недоступен Возвращает время и дату ответа в миллисекундах, прошедших с 1 января 1970 г. Возвращает время и дату срока действия ресурса вмил л исекундах, прошедших с 1 января 1970 г. Если дата срока действия ресурса недоступ на, возвраща­ ется нуль Возвращает значение поля заголовка по указанному uнОексу. (Индексы полей заголовков нумеруютс я , на­ чиная с нуля. ) Если значение параметра индекс пре­ вышает количество полей, то возвращается пустое значение null Возвращает значение поля заголовка по указанному именu_ nо/1 11. Если указанное поле не найдено, то воз­ вращается пустое значение null Возвращает ключ поля заголовка по указанному uнОеиу. (Индексы полей заголовков нумеруются, на­ чиная с нуля .) Если значение параметра uндекс пре­ вышает количество полей, то возвращается пустое значение null Возвращает отображение, содержащее все поля за­ головков вместе с их значениями Возвращает врем.я и дату последней модификации ресурса в мил л исекундах, прошедших с 1 января 1970 г. Если эта информация недоступна, то возвра­ щается нуль Возвращает поток ввода типа InputStreua, привя­ занный к ресурсу. Этот поток ввода может использо­ ваться для получения содержимого ресурса
Гл ава 22. Работа в сети 823 Обратите внимание на то , что в классе URLConnection определяется несколь­ ко методов, управляющих информацией из заголовков. Заголовок состоит из пар ключей и значений, представленных в виде символьных строк. Используя метод getHeaderField ( ) , можно получить значение, связанное с ключом заголовка. Вызывая метод getHeaderField ( ) , можно получить отображение, содержащее все заголо вки. Несколько стандартных полей заголовков доступны непосред­ ственно через такие методы, как ge tDate () и getContentType (). В следующем примере создается объект типа URLConnection с помощью мето­ да openConnection (), вызываемого для объекта типа URL, а затем он применяет­ ся для проверки свойств и содержимого документа: 1 1 Продемонстрировать приме нение класса URLConnection irnport java . net .*; irnpo rt java.io. *; irnpo rt java .util . Date ; class UCDemo { puЫic static void ma in (String args[J) throws Exception int с; URL hp = new URL ("http:// www. internic .net" ); URLConnection hpCon = hp . openConnection () ; 11 получ ить дату long d = hpCon .getDate (); if (d==O ) System.out . println ("Cвeдeния о дате отсутствуют .") ; else System. out .println ( "Дaтa : "+new Date (d) ); 11 получ ить тип содержимого System. out .println ( "Тип содержимого : "+hpCon . getContentType () ); 11 получить дату срока действия ресур са d = hpCon .getExpi ration () ; if (d==O ) System.out . println ( "Сведения о сроке действия отсутствуют .") ; else System. out . println ( "Срок действия истекае т: "+new Date (d) ); 11 получить дату последней модифи кации d = hpCon .getLastModi fied (); if (d=;=O ) System. out .println ( "Сведения о дате последней модификации .") ; else System.out . println ( "Дата последней модифи кации : "+new Date (d)); // получи ть длину содержимо го long len = hpCon .getContentLengthLong () ; if(len == -1) System.out . println ( "Длинa содержимо го недоступна .") ; else System.out . println ( "Длинa содержимого : "+len) ;
82' Часть 11. Библ иоте ка Java if(len != 0) { System.out . println ("=== Содержимое === ") ; InputSt ream input = hpCon .getinputStream () ; while ( ((с = input .read()) != -1)) { System. out . print ((c har ) с) ; input .close (); else { System. o ut . println ("C oдepжимoe недосrrуnно. ") ; В этой программе сначала устанавливается соединение по протоколу НТГР с сервером www . internic . ne t через порт 80. Затем в ней выводятся несколько значений из заголовков и извлекается соде�имое. Ради интереса попробуйте вы· полнить эту программу, наблюдая результаты ее выполнения . А затем попробуйте для сравнения &ыбрать другой sеб-сайт. Клacc нttpURLConnection В Java предоставляется подкласс HttpURLConnection, производный от клас· са URLConnect ion и поддержиsающий соединения по сетеюму протоколу НТТР. Чтобы получить объект класса HttpURLConnection, следует выз&ать метод open Connect ion () Д1 1Я объекта типа URL, как описано выше, но результат нужно при· вести к типу HttpURLConne ction. (Разумеется , при этом необходимо убедиться. что соединение по протоколу НТГР действительно установлено.) Получив ссьmку на объект класса HttpURLConne ction, можно вызsать любые методы, унаследован· ные от класса URLConnection, а также любые методы , определенные в самом классе Ht tpURLConnection. Некоторые методы из этого класса перечислены в табл . 22 .6 . Табл ица 22.6. Избранные методы из класса HttpURLConne ction Метод static Ьoolean 9etFollowR8direotв () String 9etRequestмethod () int get.ReaponaeCode () throwв IOJ:xoeption String get.ReaponseМeaвage () throws IOZxception Описание Возвращает логическое значение true, если автома­ тически следует переадресация , а иначе - логическое значение �аl ве Возвращает строковое п редставление метода, которым вы п олняется зап рос по URL. По умолчанию запрос вы п ол няется методом GZT. Доступны и другие методы, в том числе POST Возвращает код ответа п о протоколу НТТР. Если код ответа не может быть получен, возвращается значе­ ние -1. П ри разрыве соединения генерируетс я исклю­ чение ти па IОЕхсерtiоn Возвращает от ветное сообщение , связанное с кодом ответа. Если ответное сообще ние отсуrствует, то воз­ вращает пустое значение null
Гл ава 22. Работа в сети 825 ОкончаниетаМ. 22.6 Метод Описание 1tatic void 1etFollowRadirects (boolean Если п араметр споооб прин имает логическое значение true , то переадресация вы п ол няется автоматически. Если же он прин имает логическое значен ие false, то п ереадресация не происходит. По умолчанию п е ре­ адресация вып ол н яется автоматически mo oIO) void 1etRequestмethod (Strin9 mo oIO) throws ProtocolZxception Задает метод , которым делаются запросы по п рото­ колу НТГР, в соответствии со значением параметра способ. По умолчан ию принят метод GZT, но доступ ны и другие методы, в том числе POST. Если в качестве п араметра спо ооб указано неправильное значение, то ге нерируется исключен ие тип а Protocol.Exoeption В приведенном ниже примере программы демонстрируется применение клас· са Ht tpURLConne ct ion. Сначала в этой программе устанавливается соединение с веб-сайтом www . google . сот, а затем выводится метод запроса, код ответа и о� ветное сообщение. И наконец, выводятся ключи и значения из заголовка ответа. 11 Продемонс трировать применение кл асса HttpURLConnection impo rt java .net .*; impo rt java.io. *; impo rt java.util . *; class HttpURLDemo { puЫic static void ma in (String args[] ) throws Exception URL hp = new URL ("http:// www .google . com" ); HttpURLConnection hpCon = (HttpURLConne ction } hp . openConnection (} ; 11 вывести ме тод запроса System. o ut . println ( "Meтoд запроса : "+hpCon .ge tReques tMethod (} ); 11 вывести код ответа System. o ut . println ("Koд ответа : "+hpCon .getRespons eCode (} }; 11 вывести отве тное сообщение System. o ut . println ("O твeтнoe сообщение : " + hpCon .getRe sponseMe ssage () }; 11 получи ть список полей и множе ство ключей из заголовка Map< String, List<String>> hdrMap = hpCon .getHeade rFields (}; Set<String> hdrField = hdrMap .keySet () ; System.out . println ("\nДaлee следуе т заголовок :") ; 11 вывести все ключи и значения из заголовка for ( String k : hdrField} { System.out . println("Kлюч : " + k + " Значение: " + hdrMap.get (k} ); Ниже приведен результат, выводимый данной программой (разумеется , точный ответ, возвращаемый сайтом www . goog le . сот, будет меняться с течением времени).
826 Часть 11. Библиоте ка Java Метод запроса : GET Код ответа : 200 Сообщение ответа : ОК Далее следуе т заголовок : Ключ : Trans fer-Encoding Value : [chun ke d] Ключ : X-Frame -Options Va lue : [SAМEORI GIN] Ключ : nul l Value : [НТТР/1 .1 200 ОК] Ключ : Server Value : (gws ] Ключ : Cache-Cont rol Value : [private, max -age=O ] Ключ : Set -Cookie Va lue : [NID=67=rMTQWvn 5eVIYA2d8F5Iu 8L-68wiМACyaXYqeSe lbvR8 SzQQ PaDCy5mNbxuw5XtdcjY Kiwmy3oVJМlYOqZdiЬBOkQfJmtHpAt061GVwumQ 1ApgSXWj Z67yHxQX3g 3-h; expires=Wed, 23-Ap r-2014 18 :31:09 GMT ; path=/; doma in= .google . com; HttpOnly, PREF=ID=4 63Ь5df7Ь9ced9d8 :FF= O :TM=l 382466669 :LM= 1382466669 : S=3LI-oT-Dzi46UlOn ; expires=Thu , 22-0ct-2015 18 :31:09 GMT ; path= /; domain= .google . com] Ключ : Expires Va lue : [-1] Ключ : X-XSS-Protection Value : [1; mode=Ыock] Ключ : Р3Р Value : [CP="This is not а Р3Р policy ! See http://www .google . com/support/accounts/Ьin/answer . py?hl=en&answer=l 51657 formo re info."] Ключ : Date Value : [Tue , 22 Oct 2013 18 :31:09 GMT ] Ключ : Content-Type Value : [text/html;charset=IS0-8859-1] Обратите внимание на порядок вывода ключей и значений из загол ов ка. Сначала вызывается метод getHeaderFields ( ) , унаследованный от класса URLConnection, чтобы получить отображение ключей и значений из заголовка. Затем для этого отображения :вызывается метод keySet ( ) , чтобы извлечь мно­ жество ключей из заголовка. Далее полученное множество ключей перебирается в цикле for в стиле for each, где :вызывается также метод get () , чтобы получить значение, связанное с каждым ключом. Клacc URI Класс URI инкапсулирует универсалъный идентифи:катор ресурса (URI) , очень по­ хожий на URL. На самом деле URL является подмножеством URI. Если URI обо­ значает стандартн ый способ идентификации ресурсов, то URL описывает также доступ к ресурсу. Сооkiе-файп ы В состав пакета jav a.net входят классы и интерфейсы, помогающие управ· лять сооkiе-файлами , которые можно использовать для организации сеансов с вя· зи по сетевому протоколу НТТР с сохранением состояния, в отличие от сеансов связи без сохранения состояния. К их числу относятся классы C o oki e Handle r , Coo kieManager и HttpCookie, а также интерфейсы CookieP olicyиCookieS t o r e. Рассмотрение сеансов связи по сетевому протоколу НТТР с сохранением состо я· ния выходит за рамки данной книги. На заметку! Подробнее о применении сооkiе-файлов вместе с сервлетами см. в гл аве 38.
Гл ава 22. Работа в сети 827 Серверн ые сокеты по п рото колу TCP/IP Как упоминалось ранее, в Java имеются различные классы сокетов, которые должны применяться при разработке серверных приложений. В частности, класс S e rverSocket применяется для создания серверов, которые принимают запросы как от локальных, так и от уд аленных клиентских программ, желающих установить соеди нение с ними через открытые порты. Класс ServerSocket заметно отличае"Г­ ся от обычных классов типа Socket. Когда создается объект класса ServerSocket, он регистрируется в системе как заинтересованный в соединении с клиентами . Конструкторы класса ServerSocket отражают номер порта , через который требу­ ется принимать запросы на соединение, а также (хотя и необязательно) длину оче­ реди , которая организуется для данного порта. Длина очереди сообщает системе, сколько клиентских соединений можно поддерживать, прежде чем отказать в со­ единении. По умолчанию задается длина очереди 50 соединений. При определен­ н ых условиях конструкторы класса Se rverSocket могут сгенерировать исключение типа IOException. Конструкторы этого класса перечислены в табл . 22.7 . Табл ица 22.7. Ко н структо ры класса ServerSocke t Конструктор ServerSocket (int порт) throvs IO&xception ServerSocket (int порт, int МОКШМ)!М) throws IOException ServerSocket (int порт, int максимум, InetAd dr ess мжа1 1Ь ный_адрес) throws IOException Описание Создает сер верный сокет в указан ном порте с длиной оче­ реди 50 соеди нений Создает серверный сокет в указанном порте с макси­ мальной длиной очереди, оп ределяемой параметром максимум Создает серверный сокет в указанном порте с макси­ мальной длиной очереди , определяемой параметром максимум. На многоканальном хосте параметр локальный_ адрес обозначает IР-адрес , к которому привязан сокет В состав класса ServerSocket входит метод accept ().Он реализует блокиру­ ющий вызов, чтобы ожидать от клиента начала соединения, а затем возвратить обычный объект ти па Socket, который может далее служить для взаимодействия с клиентом. Дейтаграммы Сетевое взаимодействие по протоколу ТС Р/IP подходит для большинства сете­ вых нужд. Оно обеспечивает сериализируемые, предсказуемые и надежные пото­ ки ввода-вывода пакетов да нных. Но все это обходится совсем не даром. Протокол ТСР включает в себя немало сложных алгоритмов адаптации к перегруженности сете й, а также самые пессимистические предположения относительно потери пакето в. Это в какой-то степени делает неэффективным способ переноса дан ных по сети . Альтернативой ему служат дейтаграммы.
828 Часть 11. Библиоте ка Java Дейтаграм м ы - это порции данных, передаваемых между машинами. В некото­ ром отношении они подобны сильным броскам тренированного, но подслеповато­ го принимающего в сторону третьего бейсмена в бейсболе. Даже если дейтаграм· ма и передается в нужном направлении, нет никаких гарантий, что она достигнет цели или кто-ни будь окажется на месте , чтобы ее перехватить. Аналогично, когда дейтаграмма принимается , нет никакой гарантии, что она не была повреждена при передаче или что ее отправитель все еще ожидает ответа. Дейтаграммы реализуются вjava поверх сетевого протокола UDP с помощью двух классов: Da tagramPacket (контейнер данных) и DatagramSocket (механизм для передачи и приема пакетов типа DatagramPacket) . Каждый из этих IUiaccoв рассматривается далее по отдел ьности. Клacc DatagramSocket В классе DatagramSocket определяются четыре открытых конструкто ра. Их общие формы приведены ниже. DataqraшSocket () throws SocketException DataqraшSocket (int пор�) throws SocketException DataqraшSocket(int пOJ)'l' , InetAddress IP адрес) throws SocketException DataqraшSocket ( SocketAd dr ess адрес) throws SocketException Первый конструктор создает объект класса Da tagramSocket, связанный с лю­ бым незанятым портом локального компьютера; второй конструктор - объект класса Da tagramSocket, связанный с указанным портом, третий конструктор - объект класса DatagramSocket, связанный с указанным портом и объектом класса Ine tAdd re ss. Четвертый конструктор - объект класса DatagramSocket, связанный с задан ным объектом класса Socke tAddre ss. Класс Socke tAddress является абстрактным и реализуется конкретным классом InetSoc ke tAddress, который инкапсулирует IР-адрес с номером порта. Все конструкторы класса DatagramSocket могут ге нерировать исключение типа SocketException , если при создан ии сокета возникают ошибки . В классе Da tagramSocket определяется немало методов. Наиболее важными из них являются методы send () и receive (), общие формы которых представле­ ны ниже. void send (DataqramPacket пахе�) throws IOException void receive (DataqramPacket пахе�) throws IOException Метод send () отправляет в порт указанный па кет. А метод rece ive () ожида· ет приема через порт указанного па кета и возвращает полученный результат. В классе Da tagramSocket определяется также метод close (), закрывающий сокет данного типа. Начиная с версииJDК 7 класс Da tagramSocket реализует ин· терфейс Au toCloseaЫe, что позволяет управлять сокетом типа DatagramSocket в блоке оператора try с ресурсами. Другие методы из данного класса предостав· ляют доступ к различным атрибутам, связанным с сокетом типа DatagramSocket. Эти методы перечислены в табл. 22 .8.
Гл ава 22. Работа в сети 829 Табл ица 22.8 . Методы из класса DataqramSocket Метод InetAd dr ess qetinetAd dr ess () int getLocal.Port () int qetport О Ьo o lean isВound () Ьo o lean isConnected () void setSoTil lleo ut (int м1LЛ Л ш:екунд) throws SocketException Описание Возвращает адрес, ее.ли сокет подключен, а иначе - ло­ гическое значение null Возвращает номер локального порта Возвращает номер порта, к которому подключен сокет. Если же сокет не подключен ни к одному из портов, то возвращается значение -1 Возвращает логическое значение true, ее.ли сокет при­ вязан к адресу, а иначе - логическое значение false Возвращает логическое значение true, ее.ли сокет под­ ключен к серверу, а иначе - логическое значение false Устанааливает период ожидания, равный заданному ко­ личеству МW1J1ucerc:yш ) Класс Da tagramPacket В классе DatagramPacket определяется несколько ко нструкторов. Ниже при­ ведены четыре конструктора данного класса. DataqrВl llP acket (byte да.н нu е[] , int раsнер) Dataqr81 11P acket (byte даню.rе[] , int снещеюrе , int раsнер) Dataqr81 11P acket (byte даню.rе [] , int раsнер , InetAd dr ess IР-адрес , int порт) DataqraшPacke t(byte да.н нu е[], int снещеняе, int раsнер, InetAddress IР -адрес, int порт) Первый конструктор определяет буфер , который будет принимать да нные, и размер пакета. Он служит для приема данных через сокет типа Da tag r amS ocke t. Второй конструктор позволяет указать смещение в буфере , где должны быть раз­ мещены дан ные. Тр етий конструктор позволяет указать целевой адрес и порт, ис­ пользуе мые в сокете типа DatagramSocket для отправки пакета по месту назначе­ ния. Четвертый конструктор организует передачу пакетов, начиная с указанного смещения в буфере данных. Два первых конструктора следует рассматривать как запечатывание письма в конверт, а два других - как запечатывание письма в ко н­ верт и написание адреса на нем. В классе DatagramPacket определяется несколько методов, включая перечис­ ленные в табл. 22.9 . Эти методы предоставляют доступ к адресу и номеру порта отд ельного пакета, а также к исходным дан ным и их длине. Табл ица 22.9 . Методы из класса DataqramPacke t Метод Описание Ine tAd d ress qetAd d res s О Возвращает ад рес источника (для принимаемых дейтаграмм) или места назначения (для отп равляе мых дейтаграмм)
830 Часть 11. Библиоте ка Java Метод Ьуtе[] CJ8tData () int qetLenqth () int qetOffeet () int getPort () void setAd dr ess (Ine tAd d ress IР-адрес) void setData (Ьуtе [] данные) void eetData (byte [] данные , int индекс , int раз.мер> void setLenqth (int раз.мер> void setport (int порт> Окон<юние табя. 22. 9 Описание Возвращает массив байтов данных, содержащихся в дейта· грамме. Используется в основном для извлечения данных из дейтаграммы после ее приема Возвращает длину достоверных дан ных, содержащихся в массиве байтов , который должен быть возвращен из метода getData () . Эта длина может не полностью совпа· дать с длиной массива байтов Возвращает начальный индекс данных Возвращает номер порта Устан авливает адрес, по которому отправляется пакет. Адрес обозначается параметром IР-адрес Ус танавливает буфер в виде заданного массива дан нш , нулевое смещение и длину, равную количеству байтов в массиве данные Ус танавливает буфер в виде задан ного массива дан нш , смещение - по указанному и�, а длину - по заданному размеру Ус танавливает длину пакета по заданному � Ус танавливает заданный порт П ример обраб отки дейтаграмм В приведенном ниже примере реализуется очень простое сетевое взаимодей· ствие клиента с сервером. Сообщения вводятся в окне на сервере и передаются по сети на сторону клиента, где они выводятся на экран . 11 Продемонс трир овать обработку дейта грамм import java .net .*; class WriteServe r puЫic static int serverPort = 998 ; puЫ ic static int clientPort = 999; puЫic static int bu ffer size = 1024; puЫ ic static DatagramSoc ket ds ; puЫ ic static byte buffer [] = new byte [buffer_ size] ; puЫ ic static void TheServ er () throwз Exception int pos=O; while (true) { int с=System.in.read() ; switch (с) { case -1 : Syзtem. out .println ( "Cepвep завершает сеанс связи.") ; ds . close(); return; case '\r' : break; case '\n' :
Гл ава 22. Работа в сети 831 ds . send ( new DatagramP acket (buffer, pos , InetAddress . getLocalHost () ,clientPort) ); pos=O ; break; default: bu ffer [pos++] (byte ) с; puЫ ic static void TheClient () throws Except ion ( while (true) { DatagramP acket р = new DatagramPacket (buffer, buff er . length) ; ds . receive (p) ; System. out . println (new String (p . getData () , О, p.getLength () )); puЫ ic static void ma in (String args[] ) throws Exception ( if(args. length == 1) ( ds = new DatagramSoc ket (serve rPort ); TheServe r (); else ( ds = new DatagramS ocket (clientPort ); TheClient (); Этот пример программы ограничивается обменом данными между портами ло­ кальной машины с помощью конструктора класса DtagramSocket. Чтобы восполь­ зоваться этой программой, выполните в одном окне следующую команду: java WriteServer Это будет клиент. Затем выполните в другом окне такую команду: java WriteServer 1 Это будет сервер. Все, что вы введете в окне сервера, будет отправлено в окно клиента после ввода символа перевода строки. На заметку! Применение дейта грамм на вашем компьютере может быть запрещено, например, установленным на нем брандмауэром. В та ком случае вам не удастся воспол ьзоваться про­ гра ммой из приведенного выше примера. Кроме того, номера портов, указа нные в про­ гра мме, подходят для систе мы авто ра книги, но в вашей систе ме их, возможно, придется дополнител ьно настроить.
23 Класс Applet В этой главе рассматривается класс Applet, который служит основанием для создания aIUieтoв. Этот класс входит в состав пакета j ava . applet и содержит ряд методов, обеспечивающих полный контроль над выполнением аплета. Кроме того , в пакете java.applet определяются три интерфейса: AppletCont ext, AudioClip и Ap pletStub. Два типа аплетов Следует особо подчеркнуть, что имеются два типа аплетов. Первый ти п осно­ вывается непосредственно на классе App let, описываемом в этой гл аве. В aIUieтax данного типа используются средства из библиотеки Abstract Window To olkit (АWТ) для предоставления графическо го пользовательского интерфейса (ГПИ, если та· кой интерфейс вообще используется) . Этот тип аплетов доступен с самого начала существо вания Java. Второй тип аплетов основывается на классе JApp let из библиотеки Swi ng. В аплетах типа Swing применяются классы из библиотеки Swing для построения ГПИ. Библиотека Swing предоставляет более богатый и зачастую более простой в употреблении ГПИ, чем библиотека АWТ. Поэтому аплеты, построенные на ос­ нове библиотеки Swing, наиболее распространены в настоящее время. Но и тра­ диционные аплеты, построенные на основе библиотеки АWТ, по-прежнему при­ меняются, особенно когда требуется построить очень простой пользовательский интерфейс. Поэтому применение аплетов, построенных на основе обеих библио­ тек АWТ и Swing, вполне обосновано . В этой главе описываются аплеты, создаваемые на основе библиотеки АWТ. Но поскольку класс JApp let наследует от класса Applet, то все средства последнего до­ сrупны и в классе JApp let. Следовательно, бап:ьшая часть материала этой гл авы рас­ пространяется на оба упомянутых выше типа аплетов. И даже если вас интересуют только аплеты типа Swi ng, то вам все равно будет полезно и даже нужно ознакомить­ ся с мате риалом этой гл авы. Следует, однако, иметь в виду, что создание аплетов на основе библиотеки Swing связано с рядом дополнительных ограниче ний, кото­ рые будут обсуждаться далее в этой книге, когда пойдет речь о библиотеке Swing. На заметку! Подробнее о построении аплетов на основе библиотеки Swing см. в гл аве 31.
83' Часть 11. Б иблиотека Java О с нов ы р азработки аплето в В гл аве 13 была рассмотрена общая форма аплета , а также действия , которые требуется предпринять для его компиляции и запуска на выполнение. Прежде все­ го напомним вкратце основные положения об аплетах. Все классы аплетов являются производными от класса Applet. Аплеты не явля· ются самостоятельными программами, они выполняются в окне веб-браузера или средства просмотра аплетов. Иллюстрации, приведенные в этой гл аве , бьти полу· чены с помощью средства просмотра аплетов, которое называется appletviewer и входит в комплект JDК. Выполнение аплета не начинается с метода ma in (). На самом деле лишь немно­ гие аплеты имеют метод ma in ().Вместо этого запуск аплета и управление его вы· полнением осуществляется с помощью совершенно другого, описываемого ниже механизма. Кроме того , вьmод дан ных в окно аплета не осуществляется методом System. out . println (). Вместо этого в аплетах типа АWГ вывод осущесталяется различными методами из библиотеки АWГ, в том числе методом drawS tring (),ко­ торый выводит символьную строку в точку с указан ными координатами Х,У. Ввод данных в аплетах также осуществляется иначе , чем в консольных приложениях. Прежде чем использовать аплет, следует выбрать стратегию его развертывания. Для этого имеются два основных подхода. Первый подход подразумевает примене­ ние сетевого пр оmО'Кола запуска приложений на ja va (Java Network Launch Protocol - JNLP). Та кой подход обеспечивает больше удобств, особенно в отношении функцио­ нально усовершенствованных приложений для Интернета. Для разработки реальных аплетов сетевой протокол JNLP зачастую является наилучшим выбором, но подроб­ ное обсуждение этого протокола выходит за рамки данной книги. (Подроб нее о сете­ вом протоколе JNLP можно узнать из документации на комплект JDК.) Правда, про­ токол JNLP для примеров аплетов, представленных в этой гл аве, не требуется. Второй подход к развертыванию аплетов подразумевает его определение непо­ средственно в НТМL-файле, не прибегая к сетевому протоколу JNLP. Это перво­ начальный способ запуска аплетов, который применялся с момента создания jаvа и до сих пор употребляется для запуска простых аплетов. Кроме того, благодаря унаследованной простоте этот подход применяется в примерах аплетов, представ· ленных в данной книге . На момент написания данной книги корпорация Oracle рекомендовала дескриптор APPLET, поэтому в данной книге используется именно он. (Однако в настоящее время применение дескриптора APPLET не рекомендуется по спецификации HTML. Альтернативой ему служит дескриптор OB JECT. Поэтому за последними рекомендациями в этом отношении имеет смысл обратиться к до­ кументации на комплект JDK.) Когда в НТМL-файле встречается дескриптор APPLET, веб-браузер, поддерживающийjаvа, выполняет указанный аплет. Использощние дескриптора APPLET дает еще одно преимущество при разработке аплетов, поскольку он позволяет легко просматривать и проверять аплет. Для этого достаточно ввести комментарий, содержащий дескриптор APPLET, в заголовок файла исходного кодаjаvа. Та ким образом, исходный код будет документирован элементами НТМL-разметки, необходимыми аплету, и это дает возможность проверить скомпи· лированный аплет, запустив средство просмотра аплетов с заданным файлом исход· ного кодаJаvа. Ниже при веден пример такого комментария.
/* */ <applet code="MyApplet" width=2 00 height=60> </applet> Гл ава 23. Класс AppLet 835 Этот комментарий включает дескриптор APPLET, который запускает аrtлет MyApp let в окне размером 200 пикселей в ширину и 60 пикселей в высоту. Включение команды APPLET облегчает проверку аrtлетов, поэтому все аrtлеты, примеры которых представлены в этой книге, будуr содержать соответствующий дескриптор APPLET, введенный в комментарий. На заметку! Ка к отмечалось в главе 13, начиная с обновления 21 версии Java 7 аплеты Java должны быть подп исаны для предотвращения предупрежден ий о нарушении защиты при их выполнении в браузере. На самом деле выполнение аплета может быть иногда запрещено. Аплеты, которые сохра няются в локал ьной файловой системе, например, после компиляции примеров из данной книги, особенно чувствител ьны к подобному изменению. Та к, для выполнения локального аnле­ та в браузере, возможно, потреб�тся настройка пара мефОВ безопасности на панели управле­ ния Java. На момент написания данной книги ко мпания Oracle рекомендовала пользоваться не локальными аплетами, а аметами, выпол няемыми через веб-сервер. Более того, выполнение неподп исанных локал ьных аnлетов может быть заблокировано в будУЩем. В общем, подписа­ ние аплетов, которые будут распространяться через Интернет, например дnя коммерческого применения, безусловно необходимо. Обсуждение принципов и методик подписания аплетов (и других видов прикладных программ на Java) выходит за рамки да нной книги. Впрочем, обшир­ ную информацию по данному вопросу можно найти на веб-азйте компании Oracle. И наконец, как упоминалось ранее, простейший способ опробовать представленные эдесь примеры апле­ тов - воспользоваться средством их просмотра ap p letviewer. Класс App let В этом классе определяКУГСЯ методы, перечисленные в табл. 23. 1.Класс App let обе­ спечивает всю необходимую поддержку для вьmолнения аплета, в том числе для его запуска и остановки. Кроме того, этот класс предоставляет методы для загрузки и вос­ произведения графических изображений, а таюке методы для загрузки и воспроизве­ дения аудиоклипов. Класс App let расширяет класс Panel из библиотеки АWГ. В свою очередь, класс Pane l расширяет класс Container, а тот - класс Component. Все эти классы обеспечивают поддержку графического оконного интерфейса на базе Java. Краткий обзор библиотеки АWГ представлен в последующих гл авах. Табл ица 23.1. Методы из класса Ap p let Метод Описание void ci.stroy () Вызывается брауэером непосредсrвенно перед уничтожением аплета. Эrот метод переопределяется в аплете, если он нужда­ ется в выполнении ряда дейсrвий по очистке перед уничгоже­ нием аWiета AcoessiЬleContext Возвращает кшпексr доступности для вызывающего объекта getAcoessiЬleContext () Ap p letcontext Возвращает копrексr, связанный с аплетом get.Ap p letcontext ()
836 Часть 11. Библиоте ка Java Метод Strin9 getAp p letinfo () AudioClip C]8t:AudioClip (ORL uтl) AudioClip getAudioClip (ORL url, String имя_К!lипа ) ORL getCodeВa88 () URL getDocwмntвase () Ill la ge getll lla ge(ORL url, String имя_wображения) Locale qetLocale () Strin9 getparU1&ter (Strin9 имя_парамепtра ) Strinq [] [] getparU1&terI�o О void init () Ьo o lean illActive () Ьoolean isValidateRoot ( ) static final AudioClip newAudioClip (ORL uтl) void play (ORL uтl) void play (ORL url, Strinq UМЯ_ Кl l ипа) void resize (Diиlension �) Продал.:ж:енштаФ�. 23.1 Описание Переопределенные версии этого метода должны возвращать символьную строку, описывающую аплет. Реализация этого ме­ тода по умолчанию возвращает пустое значение null Возвращает объект типа AudioClip, инкапсул ирующий аудио­ клип, находящийся в месrе, обозначаемом параметром uit Возвращает объект типa Audi oClip, инкапсулирующий ауди· оклип, находящийся в месrе, обозначаемом параметром uit, и имеющим указанное имя_ Кl l WШ Возвращает URL, связанный с вызывающим аплетом Возвращает URL, указывающий на НТМL-документ, вызываю­ щий аплет Возвращает объект типа I11 1a 9e, инкапсулирующий графиче­ ское изображение, находящееся в месrе, обозначаемом параме­ тром иrl Возвращает объект типа I11 1a 9e, инкапсулирующий графиче­ ское изображение, находящееся в месте, обозначаемом параме­ тромurlи имеющимуказанноеимя_изображг нш� Возвращает объект типа Locale, используемый ра3Л ичными классами и методами, учитывающими региональные настройки Возвращает параметр по указанному и.мени_ пора.мет ро. Если указанный параметр не найден, то возвращается значение пу· croenull Метод, переопределяющий данный метод, должен возвращать таблицу объектов типа String, описывающую параметры , распознаваемые аплетом. Каждая запись в таблице должна со­ стоять из трех символьных строк, содержащих имя параметра, описание его типа и/или пределы его значений, а также все необходимые пояснения. Реализация данного метода по умол· чанию возвращает пусrое значение null Вызывается при запуске аплета на выполнение. Это первый ме­ тод, вызываемый при выполнении аплета Возвращает логическое значение true, если аплет запущен на выполнение. Возвращает логическое значение false, если выполнение аплета остановлено Возвращает логическое значение true, указывающее на то, что аплет проверяет корневой каталог Возвращает объект типа AudioClip, инкапсулирующий аудио­ клип, находящийся в месте, обозначаемом параметром url Этаr метод похож на метод get:AudioClip () , за исключением того, что он является статическим и может быть выполнен без объ­ екта типа Ap p let Если аудиоклип найден в месте, обозначаемом параметром un, то он выполняется Если ауди оклип , имеющий указанное имя_КJШnа, найден в ме­ сте, обозначаемом параметром url, то он выполняется Изменяет указанные � аплета, где Diиlension - это класс, входящий в состав пакета java . awt. Он содержит два целочис· ленных поля: width и height
Метод void resize (int tи ири на,intвьихта а ) final void setStuЬ (Ap p letStuЬ заглушка) void showStatus ( Strinq строка) void start () void stop() Описание Гл ава 23. Класс AppLet 837 Окон'IGние табл. 23. 1 Изменяет размеры аплета, обозначаемые параметрами ширrта ивьиоmа Устанавливает заданную �для аплета. Этот метод при· меняеrся исполняющей системой и никоrда не вызываеrся не­ посредственно из аплеrа. Заглушка - это небольшой фрагмент кода, обеспечивающего связь аплета с браузером Выводит указанную стртсу в строке состояния окна браузера или средства просмотра аплетов. Если же в окне браузера не поддерживается строка состояния, то ничего не происходит Вызывается браузером, когда аплет должен начать (или возоб­ новить) выполнение. Автоматически вызывается после метода ini t О в начале работы аплета Вызывается браузером для приостановки выполнения аплета. Будучи останоаленным, аплет перезапускается в результате вы­ зова метода start () Структура аплето в Как правило , аnлет - это оконная программа с ГПИ, и поэтому его структура отличается от структуры консольных программ, примеры которых были представ­ лены в части 1 данной кн иги. Если у вас имеется некоторый опыт программирова­ н ия ГПИ, то при написании аплетов вы будете чувствовать себя , как дома. Если же у вас нет такого опыта, то вам придется ус воить некоторые понятия, которые поясняются ниже. Во-первых , аплеты управляются событиями. И хотя рассмотрение обработки событий придется отложить до следующей гл авы, важно хотя в общих чертах по­ нимать, каким образом управляемая событиями архитектура оказывает вл ияние на разработку аплетов. Аплет напоминает ряд служебных процедур , обрабатыва­ ющих прерывания, действуя по следующему принципу. Аплет ожидает до тех пор, пока не произойдет какое-нибудь событие. Исполняющая система извещает аплет о событии, вызывая обработчик событий, предоставляемый аплетом. Как только это произойдет, аплет должен предпринять соответствующие действия и немедлен­ но возвратить управление. И это очень важный момент. Как правило, аплет не дол­ жен входить в режим работы, в котором он будет уд ерживать управление в течение длительного периода времени. Вместо этого он должен выполнить определенные действия в ответ на происходящие события , а затем возвратить управление испол­ няю щей системе. В тех случаях, когда аплету требуется выполнить какое-нибудь по­ вторяющееся действие (например, отображать в окне прокручивающееся сообще­ ние) , следует запустить дополнительный поток исполнения. (Соответствующий пример будет представлен ниже.) Во-вторых, только пользователь инициирует вза­ имодействие с аплетом, но никак иначе ! Как известно, если в консольной програм­ ме требуется ввести данные, то сначала пользователю выводится соответствующее пр иглаш ение, а затем вызывается некоторый метод ввода, например readLine ( ) . Совсем иначе дело обстоит в аплетах . Пользователь взаимодействует с аплетами так и тогда, как и когда он пожелает. Подобные взаимодействия передаются аплету
838 Часть 11. Библиотека Java в виде событий, на которые тот должен отреагировать. Так , если пользователь щел­ кнет кнопкой мыши в окне аплета, передается событие от щелчка кнопкой мыши. Если пользователь нажимает клавишу, когда окно аплета имеет фокус ввода , то пе­ редается событие от нажатия клавиши. Как будет показано в последующих гл авах. аплеты могуг содержать различные элементы управления, в том числе нажимаемые кнопки и устанавливаемые флажки. Всякий раз, когда пользователь взаимоде йству­ ет с одним из этих элементов упрамения, происходят определенные события . Структуру аплетов понять трудн ее, чем структуру консольных программ, но Java позволяет сделать ее как можно более простой. Если у вас имеется некото­ рый опыт написания программ под Windows (или других ОС с ГПИ), то вам долж­ но быть известно, насколько пугающе может выглядеть эта исполняющая среда. К счастью, Java предоставляет в ваше распоряжение намного более ясный и по· пятый подход, которым можно омадеть гораздо быстрее. Скелет аплета Во всех аплетах, кроме самых тривиальных, переопределяются методы , обе­ спечивающие основные механизмы взаимодействия браузера или средства про­ смотра аплетов с самим аплетом и управляющие его выполнением. К их числу от­ носятся следую щие четь1ре метода: init () , start (), stop () и de stroy (). Он и применяются во всех аплетах и определяются в классе App let. Предоставля ются таюке реализации всех этих методов по умолчанию, но переопределять их не тре­ буется лишь в очень простых аплетах. И в аплетах, создаваемых на основе библиотеки АWТ ( т.е . тех, что обсуждаются в этой гл аве), нередко переопределяется метод paint (), определенный в классе Component из библиотеки АWТ. Эгот метод вызывается в том случае, если требует­ ся повторно вывести данные из аплета. (В аплетах, создаваемых на основе библио­ теки Swing, для решения этой задачи применяется другой механизм.) Все пять упо­ мянутых выше методов могуг быть собраны в приведенный ниже скелет аплета. 11 Скелет апле та import java .awt.*; import java . appl et .*; /* */ <applet code="AppletSkel" width=ЗOO height=lOO> </appl et> puЬlic class AppletSkel extends App let 11 Этот ме тод вызывается первым puЫ ic void init () { 11 инициализация /* Этот ме тод вызывается вторым, после ме тода init () . Вызывается также при перезапуске аплета . */ puЫic void start () { 11 нач ать или возобновить выполнение аплета 11 Этот ме тод вызывается при остановке аплета puЬl ic void stop() {
11 приостановить выполнение апле та Гл ава 23. Класс AppLet 839 /* Этот ме тод вызыв ается перед унич тожением аплета . Это последний выполняемый ме тод . */ puЫ ic void de stroy () { // выполни ть завершающи е действия // Этот метод вызывается , когда окно апле та // должно быт ь восстановлено . puЬlic void paint ( Graphics g) { // повторно воспроизвести содержимое окна Несмотря на то что этот шаблон практически ничего не делает, его можно от­ компилировать и запустить на выполнение. Если он запускается на выполнение в средстве просмотра аплетов appletviewer, то создает окно , показанное на рис. 23.1. На этом и последующих рисунках вид аплета в окне средства просмотра апле­ тов appletviewe r может отличаться в зависимости от конкретной исполняющей ср еды. С целью проиллюстрировать этот факт для получения моментал ьных сним­ ков экрана были выбраны разные исполняющие среды . Applet Applet sta rte d. Рис. 23 .1 . Окно, воспроизводи мое скелетом аплета Ин ициализац и я и п рекращение работы апп ета Важно понимать порядок вызова различных методов, представленных выше в скелете аплета. При запуске аплета на выполнение по порядку вызываются сле­ дующие методы: • init () • start () • paint () Когда выполнение аплета прекращается, по порядку вызываются следующие методы: • stop () • de stroy () Рассмотрим эти методы более подробно.
840 Ч асть 11. Библиотека Java Метод ini t () Это первый метод в цепочке вызовов. Именно в нем следует инициализировать переменные. Во время выполнения аплета этот метод вызывается лишь один раз. Метод start () После метода init () вызывается метод start (), который также вызывается для перезапуска аплета после его остановки. Если метод ini t ( ) вызывается лишь один раз при первоначальной загрузке аплета, то метод start () вызывается каж· дый раз, когда НТМL-документ, содержащий аплет, воспроизводится на экране. Следовательно, если пользователь покидает веб-страни цу, а затем возвращается обратно, аплет возобновляет каждый раз свою работу с метода start (). Метод paint () Метод paint ( ) вызывается всякий раз, когда выводимые из аплета данные долж· ны быть воспроизведены повторно. Эта ситуация возникает в нескольких случаях. Например , окно, в котором выполняется аплет, может бьпъ перекрыто другим окном, а затем вновь открыто. Или же окно аплета может быть свернуто, а затем восстановле­ но до нормальных размеров. Meтoд paint () вызывается также в начале выполне ния аплета. Но в любом случае его метод ра in t ( ) вызывается всякий раз, когда требуется повторно воспроизвести данные, выводимые из аплета. Метод paint () принимает один параметр типа Graphics. Этот параметр будет содержать графический кон· текст, описывающий графическую среду, в которой выполняется аплет. Такой кон· текст используется всякий раз, когда запрашивается вывод данных из аплета. Метод s top () Этот метод вызывается, когда веб-браузер покидает НТМL-документ, содержа· щий аплет, например, при переходе на другую страницу веб-сайта. Когда вызыва· ется метод stop (),аплет может еще работать. Поэтому метод stop () следует вы· звать для приостановки потоков исполнения , которые не должны выполняться, когда аплет недоступен. Эти потоки исполнения можно перезапустить, когда вы· зывается метод start (),если пользователь возвращается на страницу. Метод de stroy () Метод de s t roy ( ) вызывается, когда исполняющая среда определяет, что аплет должен быть полностью уд ален из оперативной памяти . В этот момент следует освободить все ресурсы, используемые аплетом. Вызову метода de stroy ( ) всегда предшествует вызов метода stop (). П ереоп ределение метода update () Иногда в аплете требуется переопределить еще один метод, определ енный в библиотеке АWГ и называемый upda te ( ) . Этот метод вызывается, когда аплет запрашивает повторное воспроизведение определенной части окна. В версии ме-
Гл ава 23. Кл асс дppLet 8'1 тода upda te {) по умолчанию просто вызывается метод paint {). Но метод up­ date {) можно переопределить таким образом, чтобы выполнить повторное вос­ произведение более изощренным способом. В общем, переопределение метода update ( ) - это специальный прием, который применяется не во всех аплетах, и поэтому в примерах аплетов из этой гл авы метод upda te ( ) не переопределяется. Простые методы воспроизведения аплетов Как упоминалось выше, аплеты воспроизводятся в окне, и для выполнения операций ввода-вывода аплеты, построенные на основе библиотеки АWГ, поль­ зуются ее средствами. Методы , процедуры и приемы, связанные с библиотекой АWГ, будут обсуждаться в последующих гл авах, но некоторые из них следует рас­ смотреть те перь, поскольку они понадобятся для разработки примеров аплетов. (Напомним, что аплеты , создаваемые на основе библиотеки Swing, будут рассма­ три ваться далее в этой книге.) Как пояснялось в гл аве 13, для вывода символьной строки из аплета служит ме­ тод draws tring {) из класса Graphics. Как правило, этот метод вызывается из ме­ тода upda te {) или paint {). Он имеет следующую общую форму: vo id drawString (String сообщеюrе , int х, int у) где параметр сообщение обозначает символьную строку, которая должна быть вы­ ведена, начиная с точки с заданными координатами х,у. В окнеJava левый верхний угол имеет координаты 0,0. Метод drawString {) не распознает символы перено­ са строки. Следовател ьно, если требуется начать вывод текста с новой строки , то это придется сделать явно, указав точные координаты Х,У точки , с которой дол­ жен начинаться вывод новой строки текста. Как будет показано в последующих гл авах, имеются приемы, позволяющие облегчить этот процесс. Для установки цвета фона в окне аплета служит метод setBackground {), а для установки цвета переднего плана (например, цвета выводимого на экран текста) - метод setForeground {). Эти методы определены в классе Component и имеют следующие общие формы : void setВackground (Color .новwi цве!I.') void setForeground (Color нo:вl lift =цве!I.') где параметр новый_ цв ет обозначает устанавливаемый новый цвет. В классе Со 1 о r определяются перечисленные ниже константы , которые могут быть использова­ н ы для указания цвета. Color .Ыack Color . 11 1a 9enta Color .Ыue Color . orange Color . cyan Color . pink Color . darkGray Color . red Color . gray Color . white Color . green Color . yellow Color .lightGray
8'2 Часть 11. Библиоте ка Java Определены также версии этих ко нстант в верхнем регистре . В следующем примере кода устанавливается зеленый цвет фона и красный цвет текста: setBac kground (Color . green) ; setForeground (Color .red) ; Подходящим местом для установки цветов заднего плана (т.е. фона) и передне­ го плана является метод init ( ) . Впрочем, во время выполнения аплета эти цвета можно изменять как угодно часто. Для того чтобы получить текущие установки цветов переднего и заднего ruiaнa, достаточно вызвать методы getBackground () и getForeground ( ) соответствен· но. Они также определены в ICJiacce Component, а их общие формы приведены ниже. Color getвacltqround () Color getForeqround () Рассмотрим пример очень простого аплета, где в качестве цвета фона уста· навливается голубой, а в качестве цвета переднего плана - красный. Далее вы· водится сообщение, иллюстрирующее порядок вызова методов init (), star t () и paint ( ) при запуске аплета на выполнение. Ниже приведен исходный код из этого примера аплета. /* Простой апле т, устанавливающий цвета фона и переднего плана и выводящий симв ольную строку */ import java . awt.*; import java . applet .*; /* */ <applet code="Sample " width=ЗOO height=50> </applet> puЬlic class Sample extends Applet{ String ms g; // установить цвета фона и переднего плана puЫic void init () { setBac kground (Color . cyan) ; setForeground (Color .red) ; msg = "Inside init( ) --"; // инициализировать выводимую симв оль ную строку puЫic void start () { msg += " Inside start () --" ; 11 вывести симв оль ную строку шsq в окне апле та puЫ ic void paint ( Graphics g) { msg += " Inside paint() ."; g.drawString (msg, 10, 30) ; Этот аплет создает окно , показанное на рис. 23.2 . Методы stop () и de stroy () не переопределены, поскольку в таком простом аплете это не требуется.
Applet Гл ава 23. Кл асс AppLet 8'3 t 1s1de 1t1it() r�ide stan() - 1 �1ue р fntO Applet star1ed. Рмс. 23.2 . Окно аплета , в кото ром выводится символ ьная строка Запрос на повто р ное вос произведение Запомните гл авное правило : аплет выводит дан ные в свое окно только тогда, когда его метод upda te () или paint () вызывается из библиотеки АWТ. В связи с этим возн икает следующий интересный вопрос: каким образом аплет может инициировать собственное обновление, когда в нем изменяется информация? Так, если аплет выводит движущийся баннер (т.е. крупный заголовок) , то какой механизм вынудит аплет обновлять окно на каждом шаге прокрутки этого банне­ ра? Напомним, что одно из самых важных ограничений, накладываемых на аплет, состоит в том, что он должен быстро возвращать управление исполняющей систе­ ме . Например, аплет не может создавать в теле метода paint () циклы, в которых будет непрерывно прокручиваться баннер. Это помешало бы передаче управления обратно библиотеке АWТ. Имея такое ограничение, можно подумать, что вывод данных в окно аплета, как минимум, существенно затруднен. К счастью, это не так. Всякий раз, когда аплет нуждается в обновлении отображаемой в его окне инфор­ мации, он просто вызывает метод repaint (). Метод repaint ( ) определен в библиотеке АWТ. Он вынуждает исполняющую систему библиотеки АWТ осуществлять вызов метода update ( ) из аплета, который в реализации по умолчанию обращается к методу paint ( ) . Так им образом, чтобы вывод данных можно было выполнять из друтой части аплета в его окно , достаточ­ но сохранить выводимые дан ные и вызвать метод repaint (). Затем библиотека АWГ вьшолнит вызов метода ра in t ( ) , который может воспроизвести сохраненные данные. Та к, если часть аплета нуждается в выводе символьной строки , ее можно сначала сохранить в переменной типа String, а затем вызвать метод repaint (). В методе paint () символьная строка выводится с помощью метода drawString (). У метода repaint ( ) имеются четыре общие формы. Рассмотрим их по поряд­ ку. В следующей общей форме определяется область, подлежащая повторному вос­ произведению: void repaint (int слева , int сверху , int llO«plUllJ , int .liWCO!l'a) Здесь координаты правого верхнего утла области обозначаются параметрами сл ева и св ерху, а ширина и высота области - параметрами ширина и высота. Эти размеры указаны в пикселях. Указывая область для повторного воспроизведения, можно сэкономить время , которое требуется для обновления окна. Если же потре­ буется обновить только небольшую часть окна, то эффективнее будет обновить только эту конкретную область, а не всю ruющадь окна.
8'' Часть 11. Библиоте ка Java Вызов метода repaint {), п о существу, означает запрос аплета на скорейшее обновление. Но если система работает медленно или занята, то метод update () может и не быть вызван немедленно. Множественные запросы на повторное вое· произведение, поступающие за короткий период времени, моrут быть не обрабо­ та ны библиотекой АWГ, а следовательно, метод upda te { ) будет вызываться лишь время от времени. Это может стать серьезной проблемой во многих ситуациях, включая воспроизведение анимации, когда большое значение имеет время обнов· ления. Одн им из решений этой проблемы может стать использование следующих форм метода repaint ( ) : void repaint (lonq нажсянальна• sадержха ) void repaint (lonq нажсянальна• - sадвржжа , int х, int у, int .ихр.1 1Н а, int JIUCO�a) 1·де параметр ма ксимальна я_ эадержка обозначает макс имальное кол и чество миллисекунд, которые моrут пройти, прежде чем будет вызван метод upda te (). Следует, однако , иметь в виду, что если время истечет прежде , чем системе удаст­ ся вызвать метод upda te {),этот метод не будет вызван. Никакого возвращаемого значения или передаваемого исключения не появится, поэтому следует соблюдать особую осторожность. На заметку! Существует возможность вывода да нных в окно аплета другими метода ми, кроме pa int {) или upda te (). Дл я вывода в окно та кой метод должен получить графический конте кст, вызвав метод getGraph ics (), определенный в классе Compo nent, а затем испол ьзовать этот контекст для вы вода да нных в окно. Но для бол ьшинства п риложений лучше и проще направлять вывод да нных в окно через метод paint () и вызывать метод repaint ().когда изменяется содержимое окна. П росто й аплет с б аннером Чтобы продемонстрировать метод repaint {), обратимся к простому приме ру аплета, воспроизводящего баннер. Этот аплет должен прокручивать выводимое в виде баннера сообщение слева направо в своем окне. Прокрутка такого сообще­ ния поперек окна является повторяющейся операцией, поэтому она должн а вы· полняться в отдельном потоке , создаваемом при инициализации аплета . Ниже приведен исходный код аплета, воспроизводящего баннер. /* Простой апле т, воспроизводящий баннер */ Этот аnле т создает поток исполнения , прокручив ающий сообщение , содержаще еся в переменной шsq , справа налево в окне аплета . import java.awt.*; import java . applet .*; /* */ <applet code= "Simp leBaппe r" width=ЗOO height=S O> </applet> puЫic class SimpleBanner extends Applet implements RunnaЬle { St ring ms g = "АSimp le Moving Banne r ."; // Простой движущийся баннер Thread t = null; int state ;
vol atile boolean stopFlag; Гл ава 23. Класс Apptet 8'5 11 ус тан овить цв ета и инициализировать поток исполнения puЫic void init () ( setBackground (Color . cyan) ; setForeground (Color .red ) ; 11 запус тить поток исполнения puЫic void start () ( } t = new Thread (this ); stopFlag = false; t. start() ; 11 точка входа в поток исполнения, прокручивающий баннер puЫic void run() { } 11 воспроизвести баннер повторно for(;;){ try { repaint () ; Thread .sleep (250) ; if (stopFlag) break; } catch (InterruptedException е) { } } 11 приостановить воспроизведение баннера puЫic void stop () ( stopFlag = true ; t = null; 1 11 воспроизвести баннер puЫic void paint ( Graphics g) { char ch ; ch = msg.charAt (O); ms g = msg . substring (l, ms g.length() ); msg += ch; g.drawS tring (msg, 50, 30} ; На рис. 23.3 показано окно этого аплета, воспроизводящего баннер . Applet Applet started. Рис. 23.З . Окно аплета с прокруткой сообщения в виде баннера Рассмотрим подробнее, каким образом действует этот аплет. Прежде всего обра­ rnте внимание на то , что класс SimpleBanner расширяет класс Applet, как и следо­ вало ожидать. Но, кроме того, он реализует интерфейс RunnaЫ e. Это необходимо
8'6 Часть 11. Библиоте ка Java для создания в аплете второго потока исполнения, в котором должен прокручивать· ся баннер. В методе init () устанавливаются цвета фона и переднего плана аплета. После инициализации исполняющая система вызывает метод start ( ) для за· пуска аплета на выполнение. В методе start () создается новый поток испол· пения , который присваивается переменной t типа Thread. Затем переменной stopFlag типа boolean, которая управляет выполнением аплета, присваива ется логическое значение false. После этого вызывается метод t.start ( ) для запу­ ска потока исполнения. Напомним, что по ссылке t.start () вызывается метод start (),определенный в классе Thread, а следовательно, начинает выполняться метод run ().Иэто не приводит к вызову варианта метода start () , определенно­ го в классе Ap p let. Ведь это два отдел ьных метода. В методе run ( ) вызывается метод repaint (). Это, в свою очередь, приводит к вызову метода paint (),ав ко нечном итоге - к прокрутке содержимого перемен· ной rnsg. В промежугке между последовательными шагами цикла выполнение метода run ( ) приостанавливается на четверть секунды. Та ким образом, содержимое пере­ менной rnsg прокручивается слева направо в непрерывном движении. Переменная stopFlag проверяется на каждом шаге цикла. Когда она принимает логическое зна· чение true, выполнение цикла в частности и метода run () вообще прерывается. Если браузер воспроизводит аплет во время просмотра новой страницы, вызы· вается метод stop (),вкотором переменной stopFlag присваивается логическое значение true , тем самым прерывая выполнение метода run (). Этот механизм служит для остановки потока исполнения , когда страница больше не просматри· вается. Когда же аплет снова становится видимым, опять вызывается его метод start (),который запускает новый поток исполнения для прокрутки баннера. П рименение ст р оки со стоя ния Кроме воспроизведения информации в своем окне, аплет может также выво­ дить сообщения в строке состояния браузера или средства просмотра аплетов, в котором он запускается. Для этого достаточно вызвать метод showS tatu s ( ) со строкой сообщения , которое требуется вывести. Строка состояния служит уд обным местом для уведомления пользователя о то м, что происходит в аплете, для отображения параметров или вывода сообщения о некоторых видах ошибок. Строка состояния служит также отличным инструментом отладки , поскольку она предоставляет простой способ для вывода информации об аплете. В следующем примере аплета демонстрируется применение метода show Status () для вывода сообщения в строке состояния: 11 Исполь зовать строку состояния в окне апле та import java .aw t.*; import java . applet .*; /* */ <appl et code= "StatusWi ndow " width=ЗOO height=50> </appl et> puЫic class StatusWindow extends Applet puЬlic void init () { setBackground (Color . cyan) ;
Гл ава 23. Кл асс AppLet 8'7 11 вывести сообщение в окне аплета и в строке состояния puЫic void paint ( Grap hics g) { g.drawString ("This is in the applet window.", 10, 20) ; showS tatus ("Thi s is shown in the status windo w. ") ; Окно аплета с выводимыми сообщениями показано на рис. 23.4. Th is is in the applet wi ndow. This is shown in the status window. Рис. 23 ..4. Окно а nл ета со строкой состоя ния НТМ L-дескр ипто р APPLET Как упоминалось ранее, на момент написания данной кн иги компания Oracle рекомендовала употреблять дескриптор APPLET для запуска аплета вручную, если сетевой протокол JNLP не применяется. Средство просмотра аплетов выполнит каждый дескриптор APPLET, который оно обнаружит, в отдел ьном окне, в то время как веб-браузеры позволяют выполнять многие аплеты на одной странице. В рас­ смотренных до сих пор примерах аплетов употреблялась лишь упрощенная форма дескриптора АРPLET. А теперь пришло время рассмотреть его более основательно. Ниже приведен синтакс ис полной формы дескриптора APPLET. Элементы, за­ ключенные в квадратные скобки , являются необязательными. <APPLET > [ CODEВASE • t1RL жодоао:i бa.ur] CODE • ф.-л ап"Л ета - [ALT = ал�т еРнат•.&rаDi тежст) [NАМЕ " •юr вжsеиптrра аплета) WIDTH = n•iceлei i HE IGHT • n•жceлeil [ALIGN•Sl lp& SНJIBaюte] [VSPACE = n•xceлei i ] [HSPACE = mrжceлd] (<РАRАМ NАМЕ = •юr •!1'.Р•бута VALUi: " .sнa veюre •!1'.Р•бута>) [<РАRАМ NАМЕ • •юr:а!1'р11 1 бута2 VALUE • .sна vеюrё_а!l'р•бута>] [НТНL-ра.эиет.к:а , отображаемая в отсутстаJlе Java ] </APPLF.T> Рассмотрим каждую составляющую этого дескриптора по отдел ьности . • CODEВASE - необязательный атрибут, задающий URL кодовой базы аплета, т. е. каталог для поиска исполняемого файла класса, обозначаемого дескрип­ то ром CODE. Если атрибут CODEBASE не задан явно, в качестве этого атрибута используется URL, указывающий на каталог текущего НТМL-документа.
8'8 Часть 11. Библиотека Java • CODE - обязательный атрибуr, задающий имя файла с расширением . class, содержащего скомпилированный код аплета. Имя этого файла задается от­ носительно URL кодовой базы аплета, т. е . того каталога, где находится теку­ щий НТМL-документ, или каталога, указанного в атрибуrе CODEBASE. • ALT - необязательный атрибуr, используемый для указания краткого тексто­ вого сообщения , которое должно быть отображено, если браузер распозна­ ет дескриптор APPLET, но в данный момент не сможет выполнять аплеты Java. Это делается иначе, чем в альтернати вном коде HTML, который предо­ ставляется для браузеров, вообще не поддерживающих аплеты . • NАМЕ - необязательный атрибуr, используемый для указания имени экземпля­ ра аплета. Аплеты должны именоваться таким образом, чтобы другие аплеты на той же самой странице могли находить их по именам и взаимодействовать с ними. Для получения аплета по имени служит метод getApplet (),опреде­ ляемый в интерфейсе Ap pletContext. • WIDTH и НEIGHT - обязател ьные атрибуrы , задающие размеры (в пикселях) отображаемой области аплета. • ALIGN - необязательный атрибуr, задающий выравнивание аплета. Этот атрибуr трактуется точно так же, как и в НТМL-дескрипторе IMG. Он имеет следующие значения: LE FT . RIGHT. ТОР. ваттом, MIDDLE, BASEL INE, ТЕХТТОР. ABSMI DDLE и AB S BOTTOM. • VSPACE и HSPACE - необязател ьные атрибуты. Атрибуr VS PACE определяет пространство в пикселях выше и ниже области , занимаемой аплетом, тогда как атрибуr HSPAC E - пространство в пикселях по бокам от области , зани­ маемой аплетом. Они интерпретируются таким же образом, как и атрибуты VS PACE и HS PAC E дескриптора IMG. • РАRАН NАМЕ и VALUE - дескриптор РАRАМ позволяет указать характерные для аплета аргументы. Аплеты получают доступ к этим атрибутам с помо­ щью метода getParame tr (). Среди прочих допустимых атрибуrов следует также упомянуrь атрибуr ARCHIVE, позволяющий задать один или несколько архивных файлов, а также атрибуr OBJECT, обозначающий сохраняемую версию аплета. В общем, дескриптор APPLET должен включать только атрибут CODE ил и OBJECT, но не оба атрибута сразу. Передача параметров аплетам Как упоминалось выше, НТМL-дескриптор APPLET позволяет передавать пара­ метры аплету. Для извлечения параметра служит метод getParameter ().Он воз­ вращает значение указанного параметра в форме символьной строки. Та ким обра­ зо м, строковые представления числовых и логических значений придется преоб­ разовывать во внутренние форматы. Ниже приведен пример, демонстрирующий передачу параметров аплету. // Испол ьзовать параме тры, передаваемые апле ту import java .awt.*; import java . applet .*; /*
*/ <applet code="Pa ramDemo " width=ЗOO height=BO> <param name= fontName value=Courier> <param name= fontSize value=l4> <param name=leading value=2> <param name=accountEnaЫed value=true> </applet> Гл ава 23. Кл асс AppLet 849 puЫ ic class ParamDemo extends App let { St ring fontName ; int fontSi ze; float leading; boolean active ; 11 инициализировать выводимую символь ную строку puЫ ic void start () { String param; fontName = getParameter ("fontName") ; if ( fontName == null ) fontName = "Not Found" ; // не найдено param = getParame t er ("fontSize") ; try { if (param != null) fontSize = Integer .parseint (param) ; else fontSi ze = О; catch ( NumЬerFormatException е) { fontSize = -1; param = getParame te r("leading" ); try { if (param != null) leading = Float . valueOf ( param) .floatValue () ; else leading = О; catch ( NumЬerFormatException е) { leading = -1; param = getParame te r("accountEnaЫed" ); if (param != nu ll) active = Boolean . valueOf { param) .booleanValue (); 11 вывести параме тры puЫic vo id paint ( Graphics g) { g.drawString ("Font name : "+fontName , О, 10) ; // шрифт g.drawString ("Font size : "+fontSize, О, 26) ; // кегль g.drawString ("Leading : "+leading, О, 42 ) ; // интерлинь яж g.drawString ( "Account Active : "+active , О, 58) ; // активная // уче тная запись Пример вывода параметров в окне данного аШJета показан на рис. 23.5. Как следует из приведенного выше примера аШJета, следует проверять значе­ н ия, возвращаемые методом ge tParame ter (). Если параметр недоступен, метод getParameter ( ) возвратит пустое значение null. Должна быть также предпри­ нята попытка преобразования в числовые типы в блоке оператора try, где пере-
850 Ч асть 11. Библиоте ка Java хватывается исключение типа N umЬ erForrnatException. В aIUieтax вообще нельзя допускать появление необработанных исключений. Applet Font name: Courier Font slze : 14 Le ading: 2.0 ccount Active : tru e Ap plet star1ed. Рис. 23 .5 . Окно аплета , принимающего указанные параметры Усоверш енствование аплета, воспроизводящего баннер Параметр можно использовать для ус овершенствования рассмотренного ранее примера aIUieтa, воспроизводящего баннер. В предыдущей версии данного аплета сообщен ие, выводимое в виде баннера, было жестко закодировано. Передача со­ общения в виде параметра позволяет аплету, воспроизводящему баннер, отобра­ жать разн ые сообщения при каждом выполнении. Ниже приведена его усовершен­ ствованная версия. Обратите внимание на дескриптор APPLET в начале исходного кода дан ного аплета, где те перь определяется параметр rne ssage , связанный с за­ ключенной в кавычки символьной строкой. 11 Параме тризованный баннер import java.awt . • ; import java . applet .•; /• •/ Вывод сообщения "Java ожи вляет веб!" <applet code= "Pa ramвanne r" width=ЗOO heigh t=SO> <param name =me ssage value= " Java ma kes the Web move !"> </applet> puЫ ic class ParamBanner extends Applet implements RunnaЫ e { String msg ; Thread t = null; int state ; vol atile boolean stopFl ag; // установить цвета и инициализировать поток исполнения puЫ ic void init () { setBackground (Color . cyan) ; setForeground (Color .red); 11 запус тить поток исполнения puЫ ic void start () { msg = getParame ter("message" ); if (msg == null) ms g = "Message not found. "; // сообщение 11 не найдено
msg=""+msg; t = new Thread (this) ; stopFlag = false; t. start () ; Гл ава 23. Класс App�et 851 11 точка входа в поток исполнения, воспроизводящего Оаннер puЬlic void run() { 11 воспроизвести Оаннер пов торно for(;;){ try { repaint () ; Thread .sleep (250) ; if { stopFlag ) break; catch ( InterruptedException е) { } 11 приостановить воспроизведение Оаннера puЬlic void stop{) { stopFlag = true ; t = null; 11 воспроизвести Оаннер puЫ ic void paint ( Graphics g) { char ch; ch = msg.charAt (O); msg = ms g.substring (l, ms g.length () ); msg += ch; g.drawString (msg, 50, 30) ; Методы getDocumentвase () и getCodeBase () Нередко приходится создавать аплеты , которые должны явным образом за­ гружать мульти медийные данные и текст. Аплетам в Java разрешается загружать да нные из каталога, содержащего НТМL-файл, который запускает аплет (базу д� ку.ментов) , а также из каталога, из которого загружается класс аплета (кодовую ба3у) . Эти каталоги возвращаются как объекты класса URL (см. гл аву 22) из методов get­ DocumentBase () и getCodeBase (). Они моrут быть соединены с символьной строкой , содержащей имя файла, который требуется загрузить. Для того чтобы загрузить другой файл , достаточно вызвать метод showDocument (), определяе­ мый в интерфейсе App letContext, который рассматривается в следующем разде­ ле. Применение рассматриваемых здесь методов демонстрируется в приведенном ниже примере аплета. // ОтоОразить Оазу документов и кодовую базу import java.aw t.*; import java . appl et .*; import java .net .*; /* <applet code= "Ba ses " width=ЗOO height=50>
852 Часть 11. Библиоте ка Java </applet> */ puЫ ic class Ba ses extends Applet // вывести в окне аплета базу до куме нтов и кодовую базу puЫic void paint ( Graphics g) { String ms g; URL url = getCodeBase () ; // получи ть кодовую базу msg = "Code ba se : "+url . toString () ; // кодовая база g.drawString (msg, 10, 20) ; url = getDocumentBase () ; // получить базу до куме нтов msg = "Document base : "+url . toString () ; // база до кумента g.drawString (msg, 10, 40) ; Примерный резул ьтат, выводимый при выполнении этого аплета, показан на рис. 23.6. Ap plt"Эt Code base: tile:/h:/java/ Doc ument base: file:/h:/j ava/B ases.java Ap plet sta rted. Рис. 23 .6. Окно аплета , отоб ража ю щего базу документов и кодовую базу И нте рфейс АррlеtСоntехt и метод showD ocume nt () Одной из областей приложения Java является применение активных изобра· жений и анимации для обеспечения графических средств навигации в веб как более интересный вариант, чем простые текстовые ссылки. Чтобы предоставить аплету возможность передавать управление другому URL, следует вызвать ме­ тод showDocume nt (), определяемый в интерфейсе AppletContext. Интерфейс AppletContext позволяет получать данные из исполняющей среды аплета. Методы, определяемые в интерфейсе AppletContext, перечислены в табл . 23.2 . Контекст аплета, выполняемого в текущий момент, можно получить, вызвав метод ge tAppletContext (), определяемый в классе Applet. Как только будет получен контекст аплета, можно сразу же отобразить другой документ, вызвав метод showDocument ().Этот метод не возвращает никакого зна· чения и не генерирует никаких исключений при неудач ном исходе своего выпол· пения, поэтому пользоваться им следует осторожно. Имеются две разновидности метода showDocume nt ().Вчастности, метод showDo cument (URL ) отображает доку· мент, находя щийся по указанному URL, а метод showDocument (URL , String ) -до-
Гл ава 23. Класс AppLet 853 кумент, находящийся в указан ном месте окна браузера. Достоверными значениями параметра где второй разновидности метода showDocument ()являются " _ se lf" (отобразить в текущем фрейме), " _parent " (отобразить в родительском фрейме), "_t ор " (отобразить в самом верхнем фрейме) и " _Ы ank" (отобразить в новом окне браузера) . Имеется также возможность задать имя документа, чтобы отобразить до­ кумент по его имени в новом окне браузера. Та бл ица 23.2. Методы из инте рфейса Appletcontext Метод Ap p let getAp p let ( Strinq имя_аплета) EnU1 118 ration<Ap p let> getAp p lets О AudioClip getAudioClip (URL url) Imaqe qetill l&q8 (URLll1'l) Inputstre&JD qetstream (String ICl l IOЧ) Iterator<Strinq> getStreaDIК8ys () void setstreaш (Strinq ключ , Inpu tstream поток) throws IOExoeption void ehowDOCW1 18D t(URL ll1'l) void showDocwaent (URL url, Strinq где) void ehowStatue (Strinq mtpor«J) Описание Возвращает аплет по указанному wceнu_m1 11ema , еслион находится в контексте текущего аплета, а иначе - пу­ стое значение null Возвращает перечисление, содержащее все аплеты из контекста текущего аплета Возвращает объект типа AudioClip, инкапсулирующий аудиокли п, находящийся в месте, обозначаемом пара­ метром иrl Возвращает объект типа !Jl la qe, инкапсулирующий графическое изображение, находящееся в месте, обо­ значаемом параметром url Возвращает поток ввода, с которым связан заданный кточ. Ключи привязываются к потокам ввода-вывода методом setStream () . Если же поток ввода, с которым связан задан ный ключ, отсугствует, то возвращается пустое значение null Возвращает итератор для ключей , связанных с вызыва­ ющим объектом. Ключи связаны с потоками ввода-вы­ вода. См. также методы qetstreaa () и eetstream () Связывает указанный поток ввода с заданным К/1ЮЧйМ. Ключ удаляется из вызывающего объекта, если пара­ метр поток принимает пустое значение null Огображает документ по URL, обозначаемому параме­ тром urL Этот метод может не поддерживаться сред­ ствами просмотра аплетов Огображает документ по URL, обозначаемому параме­ тром urL Этот метод может не поддерживаться сред­ ствами просмотра аплетов. Расположение документа определяется параметром где, как описано далее Огображает содержимое указанной строки в строке со­ стояния В приведенном ниже примере аплета демонстрируется применение интер­ фейса AppletCont ext и метода showDocument ().Когда этот аплет выполняется, он получает контекст текущего аплета и использует его для передачи управления файлу Test . html. Этот файл должен находиться в том же каталоге , что и аплет. Файл Test . html может содержать любой допустимый гипертекст.
85' Часть 11. Библиотека Java /* Использование конте кста аплета, ме тодов getCodeВase () и shovDoCU1 18D t() для отображения НТМL-докуме нта */ import java.awt.*; import java . applet .*; import java .net .*; /* */ <applet code= "ACDemo " width=ЗOO heigh t=SO> </appl et> puЫic class ACDemo extends Ap plet 1 puЫ ic void зtart {) { AppletContext ас = getAppletContext () ; URL url = getCodeBase (); // получ ить URL данного аплета try 1 ac .sh owDocument (new URL (url+"Test . html") ); catch (Mal formedURLException е) { зhowStatus ( "URL not found" ) ; // URL не найден И нтерфейс AudioClip В интерфейсе Au dioClip определяются следующие три метода: play { ) (воспроизведение аудиок.липа с начала) , stop ( ) (остановка воспроизведения) и loop {} (циклическое воспроизведение) . После загрузки аудиок.липа с методом getAudioClip ( ) эти методы можно применять для его воспроизведения. Интерфейс АррlеtStuЬ Интерфейс AppletStub предоставляет средства, с помощью которых взаимо­ действуют аплет и браузер (или средство просмотра аплетов) . Как правило, этот интерфейс не реализуется в прикладном коде. Консольный вывод Несмотря на то что вы вод данных в окно аплета должен осуществляться мето­ дами ГПИ, например drawS tring (),ваплетах можно организовать и консольный вывод, в частности, для целей отладки. Когда в аплете вызывается такой метод, как System. out .println (), выводимые им данные не направляются в окно апле· та. Вместо этого они выводятся на консоль, из которой было запущено средство просмотра аплетов, или же на консоль Java, доступную в некоторых браузерах. Пользоваться консольным выводом в других целях, кроме отладки , не рекоменду· ется , поскольку он нарушает принципы графического оформления пользователь· ского интерфейса, к которым уже привык.ли большинство пользователей аплетов.
24 Обработка событий Эта гл ава посвящена события м - важнейшему аспекту java. Обработка событий и меет решающее значение для всего программирования нajava в целом, поскольку она является неотьемлемой частью разработки аплетов и прочих видов приклад­ ных программ с графu'Ческuм палъзоватмъским интерфейсом (ГПИ). Как упоминалось в гл аве 23, аплеты представляют собой управляемые событиями прикладные про­ гр аммы, использующие ГПИ для взаимодействия с пользователем. Более того , любая прикладная программа с ГПИ, например, написанная нajava для Windows , выполняется под управлением событий. Иными словами, такую программу нельзя написать не имея ясного представления об обработке событий. События поддер­ живаются в целом ряде пакетов, включая java.util, jav a. awt и java . event. Большинство событий, на которые будет реагировать прикладная программа с ГПИ, происходят при взаимодействии пользователя с этой программой. Именно события такого рода и рассматриваются в этой гл аве . События передаются при­ кладной программе самыми разными способами, каждый из которых зависит от конкретного события. Существует несколько типов событий, включая ге нери­ руемые мышью, клавиатурой и различными эл ементами управления ГПИ, в том числе кнопками, полосами прокрутки или флажками. Эта гл ава начинается с обзора механизма управления событиями вjava. Затем рассматриваются основные классы и интерфейсы событий, используемые библи­ отекой АWГ, а также ряд примеров, демонстрирующих основы обработки собы­ тий. В этой гл аве поясняется также , как пользоваться классами адаптеров, вну­ тренними и анонимными вложенными классами, чтобы упростить код обработки событий. Рассматриваемые здесь приемы будут использоваться в примерах , при­ веде нных далее в данной книге. На заметку! В этой гл аве основное внимание уд еляется событиям, имеющим от ношение к про­ граммам с ГПИ. А иногда события та кже испол ьзуются в целях, не имеющих прямого отно­ шения к подоб ного рода программам. Но во всех случаях применяются одн и и те же приемы обработки событи й. Два механизма обр а ботки событий Прежде чем приступить к обсужде нию обработки событий, нужно сделать одно важное замечание. Способ обработки событий существенно изменился с момента
856 Ч асть 11. Библиотека Java выпуска первоначальной версииjаvа 1.0 до поямения более современных версий, начиная cjava 1.1 . Способ обработки событий, принятый в версии jаvа 1.0, под· держивается до сих пор, хотя и не рекомендован для употребления в новых про­ граммах. К тому же многие методы, поддерживающие старую модель обработки событий из версииjаvа 1.0, теперь объямены не рекомендованными к употребле­ нию. Современный подход состоит в том, что события должны обрабатываться в новых программах так, как описано в данной кн иге. Модель дел е ги рования событий Современный подход к обработке событий основан на модели делегирования ш бытий, определяющей стандартные и согласованные механизмы для генерирования и обработки событий. Принцип действия этой модели довольно прост: истач11.ик ге· нерирует событие и извещает о нем один или несколько прием 11.иков. В этой модели приемник просто ожидает до тех пор, пока не получит извещение о событии. Как только извещение о событии получено, приемник обрабатывает его и возвращает управление. Преимущество такой модели заключается в то м, что логика приклад­ ной программы, обрабатывающей события , четко отделена от логики пользователь­ ского интерфейса, извещающего об этом событии. Элемент пользовательского ин­ терфейса может делегировать обработку события отдельному фрагменту кода. В модели делегирования событий приемники должны регистрироваться ис· точником, чтобы получать извещения о событиях. Это дает следующее важное преимущество: уведомления посьтаются только тем приемникам , которым тре­ буется их получать. Это более эффективный способ обработки событий, чем то т, что бьт принят в первоначальной модели из версии jаvа 1.0 . Раньше извещение о с обытии распространялось по всей иерархии вложенности до тех пор, пока оно не бьто обработано каким-нибудь ко мпонентом. Это вынуждало все компоненты получать извещения о событиях, которые они могли и не обрабатывать, что при­ водило к напрасной трате времени. Модель делегирования событий исключила подобную расточительность. В последующих разделах рассматриваются отдел ьные события и поясняется роль источников и приемников в обработке событий. События В рассматриваемой здесь модели делегирования соб,ытие представляет собой объект, описывающий изменение состояния источника. Этот объект может быть создан в результате взаимодействия пользователя с элементом управления ГПИ. К событиям приводят такие действия, как щелчок на экранной кнопке , ввод сим· вола с клавиатуры, выбор эл емента из списка и щелчок кнопкой мыши. Имеется немало и других пользовательских операций, которые могут служить примерами для наступления событий. События могут происходить не только в результате прямого взаимодействия с пользовательским интерфейсом. Например, событие может произойти по исте­ чении времени срабатывания таймера, а также в результате превышения счетчи·
Гл ава 24. Обработка событи й 857 ком некоторого значения , программного ил и аппаратного сбоя или завершения некоторой операции. Имеется возможность определять и собственные события , отвечающие характеру прикладной программы. Источники событий Источ:ник - это объект, генерирующий событие. Событие происходит при из­ менении некоторым образом внутреннего состояния объекта. Источники могут генерировать события нескольких ти пов. Приемники должны быть зарегистрированы в источнике, чтобы получать из­ вещения о событиях соответствующего типа. У события каждого типа имеется свой метод регистрации. Его общая форма выглядит так: puЬ lic void addТJrnListener ( ТJrnListener прх енюrж_ собм!l'd) где Тип обозначает имя объекта события , а пр иемник_ событий - ссылку на кон­ кретный приемник событий. Например, метод, регистрирую щий приемник со­ бытий от клавиатуры, называется addKe yListener (), а метод, регистрирующий приемник событий от перемещения мыши, - addMouseMotionListener (). Когда наступает событие, все зарегистрированные приемники получают копию объекта события . Этот процесс называется групповой рассъикой событий. Но в любом слу­ чае уведомления отправляются только тем приемникам, которые зарегистрирова­ ны на их получение. Некоторые источники допускают регистрацию только одного приемника. Ниже приведена общая форма такого метода. puЫ ic void add 7XnListener ( ТJrnListener пржвюиr1t соб11!1'd) throws java.util . TooМanyLis tenersException - где Тип обозначает имя объекта события , а пр иемник_ событий - ссылку на кон­ кретн ый приемник событий. Когда такое событие наступает, зарегистрирован­ ный приемник получает уведо мление об этом событие. Этот процесс называется целевой рассылкой событий. Источник должен также предоставлять метод, позволяющий снять приемник с регистрации определенного типа событий. Общая форма этого метода следующая: puЬl ic void remove niпListener (ТJrnLis tener пр• енюrж_ собм!l'••> Здесь Тип обозначает имя объекта события, а пр иемник_ событий - ссылку на конкретный приемник событий. Например, чтобы снять с регистрации прием­ ник событий от клавиатур ы, следует вызвать метод removeKeyListener (). Методы, которые добавляют или уд аляют регистрируемые источники собы­ тий, предоставляются источником, генерирующим событие. Например, в классе Component предоставляются методы для добавления и уд аления регистрируемых приемников событий от клавиатуры и мыши. Приемники событий Прием:ник - это объект, уведомляемый о событии. К нему предъявляются два основных требования . Во-первых, он должен быть зарегистрирован одним или не-
858 Часть 11. Библиотека Java сколькими источниками событий, чтобы получать уведомления о событиях опре­ деленного типа. И во-вторых, он должен реализовать методы для получения и об­ работки таких уведомлений. Методы, принимающие и обрабатывающие события , определены в ряде ин­ терфейсов, входящих в состав пакета j ava . awt . event. Например, в интерфейсе MouseMotionListener определяются два метода для получения уведомлений о пе­ ретаскивании объекта или перемещении мыши. Любой объект может принимать и обрабатывать одно ил и оба этих события , если его класс предоставляет реали­ зацию дан ного интерфейса. В этой и последующих гл авах речь еще не раз пойдет о дан ном и ряде других интерфейсов приемников событий. Классы событий Классы, представляющие события, находятся в самой сердцевине механизма обработки событий вJava. Поэтому обсуждение обработки событий должно начи­ наться с классов событий. Следует, однако , иметь в виду, что в Java определяется несколько типов событий и что не все классы событий будут упомянуты в этой гла­ ве. Наиболее широко употребительными являются те события , которые опреде· лены в библиотеках АWТ и Swing. В этой гл аве основное внимание уделяется собы­ тиям из библиотеки АWТ. (Многие из них относятся также и к библиотеке Swi ng. ) Несколько характерных для библиотеки Swing событий будут описаны в гл аве 31, когда речь пойдет о библиотеке Swi ng. В основу иерархии классов событий вjava положен класс EventObj ect, вхо­ дя щий в пакет java . util. Он служит суперклассом для всех событий. Ниже при­ веден единственный конструктор этого класса, где источник обозначает объект, сгенерировавший событие. EventObj ect (OЬject •с�оVНJ1 1 ж) Класс EventObj ect содержит два метода: getSource () и toString (). Метод getSource () возвращает источник событий. Ниже приведена его общая форма. Как и следовало ожидать, метод toString () возвращает символьную строку, кото­ рая служит эквивалентом события . Ob ject qetSource () Класс AWTEvent, определенный в пакете jav a.awt , является производным от класса EventObj ect. Кроме того, класс AWTEvent является (прямо или косвен­ но) суперклассом для всех событий из библиотеки АWТ, применяемых в модели делегирования событий. С помощью его метода ge tID () можно определить ти п события. Его общая форма приведена ниже. int qetID () Классы всех остальных событий из библиотеки АWТ являются производными от класса AWTEvent. Итак, подводя итог, можно сказать следующее. • Класс Е ve n t ОЬ j е с t служит супер классом для классов всех событий. • Класс AWTEvent служит суперклассом для классов всех событий из библио­ теки АWТ, обрабатываемых по модели делегирования событий.
Глава 24. Обработка событий 859 В пакете j а va . а wt . е vent определяются многие типы событий, генерируемых различными элементами пользовательского интерфейса. В табл. 24. 1 перечисле­ ны наиболее употребительные классы событий вместе с кратким описанием при­ чин, по которым они наступают. Наиболее употребительные конструкторы и ме­ тоды каждого их этих классов описаны в последующих разделах. Табл ица 2,.1, Основные классы событи й из пакета java .awt . event Кnасс собьпмя Описание ActionEvent Насгупает после щелчка на кнопке, двойного щелчка на элементе спис­ ка или выбора пункта меню Mju•t:Jl l8n tвvent Насгупает при манипулировании полосой прокругки Col llpo nent:Event Насгупает, когда компонент скрывается , перемещается, изменяет свои размеры или становится досгупным Contai.ner:вvent Насгупает после добавления или уд аления компонента из контейнера Focudvent Насгупает, когда компонент получает или теряет фокус ввода с клавиатуры InputEvent Абстрактный суnеркласс для всех классов собьr r ий, связанных с вводом данных в компонентах It81 11Even t Насгупает после щелчка на флажке или элементе списка, а также на от­ мечаемом пункге меню МoueeEvent Насгупает при получении данных, введенных с клавиатуры Насгупает при перетаскиван ии, перемещении, щелчках, нажатии и от­ пускании кнопок мыши, а также в том случае, когда курсор мыши наво­ дится на компонент или отводится от него Мou•e'l lhe e l.Event Наступает при прокругке колесика мыши Text.Event Насгупает при изменении значения в текстовой области или тексто­ вом поле 1findowZve n t Насгупает, когда окно активизируется, деактивизируется, сворачивает­ ся, разворачивается или закрывается Клacc Ac tionEvent Событие типа Ac tionEvent генерируется после щелчка на кнопке, двойного щелчка на элементе списка ил и выбора пункта меню. В классе Ac tionEvent опре­ деляются следующие целочисленные константы , которые моrут быть использова­ ны для идентификации любых модификаторов, связанных с событием действия: ALT_МASK, CTRL_МASK, МЕТА_МАSК, SНIFT_МASK и ACT ION_PERFORME D. В классе Ac tionEvent имеются следующие конструкторы: ActionEvent (Obj ect яс!l'оvнжж , int !l'яn , String жонанда) ActionEvent (Object жс!l'оvнжж , int !l'жn , String жонанда , int JЮдж фжжа!l'орr) ActionEvent (Object жс!l'оvнжж , int !l'IUI , Strinq жонаида , lonq нонен!l' , int нодя фяжа!l'орм) где параметр исто чник обозначает ссылку на объект, сгенерировавший событие; параметр тип - конкретн ый тип события ; параметр кома нда - командную строку;
860 Часть 11. Библиоте ка Java параметр момент - конкретный момент нас'I)'ПЛения события. Кроме того, пара­ метр модифика торы обозначает нажатие модифицирующих клавиш (<Alt>, <Ctrl>, <Meta> и/или <Shift>) в момент нас'I)'ПЛения события. После вызова метода getA ctionCornma nd ( ) можно получить имя команды для вызывающего объекта типа Ac tionEvent. Ниже приведена общая форма это­ го метода. Strinq qetActionC011 11 11an d() Например, когда производится щелчок накнопке, наступает событие действия, которое имеет имя команды, соответствующее метке данной кнопки. Метод getModifie rs () возвращает значение, обозначающее нажатие модифи­ цирующих кл авиш (<Alt>, <Ctrl>, <Meta> и/или <Shift>) в момент события . Ниже приведена общая форма этого метода. int qet:Мodifiers О Метод ge tWhen () возвращает момент наступл ения события , называемый вре­ меннай метхай события. Общая форма этого метода выглядит следующим образом: lonq qetWhen О Кnacc Adj ustmentEvent Событие класса Adj ustme ntEvent генерируется полосой прокрутки. Имеется пять типов событий настройки. В классе Adj ustmen tEvent определяются цело­ численные константы , которые могут использоваться для идентификации собы­ тий. Константы и их описание представлены в табл. 24.2 . Таблица 2,.2, Ко нстанты, определе нные в классе Adj ustmentEvent Константа Описание BLOCK DECREМENТ Пользователь щелкнул на полосе прокрутки для уменьшения про­ кручиваемого значения BLOCK INCREМENТ Пользователь щелкнул на полосе прокрутки для увеличения прокру· чиваемого значения TRACK UNIT DBCREМENТ UNIT INCREМENТ Перемещен ползунок Произведен щелчок на кнопке в конце полосы для уменьшения про­ кручиваемого значения Произведен щелчок на кнопке в конце полосы для увеличения про­ кручиваемого значения Имеется также целочисленная константа ADJUSTMENT_VAL UE _ CНANGED, обо­ значающая , что произошло изменение. Ниже приведен единственный конструктор класса Adj ustmentEvent, где исто чник обозначает ссылку на объект, сгенерировавший событие; идентифика тор ­ событие настройки; тип - конкретный тип собьП'Ия настройки; зна чение - связан­ ное с этим событием значение. AdjustmentEvent (AdjustaЫe ЖC!l'OVНJl l Ж, int жден!l'жф/rжа !l'ор, int !l'ЖD, int .SН4V8Н1 1 8)
Гл ава 24. О бработка событи й 861 Метод getAdj ustaЫe () возвращает объект, сгенерировавший событие. Ниже приведена его общая форма. AdjustaЫe qe tAdjus taЬle () Тип события настройки можно получить, вызвав метод getAdj ustment Туре ( ) . Он возвращает одну из констант, определенных в классе Ad j ustme ntEvent. Его общая форма следующая : int qe tAdj ustJDent'l'ype () Величину настройки можно получить, вызвав метод getValue (). Ниже при­ ведена его общая форма. int qetValue () Например , когда выполняется ман ипул ирование полосой прокрутки, этот ме­ тод возвращает значение, обозначающее произведенное изменение. Кnacc componentEvent Объект класса ComponentEvent создается при изменении размеров, положе­ н ия или видимости ко мпонента. Имеются четыре типа событий в компонентах. Для их идентиф икации в классе ComponentEvent определяются целочисленные ко нстанты , представленные в табл. 24.3. Табл и ца 2,.Э. Ко н ста нты , определе нные в классе ComponentEvent Константы ССМРОNЕNТ BIDDBN CCМPONEN' l' NoVED ССМРОNЕNТ Rli\S IZВD CCМPONEN' l' SBOWN Описание Компонент скрыт Компонент перемещен Изменен размер компонента Компонент стал видимым В классе ComponentEvent имеется следующий конструктор: Coшponen tEvent ( Coшponen t 11с'l'оvюсж , in t 'l'Яn) где параметр источник обозначает ссылку на объект, сгенерировавший событие, а параметр тип - конкретный тип события . Класс ComponentEvent служит (прямо или косвенно) суперклассомдля клaccoв ContainerEvent, FocusEvent, KeyEvent, Mo useEvent, WindowEvent и др. Метод getComp onent () возвращает компонент, сгенерировавший событие. Его общая форма выглядит следующим образом: Coiaponent qetCoшponent () Кnacc containerEvent Объект класса Containe rEvent создается при добавлении ко мпонента в ко н­ тейнер или его уд алении отгуда. Имеются два типа событий в контейнере. В клас­ се ContainerEvent определяются следующие целоч исленные константы , кото-
862 Часть 11. Библ иотека Java рые мoryr использоваться для идентификации этих событий: COMPONENT_ADDED и COMPONENT_REMOVE D. Они определяют, введен ли компонент в контейнер или же уд ален из него. Класс ContainerEvent является производным от класса ComponentEvent и имеет следующий конструктор: ContainerEvent ( Component 11 1 crov-ж , int r11 1 n, Coшponent rоNoУаненr) где параметр исто чник обозначает ссылку на ко нтейнер, который уведомляет о н аступившем событи и, параметр тип - конкретный тип события , а параметр компонент - вводимый в контейнер или удаляемый из него компонент. Вызвав метод getContainer (), можно получить ссылку на контейнер, создав· ший это событие. Ниже приведе на общая форма этого метода. Container qetContainer () Метод getChild () возвращает ссылку на компонент, который был добавлен или уд ален из контейнера. Его общая форма следующая: Co11 1p onent qetChild () Класс FocusEvent Событие типа FocusEvent генерируется при получении или потере компонен· том фокуса ввода. Та кие события определяются целоч исленными константами FOCUS GA INED и FOCUS LOST. Класс Focus Event является производн ым от класса Comp onentEvent и имеет следующие конструкторы: FocusEvent(Component 11 1 croVКJ1 1 r, int !l'llШ) l'ocusivent (Coшponent 11 1 croVНJ1 1 r, int !l'Jl lD , boolean аренен нШI nр11 1 .sнаж) FocusEvent ( Coшponent 11 1 cro VНJ1 1 r, int !l'llШ , boolean аренен н�ziСnРJ 11эна r, Coшponent другое) где параметр источник обозначает ссылку на компонент, сгенерировавший со­ бытие , а параметр тип - конкретный тип события. Если событие фокуса ввода оказывается временным, то параметр временный_ пр из на к должен принимать ло­ гическое значение true , а иначе -логическое значение false. (Событие времен· ного фокуса ввода происходит в результате другой операции в пользовательском интерфейсе. Допустим, фокус ввода находится на текстовом поле. Если пользо­ ватель передвигает мышь, чтобы переместить полосу прокрутки, то фокус ввода временно теряется .) Другой компонент, участвующий в изменении фокуса ввода и называемый про­ тивоположным компонентом, определяется параметром другое. Следовательно, если наступает событие FOCUS_GA INED, то параметр др угое будет ссылаться на компонент, теряющий фокус ввода. А если наступает событие FOCUS_LOST, то параметр другое ссылается на компонент, получающий фокус. Вызвав метод ge tOppo siteComp onent () , можно определить другие компонен· ты. Ниже приведена общая форма этого метода. Этот метод возвращает противо­ положный компонент. Coшponent qetOp po siteComponent ()
Гл ава 24. Обработка событий 863 Метод i sTemporary ( ) позволяет выяснить, является ли изменение фокуса вре­ ме нным. Его общая форма следующая : boolean isTemporary () Этот метод возвращает логическое значение true, если изменение является временным, а иначе - логическое значение fal se. Класс InputEvent Абстрактный класс InputEvent является производным от класса ComponentEvent и служит суперклассом для событий, связанных с вводом дан ных в ко мпонентах. У него имеются подклассы KeyEvent и Mou seEvent. В классе InputEvent определяется несколько целочисленных констант, пред­ ставляющих любые модификаторы клавиш, в то м числе признак нажатия управ­ ля ющих клавиш, которые могут быть связаны с событием. Изначально в классе InputEvent определены следующие восемь констант для представления модифи­ каторов клавиш: ALT НUК ВUТТОN2 НUК МЕТА НUК ALT GRAPB МАЗК BUТ'l'ONЗ МАЗК SBIF'l' НUlt BUТ'l'ONl НUК CTRL МАЗК Но поскольку существует вероятность конфликтов среди модификаторов кла­ виш , используемых в событиях от клавиатуры и мыши, а также других осложне­ ний, то класс InputEvent был допол нен приведенными ниже расширенными зна­ чения ми модификаторов клавиш. При написании нового кода рекомендуется ис­ пользовать новые расширенные модификаторы клавиш вместо первоначальных. ALTDOWNНUК ВUТ Т ОN2D01INМАЗК МЕТАDOWNМАЗК ALTGRAPBDOl lN МАЗК ВUТТОNЗ DOИN НUК SBIF'l' DOИN МАЗК BUТ'l'ONl DOWN МАЗК CТRLDOWNМАЗК Чтобы проверить, какая именно модифицирующая клавиша была нажата в мо­ мент, когда наступило событие, можно воспользоваться методами i sAlt Down ( ) , isAltGraph Down (), isControlDown (}, isMe taDown ( ) и isShiftDown (}. Ниже приведены общие формы этих методов. boolean isA.l tDown () Ьoolean isAl tGraphDown () Ьoolean isControlDown () boolean isМetaDown () Ьoolean isShiftDown () Вызвав метод ge tModifier s (),можно получить значение, содержащее все при­ знаки первоначальных модификаторов клавиш. Ниже приведена общая форма этого метода. int qetмodiAers () Расширенные модификаторы клавиш можно получить, вызвав метод get Mod ifiersEx ().Ниже приведена общая форма этого метода. int qetиodi!\ersEx ()
86' Часть 11. Библиотека Java Класс IteшEvent Событие типа ItemE vent генерируется, когда производится щелчок на флаж­ ке , элементе списка или отмечаемом пункте меню. (Флажки , списки , меню и про­ чие элементы ГПИ рассматриваются далее в этой книге.) Имеются два типа собы­ тий от выбираемых элементов, которые обозначаются целочисленными констан­ тами, перечисленными в табл . 24.4 . Табл ица 2'·'· Ко н ста нты, определенные в классе ItemEvent Константа DESELECТED SELECТED Описание Пользователь от менил выбор элемента Пользователь выбрал элемент В классе ItemE vent определяется еще одна целочисленная константа !ТЕМ_ STATE_CHANGED, которая сигнализирует об изменении состояния выбираемого эл емента. В классе ItemE vent имеется следующий конструктор: It&.l llE vent (IteaSelectaЫe •c!l'ov-ж , int !l'IUJ , OЬject эленвн!l', int СОС!l'О.-Н.е) где параметр исто чник обозначает ссылку на компонент, известивший о насту· пившем событии. Это может быть, например, список или выбираемый элемент. Параметр тип обозначает ко нкретный тип события , параметр элемент - тот эле· мент, который сгенерировал событие, а параметр состояние - текущее состояние выбираемого эл емента. Метод getitem () может быть использован для получения ссылки на выбирае­ мый элемент, сгенерировавший событие. Его общая форма выглядит следующим образом: OЬject qetitem () Метод ge titem SelectaЫe () может быть использован для получения ссылки на объект типа ItemSelectaЬle, сгенерировавший событие. Его общая форма приведена ниже. It81 11S electaЫe ge tit..SelectaЬle () Списки и флажки являются примерами выбираемых эл ементов ГПИ, реализу­ ющих интерфейс ItemSelectaЫe. Метод getStateChanged () возвращает изме­ ненное состояние события (т.е. значение SELECTED или DE SELECTED). Его общая форма приведена ниже. int qetStateChanqed () Класс KeyEvent СобЬТ ТИ е типа KeyEvent генерируется при вводе с клавиатуры. Имеются три типа клавиатурных собьrrий, обозначаемых следующими целочисленными константами: KEY _PRESSED, КЕУ _RELEASED и КЕУ_TYPED. Собьrrия первых двух типов наступают
Гл ава 24. Обработка событи й 865 при нажатии и отпусI<аНИи клавиши на клавиатуре, а собьrгие третьего типа - при вводе символа. Следует, однако, иметь в виду. что нажатие не всех клавиш приводит к вводу символа с клавиатуры. Так , при нажатии клавиши <Shift> символ не вводится. В классе KeyEvent определяется целый ряд других целочисленных констант. Например, константы VК_ 0 -VK_ 9 и VK_A -VK_ Z обозначают эквиваленты чисел и букв в коде АSСП. Ниже перечислены другие константы , определяемые в классе Ke yEvent. Vl lt ALT Vl lt DON N Vl lt Ll:l".1' Vl lt RIGBT Vl lt CANCEL Vl lt ZN'l'ER Vl lt PAGEDOlfN VК SBil".1' Vl lt CONТROL Vl lt ZSCAPE Vl lt РАGВUP VКUP Константы типа VK обозначают виртуа.л:ьные коды 'IСЛавиш, не зависящие от та­ ких модифицирующих клавиш, как <Control>, <Alt> или <Shift>. Класс KeyEvent является производным от класса Inpu tEvent. Ниже приведен од ин из его конструкторов. КeyEvent (Co11 1pO nent хс!l'оvн.ж , int !1'11 1Л , lonq нОNен!I' , int нодхфж.а:а!l'орu, int ход , char CJUalo.n) Здесь параметр источник обозначает ссылку на компонент, сгенерировавший событие; параметр тип - конкретный тип события; параметр момент - тот мо ­ мент системного времени, когда была нажата клавиша, параметр модифика торы ­ те модифицирующие клавиши, которые были нажаты при наступлении события от клавиатуры. Виртуальный код клавиши (например, VK_U P, VK_A и т. д.) пере­ дается в качестве параметра код, а символьный эквивалент нажатой клавиши, если таковой существует, - в качестве параметра симв ол. В отсутствие достовер­ ного символа параметр символ принимает значение константы CHAR_UNDE FINED. Для событий типа KEY_TYPED параметр код будет принимать значение константы VK UN DEFINED. В классе KeyEvent определяется несколько методов, но к наиболее употреби­ тельным среди них относится метод getKeyChar (), возвращающий введенный с клавиатуры символ, а также метод getKeyCode (), возвращающий код клавиши. Общие формы этих методов приведены ниже. char qetКeyChar () int qetкeyCode () Если никаких корректных символов при нажатии клавиши не вводится, то ме­ тод getKeyChar () возвращает значение константы CHAR_UNDEFINED. При насту­ плении события КЕУ_TYPED метод getKeyCode () возвращает значение константы VK UNDEFINED. Класс MouseEvent Имеется восемь типов событий от мыши. Для их обозначения в классе Mouse Event определяется ряд целочисленных констант, перечисленных в табл. 24.5 . Класс Mou seEvent является производным от класса InputEvent. Ниже приве­ ден один из его конструкторов. мouseEvent (Co11 1pO nent хс!1'0VН1 1 .а:, int !1'1UJ, lonq нонен!I' , int нод•Ф•жа!l'ОРJI , int х, int у, int llf8Jl1lIOr , boolean .-r.sоа_неню)
866 Часть 11. Библиоте ка Java Табл ица 21..5. Ко нста нты, определенные в классе Мoua eEvent Константа NoUSE CLICDD NoUSE DRAG G IШ NoUSZ ZN'l'ERZD NoUSZ ПI'l'ZD NoUSE NoVZD NoUSE PRESSIШ NoUSE RELEASED NoUS:В: ИИЕ Е L Описание Пользователь щелкнул кно п кой мыш и П ользователь перетащил мыш ь п ри нажатой кнопке Курсор мыш и н аведен на компонент Курсор мыш и отведен от компо н.ента Мышь перемещена Кнопка мыши нажата Кнопка мыш и от пущена Произведена прокеугка колесика мыш и Здесь параметр исто чник обозначает ссылку на компонент, сгенерировавший событие; параметр тип - конкретн ый тип события; параметр момент - тот мо­ мент системного времени, когда была нажата клавиша; параметр модифика торы ­ те модифицирующие клавиши, которые бьти нажаты при наступлении собы­ тия от мыши. Координаты курсора мыши передаются в качестве параметров х и у, а подсчет произведенных щелчков - в качестве параметра щелчки. Признак выз ов_меню обозначает, должно ли данное событие вызывать появление всплыва­ ющего меню на данной платформе. К числу наиболее употребительных в этом классе относятся методы getX ( ) и getY ().Они возвращают координаты Х и У курсора мыши на момент наступле­ ния события . Их общие формы следующие: int qetx () int qetY () В качестве альтернативы для получения координат курсора мыши можно вы­ звать метод ge tPoint ().Ниже приведена его общая форма. Point qetPoint () Этот метод возвращает объект типа Роint, содержащий координаты Х, У в сво­ их целочисленных членах х и у. Метод trans latePoint () изменяет местоположение события. Ниже приведе­ на его общая форма, где аргументы х и у добавляются к первоначальным коорди­ натам события . void trans latePoint (int х, int у) Метод getClickCount ( ) получает количество произведенных щелчков мыши для дан ного события . Ниже приведена его общая форма. int 9etClicltCount () Метод isPopupT rigger () проверяет, вызывает ли наступившее событие всплывающее меню на данной платформе. Его общая форма следующая : Ьoolean isPopupTriqqer () Имеется также метод getButton (), общая форма которого приведена ниже. int qetвutton ()
Гл ава 24. Обработка событий 867 Этот метод возвращает значение, представляющее кнопку, вызвавшую собы­ тие. Как правило, этот метод возвращает значение одной из следующих констант, определенных в классе Mo useEvent: lновоттсж JвuтТОN1 jв0'1"1'al 2 1BU'l"lafЭ Значение константы NOBUTTON обозначает, что ни одна из кнопок не была на­ жата ил и отпущена. В классе Mou seEvent имеются также три метода, получающие координаты мыши относительно экрана, а не компонента. Ниже приведены об­ щие формы этих методов. Point getLocationOnScreen () int getxonscreen () int getYOnScreen () Метод getLocationOnScreen () возвращает объект типа Point, содержащий обе координаты Х и У местоположения курсора мыши. Два других метода возвра­ щают по одной координате соответственно. Кnacc мou seWheelEvent Этот класс инкапсулирует событие от колесика мыши. Он является произво­ дным класса Mou seEvent. Не все мыши оснащены колесиками, но если оно есть, то располагается между левой и правой кнопками. Колесики служат ДJIЯ прокруг­ ки содержимого (изображения, текста, таблиц и т. п.). В классе MouseWhe elEvent определяются целочисленные константы , перечисленные в табл. 24.6. Табn ица 2,.6, Ко н станть1 , определенные в кnассе Мouse'l lh ee l:Вvent Константа Описание 1ПIE E L ВLOClt SCROLL Произошло событие прокругки содержимого на страницу вверх или вниз 'lfПВL ONIT SCROLL Произошло событие прокруrки содержимого на строку вверх или вниз Ниже приведен один из конструкторов, определенных в классе Mou seWheelEvent. MouseWheelEvent ( CoJDpOnent ЖC'J'O'U UI JC, int !l'ЖП, long хонен!I' , int ноджфжrа !l'ор&r, int r, int у, int llfeJIVJCЖ , Ьoolean вu.зов неюо, int способ zrporpY'J'll:Ж , int JCOJil lПl ec!l'JilO , int ПОДСV8!1') - Здесь параметр источник обозначает ссылку на компонент, сгенерировавший событие; параметр тип - конкретный тип события; параметр момент - тот мо­ мент системного времени, когда была нажата клавиша; параметр модифика торы - те модифицирующие клавиши, которые были нажаты при наступлении события про крутки содержимого. Координаты курсора мыши передаются в качестве пара­ метров х и у, а подсчет произведенных щелчков - в качестве параметра щелчки. Признак вызов_меню обозначает, должно ли да нное событие вызывать появление всплывающего меню на данной платформе. Параметр спо соб_ пр окрутки может принимать значение константы WHEEL-UNrт_SCROLL или WH EEL-BLOCK-SCROLL.
868 Часть 11. Библиотека Java Количество единиц прокрутки передается в качестве параметра количе ство, а ко­ личество еди ниц вращения колесика - в качестве параметра подсчет. В классе Mou s eWheelEvent определяются методы, предоставляющие доступ к со­ бьпию от колесика мыши. Чтобы получить количество единиц вращения колесика , следует вызвать метод gе tWhееlRоtаtiоn (),общая форма которогоприведена ниже . int qetWheelRotation () Этот метод возвращает количество еди ниц вращения колес ика. Есл и возвра· щаемое значение положительно, то колес ико повернуто проти в часовой стрелки , а если это значение отрицательно - по часовой стрелке. В версии JDK 7 внедрен метод ge tPreci seWheelRotat ion (), поддерживающий колесико с высокой раз· решающей способностью. Он действует таким же образом, как и метод getWheel­ Ro tat ion (), но возвращает значение типа douЫe. Чтобы получить тип прокрутки , следует вызвать метод getScrollТуре (), об­ щая форма которого приведена ниже. int qe tScrollТype () Этот метод возвращает значение константы WHEEL_UN IT _ SCROLL или WHEEL_ BLOCK_SCROLL. Если тип прокрутки обозначается константой WHEEL_UN IT _ SCROLL, то для получения количества единиц прокрутки можно далее вызвать метод getScrollAmount (). Общая форма этого метода выглядит следующим образом: int qetScrollAl ll ount () Класс TextEven t ЭкземШiяры этого класса описывают текстовые события. Та кие события гене­ рируются текстовыми полями и областями, когда в них вводится текст вручную или программно. В классе TextEvent определяется цело численная константа ТЕХТ VAL UE CHANGED. Ниже приведен единственный конструктор этого кл асса, где параметр исто чник: обозначает ссылку на компонент, сгенерировавший событие, а пара· метр тип - конкретный тип события. TextEvent (Object •c�ovнstж , int !D!n) Объект класса TextEvent не содержит символ ы, находя щиеся в данный мо­ мент в текстовом ко мпоненте, сгенерировавшем событие. Вместо этого для извле· чения подоб ной информации в прикладной программе должны использоваться другие методы, связанные с текстовым компонентом. Этим событие данного типа отличается от других рассматриваемых здесь событий. Ув едомление о текстовом событии следует считать сигналом приемнику, что он должен извлечь информа· цию из указанного текстового компонента. Класс WindowEvent Имеется десять типов оконных событий. Для их обозначения в классе Window Even t определяются целочисленные константы, перечисленные в табл. 24.7 .
Гл ава 24. Обработка событи й 869 Та бл ица 24.7. Ко нстанты, определенные в кnacce windowEvent Константы 1fINDOlf AC'l'IVA'l'ED 1fINDOlf CLOSED 1fINDOlf CLOSING 1fINDOlf DZAC'l'IVA'l'ED 1fINDOlf DEICONIFIED WINDOWGAINEDFOCUS - - WINDOlf ICONIFIED WINDOW LOS'l' FOCUS 1fINDOК OPENED 1fINDOlf S'l'A'l'E CВANGED Опис ание Окно активизировано Окно закрыто Пользователь запросил закрытие окна Окно деактивизировано Окно развернуrо Окно получило фокус ввода Окно свернуrо Окно утратило фокус ввода Окно открыто Состояние окна изменилось Класс WindowEvent является производным от класса Comp onentEvent. В нем определяется несколько конструкторов. Ниже приведен первый из них. WindowEvent (Window •C!l'ov.юr.к , int !l'IШ) где параметр источник обозначает ссылку на компонент, сгенерировавший со­ бытие, а параметр тип - конкретный тип события. Следующие три конструктора обеспечивают более то чный контроль оконных событий: Window:&:vent (Window •C!l'ov.юr.к , int !l'JШ , Window .цруrое) WindowEven t(Window •c!l'ov.юrж , int !l'•n , int •сходное coc!l'OJU Ut e, int .конеvное сос!l'ояюrе) - 1findowEven t(Window •с!l'оvН..к, int !l'•n , Window .цруrое , int •cxoднoe_coc!l'OJIЮI& , int .кoнevнoe_coc!l'OR.fm'e) Здесь параметр другое определяет противо положное окно при наступле­ нии события, связанного с получением фокуса ввода или акти визацией окна. Параметр исх одно е_ состояние определяет предыдущее состояние окна, а пара­ метр конечное_ со стояние - новое состояние, которое окно получает при смене состояния. Наиболее употребительным в этом классе является метод ge tWindow (). Он возвращает объект типа Window, сгенерировавший событие. Ниже приведена об­ щая форма этого метода. Window getWindow () В классе WindowEvent определяются также методы, возвращающие противо­ положное окно (при наступлении событий, связанных с получением фокуса ввода или акти визацией окна) , предыдущее состояние окна, а также его текущее состоя­ ние. Эти методы имеют следующие общие формы: 1findow getOp p ositeWindow () int getOldState () int qetNewState ()
870 Часть 11. Библиотека Java Источники событий В табл. 24.8 перечислены некоторые компоненты пользовательского интер­ фейса, способные генериро вать события, описанные в предыдущем разделе. Помимо этих компонентов ГПИ, любой класс , наследующий от класса Component , например класс Applet, способен генерировать события в других компонентах. Например, события от клавиатуры: и мыши можно получать от аплета, а также соз­ давать свои компоненты , генерирующие события. В этой гл аве обсуждается обра­ ботка только событий от клавиатуры и мыши, а в последующих гл авах речь пойдет об обработке событи й от источников, перечисленных в табл. 24 .8. Табл ица 2,.8. Примеры источников соб ытий Источник со6ытмй Кнопка Флажок Переключатель Список Описание Извещает о событиях де йствия, н аступающих после щелч ка на кнопке Извещает о событиях от элементов, н аступающих после уста­ н о вки и сбросе флажка Извещает о событиях от элементов, н аступающих при измене­ н ии выбора Из вещает о событиях действия, н аступающих после двойного щелчка на эл ементе; генерирует событие от элемента после вьщеления или отмены вьщеления элемента Пун кт меню Из вещает о событиях действия, наступающих после выбора пун кта мен ю; генерирует событие от элемента после установки и сброса флажка н епосредственно в пун кте меню Полоса прокрутки Извещает о событиях настройки при манипулировании с по­ лосой прокрутки Те кстовые компоне нты И звещает о текстовых событиях, когда пользователь вводит символ Окно Извещает об оконных событиях при активизации, закрытии, деакти виэац ии, развертыван ии, свертыван ии, открыти и окна или выходе из не го Интерфейсы приемников событий Как пояснялось ранее, модель делегирования событий состоит из двух частей: источников и приемников. Приемники создаются при реализации одного или не­ скольких интерфейсов, определенных в пакете j ava . awt . event. Когда нас'I}'Пает событие, его источник вызывает соответствующий метод, определенный в пр и­ емнике, и передает объект события в качестве аргумента. В табл. 24 .9 перечисле­ ны наиболее употребительные интерфейсы прием ников событий и представлено краткое описан ие определяемых в них методов. А в последующих разделах рассма­ триваются отдел ьные методы, доступные в каждом из этих интерфейсов.
Гл ава 24. Обработка событий 871 Табл ица 2,.9. Наибол ее употребител ьные инте рфейсы прием ников соб ыти й Интерфейс ActionLi stener Adjust.l lle ntLi stener CoшponentLi stener ContainerListener l'o cusLis tener It&Jl lL istener КeyListener МouseListener Мous.МOtionLis tener Мouse'NheelListener TextLis tener WindowFocusListener 'lfindowL istener Описание Определяет один метод для приема событий действия Определяет один метод для приема событий настройки Определяет четыре метода для распознавания момента, кода происходит сокрытие , перемещение, изменение размера ил и отображение компонента Определяет два метода для распознавания момента, когда ком­ понент добавляется в контейнер либо исключается из него Определяет два метода для распознавания момента, когда ком­ понент получает или теряет фокус ввода с клавиатуры Определяет один метод, распознающий момент, когда изменя­ ется состояние эл емента Определяет три метода, распознающих момент, когда проис­ ходит нажатие, отпус кание клавиши или ввод символа Определяет пять методов, распознающих момент, когда про­ изводится щелчок кнопкой мыши, курсор мыши наводится на компонент, отводится от компонента, нажимается и отпу­ скается кнопка мыши Определяет два метода для распознавания момента, когда вы­ полняется перетаскивание курсора или перемещение мыши Определяет один метод, распознающий момент, когда прокру­ чивается колесико мыши Определяет один метод, рас познающий момент, когда изменя­ ется текстовое значение Определяет два метода для распознавания момента, когда окно получает или теряет фокус ввода Определяет семь методов, распознающих момент, когда окно активизируется, дезактивиэируется, развертывается, сверты­ вается, открывается или закрывается Интерфейс ActionListener В этом интерфейсе определяется метод actionPerforme d (), вызываемый при наступлении соб ытия действия . Ниже приведена его общая форма. void actionPerforaed (ActionEvent coCWтжe_дei i cтJU UJ ) Интерфейс AdjustmentListener В этом интерфейсе определяется метод adj us tmentVa lueChanged ( ) , вызыва­ емый при наступлении события настройки. Ниже приведена его общая форма. void adj us tmentValueChan9ed (Adj ustment.J:vent соСWтже_на стро•жж)
872 Ч асть 11. Библиотека Java И нтepфeй ccomponentLi s tener В этом интерфейсе определяются четыре метода, вызываемых при изменении размеров ко мпонента, его перемещении, отображении или сокрытии. Ниже при· ведены их общие формы. void c01 11p onentResized ( CoшponentEvent собl l т•е от х�онента) vo id coшponentмoved (CoшponentEvent собlrт•е от хонпонента) void coш.ponentShown (Coшponent.Event собl l т•е-от - хонпонента) void coшponentHidd8n (C01 11p onent.Event собl l т•е_о-;_хонпонента) Интepфeй ccontainerLis tener В этом интерфейсе определяются два метода. Когда компонент вводится в кон· тей нер , вызывается метод componentAdded ( ) . А когда компонент уд аляется из контейнера, вызывается метод componentRemoved ( ) . Общие формы этих мето­ дов выглядят следующим образом: vo id coш.ponent:Ad ded (ContainerEvent собl l т•е от ж�онента) void coшponentReaoved (ContainerBvent собl l т'Яе_'О!r _ ж�онента) И нтерфейс FocusLis tener В этом интерфейсе определяются два метода. Когда компонент получает фокус ввода с клавиатуры, вызывается метод focusGained (}.Акогда компонент теряет фокус ввода с клавиатуры, вызывается метод focusLost (}. Ниже приведены об­ щие формы этих методов. void focusGained ( FocusEvent собl l тяе фохуса Вl lО да) void focusLost (FocusEvent собlrт••_Фож,уса_uода) И нтерфей с ItemL istener В этом интерфейсе определяется метод i temS tateChanged ( ) , вызываемый при изменении состояния элемента. Его общая форма следующая : void ite11 1S tateChan9ed (Itel lli vent собl l т•е_от _ sпвмента) И нтерфейс KeyListener В этом интерфейсе определяются три метода. Методы keyPressed (} и key R e leased (} вызываются при нажатии и отпускании клавиш соответственно. А метод keyTyped (} вызывается при вводе символа. Та к, если пользователь нажимает и отпускает клавишу <А>, последовательно наступают следующие три события: нажатие клавиши, ввод символ а, отпускание клавиши. Если же пользователь нажимает и отпускает клавишу <Home>, последо­ вател ьно наступают только два события: нажатие клавиши и ее отпускание. Общие формы этих методов выглядят следующим образом: void keyPressed (КeyEvent собl l т•е от ЖJZава-атурu) void keyReleased (КeyEvent coбl l !l'Jle о-; ЖJZa.-aтypu) void lteyТyped ( К.yEvent соб11тяе_от:ЖJZа-атурu)
Интepфeй cMouseLis tener Гл ава 21•. Обработка событи й 873 В этом интерфейсе определяются пять методов. Если кнопка мыши нажата и отпущена в одной и той же точке, то вызывается метод mo useClicked ( ) . Когда курсор мыши наводится на компонент, вызывается метод mouseEntered ( ) . А когда курсор отводится от компонента, вызывается метод mou seExited ( ) . И наконец, мe­ тoды rno usePre ssed () иrnouseReleased () вызываются, когда кнопка мыши нажима­ ется и отпускается соответственно. Ниже приведены общие формы этих методов. void mouseClicked (МouseEvent coбlz�•e о� Нl llDr ) void mouseEntered (МouseEvent coбм�•e - o� - JODJl1 1 ) void mouseExi ted (МouseEvent cochr�•• о� °Ншrж) void mous ePressed (МouseEvent собм�хё o"i Nl ll lDr ) void mouseReleased (МouseEvent собl.r�•ё _ о-; _ .141 11 1' •) И нтepфeй cMou seМotionListener В этом интерфейсе определяются два метода. В частности, метод mouseDragged ( ) вызывается неоднократно при перетаскивании объекта мышью, а метод mo use­ Moved ( ) - при перемещении курсора мыши. Их общие формы следующие: void mouseDragged (МouseEvent собм�•• о� Нl ll lDI ) void mouseМoved (MouseEvent cochr�•e_ o'Т _ .NU.rx) И нтepфe й cмou seWheelListener В этом интерфейсе определяется метод mo useWhe elMoved {), вызываемый при прокруrке колесика мыши. Его общая форма показана ниже . void mouseWheelМoved (МouseWheelEvent cochr�•e _ nерен•еюи_ жапесжжа) И нтepфe й cтextListener В этом инте рфейсе определяется метод textChanged (), вызываемый при из­ менении содержимого текстовой области ил и текстового поля. Его общая форма выглядит следующим образом: void textChanged (TextEvent �ежс�оаое_ собl.r�•е) И нтepфeй cwindowFo cusListener В этом интерфейсе определяются два метода: windowGa inedFocus ( ) и window Lo stFocus ().Они вызываются в тот момент, когда окно получает и те ряет фокус ввода. Ниже приведены общие формы этих методов. void windowGainedFocus (WindowEvent ожан н ое co&.r�e) void windowLos tFocus (WindowEvent ожан н ое _ собl.r�•е) И нтepфe й cWindowListener В этом интерфейсе определяются семь методов. Методы windowAc tiva ted ( ) и windowDeactiva ted () вызываются, когда окно акти визируется и дезактивизи­ руется соответственно.
87' Часть 11. Б иблиоте ка Java Есл и окно сворачивается в пиктограмму, то вызывается метод window Iconifi ed (). Когда же окно разворачивается, вызывается метод windowDe lcon i­ fi ed ( ) . А когда окно открывается и закрывается, вызываются методы windowO­ pened ( ) и windowC losed ()соответственно. Метод windowClosing ( ) вызывается при закрытии окна. Общие формы этих методов выглядят следующим образом: void windowActivated (WindowEvent ожон н ое собм�••) void windoWC losed (WindowEvent ожон н ое соба.r�е) void windoWC losing (WindowEvent ожонн о"ё собм�•е) void windowDeactivated (WindowEvent ожаНное собм�•е) void windowDe iconified (WindowEvent ожонное ёобм�••) void windowiconified (WindowEvent ожон н ое собм�•е) void windowOpened (WindowEvent ожан н ое _ собм�••) П рименение модел и делегир овани я событий Итак , рассмотрев принципы, по которым действует модель делегирования событий, а также представив различные ее компоненты , можно перейти от тео­ рии к практике. Пользоваться моделью делегирования событий совсем не трудно. Для этого достаточно выполнить два действия. • Реализовать соответствующий интерфейс в приемнике событий, чтобы он мог принимать события требуемого типа. • Реализовать код регистрации и снятия с регистрации, если требуется, при· емника как получателя уведомлений о событиях. Следует, однако , иметь в виду, что источник может извещать о нескольких ти­ пах событий. Каждое событие должно быть зарегистрировано отдел ьно. К тому же объект может подписаться на получение нескольких типов событий и должен реализовать все интерфейсы, необходимые для получения этих событий. Чтобы показать, каким образом модель делегирования событий фун кциониру­ ет на практике, рассмотрим примеры обработки событий от двух наиболее упо­ требительных устройств ввода: мыши и клавиатуры. Обработка событий от мыши Чтобы обработать события от мыши, следует реализовать интерфейсы MouseListener и MouseMotionLi s tener. (Можно бьuю бы также реализовать и н­ те рфейс Mou seWh eelListener, но мы не станем здесь этого делать.) Весь процесс обработки событий от мыши демонстрируется в приведенном ниже примере апле­ та. В строке состоя ния окна этого аплета выводятся текущие координаты мыши. Всякий раз , когда нажимается кнопка мыши, на месте курсора мыши появляется слово "Down " (Нажато). И всякий раз, когда кнопка мыши отпускается, - слово "Up" (Отпущено). А если производится щелчок кнопкой мыши, то в левом верх­ нем углу области отображения аплета выводится сообщение "Mou se clicked" (Произведен щелчок кнопкой мыши). Когда же курсор мыши наводится на окно аплета или отводится от него, то в ле­ вом верхнем углу области отображения аплета также выводится соответствующее
Гл ава 24. Обра ботка событий 875 сообщение. При перетаскивании курсора мыши выводится символ *, сопровожда­ ющий курсор мыши. Обратите внимание на то , что в двух переменных, rno useX и rnouseУ, сохраняются координаты местоположения курсора мыши, когда проис­ ходят события нажатия и отпускания кнопки мыши, а также события перетаски­ вания. Эти координаты затем испол ьзуются методом paint () для вывода соответ­ ствующего сообщения в той то чке, где возникает событие. // Продемонс трировать обработчики событий от мыwи irnport java .awt.*; irnport java.awt . event .*; irnport java . applet .*; /* */ <applet code= "MouseEvents" width=ЗOO height=lOO> </applet> puЫ ic class Mou seEvents extends Applet irnplernents MouseListen er, Mou seMotionLi stener String rnsg = ""; int mou seX = О, rnou seY = О; // координаты курсора мьпuи puЫ ic void init () { addМouseListener (this) ; addМouseMotionListener (this) ; 11 обработать событие от ще лчка кнопкой мыши puЫic void mous eClicked (Mous eEvent me ) { 11 сохранить координаты rnouseX = О; mouseY = 10; msg = "Mo use clicked ."; // Произведен щелчок кноп кой мыши repaint () ; 11 обработать событие наведения курсора МЫПIИ puЫ ic void mouseEntered (MouseEvent me ) { // сохранить координаты mouseX = О; rnouseY = 10; rnsg = "Mouse entered ."; // Курсор наведен repaint () ; // обработать событие от ведения кур сора мыши puЫic void mouseExited (Mou seEvent rne ) { // сохранить координаты rnouseX = О; mouseY = 10; msg = "Mo use exited. "; // Курсор отведен repaint () ; // обработать событие нажатия кнопки мыши puЫ ic void mous ePressed (MouseEvent me ) { // сохранить координаты mouseX = me .getX() ; mouseY = me .getY (); msg = "Down" ; // Кнопка мыши нажата repaint () ;
876 Часть 11. Библиотека Java 11 обработать событие отпускания кнопки мыши puЫ ic void mou seReleased (Mou seEvent me ) ( 11 сохранить координаты mouseX = me .getX() ; mouseY = me .getY () ; msg = 11Up"; // Кнопка мыши отпуще на repaint () ; 11 обработать событие перетаскивания курсора мыши puЫic void mouseDragged (MouseEvent me ) { // сохранить координаты mouseX = me .getX() ; mouseY = me .getY () ; msg = "*"; showStatus ( 11Dragging mouse at 11 + mouseX + 11 , 11 + mouseY) ; 11 Перетаскивание курсора мьrши в точку с ука занными координатами repaint () ; // обработать событие перемещения мыши puЫic void mouseMoved (Mou seEvent me ) ( 11 показать состояние showStatus("Moving mouse at 11 + me.getX() + 11, 11 + те.getY()); 11 Перемещение курсора ыыши в точку с ука занными координатами 11 вывести сообщение из переменной ms g на текущей позиции 11 с координатами Х, У в окне аплета puЫic void paint ( Graphics g) g.drawString (msg, mo useX, mouseY) ; Пример выполнения данного аплета приведен на рис. 24. 1 . Applet �own Moving mouse at 1 46, 51 Рис. 2, ,1 , Пример выполнения аплета , в котором обрабатываются события от мыwи Рассмотрим данный пример аплета более подробно. В частности, класс Mou seEvents расширяет класс Applet и реализует интерфейсы MouseListene r и Mou seMotionListener. Оба эти интерфейса содержат методы, принимающие и обрабатывающие различные типы событий от мыши. Обратите внимание на то, что аплет одновременно является источником и приемником этих событий. И это
Гл ава 24. Обработка событий 877 вполне допустимо , поскольку класс Component, предоставляющий методы add Mo useListener () и addMouseMotionLi s tener (), служит суперклассом для клас­ са Арр l е t. Вообще , использование одного и того же объекта в качестве источника и приемника событий характерно для аплетов. В методе init ( ) аплет регистрирует себя в качестве приемника событий от мыши. Это делается с помощью методов addMou seListener () и addMou se Mo tionL istener (), которые, как упоминалось ранее, являются членами класса Component. Ниже приведены общие формы этих методов. void addМouseLis tener (МOuseLis tener .шl) void addМouseМOtionLis tener (MouseМotionListener .1 11 11 1.I ) Здесь параметр тl обозначает ссылку на объект, принимающий события от мыши, а параметр mm l - ссылку на объект, принимающий события перемеще­ ния мыши. В рассматриваемом здесь примере аплета оба эти метода принимают в качестве параметра один и тот же объект. Далее в данном примере аплета реализуются все методы, определенные в интер­ фейсах MouseListener и Mo useMotionListener, т. е . обработчики разных собьrгий от мыши. Каждый из них обрабатывает свое событие, а затем возвращает управление. Обраб отка со б ыти й от кл авиатуры Для обработки событий от клавиатуры испол ьзуется та же общая архитектура, что и для событий от мыши, как показано в примере аплета в предыдущем разделе. Отличие, разумеется, состоит в том , что в данном случае требуется реализовать интерфейс KeyListener. Прежде чем обратиться к конкретному примеру, имеет смысл еще раз рассмо­ треть процесс генерирования событий от клавиатуры. При нажатии клавиши на клавиатуре наступает событие типа КЕУ_PRESSED. Это приводит к вызову об­ работчика событий ke yPre ssed (). А при отпускании клавиши наступает собы­ тие типа KEY _R ELEASED и вызывается обработчик событий ke yReleased ().Если в результате нажатия клавиши клавиатура формирует символ, то наступает также событие типа KEY_TYPED и вызывается обработчик событий keyTyped ().Так им образом, всякий раз, когда пользователь нажимает клавишу, наступают как мини­ мум два, а то и три события . Если глав ный интерес представляют только вводимые с клавиатуры символ ы, то сведения о нажатии и отпускании клавиш можно проиг­ норировать. Но если прикладная программа должна обрабатывать нажатие специ­ альных клавиш (например, клавиш со стрелками или функциональных клавиш ), то их нажатие следует отслеживать в обработчике событий keyPressed (). В приведенном ниже примере аплета демонстрируется процесс ввода с клави­ атуры. Введенные с клавиатуры символы выводятся в окне аплета, а в строке со­ стоя ния показывается, нажата ли клавиша или отпущена. // Продемонстрироват ь обработчики событий от кла виатуры import java .awt.*; import java .awt . event .*; import java . applet .*; /* <applet code= "Simp leKey" width=ЗOO hei ght= lOO> </applet>
878 Часть 11. Библ иотека Java */ puЬl ic class SimpleKey extends App let implements KeyLi stener { String msg = ""; int Х = 10, У=20; // координаты вывода puЫic void init {) { addKeyLi зtener {this) ; puЫic void keyPressed { Ke yEvent ke ) { showS tatus ("Key Down" ); // Клавиша нажата puЫ ic void keyReleased { KeyEvent ke ) showS tatus {"Key Up ") ; // Кл авиша отпущена puЫic void keyTyped { KeyEvent ke) { msg += ke . getKeyChar{); repaint {) ; ) 11 вывести символы , введенные с клавиа туры puЫ ic vo id paint { Graphics g) { g.drawString (msg, Х, У) ; Пример выполнения данного аплета приведен на рис. 24.2. Applet This is э test. Key Vp Ри с. 2, .2. Пример выполнения аnлета , в кото ром обрабаты ваются события от кл авиатуры Если же требуется обрабатывать нажатие специальных клавиш ( например, кла· виш со стрелками и функциональных клавиш) , то на их нажатие следует реагиро­ вать в обработчике событий keyPre ssed (),поскольку обработчик событий key­ Typed ( ) не реагирует на нажатие таких клавиш. Чтобы вывить нажатие подобных клавиши, следует воспользоваться виртуальными кодами клавиш. В следующем примере аплета выводятся наименования некоторых специальных клавиш: // Продемонстрировать некоторые виртуальные коды клавиш import java .aw t.*; imp ort java . awt . event .*;
import java . applet .*; /* Гл ава 24. Обработка событий 879 <applet code="KeyEvents" width=ЗOO height=lOO> </appl et> */ puЫ ic class Ke yEvents extends Applet implements KeyLi stener { String msg = ""; int Х = 10, У=20; // координаты вывода puЫ ic void init () { addKeyListener (thi s) ; puЫic void keyPressed ( KeyEvent ke ) { showStatus ("Key Down") ; // Клавиша нажа та int key = ke . getKeyCode () ; switch ( key) { case KeyEvent .VK Fl : msg += "<Fl>" ; break; case KeyEvent .VK F2 : msg += "<F2>" ; break; case KeyEvent .VK FЗ : msg += "<FЗ>"; break; case KeyEvent .VK PAGE DOWN : msg += "<PgDn>"; - break; case KeyEvent .VK PAGE UP : msg += "<PgUp>"; - break; case Ke yEve nt .VK LEFT : msg += "<Left Arrow>"; / / Клавиша <+- - > break; case KeyEvent .VK RIGHT : msg += "<Right Ar row>" ; / / Клавиша <--+> break; ) repaint () ; puЫ ic void keyReleased ( KeyEvent ke ) showS tatus ("Key Up ") ; // Клавиша отпущена puЫ ic void keyTyped ( KeyEvent ke ) { msg += ke . getKeyChar () ; repaint () ; // вывести символы, введенные с кл авиатуры puЫ ic void paint ( Graphics g) { g.drawS tring (msg, Х , У) ; Пример выполнения данного аплета приведен на рис. 24.3 .
880 Часть 11. Библиотека Java Applet <PgUp> <PgDn>�Rig ht Ar row� <LeftArrow::. <F1 � Key Up Рис. 2,.3 . Пример выполнения аплета , в кото ром испол ьзуются виртуал ьные коды некоторых кл авиш Процедуры, продемонстрированные в приведенных выше примерах обработки событий от мыши и клавиатуры, моrуг бьпъ обобщены для обработки собыmй лю­ бого rnпa, включая события от элементов управления. В последующих гл авах будуr представлены многочисленные примеры обработки событий других mпов, но все они следуют той же основной схеме, что и в рассмотренных выше примерах аплетов. Кл а ссы адаптеров В Java имеется специальное средство, которое называется классом адаптера и в некоторых случаях позволяет упростить реализацию обработчиков событий. В классе адаптера предоставляется пустая реализация всех методов инте рфейса приемника событий. Классы адаптеров уд обны в тех случаях, когда требуется при­ нимать только те события, которые обрабатываются конкретным интерфейсом приемника событий. В качестве приемника событий можно определить новый класс, расширив оди н из классов адаптеров и реализовав только те события , об­ работка которых представляет особый интерес . Например , вклacce Mou seMotionAdap ter имеются два метода, mоusеDrаggеd () иmouseMoved (), которые определены в интe pфeйce MouseMotionL istener. Если интерес представляют только события перетаскивания курсора мыши, то для их обработки достаточно расширить класс Mo useM otionAdapter и переопределить метод mou seDragged (). Пустая реализация метода mo useMoved () будет автомати­ чески обрабатывать события перемещения курсора мыши. В табл. 24. 10 перечислены наиболее употребительные классы адаптеров из па­ кета j ava . awt . event, а также интерфейсы приемников событий, реализуемые каждым из них. В приведенном ниже примере демонстрируется применение класса адаптера для вывода сообщения в строке состояния, находящейся в окне средства просмот­ ра аплетов или браузера, когда производится щелчок кнопкой мыши или перета­ скивание ее курсора, а все прочие события от мыши игнорируются. Аплет из дан­ ного примера состоит из трех классов. В частности , класс AdapterDemo расширяет класс Applet. В его методе init () создается экземпляр класса MyMo useAdap ter,
Гл ава 24. Обработка событий 881 который регистрируется для получения уведомлений о событиях от мыши, а так­ же экземпляр класса MyMou seMotionAdapter, который регистрируется для полу­ чения уведомлений о событиях перемещения мыши. Оба конструктора принима­ ют ссылку на аплет в качестве аргумента. Табл ица 21.. 1 О. Наиболее употребител ь ные интерфейсы приемников событий, реал изуемые классами ада пте ров Кnасс адаmера Интерфейс приемника событий Co11 1p onentAdapter ContainerAdapter Focu8Adapter ReyAdap ter МouseAdapter МouseNotionAdapter 1findowAdap ter C01 11pO nentListener ContainerListener FocusLi s tener ReyLiatener МouaeLiвtener МouaeИotionListener 1findowLis tener Класс MyMouseAdap ter расширяет класс MouseAdap ter и переопределяет метод rno useClicked (). Все прочие события от мыши игнорируются кодом, на­ следуемым из класса MouseAdapter. Класс MyMouseMotionAdap ter расширяет класс Mou seMotionAdap ter и переопределяет метод rno useDragged (). Другое со­ бытие перемещения мыши негласно игнорируется кодо м, наследуемым из класса Mo useAdapter. (В клacce MouseAdap ter предоставляется также пустая реализация интерфейса MouseMotionL istener. Но ради наглядности примера каждый тип событий от мыши обрабатывается по отдельности . ) Обратите внимание на то , что оба класса приемников событий сохраняют ссылку на аплет. Эти сведения предоставляются в виде аргумента и используется в дальнейшем для вызова метода showS tatus (). 11 Продемонстрировать применение класса адаптера import java .aw t.*; import java.awt . event .*; import java . applet .*; /* •/ <applet code="AdapterDemo " width=ЗOO he ight=lOO> </applet> puЫic class Adap terDemo extends Applet { puЫic void init () { addMouseListener (new MyMouseAdapter (this ) ); addМou seMotionLi stener (new MyMouseMotionAdapter (thi s) ); class MyMouseAdap ter extends MouseAdapter { AdapterDemo adapterDemo ; puЫic MyMous eAdapter (AdapterDemo adapterDemo ) this . adapterDemo = adapterDemo ; } 11 обра ботать событие от щелчка кнопкой мыши puЫic void mouseClicked (MouseEve nt me ) { adapterDemo .showStatus ( "Mouse clicked" );
882 Часть 11. Библиотека Java 11 Произведен щелчок кнопкой мыши class MyMous eMotionAdapter extends MouseMotionAdap ter { Ad apterDemo adapterDemo ; puЫ ic MyMouseMotionAdapter (AdapterDemo adapterDemo ) this . adapterDemo = adapterDemo ; 11 обработать событие пере таскивания курсора мыши puЫic void mo useDragged (MouseEvent me ) { adapterDemo .showStatus ( "Mouse dragged" ); // Перетаскивание курсора мыши Как видите , благодаря тому, что отпадает необходимость реализовывать все методы , определенные в интерфейсах Mou seMotionListener и Mou seListener, экономится немало труда, а код избавляется от нагромождения пустых методов. В качестве упражнения можете попробовать переписать один из приведенных ра­ нее примеров, где обрабатываются события от ввода с клавиатуры, воспользовав­ шись классом KeyAdapter. В нутренние кл а ссы Основные положения о внутренних классах были представлены в гл аве 7. А здесь поясняется, почему они так важны. Напомним, что внутренним называется класс , определенный в другом классе или даже в выражении. В этом разделе по­ казано, каким образом внутренние класс ы упрощают код, в котором применяются классы адаптеров событий. Чтобы стала понятнее выгода , которую приносят внутренние классы, рассмот­ рим приведенный ниже пример аплета. В этом примере внуrренние классы не применяются. Гл авное назначение аплета из этого примера - вывести сообщение "Mouse Pressed" (Кнопка мыши нажата) в строке состояния, находящейся в окне средства просмотра аплетов или браузера, когда нажата кнопка мыши. В данном аплете имеются еще два класса верхнего уровня. Класс MousePressedDemo расши­ ряет класс Ap plet, а класс MyMouseAdap ter - класс Mo useAdap ter. Метод init () из класса MousePressedDemo создает экземпляр класса MyMouseAdap ter и предо­ ставляет этот объект в качестве аргумента методу addMouseListener (). Обратите внимание на то , что ссылка на аплет передается в качестве аргумен­ та конструктору класса MyMouseAdap ter. Эта ссылка сохраняется в переменной экземпляра для последующего использования в мeтoдe mo usePressed {). Когда на­ жимается кнопка мыши, вызывается метод showS tatus {) по сохраненной ссылке на аплет. Иными словами, метод showS tatus () вызывается относительно ссылки на аплет, сохраненной в переменной экземпляра класса MyMouseAdap ter. // В этом аплете внутренний кл асс НЕ применяется import java . applet .*; import java .awt . event .*; /* <appl et code= "MousePressedDemo " width=200 heigh t=lOO>
Гл ава 24. Обработка событи й 883 </applet> */ puЫic class MousePress edDemo extends App let { puЫic vo id init () { addMouseListener (new MyMouseAdapter (this) ); class MyMouseAdap ter extends MouseAdap ter { MousePressedDemo mousePressedDemo ; puЫic MyMouseAdapter (MousePressedDemo mousePressedDemo ) this .mousePressedDemo = mousePressedDemo ; 1 puЫ ic void mousePressed (Mou seEvent me ) { mo us ePressedDemo.showS tatus ("Mouse Pressed" ); // Кнопка МЫIDИ нажа та В приведенном ниже примере показано, как усовершенствовать аплет из пре­ дыдущего примера, используя внутренний класс. В данном примере InnerClass Demo - класс верхнего уровня , расширяющий класс App let, а MyMouseAdapter - внуrренний класс , расширяющий класс MouseAdap ter. А поскольку класс MyMo useAdap ter определен в области действия класса Inne r ClassDemo , то он имеет доступ ко всем переменным и методам, находящимся в контексте этого класса. Та ким образом, метод showStatus () можно вызвать непосредственно из метода mo u sePre s sed (). И теперь это больше не нужно делать через сохраняемую ссылку на аплет, а следовательно, и передавать конструктору MyMouseAdap ter () ссьmку на вызывающий объект. 11 Продемонстрировать приме нение внутреннего класса import java . applet .*; import java.awt . event .*; /* */ <applet code= "InnerClassDemo " width=200 height=lOO> </applet> puЬlic clas s InnerClassDemo extends Applet { puЫic void init () { addМouseListener (new MyMous eAdapter () ); 1 class MyMouseAdapter extends MouseAdapter { puЫic void mou sePressed (MouseEvent me ) { showS tatus ("Mouse Pressed" ); // Кнопка мыmи нажа та Анонимные внутренние кл ассы Анонимнъш называется такой внуrренний класс , которому не присвоено имя. В этом разделе показывается, как анонимный внуrренний класс позволяет упро­ стить написание обработчиков собьrгий. Рассмотрим в качестве примера приве­ ден ный ниже аплет. Как и прежде, назначение этого аплета - вывести сообщение "Mouse Pre ssed" в строке состояния, находящейся в окне средства просмотра аплетов или браузера, когда нажата кнопка мыши.
88' Часть 11. Библиотека Java 11 Продемонс трировать применение анонимного внутреннего кл асса import java . applet .*; import java.awt . event .*; /* */ <applet code= "Anonymous innerClassDemo " width=200 height= lOO> </applet> puЬlic class AnonymousinnerClas sDemo extends Applet puЫic void init () { addMouseListener (new MouseAdapter () { )); puЫic void mousePressed (MouseEvent me ) showS tatus ("Mou se Pressed" ); 11 Кнопка мыши нажа та В данном аплете присугствует только один класс верхнего уровня Anonymo us InnerClassDemo . Метод addMou seLi stener () вызывается из метода init ().Его аргументом служит выражение, определяющее и создающее экземпляр анонимно­ го внугреннего класса. Проанализируем это выражение подробнее. Синтаксис new MouseAdapter ( ) { ...} указывает компилятору, что в коде, за­ ключенном в фигурные скобки , определяется анонимный внугренний класс. Более того, этот класс расширяет класс MouseAdapter. И хотя этот новый класс не имеет имени , его экземпляр автоматически создается при выполнении данного выражения. Анонимный внугренний класс определяется в контексте класса An onymo us Inn erClassDemo и поэтому имеет доступ ко всем переменным и методам, нахо­ дя щимся в контексте данного класса. Следовательно, в нем можно вызвать метод showS tatus ( ) непосредственно. Как видите , именованные и анонимные внутренние классы разрешают некото­ рые неприятные затруднения простым и уд обным способом. Они также позволя­ ют создавать более эффективный код.
25 Введение в библиотеку AWT:работа с окнами, графикой и текстом Библиотека Abstract Window To olkit (АWТ) стала первым каркасом ГПИ, начи­ ная с версии Java 1.0 . Эта библиотека включает многочисленные классы и мето­ ды, позволяющие создавать окна с простыми элементами управления . Библиотека АWТ уже была представлена в главе 23, где она использовалась в нескольких кратких примерах аплетов. А в этой гл аве она рассматривается более подробно. В частности , в ней будет показано, как создавать окна и управлять ими, обращать­ ся со шрифтам и, выводимым текстом и применять графику. Различные элементы управления из библиотеки АWТ, в том числе полосы прокрутки и экранные кноп­ ки, описываются в гл аве 26, где также поясняются дополнительные особенности механизма обработки событий в J ava. А в гл аве 27 представлено введение в подси­ стему АWТ для формирования и обработки изображений. Следует особо подчеркнуть, что новые прикладные программы с ГПИ ред­ ко приходится разрабатывать только средствами библиотеки АWТ, поскольку для этой цели в Java внедрены более эффективные библиотеки Swing и JavaFX. Несмотря на это , библ иотека по-прежнему остается важным компонентом Java. Ниже поясняются веские на то основания. На момент написания данной книги наиболее уп отребительной для построе­ ния ГПИ считалас ь библиотека Swing, поскол ьку она предоставляет для этой цели более развитые и уд обные средства , чем библиотека АWТ. Из этого можно сделать пос пешный вывод, что библиотека АWТ больше не отвечает требованиям разра­ ботки современных прикладных программ с ГПИ, поскольку явно уступает в этом отношении библиотеке Swing. Но это ложный вывод. Напроти в, ясное представ­ ление о функциональных возможностях библиотеки АWТ не теряет своей актуаль· ности до сих пор, потому что она положена в основу библиотеки Swing, причем многие классы АWТ применяются (прямо ил и косвенно) в Swi ng. Та ким образом, для эффективного применения библиотеки Swing по-прежнему требуются проч­ ные знания и навыки обращения с библиотекой АWТ. Для построения ГПИ в Java появилась новейшая библиотека JavaFX. Предполагается, что в какой-то момент в будущем она полностью заменит библио­ теку Swing, когда станет наиболее употребительной для построения ГПИ приклад­ ных программ нa java. Но даже если это и произойдет, то все равно останется еще немало унаследованного кода , опирающегося на библиотеку Swi ng, а следователь­ но, и на библиотеку АWТ. И этот код еще какое-то время придется сопровождать. И наконец, библиотека АWТ оказывается по-прежнему уд обной для разработки
886 Часть 11. Библ иотека Java мелких прикладных программ (особенно аплетов) , где предъявляются минималь­ ные требования к ГПИ. Та ким образом, основател ьные знания и навыки работы с библиотекой АWГ по-прежнему требуютс я, несмотря на то , что она является са­ мой старой библиотекой jаvа для построения ГПИ. Чаще всего библиотека АWГ применяется для разработки аплетов. Но с ее по­ мощью можно также создавать автономные оконные программы, выполняющиеся в такой среде с mи, как, например, Windows. Ради уд обства изложения материала этой гл авы большинство примеров представлены в ней в виде аплетов, которые нетрудно выполнить в средстве просмотра аплетов. И лишь в некоторых приме­ рах де монстрируется создание автономных оконных программ, которые можно запускать на выполнение непосредственно. И последнее предварительное замечание: библиотека АWТ довольно обширна, и для полного ее описания потребуется отд ельная книга. Поэтому в данной книге про­ сто невозможно описать во всех подробностях каждый класс, метод или переменную экземпляра из библиотеки АWТ. Те м не менее в этой и последующих гл авах поясня­ ются основные приемы, которые следует освоить, чтобы поЛЬ30ваться библиотекой АWТ . Опираясь на эти приемы как на прочное основание, можно самостоятельно из­ учить остальные части библиотеки АWТ, чтобы затем перейти к библиотеке Swing. на замеnсуl Если вы еще не читали главу 24, сделайте это теперь. В ней представлен краткий обзор механизма обработки событий, который будет применяться во многих при мерах из этой гл авы. Классы библ и отеки АWТ В пакете j ava . awt , одном из наиболее крупных в Java, содержатся классы би­ блиотеки АWТ. Правда, благодаря логической организации в виде нисходящей иерархии классов в этом пакете понять его и пользоваться им намного легче , чем может показаться на первый взгляд. В табл. 25 . 1 приведены лишь некоторые из многих классов АWГ. Табл ица 25.1. Избранные классы иэ библиоте ки АWТ Кnасс AJl'l'Event AJl'l'Eventиul.ticaster ВorderLayout Button Canvas CardLayout CheckЬox CheckЬoxGroup CheckЬoxМe n uit:ea Choice Оnм санме Инкапсулирует события из библиотеки АWГ Доставляет события многим приемникам Диспетчер граничной компоновки с помощью следующих пяти компонентов: North, South, Еавt, 1fest и Center Создает элемент управления в виде экранной кнопки Пустое, свободное от семантики окно Диспетчер карточной компоновки, эмулирую щий индексиро­ ванные карты. Огображается только одна, самая верхняя карта Создает элемент уп равления в виде флажка Создает группу флажков Создает переключаемый пункт меню Создает ра скрывающийся список
Гл ава 25. Введение в библиоте ку АWТ: работа с окнами, графикой и те кстом 887 Класс Color Coiaponent Container CUrsor Dialog Di11 1& nsion Event EventQueue FileDialoq FlowLayout Font Fontмetrics Fraшe Graphics GraphicsDevic:e Продал:женш табл. 25.1 Опис ание Уп равляет цветом переносимым и не зависящим от платфор­ мы способом Абстрактный суперКJ J асс для различных компонентов АWГ ПодКJ J асс, который является производным от класса Coшponent и может содержать другие компоненты Инкапсулирует растровый курсор Создает диалоговое окно Определяет размеры объекта. Ширина сохраняется в поле width, высота - в поле heiqht Инкапсулирует события Организует очередь событий Создает окно, в котором можно выбрать файл Диспетчер поточной компоновки, размещающий компоненты слева направо и сверху вниз Инкапсулирует печатный шрифт Инкапсулирует типографские параметры шрифтового оформ­ ления текста. Эти параметры помогают отображать текст в окне Создает стандартное окно, снабже нное строкой заголовка, эле­ ментами управления размерами и строкой меню Инкапсулирует графический контекст. Этот контекст исполь­ зуется различными методами вывода дан ных в окне Описывает графическое устройство вывода, например экран монитора или принтер GraphicвEnvironшent Описывает коллекцию доступных объектов типа Font и GraphicsDevice GridВagConвtraintв Описывает различные огран ичения, накладываемые на КJ J acc GridВagLayout GridВagLayout Диспетчер сеточно-контейнерной компоновки , отображаю­ щий компоненты в соответствии с ограничениями, наКJ J адыва­ емыми средствами класса GridВagCon вtraintв GridLayout Диспетчер сеточной компоновки, отображающий компоненты в виде двухмерной сетки Iшage Инкапсулирует графические изображения Insets Инкапсулирует гран ицы контейнера LaЬel Создает метку, отображающую символьную строку Li st Создает список, из которого пользователь может выбирать от­ дельные эл ементы. Аналоги чен стандартному списковому окну в Windows МediaTraclter Уп равляет мультимедийными объектами Мenu Создает ниспадающее меню М.nuВar Создает строку меню МenuCoiaponent Абстрактный КJ J acc, реализуемый разл ичными КJ J ассами меню
888 Часть 11. Библ иоте ка Java Класс Мenuit81 11 МenuShortcut Panel Point Polyqon PopupМenu PrintJoЬ Rectanqle RoЬot ScrollЬar ScrollPane SystU1Color TextArea TextC01 11p onent TextField тoolkit Window Окинчаниетабя.25.1 Описание Соадает пункт меню Инкапсулирует "горячую клавишу" для быстрого выаова пункта меню Простейший конкретный подкласс, проиаводный от класса Container Инкапсулирует пару декартовых координат, хранящихся в его членах хиу Инкапсулирует многоугольник Соадает всплывающее меню Абстрактный кл асс, представляющий аадание на печать Инкапсулирует прямоугольник Подцерживает автоматическую проверку прикладных про­ грамм на основе библиотеки АWТ Соадает элемент управления в виде полосы прокрутки Контейнер, п редоставляющий гориаонтальную и вертикаль­ ную полосы п рокрутки для другого компонента Содержит цвета для таких виджетов (т.е. эл ементов) ГПИ, как окна, полосы прокрутки, текстовые поля и пр. Соадает элемент управления в виде многострочноrо текстово­ го редактора Суп еркласс для классов TextArea и TextField Соадает элемент уп равления в виде однострочного текстового редактора Абстрактный класс, реалиауемый в библиотеке АWТ Соадает окно беа рамки , строк меню и ааголовка Базовая структура библиотеки АWТ не менялась еще с версии jаvа 1.0, и поэто­ му некоторые первоначальные методы из этой версии уже устарели и заменены новыми , хотя они все еще поддерживаются в Java ради обратной совместимости. Но поскольку эти методы не предназначены для применения в новом коде, то они в данной книге не рассматриваются. Ос новные пол ожения об окнах Окна определяются в библиотеке АWТ в соответствии с иерархией классов, которые обеспечивают функциональные и кон кретные возможности на каждом уровне. Два наиболее употребительных класса для формирования окон происхо­ дят от класса Р ane l, применяемого в аплетах, или же от класса Fr ame , создающего стандартное окно прикладной программы. Бальшую часть функциональных воз· можностей эти окна наследуют от своих родительских классов. Поэтому описание иерархии классов, связанных в этими двумя классами, является основополагаю­ щим для их понимания . На рис. 25 . 1 показана иерархия классов Pane l и Frame . Рассмотрим каждый класс из этой иерархии в отдел ьности.
Гл ава 25. Введе ние в библиоте ку AWT: работа с окнами, граф икой и те ксто м 889 Component Container // Window Panel // Frame Рис. 25 .1 . Иерархия кл ассов Panel и Frame Класс Component На вершине рассматриваемой здесь иерархии находится класс Component. Этот класс является абстрактным, инкапсулируя все атрибуты визуального компонента. За исключением меню, все элементы поль:ювательского интерфейса являются подклас­ сами, производными от класса Component. В этом классе определяется больше сотни открытых методов, отвечающих за управление собЬТ ТИ ями, в том числе от клавиату­ ры и мыши, перемещения и изменения размеров и перерисовки окон. (Многие из этих методов уже применялись при создании аплетов в примерах глав 23 и 24.) Объект класса Component отвечает за запоминание текущего цвета фона и переднего плана, а также шрифта, выбранного для форматирования текста, отображаемого в окне. Класс Container Класс Containe r является производным от класса Component. Он предоставляет дополнительные методы, позволяющие вкладывать в него другие объекты класса Component. В объекте класса Container могут храниться и другие объекты этого же класса, поскольку они сами являются экземrumрами класса Component. Это дает возможность построить многоуровневую систему вложенности. Контейнер отвеча­ ет за компоновку (т.е. расположение любых компонентов) , которую он содержит. Это достигается с помощью различных диспетчеров компоновки, речь о которых пойдет в гл аве 26. Класс Panel Класс Pane l является конкретным и производным от класса Container. Та ким об­ разом, класс Pane l можно рассматривать как представляющий рекурсивно вложен­ ный , конкретный экранный компонент. Кроме того, класс Panel служит суперклас­ сом для класса Арр le t. Когда данные, выводимые на экран, направляются в аплет, они воспроизводятся на поверхности объекта типа Pane l. По существу, класс Pane l пред­ ставляет окно без строк заголовка и меню, а также ограничивающей рамки. Именно
890 Часть 11. Библиоте ка Java поэтому указан ные элементы не видны, когда аплет запускается на выполнение в окне браузера. Когда же аплет запускается в средстве просмотра аплетов, то последнее пре­ доставляет строку заголовка и ограничивающую рамку для окна. В объект класса Pane l могут быть добавлены и другие компоненты с помощью метода add (), наследуемого от класса Container. Как только эти компоненты бу­ дут введены, их можно будет размещать и изменять их размеры вручную, исполь­ зуя методы setLocation (), setSize (), setPre ferredSize () или setBounds (), определенные в классе Component. Класс Window В этом классе создается акно верхнего уровня, которое не содержится в другом объекте, а располагается непосредственно на рабочем столе. Как правило, созда­ вать объекты класса Window непосредственно не требуется. Вместо этого обычно испол ьзуется подкласс Frame , производный от класса Window. Класс Frame Этот класс инкапсулирует то , что обычно вос принимается как окно. Этот класс является производным от класса Wi ndow и представляет окно в виде фрейма со строками заголовка и меню, обрамлением и элементами управления размерами окна. Внешний вид фрейма ти па Frame может отличаться в зависимости от кон­ кретной среды. На рисун ках , приведенных в данной книге, представлены момен­ тал ьные снимки экрана, отражающие особенности разных сред. Класс Canvas В связи с те м, что класс Canva s не является частью иерархии аплетов или об­ рамляющих окон, он может показаться на первый взгляд не особенно полезным. Этот класс является производным от класса Component и инкапсулирует пустое окно , в котором можно рисовать и воспроизводить содержимое. Пример приме­ нения класса Canvas будет приведен далее в этой книге. Работа с обрамл яющ ими окнами После аплета тип окон, которые придется чаще всего создавать на основе биб­ лиотеки АWГ, является производным от класса Frame . Этот класс служит для соз­ дания дочерних окон в аплетах , а также окон верхнего уровня или дочерних окон для автономных прикладных программ. Как упоминалось ранее , этот класс позво­ ляет создавать окна в стандартном стиле. Ниже приведены два конструктора класса Frame. Fra1 11 e() throvв HeadlesвException Fraae (Strinq saroлOJIOЖ) throvв BeadleввException В первой форме конструктора создается стандартное окно без заголовка, а во вто­ рой форме - окно с указанным за голов ком. Следует, однако, иметь в виду, что задать
Глава 25. Введение в библиотеку AWT: работа с окнами, графикой и текстом 891 конкретные размеры окна нельзя, - их следует устанавливать после его создания. При попытке создать экземпляр класса Frame в среде, где не поддерживается взаи­ модействие с пользователем, генерируется исключение типа HeadlessException. Имеется ряд ключевых методов, которые придется вызывать, работая с обрамляю­ щими окнами типа Frame . Эти методы рассматриваются в последующих разделах. Уста новка размеров окн а Метод setSize () используется для установки размеров окна. Ниже приведена общая форма этого метода. void setSize (int ноаа• /IDltPIUI& , int ноаая вsrco!l'a) void setSize (Dimension - нoвsre_.PSSИepl l ) - Новые размеры окна обозначаются параметрами новая ширина и нов ая высота или же в полях width и height объекта типа Dimension, передаваемого в качестве параметра новые_ра змеры. Размеры окна задаются в пикселях. Метод getSi ze ( ) вызывается для получения текущих размеров окна. Ниже по­ каза но, выглядит одна из его общих форм. Этот метод возвращает текущий размер окна в полях width и height объекта класса Dimension. Dil8 8 ns ion getSize () Сокрытие и ото б раже ние окна Кактолько обрамляющее окно будет создано, оно останется невидимым до тех пор, пока не будет вызван метод setVi sЫe ().Ниже приведена его общая форма. void setVisiЬle (boolean приsнаж_"дянос!l'•) Компонент становится видимым, если в качестве аргумента пр из на к_ видимо сти методу setVisЫe () передается логическое значение true. В против­ ном случае компонент остается скрытым. Установка за головка окна Заголовок обрамляющего окна можно изменить, вызвав метод setTitle (),об­ щая форма которого приведена ниже, где параметр новый_ за головок обозначает устанавливаемый в окне новый заголовок. void setTitle (Strinq ноИIDi_sаго.повож) Закрытие о б ра мл яющего окна При использовании обрамляющего окна прикладная программа долж на уда­ лять окно с экрана после его закрытия, вызывая метод setVi siЫe ( false ). Чтобы перехватить событие закрытия окна, следует реализовать метод window Closing () из интерфейса Wi ndowL istener. В методе windowClosing ( ) следу­ ет удал ить окно с экрана. Этот прием демонстрируется в примере, приведенном в следующем разделе.
892 Часть 11. Библиоте ка Java Соэда ние обрамл яюще го окна в апл ете , построенном на о с нове библ иотеки АWТ Получить окно можно, создав экземпляр класса Frame , но поступать именно та­ ким образом приходится редко , поскольку пользы от такого окна немного . В част­ ности , нельзя не только принимать или обрабатывать события , происходящие в таком окне , но и просто выводить в него данные. Поэтому на практике придется в основном создавать подклассы, производные от класса Fr ame . Это даст возмож­ ность переопределять методы из класса Fr ame и обеспечивать обработку событий. Создать новое обрамляющее окно в аплете, построенном на основе библиоте­ ки АWГ, на самом деле совсем не трудно. Сначала создается подкласс, производный от класса Frame . Затем переопределяется любой стандартный метод аплета, напри­ мер init (),start () и stop (),чтобы отображать или скрьmать обрамля ющее окно (или фрейм) по мере надобности. И наконец, реализуется метод windowClosing (). Как только будет определен подкласс , производный от класса Frame , появится возможность создать объект этого класса. Это приведет к появлению обрамляю­ щего окна, которое , однако , будет первоначально невидимым. Сделать его види­ мым можно, вызвав метод setVisiЫe ().Окно создается с размерами по высоте и ширине, выбираемыми по умолчанию. Размеры окна можно установить и явным образом, вызвав метод setSize (). В приведенном ниже примере аплета создается подкласс SampleFrame , произв<r дный от класса Frame . Экземпляр этого класса создается в виде окна в методе init ( ) из класса App letFrame . Обратите внимание на то, что в классе Sample Frame вызы­ вается конструктор класса Frame . Это позволяет создать стандартное обрамляющее окно с заголовком, передаваемым в качестве параметра за голов ок. В данном приме­ ре аплета методы start ( ) и stop () переопределяются таким образом, чтобы пока­ зывать или скрывать дочернее окно соответственно. Это дает возможность автома­ тически удалить окно, когда прерывается вьmолнение аплета, закрывается окно или осуществляется переход на другую страницу, если аплет выполняется в окне браузера. А когда браузер возвращается к аплету, отображается также дочернее окно. 11 Создать дочернее окно в аплете import java . awt .*; import java.awt . event .*; import java . applet .*; /* */ <applet code= "AppletFrame " width=ЗOO height=50> </app let> 11 создать подкласс , производный от кл асса Frame class SarnpleFrarne extends Frarne { SarnpleFrame (St ring title ) { supe r (title ) ; 11 создать объе кт для обработки событий в окне MyWindowAdapter adapter = new MyWindowAdapter(this) ; // заре гистрировать его в каче стве приемника событий addWindowLi stener ( adapter );
Гл ава 25. Введе н и е в библиотеку AWT: работа с окнами, граф ико й и текстом 893 puЫ ic void paint ( Graphics g) { g.drawString ("This is in frame window" , 10, 40) ; // Это сообщение выводится в обрамляющем окне cla ss MyWindowAdapter extends WindowAdapter { SampleFrame sampleFrame ; puЫ ic MyWindowAdapter ( SampleFrame sampleFrame ) this .sampleFrame = sampleFrame ; puЫic void windowCloзing ( Wi ndowEvent we ) { sampleFrame .setVi siЬle (false) ; // создать обрамляюще е окно puЬlic class Ap pletFrame extends Ap plet { Frame f; puЬl ic void init () { f = new SampleFrame ("A Frame Window " ); // Обрамляюще е окно f.setSize (250, 250) ; f.se tVi siЬle (true) ; puЫic void start () { f.setVi siЬle (true) ; puЫic void stop () { f.se tVisiЬle (false) ; puЫic void paint ( Graphics g) { g.drawS tring ("This is in applet window ", 10, 20) ; 11 Это сообщение выводится в окне аnлета Пример выполнения данного аплета приведен на рис. 25.2. App let This is in applet wind ow 9А Fraшe Wi11dow l:J[QJГxl "л.P_P _ 10 . t " s . ta.. . rt_e _ d _ . _"" This is inframewindow Рис. 25.2. Создание дочернего окна в апл ете
89' Часть 11. Библиоте ка Java Обработка событий в обрамляющем окне Класс Frame является производным от класса Component, поэтому наследует все , что определено в классе Component. Это означает, что обращаться с обрам· ляющим окном можно таким же образом, как и с гл авным окном аплета. В част­ ности , для вывода данных в окне можно переопределить метод paint (), а для восстановления окна и добавления к нему обработчика событий - вызвать метод repaint (). Всякий раз, когда в окне происходят события , вызываются обработ­ чики событий, определенные для этого окна. В каждом окне обрабатываются свои события. Например, в приведенном ниже аплете создается окно , реагирующее на события от мыши. Гл авное окно аплета также реагирует на события от мыши. Поэкспериментировав с этой программой, вы обнаружите, что события от мыши посылаются тому окну, в котором они происходят. 11 Обра ботать события от мыши в дочернем окне и окне аплета import java .awt.*; import java .awt.event .*; import java . applet .*; /* */ <applet code= "WindowEvents" width=ЗOO he ight=50> </appl et> 11 созда ть подкласс , производный от класса Fraiae class SampleFrame extends Frame implements MouseLi stener, Mou seMotionLi stener String msg = ""; i nt mo useX= lO, mo useY=40; int movX= O, movY=O; SampleFrame (String title ) sup er (title) ; 11 зарегистрировать этот объект в каче стве приемника // собственных событий от мыши addМouseListener (thi s) ; addМou seMoti onLi stener (this ); 11 создать объект для обработки событий в окне MyWindowAdap ter adapter = new MyWindowAdapter (this ); 11 зарегистрировать этот объе кт в качестве приемника 11 событий в окне addWindowLi stener ( adapter); 11 обработать событие от щелчка кнопкой мыши puЫ ic void mou seClicked (MouseEvent me ) ( } 11 обработать событие наведения кур сора МЬIШИ на окно puЫ ic void mouseEntered (MouseEvent evtObj ) { 11 сохранить координаты mou seX = 10; mo useY = 54; msg = "Mouse just entered child."; 11 Кур сор мыши был толь ко что наведен на дочернее окно repaint () ; 11 обработать событие от ведения кур сора мыши от окна puЫ ic void mou seExited (MouseEvent evtObj ) {
Гл ава 25. Введени е в библиотеку АWТ: работа с окнами, граф икой и те ксто м 895 11 сохранить координаты mouseX = 10; mouseY = 54; msg = "Mouse just left child window. "; // Курсор мыши был толь ко что отведен от дочернего окна repaint () ; // обработать событие нажатия кнопки МЬIШИ puЫ ic void mousePressed (MouseEvent me ) { 11 сохранить координаты mou seX = me .getX() ; mouseY = me . getY (); msg = "Down" ; // Кнопка мыши нажата repaint () ; 11 обработать событие отпускания кнопки мыши puЬl ic void mou seReleased (MouseEvent me ) { // сохранить координ аты mouseX = me .getX() ; mouseY = me .getY (); msg = "Up"; // Кнопка мыши отпуще на repaint () ; // обработать событие перетаскивания курсора мыши puЫic void mouseDragged (MouseEve nt me ) { 11 сохранить координаты mou seX = me .getX() ; mo useY = me .getY (); movX = me .getX() ; movY = me .getY() ; msg = "*"i repaint () ; // обработать событие перемещения МЬIШИ puЫic vo id mouseMoved (MouseEvent me ) { 11 сохранить координаты movX = me .getX() ; movY = me .getY (); repaint (O, О, 100, 60 ) ; puЫic void paint ( Graphics g) { g.drawS tring (msg, mou seX, mo useY); g.drawString("Mouse at " + movX + ", "+movY, 10, 40); // Курсор мыши в точке с ука занными координатами cl ass MyWindowAdapt er extends WindowAdapter { SampleFrame sampleFrame ; puЫic MyWi ndowAdap ter (Samp leFrame sampleFrame ) this . sampleFrame = sampleFrame ; puЫ ic void windowClosing (WindowEvent we ) { sampleFrame .setVi siЬle (false) ; 11 Окно аплета puЫ ic class WindowEventз extends Applet impleme nts Mo useListener, MouseMotionListener
896 Часть 11. Библ иотека Java SampleFrame f; String msg = ""; int mou seX=O , mo useY=lO; int movX=O, movY=O; 11 создать обрамляюще е окно puЫ ic void init () { f = new SampleFrame ("H andle Mouse Eventз") ; 11 Обработка событий от ыы:ши f. setSize (ЗOO, 200) ; f.setVisiЬle (true) ; // зарегистрировать объе кт в каче стве приемника // собственных событий от мыши addМouseLiзtener (this) ; addМouзeMotionListener (thiз) ; // удалить обрамляющее окно при остановке аплета puЫ ic void stop() { f.setVisiЬle (falзe) ; // показать обрамляюще е окно при запуске апле та puЫ ic void start () { f. setVisiЬle (tru e) ; // обработать событие от щелчка кнопкой МЬШIИ puЫ ic void mo useClicked (MouseEvent me ) { } // обработать событие наведения кур сора МЬIШИ на окно puЫ ic void mou seEntered (MouseEvent me ) { 11 сохранит ь координаты mouseX = О; mou seY = 24; msg = "Моuзе just ente red applet window."; // Кур сор NЫDJИ был только что наведен на окно аnлета repaint () ; // обработать событие отведения курсора мыши от окна puЫ ic void mou seExited (MouseEvent me ) { 11 сохранить координ аты mouseX 2 О; mo useY = 24; msg = "Моuзе just left appl et window ."; // Кур сор мыши был только что отведен от окна аплета repaint () ; // обработать событие н ажа тия кнопки ыы:ши puЫ ic void mousePressed (MouseEvent me ) { 11 сохранить координаты mo useX = me .getX() ; mouseY = me .getY() ; msg = "Down"; // Кнопка мыши нажа та repaint () ; // обработать событие отпускания кнопки мыши puЫ ic void mouseReleased (MouseEvent me ) { // сохранить координаты mou seX = me.getX() ;
Гл ава 25. Введени е в библ иоте ку AWT: работа с окнами, графико й и те ксто м 897 mouseY = me .getY(); rnsg = " Up "; // Кнопка мыши отпущена repaint () ; 11 обработать событие перетаскивания курсора мыши puЫ ic void mou seDragged (Mou seEvent rne ) { 11 сохранить координаты mo useX = rne .getX() ; mouseY = rne .getY(); rnovX = rne .getX() ; rnovY = rne .getY(); msg = "*"; repaint () ; 11 обработать событие перемещения NЬIШИ puЫ ic void mou seMoved (MouseEvent rne ) ( 11 сохрани ть координаты movX = rne .getX() ; rnovY = rne .getY(); repaint (0, О, 100, 20) ; 11 отобразить сообще ние в окне аплета puЬlic void paint ( Graphics g) { g.drawS tring (msg, rno useX, mo useY) ; g.drawString("Mouse at " + rnovX + ", " + rnovY, О, 10); 11 Курсор МЫIПИ в точке с указанными координа тами Пример выполнения данного аплета показан на рис. 25.3 . Applet Mouse at 232, 37 Mouse just left applet wi ndow. Applet starte d. � <с • 0 Ha11dte Mo use Eve 11 ts GJ[g_!Гxl Mouse at 128, 1 08 * Рис. 25.3 . Обработка событи й от мыши в дочернем окне и окне аnлета
898 Ч асть 11. Библ иоте ка Java Соэда н ие оконной прикл адной про гра м мы Создание аплетов является типичным примером применения библиотеки АWГ вjava, тем не менее на основе библиотеки АWТ можно также создавать автоном· ные прикладные программы. Для этого достаточно создать экземпляр нужного окна или нескольких окон в методе ma in (). Например , в следующей прикладной программе создается обрамляющее окно, реагирующее на щелчки кнопкой мыши и нажатия клавиш. 11 Создать прикладную программу на основе библиотеки AWT import java . awt .*; import java.awt . event .*; import java . applet .*; 11 создать обрамляющее окно puЫ ic class AppWindow ext ends Frame { String keymsg = "This is а test ."; // Это тест String mousemsg = ""; int mou seX=ЗO, mouseY=ЗO ; puЫ ic AppWindow () { addKeyListener (new MyKe yAd apter (this) ); addМouseLi stener (new MyMous eAdapter (thi s) ); addWindowListener (new MyWindowAdapte r() ); puЫic void paint ( Graphics g) { g.drawString ( keyms g, 10, 40) ; g.drawString (mousemsg, mouseX, mo useY) ; // создать окно puЫic static void rnain ( String args[]) { AppW indow appwi n = new AppWi ndow () ; appwin .setSize (new Dimension (ЗOO, 200) ); appwin .setTitle ( "An AWT-Based Application" ); 11 Приложе ние на основе библиотеки АWТ appwin .setVi siЬle (true) ; class MyKe yAdap ter extendз KeyAdapter { AppWindow appWindow ; puЬlic MyKe yAdapter (AppWindow appWindow ) this . appWindow = appWindow ; puЫ ic void ke yTyped (KeyEvent ke ) { appWindow .keymsg += ke . ge tKeyChar () ; appWindow .repaint () ; }; class MyMouseAdapt er extends MouseAdapter { AppWindow appWindow ; puЫ ic MyMouseAdapt er (AppWindow appWindow ) this . appWindow = appWindow ; puЫ ic void rno usePressed (MouseEvent me ) {
Глава 25. Введение в библ иоте ку АWТ: работа с окнами, графикой и те кстом 899 appWindow .mouseX = me . getX(); appWindow .mouseY = me .getY () ; appWindow .mousemsg = "Mouse Down at " + appWindow .mouseX + ", " + appWindow .mous eY; 11 кнопка мыши нажата в указанной точке appWindow .repaint () ; class MyWindowAdapter extends WindowAdapter { puЫ ic void windowClosing { WindowEvent we ) System. exit (O) ; Пример выполнения данной прикладной программы показан на рис. 25.4. Однажды созданное обрамляющее окно начинает действовать самостоятельно. Обратите внимание на то , что метод mа in () завершается вызовом метода appwin . setVi siЫe (true ) . Но программа продолжает выполняться до тех пор, пока не закроется окно. По существу, при создании оконной прикладной программы ме­ тод ma in () служит для запуска окна верхнего уровня . После этого прикладная про­ грамма функционирует как приложение с ГП И, а не как консольное приложение вроде тех, что были представлены ранее. �ouse Down at 81 , 97 Рис. 25., , Пример выполнени я прикладной программы на основе библиоте ки АWТ Отображение информации в окне В самом общем смысле окно является контейнером для хранения информации. В предыдущих примерах текст уже выводи.лея в небольших количествах в окне, но в них еще не использовались возможности окон воспроизводить высококаче­ ствен ный текст и графику. На самом деле истинный потенциал библиотеки АWГ проявляется в поддержке именно таких возможностей окон. Поэтому ниже будут обсуждаться средства, предоставляемые библиотекой АWГ для вывода текста, гра­ фики и шрифтового оформления. Из этого обсуждения станет очевидной эффек­ тивность и уд обство этих средств.
900 Часть 11. Библиотека Java П оддержка граф ики В состав библиотеки АWТ входит большое разнообразие методов, поддержи­ вающих графику. (Эти методы поддерживаются также в окнах, создаваемых сред­ ствами библиоте ки Swing.) Вся графика воспроизводится относительно окна. Это может быть гл авное или дочернее окно аплета или же окно автономной приклад­ ной программы. Начало отсчета каждого окна находится в верхнем левом углу и имеет координаты 0,0, указываемые в пикселях. Весь вывод в окно выполняется в ко нкретном графическом контексте. Гр афи-ческий контекст, инкапсулируемый в классе Graphics, получается следующими двумя способами: • передается в качестве аргумента методу, например paint () или update (); • возвращается методом getGraphics ( ) из класса Cornponent. Среди прочего, в классе Graphics определяется ряд методов для рисования различных графических объектов, в том числе линий, прямоугольн иков и дуг. В одних случаях графические объекты рисуются только по контуру, в других - до­ полнительно заполняются цветом. Гр афические объекты рисуются и заливаются те кущим выбранным цветом, которым по умолчан ию является черный. Если рису­ емый графический объект выходит за пределы окна, он автоматически усекается. В этом разделе представлены избранные методы рисования из класса Graphics. На заметку! В версии 1.2 графические возможности Java расширились благодаря внедрению не­ скол ьких новых классов. К их числу относится класс Graphics2D, расширяющий класс Graphics. В этом классе поддержи вается ряд эффективных усовершенствований основных фун кциональных возможн остей класса Graphics. Для доступа к эти м расширенным фун кци­ ональным возможностям следует привести к типу Graphics2D графический контекст, полу­ чаем ый из такого метода, ка к paint (). В целях демонстрации графических возможностей Java в да нной книге достаточно и средств класса Grаphics, но для разработки пол ноценных графических прикладных программ придется досконально изуч ить класс Graphics2D. Ри сование линий Линии рисуются методом drawLine (), общая форма которого приведена ниже. void drawLine (int на vааоХ, int наvа.поУ, int жонецХ, int жонецУ) Метод drawLine () рисует линию текущим цветом от точки с координатами на ча лах, на чало У к точке с координатами конецХ, конец У. Рисование п рямоугол ьников Методы drawRe ct ( ) и fillRect ( ) рисуют ко нтурный и заполняемый пря моу­ гольники соответственно. Ниже приведены их общие формы. void drawRec t(int сверху, int слева , int 1DtpJU1a , int auco!l'a) void fillRect (int сверху, int слева , int шхр11 1Н а, int auco!l'a) Левый верхний угол прямоугольника расположен в точке с координатами св ерх у, сл ева . Размеры прямоугольника задаются в качестве параметров ширина и высо та .
Глава 25. Введение в библиотеку АWТ: работа с окнами, графикой и текстом 901 Для рисования прямоугольника со скругленными углами служат методы draw­ RoundRe ct () и fillRoundRe ct ().Ниже приведены их общие формы. void drawRoundRec t(int сверху, int слева , int JDrpJtНa , int а1 1 со!1'а , int дианв!l'рХ, int диане!l'рУ) void fillRoundRect (int С8врху , int слеJМ , int JDrpJrНa , int auco!l'a , int диане!l'рХ, int дианв!l'рУ) Прямоугольники , нарисованные этими методами , будуг иметь скругленные углы. Диаметр скругления по оси Х обозначается параметром диаме трХ, а диаметр скругления дуги по оси У- параметром диаме тр У. Рисование эппипсов и окружностей Для рисования эллипса служит метод drawOval (), а для его заливки -метод f i 11Ovа l ( ) . Общие формы этих методов выглядят следующим образом: void drawOval (int сверху , int слева , int 11U1р1 1.Н а,inta1 1 co!l'a) void fillOVal (int С8ерХУ, int еле.а , int unq»u1a , int .&IZCO!l'a) Эллипс рисуется внутри ограничивающего прямоугольника, верхний левый угол которого имеет координаты св ерху, слев а, а размеры обозначаются параметрами шир ина и высо та . Чтобы нарисовать круг, следует указать ограничивающий квадрат, т.е. прямоугольник с одинаковыми значениями параметров шир ина и высота. Рисование дуг Дуги могут быть нарисованы методами drawArc () и fillArc (), общие формы которых выглядят так: void drawArc(int сверху, int слеаа, int unq»u1a, int .li iW CO!l'a , int на чалышй_угол , int уголуа.sворо !l'а) void fillArc (int сверху , int с:.пева , int uпq»и1а , int .вuco!l'a , int начальнмй_угол, int уголуаsворо!l'а) Дуга ограничивается прямоугольником, верхний левый угол которого нахо­ дится в точке с координатами св ерху, сл ева , а его размеры обозначаются пара­ метрами ширина и высо та . Дуга рисуется из положения, обозначаемого параме­ тром на ча льный_уГол, на угловое расстояние , определяемое параметром угол_ ра зв орота . Угл ы указываются в градусах. Нуль градусов соответствует горизон­ тали в положении часовой стрелки , показывающей три часа. Дуга рисуется в на­ правлении против часовой стрелки , если значение параметра угол_р азворота положительно , и по часовой стрел ке , если значение параметра угол_разворо та отрицательно. Та ким образом, чтобы нарисовать дугу от 1 2 до 6 часов, следует ука­ зать на чаль ный_угол равным 90° , а угол_разворо та - 180°. Ри сование многоу гоп ь ников Используя методы drawPolygon () и fillPolygon (),можно рисовать фигуры произвольной формы. Ниже приведены общие формы этих методов. void drawPolyqon (int х[] , int у[ ] , int жOJDtvec-o !l'oveж) void fillPolyqon (int х[ ] , int у( ] , int Ж�V8C!l'BO= !l'OV8Ж)
902 Часть 11. Библиотека Java Ко нечные точки многоугольника указываются парами координат в массивах х и у. То чки с координатами в массивах х и у обозначаются параметром количе ство_ то чек. Имеются альтернативные формы этих методов, где многоугольник опреде· ляется объектом класса Polygon. Демо нстрация м етодов р исования Описанные выше методы рисования демонстрируются в следующем примере аплета: 11 Нарисовать графические элементы import java.aw t.*; import java . applet .*; /* */ <applet code="GraphicsDemo " width=350 height=700> </appl et> puЫ ic class Graphi csDemo extends Appl et { puЫ ic void paint (Graphics g) { 11 нарисовать линии g.drawLine (O, О, 100, 90) ; g.drawLine (O, 90, 100, 10) ; g.drawLine (40, 25, 250, 80) ; 11 нарисовать прямоугол ьники g.drawRect (10, 150, 60, 50) ; g. fillRect ( 100, 150, 60 , 50) ; g.drawRoundRect (190, 150, 60 , 50, 15, 15) ; g. fillRoundRect (280, 150, 60 , 50, 30, 40) ; 11 нарисовать эллипсы и окружности g.drawOval (lO, 250, 50, 50 ) ; g. fill0val (90, 250, 75, 50) ; g.draw0val (190, 260, 100, 40) ; 11 нарисовать дуги g.drawArc (lO, 350, 70, 70, О, 180) ; g. fil1Arc (60, 350, 70, 70, О, 75) ; 11 нарисовать int xpoints [) int ypoints [] intnum=5; NНОГОУГОЛЬНИК { 10, 200, 10, 200, 10} ; = {450, 450, 650, 650, 450) ; g.drawPolygon (xpoints, ypoints, num) ; Пример выполнения данного аплета показан на рис. 25.5. Изменени е размеро в граф ики Нередко возникает потребность установить такие размеры граф ического объекта, чтобы вписать его в текущие размеры того окна, в котором он рисует­ ся. Для этого следует получить текущие размеры окна, вызвав метод ge tSize () для объекта окна. Этот метод возвратит размеры окна, инкапсулируя их в объекте класса Dimen sion. Получив текущие размеры окна, можно соответственно мае· штабировать графику, рисуемую в этом окне.
Глава 25. Введение в библиотекуAWT: работа с окнами, графикой и текстом 903 , Applet Vi� Grapl\icsOemo Dо о Applet started. Рис. 25.5. Пример рисования графики в окне аплета Для демонстрации этого приема рассмотрим пример аплета, где исходный ква­ драт размерами 200х200 пикселей будет увеличиваться на 25 пикселей после каж­ дого щелчка кнопкой мыши до тех пор, пока не достигнет размеров 500х500 пиксе­ лей . А в результате последующего щелчка установятся исходные размеры квадрата 200х200 пикселей, и весь процесс начнется с самого начала. Прямоугольник рисуется по внутренней границе окна, а внутри прямоугольни­ ка - знак Х, чтобы таки м образом заполнить окно. Этот аплет можно запустить на выполнение в средстве просмотра аплетов appletviewer, но нельзя в окне бра­ узе ра. 11 Изменение размеров графики , чтобы вписать ее в текущие размеры окна import java . applet .*; import java .aw t.*;
90' Часть 11. Библ иотека Java import java.awt . event .*; /* */ <applet code="ResizeMe " width=200 height=200> </applet> puЫ ic class Re sizeMe extends Applet ( final int inc = 25; int max = 500; int min = 200; Dimension d; puЫ ic Res izeMe () ( addMouseListener (new Mous eAdapte r() { }); puЫ ic void mou seReleased ( MouseEvent me ) ( int w = (d.width + inc) > max?min : (d.width + inc) ; int h = (d.height + inc ) > max?min : (d.height + inc) ; setSize (new Dimension (w, h) ); puЬlic void paint ( Graphics g) ( d = getSize (); g.drawLine (O, О, d.width- 1, d.height-1) ; g.drawLine (O, d.heigh t-1, d.width- 1, 0 ); g.drawRe ct (O, О, d.width- 1, d.height-1) ; Работа с цветом В Java поддерживаются цвета в переносимой , аппаратно-независимой форме. Цветовая система в библиотеке АWГ позволяет сначала задать какой угодно цвет, а затем найти наилучшее соответствие этому цвету с учетом аппаратных ограни­ чений, накладываемых на отображение в том устройстве, где выполняется аплет или прикладная программа. Та ким образом, прикладной код не должен зависеть от того , насколько отличается поддержка цвета в разных аппаратных устройствах. Цвет инкапсулируется в классе Color. Как пояснялось в гл аве 23, в классе Color определяется несколько констант (вроде Color . Ыасk) для описания наиболее употребительных используемых цветов. Имеется также возможность создавать свои цвета , используя один из до­ ступных конструкторов цвета. Ниже приведены три наиболее часто используемые формы конструкторов класса Color. Color (int жрасюdi, int аелею.di , int Cl lU UIJi ) Color (int вначеюrе цse!l'a .RGВ) Color (float жpaCНJ.il, float аел8Н1 11 11 , float Cl lU UIJi ) Первый конструктор данного класса принимает три аргумента, задающие цвет в определенном сочетании красной, зеленой и синей составляющих. Значения этих составляющих должны находиться в пределах от О до 255, как показано ниже. new Color (255, 100, 100) ; // светло-красный цвет
Глава 25. В ведение в библиоте ку AWT: работа с окнами, граф икой и текстом 905 Второй конструктор класса Color принимает единственный аргумент в виде целочисленного значения цвета, составленного из трех основных цветов (RGB). Это целочисленное значение организовано таким образом, чтобы на красную со­ ставляющую приходились двоичные разряды с 16-го по 23-й бит, на зеленую со­ ставляющую - с 8-го по 15-й бит, на синюю - с О-го по 7-й бит. Ниже приведен при­ мер применения такого конструктора. int newRed ; (OxffOOOOOO 1 (ОхсО « 16) 1 (ОхОО « 8) 1 ОхОО) ; Color darkRed ; new Color ( newRed) ; И последний конструктор класса Color принимает три значения составляю­ щих цвета в формате с плавающей то чкой и в пределах от О,О до 1,О, обозначаю­ щих относительные значения красной, зеленой и синей составляющих цвета. Составив цвет с помощью одного из описанных выше конструкторов, можно воспользоваться им в качестве образца для установки цвета переднего плана и/ или фона с помощью методов setForeground () и setBackground (),описанных в гл аве 23. Этот цвет можно также выбрать в качестве текущего для рисования. Методы из класса Color В классе Color определяется несколько методов, помогающих манипулировать цветами . Рассмотрим некоторые из них. И спол ьз ование цвета , насы ще нности и я ркости Цветовая модель HSB (оттенок-насыщенность-яркость) представляет собой альтернативу модели RGB (красный-зеленый-синий) для указания конкретного цвета. Образно говоря , оттенок в модели HSB представляет собой цветовой круг. Он может быть задан числовым значением в пределах от О,О до 1,О, приближен­ но образующих радугу со следующими основными цветами: красный, оранжевый, желтый зеленый, голубой , синий и фиолетовый. Насыщснностъ определяет интен­ сивность цвета по шкале в пределах от О,О до 1,О. Значения ярк ости также нахо­ дятся в пределах от 0,0 до 1,О, где 1 - белый, О - черный. В классе Color поддер­ живаются два метода, позволяющих выполнять взаимное преобразование цвета и з моделей RGB и HSB. Ниже приведены общие формы этих методов. static int HSBtoRGB (float owweнox , float нaCIDlfeн н ocwъ , float .irpxocwъ) static float [) RGBtoHSB (int хра сюпi, int .эелеюиi , int CJU Ur ii, float .эначеюrя[ ]) Метод HSBtoRGB () возвращает упакованное значение цвета RGB , совмести­ мое с конструктором Color ( int) . Метод RGBtoHSB () возвращает массив чисел с плавающей точкой, представляющих значения цвета HSB, соответствующие со­ ставля ющим цвета RGB. Есл и массив зна чения не пуст, то он принимает задан ные значения цвета HSB и возвращается . В противном случае создается новый массив, в котором возвращаются значения цвета HSB . Но в любом случае в элементе этого массива по индексу О содержится цвет, в элементе по индексу 1 - насыщенность, в элементе по индексу 2 - яркость.
906 Часть 11. Библиоте ка Java Методы qetRed (), qetGreen () , qetвlue () Вызвав методы getRed (),getGreen () и getBlue (), можно получить красную, зеленую и синюю составляющие цвета по отдел ьности. Ниже приведены общие формы этих методов. int qet.Red() int qetGreen () int qetвlue () Каждый из этих методов возвращает соответствующую составляющую цвета RGB , извлекаемую из младших 8 битов целого числа в вызывающем объекте типа Color. Метод qetRqb () Для получения упакованного представления цвета RGB предусмотрен метод getRed (). Ниже приведена его общая форма. Значение, возвращаемое этим мето­ дом, организовано описанным выше образом. int qetRGB () Уста новка текущего цвета граф и ки По умолчанию графические объекты рисуются текущим цветом переднего пла­ на. Этот цвет можно изменить , вызвав метод setColor () из класса Graphics: void setColor (Color ноssm_.цве�) где параметр новый_ цв ет обозначает новый цвет рисования. Вызвав метод get Color (), можно получить текущий установленный цвет, как показано ниже. Color qe tColor () Аппет, дем онстрирующи й цвета В следующем примере аплета составляется несколько цветов, с помощью кото­ рых воспроизводятся различные графические объекты: 11 Продемонстрировать цвета import java.aw t.*; import java . appl et .*; /* */ <applet code= "Colo rDemo " width=ЗOO height=2 00> </appl et> puЫ ic class Colo rDemo extends Applet { 11 нарисовать линии puЫic void paint ( Graphi cs g) { Color cl new Color (255, 100, 100) Color с2 = new Color (lOO, 255, 100) Color сЗ = new Color (lOO, 100, 255) g.setColor (cl) ; g.drawLine (O, О, 100, 100) ; g.drawLine (O, 100, 100, 0) ; g.setColor (c2) ; g.drawLine (40, 25, 250, 180) ; g.drawLine (75, 90, 400, 400) ;
Гл ава 25. Введение в библиоте ку AWT: р абота с окнами, граф и кой и те ксто м 907 g.setColor (cЗ) ; g.drawLine (20, 150, 400, 40) ; g.drawLine (5, 290, 80, 19) ; g.setColor (Color .red) ; g.drawOva l (lO, 10, 50, 50 ) ; g. fill0val (70, 90, 140, 100) ; g. setColor (Color .Ыu e) ; g.drawoval (l90, 10, 90, 30) ; g.drawRe ct (lO, 10, 60, 50) ; g.setColor (Color . cyan) ; g.fillRect (lOO, 10, 60, 50) ; g.drawRoundRe ct (190, 10, 60 , 50, 15, 15) ; Установка р,ежи м а рисовани я Режим рисования определяет, каким образом графические объекты рисуются в окне. По умолчанию новое содержимое, выводимое в окне , замещает любое су­ ществующее в нем содержимое. Но, вызвав метод setXORMode ( ) , можно также по­ лучить новые графические объекты , объеди ненные с помощью логической опера­ ции исключающее ИЛИ с предыдущим содержимым окна. Ниже приведена общая форма этого метода. void setxORМode (Color xorColor) Здесь xorColor обозначает цвет, объединяемый с помощью логической опера­ ции исключающее ИЛИ с содержимым окна во время рисования. Преимущество режима рисования с объединением содержимого по исключающему ИЛ И состоит в том , что он гарантирует видимость нового объекта независимо от того , каким цветом бьm нарисован прежний объект. Чтобы вернугься к методу рисования с перекрытием, нужно вызвать метод se­ trPaintMode ( ) следующим образом: void setPaintиode () Как правило, режим рисования с замещением графических объектов применя­ ется для обычного вывода, а режим рисования с объединением содержимого по ис­ ключающему ИЛИ - для специальных целей. В приведенном ниже примере аплета отображается перекрестье , отслеживающее курсор мыши. Вертикальная и горизон­ тальная черточки перекрестья объединяются по исключающему ИЛИ в режиме ри­ сования и поэтому остаются видимыми в окне независимо от цвета фона. // Продемонс триро вать приме нение режима рисования // с оОъединением содержимого по ис ключающему ИЛИ irnport java .awt.*; irnport java . awt . event .*; irnport java . applet .*; /* <applet code= "XOR" width=4 00 height=2 00> </applet>
908 Часть 11. Библ иоте ка Java */ puЫic class XOR extends App let { int chsX=lOO, chsY=lOO; puЫic XOR() { addМou seMotion Listener (new MouseMotionAdapter() puЫ ic voi d mous eMoved (MouseEvent me ) { int х = me.getX(); int у=me . getY(); chsX = х- 10; chsY = у- 10; repaint () ; }); puЫic void paint ( Graphics g) { g.drawLine (O, О, 100, 100) ; g.drawLine (O, 100, 100, 0) ; g.setColor (Color .Ыu e) ; g.drawLine (40, 25, 250, 180) ; g.drawLine (75, 90, 400, 400) ; g.setColor (Color . green) ; g.drawRect (lO, 10, 60, 50 ) ; g.fillRect (lOO, 10, 60 , 50) ; g.setColor (Color .red ) ; g.drawRoundRect (190, 10, 60, 50, 15, 15) ; g. fillRoundRect (70, 90 , 140, 100, 30, 40) ; g.setColor (Color .cyan) ; g.drawLine (20, 150, 400, 40) ; g.drawLine (5, 290, 80, 19) ; 11 объедини ть черточки перекре сть я по ис ключающему ИЛИ g.setXORМode (Color . Ыack) ; g.drawLine (chsX-10, chsY, chsX+ lO, chsY) ; g.drawLine (chsX , chsY-10, chsX , chsY+lO) ; g.setPaintMode () ; Пример выполнения данного аплета показан на рис. 25.6. Ap plet 1� Applet star1ed. Рис. 25.6 . Пример применен ия режима рисования с объеди нением содержи мого по исключающему ИЛИ
Гл ава 25. Введение в библиотеку AWT: работа с окнами, графикой и текстом 909 Работа со ш р ифтами В библиотеке АWГ поддерживается немало печатных шрифтов. Ранее традици­ онные для печати шрифты были важной частью формируемых на компьютере до­ кументов и изображений. Библиотека АWТ обеспечивает уд обство обращения со шрифтами, абстрагируя операции с ними и позволяя динамически выбирать их. У шрифтов имеется наименование семейства, логическое имя шрифта и наи­ менование гарнитуры. Наимеиование семейства является обобщенным назван ием шрифта , например Courier. Логи'Чl!ское имя определяет название, связываемое с конкретным шрифтом во время выполнения , например Monospaced, а наимена­ вание гарнитуръt - конкретный шрифт, например Courier Italic. Шрифты инкапсулированы в классе Fon t. Некоторые методы, определенные в классе Fon t, перечислены в табл. 25.2. В этом классе определяется также ряд статических методов. Табл ица 25.2 . Избранные методы из класса Font Метод static Font decode (Strinq строка) Ьoolean equals (OЬject обr.ект_шриф11 14 ) Strinq qetF&l ll.i ly () static Font qetFont (Strinq аюйство) static Font qetFont (Strinq свойство , Font стандартный_шрифт) Strinq qetFontN8JD8 () String getN&l ll8 () int qe tSize () int getStyle () int hashCode () Ьoolean isBold () boolean isitalic () boolean isPlain () Strinq toStrinq () Описание Возвращает шрифт по имени, обозначаемому параметром строка Возвращает логическое значение true , если вызывающий объект содержит такой же шрифт, как и указанный в каче­ стве параметра обr,ект_шриф11 14 Возвращает наименование семейства шрифт ов, к которому относится вызывающий шрифт Возвращает шрифт, связанный с указан ным системным свойством. Если указанное свойство отсутствует, то возвра­ щается пустое значение null Возвращает шрифт, связан ный с указанным систем ным свойство м . Если указанное свойство отсутствует, то возвра­ щается заданный стандартный_шрифт Возвращает название гарнитуры вызывающего шрифта Возвращает логическое имя вызывающего шрифта Возвращает размер вызывающего шрифта в пунктах Возвращает значения начертаний вызывающего шрифта Возвращает хеш-код, связан ный с вызывающим объектом Возвращает логическое значение true , если шрифт содер­ жит значение полужирного начертан ия ВOLD , а иначе -ло­ гическое значение false Возвращает логи ческое значение true, если шрифт содер­ жит значение наклонного начертания ITALIC, а иначе - логическое значение false Возвращает логическое значение true , если ш рифт содер­ жит значение простого начертания PLAIN, а иначе - логи­ ческое значение f'al se Возвращает строковый эквивалент вызывающего шрифта
910 Часть 11. Б иблиоте ка Java В классе Font определены переменные, перечисленные в табл . 25.3. Табл ица 25.Э. Переменные, определенные в классе Font Переменная Strinq n8Jl l8 float pointSize int siir:e int style Значение Имя ш рифта Размер шрифта в пунктах Размер шрифта в точках Начертани е ш рифта Определение доступных шрифтов Для работы со шрифтами зачастую нужно знать, какие именно шрифты уста­ новлены и доступны на конкретном компьютере. Для получения этих сведе­ ний служит метод getAva ilaЫeFont Fami lyName s (}, определенный в классе GraphicsEnvi ronment, общая форма которого приведена ниже. String [] getAvailaЬleFontFamilyNames () Этот метод возвращает массив символьных строк, содержащих наименования доступных семейств шрифтов. Кроме того , в классе Grap hicsEnvironment опре­ делен метод getAl lFon ts (} , общая форма которого показана ниже. Этот метод возвращает массив объектов типа Font, описывающих все доступные шрифты . Font [] getAllFonts () Эти методы являются членами класса GraphicsEnvironment, поэтому для их вызова потребуется ссылка на объект данного класса. Эту ссылку можно получить, вызвав статический метод getLoca lGraphicsEnvironme nt (}, определенный в классе Grap hicsEnvironment: static Graphi csEnvironшent qe tLocalGraphicsEnvironшent () В следующем примере аплета показано, как получить наименования всех до­ ступных семейств шрифтов: 11 ОтоОразить доступные шрифты /* */ <appl et code=" ShowFont s" width=550 height= бO> </appl et> import java . applet .•; import java .aw t.*; puЫ ic class ShowFont s extends Applet puЫ ic void paint (Graphics g) { String msg = ""; String FontLi st ( ]; GraphicsEnvi ronme nt ge = GraphicsEnvi ronment . getLocalGrap hicsEnvi r onme nt () ; FontList = ge .getAvailaЫeFontFami l yNames (); for(int i = О; i < FontList.length; i++)
Гл ава 25. Введение в библиотеку AWT: работа с окнами, графикой и текстом 911 msg += FontList[i] + " "; g.drawSt ring (msg, 4, 16) ; Пример выполнения дан ного аплета показан на рис. 25.7. Но имейте в виду, что список шрифтов может у вас отличаться от показанного на рис. 25.7 . Ap plet AЬadl МТ Condensed Ab adl tl<IT Condensed Extra Bold AЬadi МТ Condensed LlghtAl gerlan Ap plet started. Рис. 25 .7 . Пример получения списка доступных шрифтов Соэда ние и выб ор шрифта Чтобы выбрать новый шрифт, следует сначала создать объект класса Fon t, описы­ вающий шрифт. Один из конструкторов класса Fon t имеет следующую общую форму: Font (String ХНR_11 1р71 фта , int наvертанхе , int ра.знер_•_n,УRжтах) где параметр имя_шрифта обозначает наименование нужного шрифта. Это наиме­ нование может быть задано с помощью логического имени или названия гарниту­ ры. Во всех cpeдax Java поддерживаются следующие шрифты: Dialog, Dialoglnput, Sans Serif, Serif и Monospaced. Шрифт Dialog применяется в диалоговых окнах си­ стемы и выбирается по умолчанию в отсутствие каких-нибудь других устаномен­ ных шрифтов. Имеется также возможность воспользоваться любым другим шриф­ том, который поддерживается в конкретной исполняющей среде. Те м не менее такие шрифты могут быть доступны не всегда. Стиль (или начертание) шрифта обозначается параметром на черта ние. Значение начертания шрифта может состоять из одной или нескольких следую­ щих констант: Font . PLAIN (простое начертание) , Font . BOL D (полужирное на­ чертание) и Font . ITALIC (наклонное начертание). Начертания шрифтов можно сочетать, объединяя их с помощью операции логическое ИЛИ. Например, соче­ тание констант Font . BOLD и Font . ITAL IC задает полужи рное наклонное начер­ тание шрифта. И наконец, размер шрифта в пунктах обозначается параметром ра змер_ в _ пунк тах. Чтобы воспользоваться только что созданным шрифтом, следует выбрать его с помощью метода setFont (), определенного в классе Cornponent. Ниже приве­ де на общая форма этого метода, где объект_шрифта обозначает объект, содержа­ щий требуемый шрифт. void setFont (Font 06ъежт_11 1р71 фта) В приведенном ниже примере аплета выводятся образцы каждого из стандарт­ ных шрифтов. Всякий раз, когда в окне аплета производится щелчок кнопкой мыши , выбирается новый шрифт и отображается его наименование.
912 Часть 11. Библ иотека Java // Показать шрифты import java . appl et .*; import java .aw t.*; irnport java .awt . event .*; /* */ <ap plet code= "Sampl eFonts" width=2 00 height=lOO> </applet> puЫ ic class SampleFonts extends Applet { int next = О; Font f; String rns g; puЫic void init () { f = new Font ("Dialog" , Font .PLAI N, 12) ; rnsg = "Dialog" ; setFont (f) ; addМouseListener (new MyMouseAdapter (this) ); puЫ ic void paint (Graphi cs g) g.drawString (rnsg, 4, 20) ; class MyMouseAdapter extends Mou seAdapter { SarnpleFont s sarnpleFonts; puЫic MyMouseAdapter ( SarnpleFontз sampleFontз ) { thiз .samp leFontз = зampleFonts; puЫic void mou sePreззed (MouзeEvent rne ) 11 сменить шрифт после каждого щелчка кнопкой мыши sarnple Fonts.next++; зwitch ( sampleFont s.n ext ) { case О: sampleFont s.f = new Font ("Dialog" , Font .PLAI N, 12 ); sarnpleFont з.rnsg = "Dialog" ; break; case 1: sarnpleFonts.f new Font ("D ialoginput ", Font .PLAIN, 12 ) ; sampleFonts.msg = "Dialog!nput "; break; case 2: sampleFonts .f new Font ("S ansSerif" , Font .PLAI N, 12 ); sarnpleFont s.rnsg = "SansSerif"; break; сазе З: зampleFonts.f new Font ("Serif", Font .PLAIN, 12) ; sarnpleFont s.msg = "Serif"; break; case 4: sarnpleFont s.f new Font ( "Monospaced", Font . PLAIN, 12) ; sampleFonts .msg = "Mono spaced"; break; if (samp le Font s.n ext 4) зampleFont s.next -1;
Гл ава 25. Введение в библиотеку AWT: работа с окнами, графикой и текстом 913 sampleFonts . setFont (sampleFonts .f); sampl eFonts . repaint () ; Пример выполнения данного аплета показан на рис. 25.8 . Applet SansSerif Applet sta rted. Рис. 25.8. Пример отображе ния образцов доступ ных шрифтов Получение сведений о шрифте Допустим, требуется получить сведения о текущем выбранном шрифте. Для это­ го следует сна чала получить текущий шрифт, вызвав метод ge t Font ( ) . Этот метод определен в классе Grap hics, а его общая форма выглядит так: Font 9etFont () Получив текущий выбранный шрифт, можно извлечь сведения о нем, вызвав различные методы, определенные в классе Font. В следующем примере аплета отображаются наименование, семейство, размер и начертание выбранного в дан­ ный момент шрифта: // Вывести сведения о шрифте import java . appl et .*; import java .aw t.*; /* •/ <applet code=" Font lnfo" width=ЗSO height= 60> </applet> puЫ ic class Font lnfo extends Applet puЫic vo id paint ( Graphics g) { Font f = g.getFont (); String fontName = f.getName () ; String font Family = f.getFamily () ; int fontSize = f.getSize () ; int fontStyle = f.getStyle () ; String msg = "Family : " + fontName ; // String ms g = "Семейство : " + fontName ; msg += ", Font : " + fontFamily; // Шрифт msg += ", Size : " + fontSize + ", Style : "; // Размер шрифта if ((f ontStyle & Font .BOLD ) == Font .BOLD ) ms g += "Bold "; 11 Полужирное начертание
91' Часть 11. Б ибл иоте ка Java if ((fontStyle & Font .ITALIC) == Font .ITALIC) msg += "Italic "; 11 Наклонное начертание if ((fontStyle & Font .PLA IN) == Font .PLA IN) msg += "Plain "; 11 Простое начертание g.drawString (msg, 4, 16) ; } Управл ение фор мати р ованием выводимого текста Как пояснялось выше, вjava поддерживается немало шрифто в. В большинстве из них символ ы не имеют оди накового размера, и поэтому большинство шриф­ тов пропорциональн ы. Высота каждого символа, длина подстрочнъtх элеме нтов (т.е. нижней части некоторых букв, например у) , промежугок между горизонтальными линиями, а также размер шрифта в пун ктах меняются в разных шрифтах. Все эти (и другие) атрибугы шрифтов являются переменными и не представляют особого интереса для программирующего на Java, поскольку в этом языке не требуется руч­ ное управление почти всем вывод имым тексто м. Уч итывая , что размер каждого шрифта может отличаться и что шрифты мoryr изменяться в процессе выполнения прикладной программы, должен существовать какой-то способ определения размеров и прочих разнообразных атрибугов текуще­ го выбранного шрифта. Например, чтобы вывести одну строку текста после другой , необходимо каким-то образом узнать высоту шрифта и количество пикселей между строками. Для этой цели в библиотеке АWГ предусмотрен класс FontMetrics, ин­ капсулирую щий разнообразные сведения о шрифте. Прежде всего следует опреде­ лить общую те рминологию, употребляемую для описания шрифтов (табл . 25.4). Табл ица 25.,. Общая те рминоnогия, употребляемая для описания шрифтов Высота Размер строки текста сверху вниз Базовая линия Л иния, по которой выравниваются нижние края символов (за исключе- нием подстрочных элементов) Подъем Расстояние от базовой линии до верхнего края символов Спуск Расстоя ние от базовой линии до нижнего края символов Интерлиньяж Расстоя ние между нижним и верхним краями текстовой строки текста Как вы, должно быть, уже заметили, метод drawS tring () применялся во мно­ гих представлен ных ранее примерах. Этот метод выводит символьную строку те­ кущим цветом и шрифтом, начиная с указан ного местоположения . Но это место­ положение находится на левом краю базовой линии символ ов, а не в левом верх­ нем углу, как это принято в других методах рисования. Ти пичная ошибка состоит в попытке нарисовать символ ьную строку в точке с теми же самыми координата· ми, где обычно рисуется рамка. Так, если требуется нарисовать прямоугольник,
Гл ава 25. Введе н и е в библиоте ку АWТ: работа с окнами, графикой и тексто м 915 начиная с точки , имеющей координаты О, О, то он появится полностью. Если же попытаться вывести символьную строку "Type setting " (Набор текста) , начиная с то чки , имеющей координаты О,О, то появятся только подстрочные элементы букв у, р и g. Как станет ясно в дальнейшем, используя типографские параметры шрифта, можно определить правильное местоположение каждой отображаемой символьной строки . В классе FontMetrics определяется несколько методов, которые помогают управлять форматированием выводимого текста. Некоторые из наиболее употре­ бительных методов перечислены в табл. 25 .5. Эти методы помогают правильно отобразить текст в окне. Рассмотрим их применение на ряде примеров. Таблица 25.5. Иэбраннь1е методы иэ класса Fontмetrics Метод int bytesWidth (Ьyte Ь[] , int Нl lЧQJIO ,intко. .с ичество_байтов) int charWidth (char с[ ] , int НQIUJ JI O, int KOJIU'fecmвO_CWUIOI I08 ) int charWidth (char с) int charWidth (int с) int qet:A8cent () int qetDesoent () Font qetFont () int getвeiqbt () int qetLeading () int qetмaxAdvance () int qetмaxA8cent О int qe tмaxDescent () int [ ] qetWidths О Описание Возвращает ширину заданного rwлuчест ва _байтов из массива Ь, начиная с позиции НD'1 11ЛО Возвращает ширину заданного КОАичгаnва_Сl/М80Jl ов из массива с, начиная с позиции НD'1 11ЛО Возвращает ширину заданного символа с Возвращает ширину заданного символа с Возвращает подъем шрифrа Возвращает спуск шрифта Возвращает текущий шрифт Возвращает высоту текстовой строки. Это значение может быть использовано Дl l Я вывода многостроч· ноrо текста в окне Возвращает пробел между строками текста Возвращает ширину самого широкого символа. Если эта величина недоступна, возвращается значе­ ние -1 Возвращает максимальный подъем Возвращает максимальный спуск Возвращает ширину первых 256 символов int strinqWidth (String стfюк4) Возвращает ширину заданной строrш String toS tring () Возвращает строковый эквивалент вызывающего объекта Ото б раже ние много строчного текста Чаще всего класс Fo ntMetrics используется для определения расстояния меж­ ду строками текста. Кроме того , он определяет длину строки, которую нужно ото­ бразить. Далее будет показано, каким образом решаются эти задач и.
916 Часть 11. Библиотека Java Для того чтобы отобразить многострочный текст, прикладная программа должна отслежи вать текущую позицию выводимого текста. Всякий раз , когда встречается символ новой строки, координата У должна быть перенесена в на­ чало следующей строки . Когда отображается строка, координата Х должна быть установлена в точку, где эта строка оканчивается. Это позволит начать вывод следую щей строки таким образом, чтобы она начиналась сразу после окончания предыдущей строки. Чтобы определить расстоя ние между строками, можно использовать зна­ чение , возвращаемое методом getLeading (). Чтобы определить общую высо­ ту шрифта, следует добавить значение, возвращаемое методом getAscent (), к значению, возвращаемому методом getDescent (). Затем эти значения мож­ но использовать для расположения каждой строки выводимого текста . Но во многих случаях эти значения вряд ли понадобятся по отдел ьности . Зачастую нужно знать лишь общую высоту строки , состоящую из суммы величин интер­ линьяжа (междустрочного инте рвала) , подъема и спус ка шрифта. Эту величину можно получить , вызвав метод getHe ight ().Координату У следует увеличить на эту величину всякий раз, когда требуется перейти на следующую строку вы­ водимого текста. Чтобы начать вывод текста с того места, где был завершен вывод предыдущего текста в той же самой строке , следует знать длину в пикселях каждой отображае­ мой строки. Чтобы получить эту величину, следует вызвать метод stringWidth (). Эту величину можно использовать для вычисления координаты Х всякий раз, ког­ да выводится строка. В приведенном ниже примере показано, как вывести многострочный текст в окне аплета, где отображается также несколько предложений на одной и той же строке . Обратите внимание на переменные curX и curY. В них отслеживается те­ кущая позиция выводимого текста. 11 Продемонс трировать вывод мн огострочного текста import java . applet .*; import java . awt .*; !* */ <appl et code="MultiLine " width=З OO height=lOO> </app let> puЫ ic class Mu ltiLine extends Applet { int curX =O , curY=O; 11 текуща я позиция puЫic void init () { Font f = new Font ("S ansSerif", Font .PLAIN, 12) ; setFont (f) ; puЫic void paint ( Graphics g) { FontMetrics fm = g.getFontMe trics (); nextLine ("This is on line one. ", g) ; 11 Этот текст выводится в первой строке nextLine ("This is on line two.", g) ; 11 Этот текст выводится во второй строке sameLine ("T his is on same line.", g) ; 11 Этот текст выводится в той же самой строке sarneLine (" This , too.", g) ;
Гл ава 25. Введение в библиотеку АWТ: работа с окнами, графи кой и текстом 917 // И этот текст выводится в той же самой строке nextLine ("Thi s is оп line three .", g) ; // Этот текст выводится в тре ть ей строке curX = curY =О; 11 установить координаты в исходное // состояние перед каждой перерисовкой 11 перейти на следующую строку void nextLine (String s, Graphics g) { FontMetrics fm = g.ge t FontMetrics () ; curY += fm . ge tHeight (); // перейти на следующую строку curX = О; g.drawString (s, curX , curY) ; curX = fm .stringWi dth (s) ; // перейти в конец строки 11 отобразить текст в той же самой строке void same Line (String s, Graphics g) { FontMetrics fm = g.getFontMetrics (); g.drawS tring (s, curX, curY) ; curX += fm .st ringW idth (s) ; // перейти в конец строки Пример выполнения данного аплета показан на рис. 25.9 . his ls оп liпe опе. his is оп liпe two Thls is оп same line.Thls,too. his ls оп llпe thre e. Ap plet started . Рис. 25 .9 . Пример вывода многострочного те кста в окне аплета Центровка те кста В приведенном ниже примере аплета текст центруется в окне слева направо и сверху вниз. Сначала в этом аплете получаются величины подъема, спуска и ши­ рины строки, а затем вычисляется позиция , на которой текст должен быть ото­ бражен отцентрованн ым. // Центровка текста import java . applet .*; import java .awt.*; /* */ <applet code= "Cente rText " width=2 00 height= lOO> </applet> puЫic class Cente rText extends Applet { final Font f = new Font ("S ansSeri f" , Font .BOLD , 18) ;
918 Часть 11. Библ иотека Java puЫ ic void paint ( Graphics g) { Dimension d = this . ge tSize (); g.setColor (Color .white) ; g. fillRect (O, О, d.width, d.height ); g. setColor (Color .Ыack) ; g.se tFont (f) ; draWCenteredString ("This is centered .", d.width, d.height , g) ; 11 Этот текст отцентрован g.drawRect (O, О, d.width-1, d.height-1) ; puЬl ic void drawCenteredString (String s, int w, int h, Graphics g) { FontMe trics fm = g.getFontMetrics () ; int х = (w - fm.stringWidth(s)) / 2; int у= (fm. getAscent () + (h - (fm. getAscent () + fm . getDescent () ))/2) ; g.drawString (s, х, у) ; Пример выполнения данного аплета показан на рис. 25. l О. Apptet This is centered. Ap plet starte d. Рис. 25.10. Пример центровки текста в окне аnлета Выравнивани е мно гостро чно го текста В редакторе текст обычно отображается выровненным таким образом, чтобы один или оба его края образовывали ровную вертикальную линию. Так , в боль· шинстве редакторов текст можно выравнивать по левому и/или по правому краю, а также по центру. В приведенном ниже примере аплета показано, как добиться выравнивания многострочного текста. В этом аплете выравниваемые строки текста разбиваются на отдел ьные слова. Длина каждого слова отслеживается в текущем шрифте, и когда слово не умеща· ется в текущей строке , то производится автоматический переход на следующую строку. Каждая завершенная строка отображается в окне, выровненной выбран· ным в дан ный момент стилем. Всякий раз, когда производится щелчок кнопкой мыши в окне аплета, стиль выравнивания изменяется. Пример выполнения дан· ного аплета показан на рис. 25.11 .
Гл ава 25. Введение в библиотеку AWT: работа с окнами, граф ико й и тексто м 919 Дpplet uipui io а Java window is ciual l y quite easy. As you have seen, lhe АWТ provides support for fonis, colors, icxt, and graphics f course, you musi effectively · · еlheseiiemsifyouareio chieve professional results . Applet started. Дpplet Output io а Java window is actuaDy quiie easy. As you hav seen, lhe AWT provides support for fonis, colors, tcxt, and graphics . Of course, you musi effectively utilizelheseiiems ifyou areio achieve professional resulis Ap plet started Рис. 25.1 1 . Пример выравнивания многострочного текста в окне аплета 11 Продемонстрировать выравнивание многострочного текста import java . applet .*; import java .awt.*; import jav a.awt . event .*; import java .util .*; /* <title>Text Layout </title> <applet code= "TextLayout " width=200 height=200> <pararn name=" text" value="Output to а Java window is actual ly qu ite easy. As you have seen, the AWT provide s support for fonts, colors , text , and graphics. <Р> Of course, уои rnu st effectively utilize these iterns if you are to achieve profe ssional results ."> <pararn name=" fontname " value= "Seri f"> <pa ram name= "fontSize" va lue="l4"> </applet> */ puЫ ic class TextLayout extends App let ( final int LEFT = О; final int RIGHT = 1; final int CENTER = 2; final int LEFTRIGHT =З ; int align; Dimension d; Font f; FontMe trics fm ; int fontS ize; int fh, Ы; int space ; String text ; puЫic void init () ( setBackground (Color .white ); text = getPa rame t er ( "text" ); try ( fontSize = Integer.parseint (getParame t er ("fontSi ze" ));} catch (NurnЬerFormatExcept ion е) ( fontSi ze=l4 ;
920 Часть 11. Библиотека Java align = LEFT; addMouseListener (new MyMouseAdapter (thiз) ); puЫ ic vo id paint ( Graphics g) { upda te (g) ; puЫic void update ( Graphics g) { d = getSize (); g.setColor ( getBackground () ); g. fillRect (O, O, d.width, d.heigh t) ; if ( f==null) f = new Font (getParame ter(" fontname") , Font . PLAI N, fontSize) ; g. setFont (f) ; if(fm == null) { fm g.getFontMetrics (); Ы = fm. getAscent(); fh = Ы + fm .getDescent (); space = fm .stringWidth (" ") ; g. setColor (Color .Ыack) ; StringTokeni zer st = new StringTokeni zer (text) ; intх=О; int nextx; intу=О; String word, sp; int wordCount = О; String line = ""; wh ile (st.ha зMoreTokens () ) word = st . nextToken () ; if (word . equals ("<P>" )) drawString (g, line , wordCount , ) fm .st ringWidth (line) , у+Ьl ); line = ""; wo rdCount = О; х=О; у=у+(fh * 2); else { int w = fm .st ringWidth (word ) ; if (( nextx = (x+space+w) ) > d.width ) { drawString (g, line , wordCount , fm .st ringWidth (line) , у+Ы) ; ) line = ""; wordCount = О; х О; у=у+fh; if (x!=O) {зр ="";} else {sp ""; } line=line+зр+word; х=х+зрасе+w; wo rdCount++; drawString (g, line , wordCount , fm .stringWidth (line) , у+Ы) ; puЫic void drawString ( Graphi cз g, String line ,
Глава 25. В ведение в библиотеку AWT: работа с окнами, графикой и текстом 921 int wc, int lineW, int у) { swi tch (align) { case LEFТ : g.drawString (line , О, у) ; break; case RIGHT : g.drawString (line , d.width-lineW ,у); break; case CENTER : g.drawS tring (line , (d.width- lineW) /2 , у) ; break; case LEFТRIGHT : if (linew < (int ) (d. width* . 75) ) g.drawS tring (line , О, у) ; } else { int toFill = (d. width - lineW) /wc ; int nudge = d.width - lineW - (toFill*wc) ; int s = fm.stringWidth(" "); StringTokeni zer st = new StringTokenizer (line) ; intх=О; whi le (st.hasMoreTokens () ) { break; String word = st . nextToken () ; g.drawString (word, х, у) ; if(nudge>O) { х = х + fm .stringWidth (word) + space + toFill + toFill + 1; nudge --; else { х = х + fm .stringWidth (word) + space + toFill; class MyMouseAdapter extends Mous eAdapter { TextLayout tl; puЫ ic MyMouseAdapt er ( TextLayout tl ) { this.tl = tl; puЫ ic void mo useClicked (MouseEvent me ) { tl.align = (tl.align + 1) % 4; tl . repaint () ; Рассмотрим подробнее, каким образом действует этот aIUieт. Сначала в нем соз­ дается несколько констант для определения стиля выравнивания , а затем объявля­ ется ряд переменных. Метод init () получает отображаемый текст. Далее размер шрифта инициализируется в блоке операторов try/ catch, где затем устанавлива­ ется размер шрифта 14 пунктов, если параметр fontSize отсутствует в дескрип­ торе НТМL-разметки. Параметру text присваивается длинная строка текста с де­ скриптором HTML <Р> в качестве разделителя абзаца. Метод update () реализует механизм выравнивания текста в данном приме­ ре. В этом методе устанавливается шрифт, а из объекта типографских параме­ тров шрифта получается базовая линия и высота. Далее создается объект класса StringTokenizer, с помощью которого извлекается следующая лексема (строка,
922 Часть 11. Библиоте ка Java отделенная пробелами) из строки , заданной в параметре text. Если следующей лексемой оказывается дескриптор <Р>, то выполняется вертикальная прокрутка. В противном случае метод upda te ( ) проверяет, умещается ли длина лексемы, на· бранной текущ им шрифтом, по ширине столбца. Если строка уже заполнена тек· стом или же лексем для вывода больше не осталось, то строка выводится специ­ альной версией метода drawString (). Первые три случая вызова метода dr а wS t r ing ( ) просты. В каждом из них стро­ ка, передаваемая в переменной line , выравнивается по левому или правому краю или же по центру столбца в зависимости от текущего стиля выравнивания. Та к, есл и выбран стиль LEFTRI GHT, выравнивание производится по обоим краям стро­ ки. Это означает, что оставшееся свободное пространство нужно вычислить ( как разность ширины строки и столбца) и равномерно распределить между словами. В последнем методе из класса адаптера выбирается следующий стиль выравнива· ния всякий раз , когда производится щелчок кнопкой мыши в окне аплета.
26 элементов управления, диспетчеров компоновки и меню из лиотеки В этой гл аве продолжается обзор библиотеки АWТ. Сначала будуr рассмотре ны стандартные элементы управления и диспетч еры компоновки. Затем речь пойдет о меню и строке меню. Далее обсуждаются два компонента верхнего уровня: обыч­ ное диалоговое окно и диалоговое окно выбора файлов. И в конце гл авы обработ­ ка событий будет рассмотрена под другим углом. Эле.меи тами управления называют компоненты , кото рые дают пользователю возможность по-разному взаимодействовать с прикладной программой . К числу наиболее рас пространенных элементов управления относ ится экранная кноп­ ка. Диспетчер компоновки автоматически размещает компоненты в ко нтейнере. Поэтому внешний вид окна зависит как от состава элементов управления, так и от диспетчера компоновки , с помощью которого они размещаются в окне. Кроме элементов управления, обрамляющее окно может также включать стра­ ку меню стандартного стиля. Каждый пункт полосы меню раскрывает меню, в кото­ ром пользователь может выбрать необходимую ему команду. Строка меню всегда располагается в верхней части окна. Несмотря на отличия во внешнем виде , строки меню обрабатываются почти так же , как и другие элементы управления. И хотя размещать ко мпоненты в окне можно вручную , сделать это , как правило, не так-то просто . Диспетчер компонов­ ки выполняет эту задачу автоматически . В примерах, приведенных в начале этой гл авы , где расс матриваются различные элементы управления , применяется дис­ петчер компоновки , выбираемый по умолчанию. Он отображает компоненты в контейнере, размещая их слева направо и сверху вниз. После того как будуr рас­ смотрены элементы управления, речь пойдет о диспетчерах компоновки , а также будет разъяснено, как лучше всего располагать элементы управления в окне. Прежде чем перейти непосредственно к изложению материала этой гл авы , сле­ дует особо подчеркнуть, что в настоящее время ГПИ прикладных программ ред­ ко строятся только средствами библиотеки АWТ, поскольку для этой цели в Java внедрены более эффективные библиотеки Swing иjavaFX. Те м не менее материал, представленный в этой гл аве, не теряет по-прежнему свою актуальность по ряду причин. Во-первых, большую часть сведений и приемов, связан ных с эл ементам уп равления и обработкой событий, можно распространить и на другие библиоте­ ки Jаvа, предназначенные для построения ГПИ. (Как упоминалось в предыдущей гл аве , библиотека Sw ing построена на основе библиотеки АWТ. ) Во-вторых, обсуж­ даемые здесь диспетчеры компоновки могут применяться и в библиотеке Sw ing .
92' Часть 11. Библиотека Java В-третьих, компоненты библиотеки АWГ могут оказаться более подходящими для разработки мелких прикладных программ с ГПИ. И наконец, не исключено, что придется сопровождать или обновлять унаследованный код, в котором при­ меняются средства из библиотеки АWГ, и эта причина, вероятно, важнее всего для всех программирующих нajava. О сновные пол ожения об эл е м ентах управл ения В библиотеке АWГ поддерживаются следующие ти пы элементов управления. • Метки • Экранные кнопки • Флажки • Списки выбора • Списки • Полос ы прокрутки • Эл ементы редактирования текста Все эти элементы управле ния относятся к подклассам, производн ым от класса Comp one nt. В вод и удаление элем е нтов управления Чтобы включить элемент управления в состав окна, его нужно сначала ввести в него. Для этого необходимо создать экземпляр требуемого элемента управле­ ния, а затем ввести его в окно с помощью метода add ( ) , определенного в классе Container. У метода add ( ) имеется несколько общих форм. В примерах, приведен­ ных в начале данной гл авы , используется следующая общая форма этого метода: CoJDponent add (Coшponent CCUJ1Xa) где параметр ссылка обозначает конкретную ссылку на экземпляр элемента управ­ ления , который требуется ввести . Метод add ( ) возвращает ссылку на этот объект. Как только элемент управления будет введен , он появится в родительском окне при его отображении. Иногда требуется уд алить эл ементы управления из окна. Для этой цели служит метод remo ve (), который также определен в классе Container. Ниже приведена одна из его общих форм. void re1 11 ove ( Coшponent ссuлха) Здесь параметр ссылка обозначает конкретную ссылку на экземпляр элемента управления, который требуется уд алить. Вызвав метод remo veAl l (), можно уда­ лить все эле менты управления.
Гл ава 26. П р и м енение элементов управления , диспетче ров компо новки ... 925 Реагирование на элементы управления За исключением меток, которые являются пассивными элементами пользова­ тельс кого интерфейса, каждый элемент управления извещает о событии в тот мо­ мент, когда к нему обращается пользователь. Например, когда пользователь щел­ кает на экранной кнопке, наступает событие, обозначающее э rу кнопку. Как пра­ вило, для обработки событий от элементов управления в прикладной программе сначала реализуется соответствующий интерфейс , а затем регистрируется полу­ чатель событий от каждого элемента управления. Как пояснялось в гл аве 24, после установки приемника событий извещения о событиях будут передаваться ему ав­ томатически . В последующих разделах описывается соответствующий интерфейс для каждого из перечисленных выше элементов управления. Ис кл ю чение ти па BeadlessException Большинство рассматриваемых в этой гл аве элементов управления из библи­ отеки АWГ имеют конструкторы , способные генерировать исключение типа Headles sException при попытке создать экземпляр компонента ГПИ в неинтерак­ тивной среде (т.е. в такой среде , где , например, нет монито ра, мыши или клавиаrу­ ры). Исключение типа HeadlessException бьvю внедрено в версии jаvа 1.4 . С по­ мощью этого исключения можно написать код, который можно приспособить к не­ интерактивным средам. (Разумеется, это возможно далеко не всегда .) Исключение типа HeadlessException не обрабатывается в примерах программ, представлен­ ных в этой гл аве , поскольку для демонстрации элементов управления из библиотеки АWГ требуется интерактивная среда. Метки Самым простым элементом управления является метка. Метка представлена объек­ том класса Labe 1 и содержит символьную строку, которая отображается как метка. Метки являются пассивными элементами управления, не подцерживающими взаимо­ действие с пользователем. В классе Label определяются следующие конструкторы: LaЬel () throwa HeadleaвException LaЬel ( String c!rp()xa ) throws HeadleввException LaЬel ( String с�роха , int способ) throws HeadlessException В первой форме конструктора создается пустая метка, а во второй форме - метка, содержащая заданную строку, которая выравнивается по левому краю. В третьей форме конструктора создается метка, содержащая заданную строку, выравнивание которой определяется параметром спо соб. Этот параметр должен принимать одну из трех констант: Labe l . LEFT, Label . RIGHT или Label . CENTER. Изменить состояние текста в метке можно с помощью метода setText (),апо­ лучить текущую метку - с помощью метода getText (). Ниже приведены общие формы этих методов. void aetText ( String C!l'poxa) String getText ()
926 Часть 11. Б иблиотека Java В методе setText () параметр строка обозначает новую метку. А метод get­ Text () возвращает текущую метку. Выровнять строку в метке можно с помощью метода setAlignment ().Чтобы получить текущ ий стиль выравнивания , следует вызвать метод ge tAli gnment ( ) . Ниже приведены общие формы этих методов. void setAliqnment (int способ) int 9e tAlign:aient () Параметр спо соб должен принимать одну из трех перечисленных выше констант. В следующем примере создаются три метки, которые затем вводятся в окне аплета: 11 Продемонс трировать приме нение ме ток import java .aw t.*; import java . applet .*; /* */ <applet code= "Label Demo " width=ЗOO height=2 00> </applet> puЫic class Label Demo extends Applet puЫic void init () { Label one = new Label ("One") ; Label two = new Label ("Two " ); Label three = new Label ( "Three") ; 11 ввести ме тки в окне аплета add (one) ; add (two) ; add (three ); На рис. 26. 1 приведен результат вывода меток в окне аплета LabelDemo . Обратите внимание на то , что размещение меток в окне выполнено с помощью диспетчера компоновки , выбираемого по умолчанию. Далее в этой гл аве будет по­ казано, как то чнее управлять размещением меток в окне. Applet started_ One Two Three Рис. 26 .1 . Вывод меток в окне аплета Labe l Demo Экранные кн опки Вероятно, наиболее широко употребляемым элементом управления является gкр анная кнопка - компонент, который содержит метку и извещает о событии, когда
Гл ава 26. П р именение эл ементо в упр авления, диспетчеров ко мпоновки ... 927 пользователь щелкает на нем кнопкой мыши. Экранные кнопки представлены объ­ ектами класса Button. В классе Button определяются следующие конструкторы: вutton () throva HeadlessException ВUtton (Strinq C!l'pOxa) throva Beadle aaException В первой форме конструктора создается пустая кнопка, а во второй форме - кнопка с заданной меткой строка . Как только кнопка будет создана, с помощью ме­ тода setLabel ( ) можно задать ее метку. А получить метку можно с помощью метода getLabel (). Ниже приведены общие формы этих методов, где параметр строка обозначает новую метку для кнопки. void aetLaЬel (Strinq C!l'pOXa ) Strinq qetLaЬel () Обработка событий от кнопок Когда пользователь щелкает на экранной кнопке , наступает событие действия. Извещение о нем посьmается любому приемнику событий, который бьm предвари­ тел ьно зарегистрирован на получение извещений о событиях действия от данного компонента. Каждый приемник событий реализует интерфейс ActionListener. В этом интерфейсе определяется метод actionPerforme d ( ) , который вызывается при наступлении события действия. В качестве параметра этому методу передается объект класса Ac tionEvent. Он содержит ссьmку на кнопку, сгенерировавшую собы­ тие, а также ссьmку на строку с камандой действия, связанную с этой кнопкой. По умол­ чанию строкой с командой действия является метка кнопки . Как правило, для обозна­ чения кнопки служит ссьmка на кнопку или строка с командой действия. (Оба способа обозначения экранных кнопок демонстрируются в приведенных далее примерах.) В приведенном ниже примере аплета демонстрируется создание трех кнопок с метками "Yes" (Да) , "No" (Нет) и "Undecided" (Неопределенно). Когда пользо­ ватель щелкает на одной из этих кнопок, выводится сообщение, извещающее о вы­ боре кнопки. В данном примере команда действия кнопки (которая по умолчанию является ее меткой) служит для определения нажатой кнопки. Чтобы получить метку, вызывается метод getActionCommand ( ) для объекта типа Ac tionEvent, который передается методу actionPerfo rme d ( ) . Пример выполнения аплета Bu ttonDemo приведен на рис. 26.2. 11 Продемон стрировать приме нение кнопок import java.awt.*; import java . awt . event .*; import java . applet .*; /* */ <appl et code= "ButtonDemo" width=2 50 height=l50> </applet> puЫic class But t on Demo ext ends Applet implement s ActionListener { String ms g = ""; Button ye s, по , maybe; puЫic vo id init () { yes = new Button ("Yes") ; no = new Button ("No " );
928 Часть 11. Библ иотека Java ma ybe = new Button ( "Unde cided" ) ; add (yes ); add(no ) ; add (maybe ); ye s.addActionListener (this ); no . addActionListener (thi s ); ma ybe .addActionListener (thi s) ; puЫ ic void actionPe rformed (ActionEvent ае) { String str = ae . getAc tionCommand () ; if(str. equ als ("Yes") ) { } msg = "You pressed Yes ."; 11 Нажа та кнопка Yes else if (str.equals ("No" )) { mэg = "You pressed No ."; 11 Нажата кнопка Мо } else { msg = "You pressed Undecided ."; 11 Нажа та кнопка Undecided } repaint (); puЬlic void paint ( Graphics g) { g.drawString (msg, 6, 100) ; Apptet v.-er: But ton .• Applet [! !�j � Undedded \ You pressed Ye s. Applet started. Рис. 26.2 . Выбор одной из трех кнопок в окне аплета ButtoпDemo Как упоминалось выше , помимо строк с командами действия кнопок, выяснить, какая именно кнопка была нажата, можно, сравнив объект, полученный из метода getSource () , с объектами кнопок, введенных в окне. Для этого придется вести спи­ сок вводимых объектов. Та кой способ демонстрируется в следующем примере аплета: 11 Распознать объе кты типа Вutton import jav a.awt.*; import java .awt . event .*; import java . applet .*; /* */ <applet code= "ButtonLi st" width=2 50 height=l50> </appl et>
Гл ава 26. П рименение элементов уп р авления, диспетче р ов ко мпоновки". 929 puЫic class ButtonList extends App let implements ActionListener { String msg = ""; Button Ыi st [] = new Button[З] ; puЫic void init () { Button yes = new Button ("Yes"); Button no = new Button ("No") ; Button ma ybe = new Button ("Undecided" ) 11 сохранить ССЬIЛКИ на кноп ки при их вводе Ыist [O] (Button) add (yes ); Ыi st [l) (Button ) add (no) ; Ыi st [2) = (Button ) add (maybe) ; 11 эареrистрировать приемники на получение уведомлений 11 о событиях действия for(inti=О;i<3;i++){ Ыist [i] .addActionLi stener (this) ; puЫic void actionPerforme d (ActionEvent ае ) { for(inti=О;i<З;i++){ if (ae .getSource () == Ыist [i] ) { } msg = "You pressed " + Ыist[i] .getLabel (); 11 Нажа та указанная кнопка } repaint () ; puЫic void paint ( Graphics g) { g.drawString (msg, 6, 100) ; В данном примере при вводе кнопок в окне аплета ссылка на каждую кнопку записывается в массив. ( Напомним, что метод add ( ) возвращает ссылку на кноп­ ку при ее вводе. ) В дальнейшем этот массив используется в методе action Pe rformed (), чтобы выяснить, какая именно кнопка была нажата. В простых прикладных программах распознать кнопки по их меткам, как пра­ вило , нетрудно. Но если метку кнопки предполагается изменять во время выпол­ нения программы или использовать кнопки с одинаковыми метками, то выяснить, какая именно кнопка была нажата, можно очень просто , если воспользоваться ссьшкой на ее объект. А если вызвать метод setAct ionCommand ( ) , то в строке с ко­ мандой действия , связанной с кнопкой, можно задать содержимое , отличающееся от метки кнопки . Этот метод изменяет строку с командой действия, не оказывая никакого вл ияния на строку, используемую в качестве метки кнопки. Та ким обра­ зом, задав строку с командой действия, можно отдел ить команду действия от мет­ ки кнопки. В некоторых случаях события действия , генерируемые кнопками или другими элементами управления , можно обрабатывать с помощью анонимного внутренне­ го класса ( см. гл аву 24) или лямбда-выражения ( см. гл аву 15). В качестве примера ниже показано, как установить обработчики событий действия в предыдущих при­ мерах аплетов, используя лямбда-выражения.
930 Часть 11. Б иблиоте ка Java 11 Приме ни ть лямбда- выраже ния для обработки событий действия ye s . addActionLi stener ( (ae) -> { msg = "Уои pressed " + ae . getActionCommand () ; // Нажа та кнопка repaint (); 1); no . addA ction Listener ( (ae) -> { msg = "Уои pressed " + ae . getActionCommand () ; repaint (); 1); maybe . addActionLi stener ( (ae) -> { ms g = "Уои pressed " + ae . getActionCommand (); repaint (); 1); Этот код вполне работоспособен потому, что в интерфейсе Ac tionListener определяется единственный абстрактный метод, а следовательно, он является функ­ циональным интерфейсом и может применяться в лямбда-выражениях. В общем, лямбда-выражение можно применять для обработки события от элемента управле­ ния из библиотеки АWГ, если в его приемнике определяется функциональный ин­ терфейс. Например, интерфейс ItemListener также является функциональным. Безусловно, традиционный способ обработки событий, применение для этой цели анонимного внугреннего класса или лямбда-выражения зависит от характера при­ кладной программы. В остальных примерах, представленных далее в этой гл аве, применяется традиционный способ обработки событий, чтобы исходный код этих примеров можно было скомпилировать практически в любой версии jаvа. Но ради интереса можно попытаться переделать обычные обработчики событий на лямбда­ выражения или анонимные внугренние классы там, где это уместно. Фл ажки Фла жО'К представляет собой элемент управления, предназначенный для вклю­ чения или отключения какого-нибудь режима. Он состоит из небольшой прямоу­ гольной ячейки , которая может содержать отметку в виде галочки, если флажок установлен, или быть пустой. Щелчком на флажке можно изменить его текущее состояние, т. е. установить или сбросить. Флажки могут использоваться как по от­ дельности , так и в группе. Они представлены объектами класса Checkbox. В классе Checkb ox поддерживаются следующие конструкторы: ChecltЬox () throws HeadlessException ChecltЬox (String C!Z'pOXa) throws HeadlessException CheokЬox (String c!Z'pOxa , boolean вхтоvено) throws HeadleвsException ChecltЬox (Strinq C!Z'pOXa , boolean вхл10Vено , ChecltЬoxGroup rpyn n a ф.пажхоа) throws HeadlessException - ChecltЬox (String с!Z'рОжа , CheckЬoxGroup rр,уппа ф.пажжоа , boolean .1 1 хл10Vено) throws HeadlesвException - В первой форме конструктора создается флажок с пустой изначально меткой. Исходно флажок находится в сброшенном состоянии. Во второй форме кон­ структора создается флажок, метка которого определяется параметром строка . Исходно флажок находится в сброшенном состоянии. В третьей форме можно
Гл ава 26. Применение эл ементо в управления, диспетчеров ко мпоновки" . 931 установить исходное состояние флажка. Если параметр включено принимает ло­ гическое значение true, то флажок будет исходно установлен, а иначе - сброшен. В четвертой и пятой формах создается флажок, метка которого определяется па­ раметром строка , а группа - параметром группа_ флажков. Если флажок не яв­ ляется частью группы, то параметр группа_ флажков должен принимать пустое значение nu ll. (Группы флажков описываются в следующем разделе .) Значение параметра включено определяет исходное состояние флажка. Для получения текущего состояния флажка служит метод gе tStаtе (),а для его установки в нужное состояние - метод setState (). Вызвав метод getLabe l (), можно получить текущую метку, связанную с флажком. А для того чтобы задать метку, следует вызвать метод setLabe l (). Ниже приведены общие формы этих методов. boolean qe tState () void setState (Ьoolean аж.пюvено) Strinq qetLaЬel () void setLaЬel (Strinq с�жа) Если параметр включено принимает логическое значение true , то флажок будет установлен. А если этот параметр принимает логическое значение false, то флажок будет сброшен. Заданная строка становится новой меткой, связанной с вызывающим флажко м. Обработка событий от флажков Всякий раз, когда флажок устанавливается или сбрасывается, наступает со­ бытие от элемента. Извещение о нем передается любому приемнику событий, который ранее зарегистрировался на получение извещений о событиях от эле­ ментов данного ко мпонента. Каждый приемник событий реализует интерфейс IternL istene r. В этом интерфейсе определяется метод i ternStateChanged (). Объект класса IternEvent задается в качестве параметра данного метода. Он со­ держит сведения о событии (например, выбор или отмена выбора) . В приведенном ниже примере аплета демонстрируется создание и применение четь1рех флажков. Исходно установлен первый флажок. Состояние всех флажков отображается в окне аплета. Оно обновляется всякий раз, когда изменяется состо­ яние любого флажка. Пример установки флажков в окне данного аплета приведен на рис. 26.3 . 11 Продемонс трировать приме нение фл ажк ов irnport java .aw t.*; irnport java .awt . event .*; irnport java . applet .*; /* */ <applet code= "CheckЬoxDerno " width=240 height=200> </applet> puЫic class CheckboxDerno extends Applet implements ItemListener { String rnsg = ""; CheckЬox windows , android, solaris, rnac ; puЫic void init () {
932 Часть 11. Библиотека Java windows new Checkbox ("Windows ", null, tru e) ; android new Checkbox ("Android") ; solaris new Checkbox ("Solari s") ; mac = new Checkbox ("Mac OS ") ; add ( wi ndows ); add ( android ); add (solaris ); add(mac) ; windows .add itemListener (thi s) ; android . additemListener (this) ; solari s.addi temListener (thi s) ; mac . additemLi stener (this ); puЫ ic void itemS tateChanged (ItemEvent ie) { repaint () ; 11 отобразить текуще е состояние флажков puЫic void paint ( Graphics g) { ms g = "Current state : "; 11 Текуще е состояние g.drawSt ring (msg, 6, 80) ; ms g =" Windows : "+windows .getState () ; g.drawString (msg, 6, 100) ; msg =" Android : "+android . getState () ; g.drawSt ring (msg, 6, 120) ; msg =" Solaris : "+solaris . getState () ; g.drawString (msg, 6, 140) ; ms g =" Мае OS : "+mac .getState () ; g.drawSt ring (msg , 6, 160) ; A pplet Р" Windows P �i if:9-@ Г Solar1s ГMacOS Current state: Wi ndows: true Androld: true Solaris: fa lse Мае 08: fa lse Applet starled. Рис. 26 .3 . Уста новка флажко в в окне аплета CheckЬoxDemo Кнопки -перекл юч ател и Допускается создание группы взаимоисключающих флажков, где одновремен· но может быть установлен оди н (и только один) флажок. Та кие флажки нередко
Глава 26. П р именение элементо в управления , диспетчеров компо новки... 933 называют кнопками-переключателями, потому что они похожи на переключатели ка­ налов в автомобильном радиоприемнике, где одновременно можно выбрать толь­ ко одну радиостанцию. Чтобы создать ряд кнопок-переключателей, нужно сначала определить группу, к которой они должны принадлежать, а затем указать эту груп­ пу при создании кнопок-перекл ючателей. Кнопки-переключатели представлены объектами класса CheckboxGroup. В этом классе определен только конструктор по умолчанию, создающий пустую группу. Чтобы выяснить, какая именно кнопка-переключатель установлена на данный момент, следует вызвать метод ge tSelectedCheckBox ().Аустановить кнопку-пе­ реключатель можно с помощью метода setSelectedChe ckbox ().Ниже приведе­ ны общие формы этих методов. CheckЬox qe tSelectedCheckЬox () void setSelectedCheckЬox (CheckЬox жнопжа-переж.mоvа �ель) Здесь параметр кнопка -переключа тель обозначает ту кнопку-переключатель, которую требуется установить. При этом установленная ранее кнопка-переключа­ тель сбрасывается. В следующем примере аплета демонстрируется применение группы кнопок-переключателей: // Продемонстрировать применение кнопок-переключателей import java .aw t.*; import java . awt . event .*; impo rt java . applet .*; /* */ <appl et code= "CBGroup " width=240 height=200> </applet> puЫic class CBGroup extends Ap plet impleme nts ItemListener { String msg = ""; Checkbox windows , android, solaris, mac ; CheckboxGroup cb g; puЫic void init () { cbg = new Chec kboxGroup () ; windows new Checkbox ("Windows ", cbg , true) ; android = new Che ckbox ("And roid", cbg , false) ; solaris = new Che ckbox ("Solaris", cbg , false) ; mac = new Chec kbox ("Mac OS ", cbg , false) ; add ( windows ); add ( android ); add (solaris ); add(mac) ; windows . additemLi stener (thi s) ; android . add!temListener (thi s) ; solaris . additemListener (thi s) ; mac . additemLi stener (this) ; puЬlic void itemStateChanged (ItemEvent ie) { repaint () ; // отобразить те куще е состояние кнопо к-переключателей puЫic void paint ( Graphi cs g) {
93' Часть 11. Библиоте ка Java ms g = "Current selection : "; ms g += cbg .getSelectedCheckЬox () .getLabe l () ; g.drawString (msg, 6, 100) ; Пример установки кнопок-переключателей в окне аплета CBGroup приведен на рис. 26.4 . Обратите внимание на круглую форму кнопок-переключателей. Applet ("Windows Co"�drolil1 r Solar1s (" MacOS Current selec1ion: Android Ap pt et star1ed. Рис. 26 ., . Уста новка кнопок-переключател ей в окне аплета CBGroup Элементы управления выбором Класс Choice служит для создания раскръюающегося списка элементов, из кото­ рого пользователь может делать свой выбор. Поэтому элемент управления типа Ch oice является разновидностью меню. Будучи неактивным, компонент типа Choice занимает ровно столько свободного пространства, сколько требуется для отображения выбранного на данный момент элемента. Когда пользователь вы­ бирает этот элемент щелчком на нем, раскрывается весь список, в котором мож­ но выбрать новый элемент. Каждый элемент в списке представлен символьной строкой, которая появляется в виде выровненной по левому краю метки в том по­ рядке, в каком дан ный элемент вводится в объект класса Choice. В классе Choice определяется только конструктор по умолчанию, который создает пустой список. Чтобы ввести элемент выбора в список, следует вызвать метод add ( ) . Этот ме­ тод имеет следующую общую форму: void add (Strinq .,. . ) где параметр имя обозначает вводимый элемент. Ввод элементов в список осущест­ вляется в том порядке, в каком делаются вызовы метода add ( ) . Чтобы выяснить, какой именно элемент выбран на данный момент из списка, достаточно вызвать один из двух методов: getSelecteditem ( ) или getSelect­ edindex ().Ниже приведены общие формы этих методов. String' qetselectedit:a a () int qetSelectedindex ()
Глава 26. П р именение эл ементо в упр авления, диспетче р ов ком п оновки ... 935 Метод getSelectedi tem () возвращает символьную строку, содержащую имя элемента, а метод getSelectedindex ( ) - индекс элемента. Первый элемент имеет нулевой индекс. По умолчанию выбирается первый элемент, введенный в список. Чтобы выяснить, сколько элементов содержится в списке, следует вызвать метод get ltemCount ().Адля того чтобы сделать элемент выбранным в данный момент, достаточно передать методу select () начинающийся с нуля целочислен­ ный индекс или символьную строку, совпадающую с именем элемента в списке. Общие формы этих методов приведены ниже. int ge titell l Count () void select (int Jl l(,lf eжc) void select (Strinq •Xl l ) По указанному индексу можно получить имя, связанное с элементом, доступ­ ным по этому индексу, вызвав метод ge titem (),имеющий приведенную ниже об­ щую форму, где индекс обозначает конкретный индекс требуемого элемента. Strinq qe titem (int Jl l(,lf eжc) Обработка событий от раскрывающихся списков Всякий раз, когда из раскрывающегося списка выбирается элемент, наступает со­ ответствующее событие. Извещение об этом событии передается всем приемникам , которые предварительно зарегистрировались на получение извещений о событиях от элементов данного компонента. Каждый приемник событий реализует интер­ фейс ItemL istene r. В этом интерфейсе определяется метод itemS tateChanged ( ) . В качестве параметра этого метода передается объект типа I temEvent. В приведенном ниже примере аплета демонстрируется создание двух раскры­ вающихся списков типа Choice. В одном из них производится выбор операцион­ ной системы, а в другом - браузера. Пример выбора элемента из раскрывающего­ ся списка в окне аплета Choice Demo приведен на рис. 26.5. // Продемонстрировать приме нение раскрывающихся списков import java .awt.*; import java .awt . event .*; import java . applet .*; /* */ <applet code= "ChoiceDemo " width=ЗOO height= lBO> </applet> puЬl ic class ChoiceDemo extends App let implements ItemL istener { Choice os, brow ser; String msg = ""; puЫic void init () { os = new Choice (); browser = new Choice () ; 11 ввести элементы в список операционных систем os .add ( "Windows") ; os .add ( "Android" ); os .add ( "Solaris") ; os.add("Mac OS");
936 Ч асть 11. Библ иоте ка Java 11 ввести элементы в список браузеров browser .add("Iпternet Explorer") ; browser .add("F ire fox" ); browser .add ( "Chrome") ; 11 ввести списки выбора в окне add(o s) ; add (browse r) ; 11 заре гис триро вать приемники событий от элементов os . addi temListener (this) ; browser.addi temListen er (this) ; puЫic vo id itemS tateChanged (ItemEvent ie ) { repaint () ; 11 отобразить резуль таты текуще го выбора из списков puЫic vo id paint { Graphics g) { msg = "Current OS : "; 11 Текущая ОС msg += os . getSelecteditem(); g.drawString (msg, 6, 120) ; msg = "Current Browser: "; 11 Текущий браузер msg += browser.getSelecteditem () ; g.drawS tring (msg, 6, 140) ; Windows • Current OS: Wi ndows current Browser: Flrefox Applet started. Рис. 26 .5. Выбор элемента из раскрывающегося списка в окне аnлета Cho ice Demo Использование списков В классе Li st предоставляется компактный прокручиваемый список, из ко­ торого можно выбирать многие элементы. В отличие от объекта класса Choice, который отображает только один элемент, выбранный из раскрывающегося спи· ска, объект класса List можно создать таким образом, чтобы он показывал любое количество эл ементов выбора в видимом окне. Подобный список можно создать и таким образом, чтобы из него можно было выбрать несколько элементов. В к.лас· се List предоставляются следующие конструкторы:
Гл ава 26. П р именение эл ементов управления, диспетч еров ко мпоновки ". 937 List () throvs Headlessixception List (int xomrvec�ao C!I'pOX) throvs HeadlessException List (int xomrvec�•o-с�ож, boolean .вuбор несжолъ.аrх sленен �о•) throvs Headles SException - - В первой форме конструктора создается элемент управления типа L i s t, который позволяет одновременно выбрать только один элемент. Во второй форме конструк­ тора параметр количе ство_ строк обозначает количество элементов, которые бу­ дут постоянно видимы в списке, тогда как остальные элементы можно просмотреть, прокрутив список. Тр етья форма позволяет выбрать одновременно два или несколь­ ко элементов из списка, если параметр выбор_не скольких_ элементов принимает логическое значение true. Если же этот параметр принимает логическое значение false, то из списка можно будет выбрать только один элемент. Для ввода элемента в список следует вызвать метод add { ) , у которого имеются две формы: void add (String •НR) void add (String .,. . , int Jr.НДехс) где параметр имя обозначает имя элемента, вводимого в список. В первой форме элементы вводятся в конце списка, а во второй элемент вводится в список по ука­ занному индексу. Индексация элементов в списке начинается с нуля. Чтобы вве­ сти эл емент в конце списка, следует указать значение -1 параметра индекс. Чтобы выяснить, какой элемент выбран в данный момент из тех списков, где допускается выбирать только один элемент, следует вызвать метод ge tSelected Item { ) или ge tSelectedindex {).Ниже приведены общие формы этих методов. Strinq getSelecteditem () int qe tSelectedindex () Метод getSelectedltern {) возвращает символьную строку, содержащую имя элемента. Есл и выбрано несколько элементов или пока еще не выбран ни один из них, то возвращается пустое значение nu ll. Метод ge tSelectedlndex {) возвра­ щает индекс выбранного элемента. Первый элемент имеет нулевой индекс . Если же выбрано несколько элементов или пока еще не выбран ни один из них, то воз­ вращается пустое значение -1 . Чтобы выяснить, какие элементы выбраны в данный момент из тех списков, где до­ пускается одновременно выбирать несколько элементов, следует вызвать метод get Selectedl tems {) или ge tSelectedlndexes {). В частности, метод getSelected Items {) возвращает массив, содержащий имена выбранных на данный момент эле­ ментов, а метод ge tSelectedindexes {) - массив, содержащий индексы выбранных в данный момент элементов. Ниже приведены общие формы этих методов. Strinq [] qetSelectedi teшs () int [] qe tSelectedindexes () Чтобы выяснить, сколько элементов содержится в списке, следует вызвать ме­ тод ge tltemC ount {). А с помощью метода select {) можно определить, какой именно эл емент выбран в данный момент. Для этих целей служит целочисленный индекс , начинающийся с нуля. Ниже приведены общие формы этих методов. int qetitemCount () void select ( int J1U1Дexc)
938 Часть 11. Библиоте ка Java По указанному индексу можно получить имя , связанное с элементом, досrупным по этому инде.ксу, если вызвать метод ge titem() . Этот метод имеет приведенную ниже общую форму, где индекс обозначает конкретный индекс требуемого элемента. Strinq qe tit- (int .-яде.хе) Обработка событий от списков Для обработки событий от списков следует реализовать интерфейс Ac tionListener. Всякий раз, коl'да производится двойной щелчок на элементе спи­ ска типа List, создается объект типа ActionEvent. Его метод ge tAc tionCornmand () можно вызвать для извлечения имени вновь выбранного элемента. Кроме того, при выборе или отмене выбора элемента одним щелчком на нем создается объект типа ItemE vent. Вызвав его метод getStateChanged (), можно выяснить, чем бьvю вы­ звано данное событие: выбором элемента или отменой его выбора. А метод ge titem SelectaЫe () возвращает ссьшку на объект, инициировавший данное событие. В приведенном ниже примере aIVIeтa раскрывающиеся списки типа Choice из предыдущего примера заменены: списками типа Li s t: одним - для множественного выбора, а другим - для однократного выбора. Пример выбора нескольких элемен­ тов из раскрывающихся списков в окне aIVIeтa ListDemo приведен на рис. 26.6. 11 Продемонс трировать приме нение списков import java.aw t.*; import java .awt . event .*; import java . applet .*; /* */ <applet code="ListDemo " width=ЗOO height=l80> </app let> puЫ ic class ListDemo extends Applet implement s ActionLi stener { List os , browser; String msg = ""; puЬlic vo id init () os = new List (4, true) ; brows er = new List (4, false) ; 11 ввести элементы в список операционных систем os .add ( "Windows") ; os .add ( "Android" ); os .add("S olaris ") ; os .add("Mac OS"); 11 ввести элементы в список брауэеров brows er .add("Internet Explorer") ; browser .add("Firefox" ); browser .add ( "Chrome") ; browse r.select (l) ; 11 ввести списки в окно add(os ) ; add (browse r) ; 11 зарегис трировать приемники событий действия os . addActionListener (this) ; brow ser . addActionListener (thi s) ;
Гл ава 26. П р именение эл ементов упр авления , диспетчеров ком п оновки ... 939 puЫic void actionPerformed (ActionEvent ае ) { repaint () ; 11 отобразить текущие резуль таты выбора из списков puЫ ic vo id paint ( Graphics g) { int idx[]; msg = "Current OS : "; 11 Текуща я ОС idx = os . ge tSelectedlndexe s (); for (int i=O ; i<idx . length; i++) msg += os .getitem (idx[i] ) +" "; g.drawString (msg, 6, 120) ; msg = "Current Brow ser : " ; 11 Текущий браузер msg += browse r.getSelectedi tem () ; g . drawString (msg, 6, 140) ; Windows . Solaris Mac OS Current OS: And ro id Current Browser: Chrome Ap plet started. lntemet Explorer Rrelox . Рис. 26 .6 . Выбор нескол ьких элементов из раскрывающихся списков в окне аплета ListDemo Управление полосами прокрутки Полосы пракру тки служат для выбора непрерывных значений в заданных пре­ делах от минимума и максимума. Полосы прокрутки моrут быть ориентированы по вертикали и по горизонтали. В действительности полоса прокрутки состоит из нескольких отдел ьных частей. На обоих концах полосы находится кнопка со стрелкой , щелчок на которой вызывает перемещение текущего значения полосы прокрутки на одну единицу в направлении, указываемом стрелкой. Те кущее про­ круч иваемое значение по отношению к заданным минимальному и максимально­ му пределам обозначается с помощью ползунка на полосе прокрутки. Пользователь может перетащить ползунок на другую позицию, после чего новое значение будет отражено на полосе прокрутки . Для прокрутки с шагом больше 1 пользователь мо­ жет щелкнуть кн опкой мыши на самой полосе прокрутки по обе стороны от пол­ зунка. Как правило, такое действие сводится к некоторой форме перелистывания страниц вверх и вниз. Полосы прокрутки инкапсулируются в классе Scrollbar.
940 Часть 11. Б иблиоте ка Java В классе Scrollbar определяются следующие конструкторы: Scrollbar () ScrollЬar (int с�•лъ ) Scrol lЬar (int с�•лъ , int •сходное эна vеюrе , int раsиер_полsунжа , int ЮUD1н;ун , int наж СJ1Н;ун) В первой форме конструктора создается вертикальная полоса прокрутки, а во второй и в третьей формах можно задавать ориентацию полосы прокрут­ ки . Если параметр стиль принимает значение константы Scrollbar . VERT I CAL , создается вертикальная полоса прокрутки, а если он принимает значение кон­ станты Scrol lbar . HORI ZONTAL - то горизонтал ьная полоса прокрутки. В тре­ тьей форме конструктора параметр исходное_ зна чение обозначает кон крет­ ное значение, определяющее исходное положение полосы прокрутки; параметр размер_ ползунка - высоту ползунка в текущих еди ницах; параметры минимум и ма ксимум- соответствующие предельные значения прокрутки. Если полоса прокрутки создается с помощью одного из первых двух конструк­ торов, то , прежде чем воспользоваться ею, следует определить параметры поло­ сы прокрутки с помощью метода setVa lues (), общая форма которого приведена ниже. Параметры этого метода имеют то же самое назначение, что и в третьей форме описанного выше конструктора. void setValues (int •сходное sнa veюre , int раsнер ползун.ха , int НIU UIJl ;yн, - int нажсян;ун) - Чтобы выяснить текущее значение прокрутки , следует вызвать метод get Value (), который возвращает текущее установленное значение. А для того чтобы установить текущее значение, следует вызвать метод setVa lue ().Общие формы этих методов показаны ниже. int qetValue () void setValue (int новое_sнаvен.е) Здесь параметр нов ое_ зна чение обозначает устанавливаемое новое значение прокрутки. После установки этого значения ползунок расположится на полосе прокрутки в соответствии с новым значением. Вызывая методы getMinimшn () и getMaximum (), можно получить минималь­ ное и максимальное значения прокрутки соответственно. Ниже приведены общие формы этих методов. int qetмinimwa () int qetмaximUl l () По умолчанию прокрутка вверх или вниз производится с единичным шагом. При этом значение единичного шага добавляется к значению прокрутки или со­ ответственно вычитается из него. Изменить шаг прокрутки можно , вызвав ме­ тод setUnitlncreme nt ( ). А постраничная прокрутка по умолчанию выполня­ ется с шагом 1 0. Величину этого шага можно изменить, вызвав метод setBlock Increme nt ().Ниже приведены общие формы этих методов. void setUni tincr81 11e nt (int нoJWJi aar) void setвlockincr81 118 nt (int нoвu'i _ aar)
Гл ава 26. П р именение эл ементо в упр авл ения , диспетч еров компоновки ". 9'1 Обработка событий от полос прокрутки Для обработки событий от полос прокрутки следует реализо вать интерфейс Ad j ustmentLi s tener. Всякий раз, когда пользователь начинает прокрутку, фор­ мируется объект класса Adj ustme ntEvent. Его метод getAdj ustme ntType () слу­ жит для определения типа события настройки. В табл. 26. 1 перечислены типы со­ бытий настройки . Та бл ица 26. 1. События настройки Соб�.1тие Описание BLOCK DECREМEN'l' BLOCK INCREМENТ TRACK Л истание страницы вниз Листание стран ицы вверх Абсолютное отслеживание UNIT DECREМENТ UNIT INCREМENТ Нажата кнопка перехода на одну строку вниз Нажата кнопка перехода на одну строку вверх В приведенном ниже примере аплета демонстрируется создание и применение вертикальной и горизонтальной полос прокрутки , а также отображаются текущие установки обеих полос прокрутки. При перетаскивании курсора мыши в окне ко­ ординаты его положения, фиксируемые вместе с каждым событием перемещения, будут использоваться для обновления обеих полос прокрутки. Звездочка отобра­ жается на текущей позиции при перетаскивании. Обратите внимание на метод setPre ferredSize (), вызываемый для установки размеров обеих полос про­ крутки. Пример прокрутки вертикальной и горизонтальной полос в окне аплета SBDemo приведен на рис. 26.7 . // Продемонстрирова ть приме нение полос прокрутки import java .awt.*; import java .awt . event .*; import java . applet .*; /* */ <applet code= "SBDemo " width=ЗOO height=2 00> </appl et> puЫ ic class SBDemo extends Applet impleme nts Ad jus tme ntListener, MouseMotionListener String ms g = ""; Scrollbar ve rtSB, ho rzSB; puЫic void init () ( int width = Integer .parseint ( getParame t er ( "width" )); int height = Integer.parseint (getPa rameter ( "height" )); ve rtSB = new Scrollbar ( Scrollbar . VERT ICAL , О, 1, О, height) ; ve rtSB .se tPreferredSi ze (new Dimension (20, 100 ) ); horzSB = new Scrollbar ( Scrollbar . HORI ZONTAL , О, 1, О, width); hor zSB . setPreferredSize (new Dimension (lOO, 20) ); add (vertSB) : add (horzSB) ;
9'2 Часть 11. Библ иотека Java 11 зарегистрировать приемники событий настройки ve rtSB . addAdj ustmentListener (this ); horzSB . addAdj ustmentListen er (thi s) ; adclМouseMotionLi stener(t his) ; puЬlic void adjustmentValueChanged (Adj ustme ntEvent ае) { repaint () ; } 11 обнови ть полосу прокрутки в ответ на пере таскивание 11 курсора мыши puЬl ic void mouseDragged (Mous eEvent me } int х = me.getX(); int у=me . getY(); ve rtSB .setVa lue (y) ; horzSB . se tVa lue (x) ; repaint () ; 11 Это требуе тся для интерфейса МouseNotionLiatener puЫic void mo useMoved (MouseEvent me ) { } 11 отобразить текущие значения прокрутки puЫ ic void paint ( Graphics g) { ms g = "Vertical : "+ve rtSB . getValue (); 11 Прокрутка по вертикали ms g += " , Horizontal : "+horzSB . getValue (); 11 Прокрутка по горизонтали g.drawString (msg, 6, 160) ; 11 отобразить текуще е положе ние перетаскиваемого 11 курсора мыши g.drawString ("*", horzSB . getValue () , ve rtSB . getValue () ); Applet Viewet: SB Applet Vertlcal: 117, Horlzontal: 152 Appl et starteel. Рис. 26 .7. Прокрутка верти кал ьной и го ризонтальной полос в окне аплета SBDemo
Гл ава 26. П р именение элементов управления, диспетч е ров компо новки .. . 943 Те ксто вые пол я Класс TextField реализует однострочную область для ввода текста, которая называется текстовъLМ полем. В текстовых полях пользователь может вводить текст построчно и редактировать его. Класс Text Field является производным от клас­ са TextComp onent и имеет следующие конструкто ры: TextField () throws HeadlessException TextField (int жатwvество CJ1tн:вo.iros) throws HeadlessException TextField (Strinq c!l'pOxa) - throws HeadlessException TextField (Strinq C!rpOXa , int xamwvecт•o сжнволов) throws HeadlessException - В первой форме конструктора создается текстовое поле , а во второй форме - текстовое поле шириной, равной заданному количе ству_ симв ол ов. В третьей форме конструктора создаваемое текстовое поле инициализируется указанной строкой, а в четвертой форме - текстовое поле не только инициализируется ука­ зан ной строкой, но и получает ширину, равную заданному количеству_ симв олов. В классе TextField и его суперклассе TextComp onent предоставляется ряд ме­ тодов для обращения с текстовыми поля ми. Чтобы получить строку из текстового поля , следует вызвать метод getText (), а для того чтобы задать в нем текст - ме­ тод setText ().Ниже приведены общие формы этих методов, где строка обозна­ чает новую строку текста. Strinq qetтext () void setтext (Strinq строжа) Пользователь может выделить часть текста в текстовом поле , но это можно сделать и программно с помощью метода select (). Вызвав в программе метод ge tSelectedText ( ) , можно получить текст, выделенный в дан ный момент. Ниже приведены общие формы этих методов. Strinq qe tSelectedText () void select (int .наvалъншi_жндежс , int жо.неv.ншi_11U1дежс) Метод ge tSelectedText () возвращает выделенный текст. А метод select () выделяет символы от позиции на чальный_ индекс и до позиции коне чный_ инде кс-1 . Вызвав метод setEditаЫе (), пользователю можно разрешить изменять со­ держимое текстового поля , а вызвав метод isEditаЫе ( ) - редактировать текст. Ниже приведены общие формы этих методов. Ьoolean isEdi tаЫе () void вetldi taЬle (Ьoolean npaJ1 xa) Метод isEditaЫe () возвращает логическое значение true , если текст мож­ но изменять, а если текст изменять нельзя - то логическое значение fal se. Если параметр пр авка метода setEditaЫe ( ) принимает логическое значение true , текст можно изменять. А если этот параметр принимает логическое значение fa lse, то текст изменять нельзя . Иногда нужно сделать невидим текст, вводимый пользователем (например , при вводе пароля). С помощью метода setEchoChar () можно запретить эхоото-
9" Часть 11. Библ иотека Java бражение вводимых символ ов. Этот метод задает одиночный символ , который объект класса TextField будет отображать вместо вводимых символ ов. Вызвав метод getEchoChar () , можно выяснить, находится ли текстовое поле в режиме эхоотображения вводимых символов. А для того чтобы получить эхо-символ ото­ бражения , следует вызвать метод getEchoChar () . Ниже приведены общие формы этих методов. void setEchoChar (cha.r CJrJC80JJ) Ьoolean echoCharisSet () char qetEchoChar () Здесь параметр символ обозначает эхо-символ. Если указать нулевое значение параметра симв ол, то восстанавливается нормальное отображение вводимых сим· волов на экране. Обработка событий в текстовых полях Те кстовые поля выполняют собственные функции редакти рования, и поэтому прикладная программа, как правило, вообще не будет реагировать на события вво· да отдельных символов в текстовом поле. Но в то же время может возникнуть по­ требность реагировать на нажатие пользователем клавиши <Enter>. При нажатии этой клавиши наступает событие действия. В следующем примере аплета создается классическая экранная форма для вво­ да имени пользователя и пароля : 11 Продемонстрировать приме нение текстового поля import java .aw t.•; import java .awt . eveпt .*; import java . appl et .*; /* */ <applet code="TextFieldDemo " width=ЭBO height= l50> </appl et> puЫ ic class TextFieldDemo extends Applet implements Ac tioпListeпer TextField name , pass ; puЫic void init () { Label namep = new Label ( "Name : ", Label . RI GHT) ; 11 Имя поль зователя Label passp = new Label ("Password : " , Label . RIGHT) ; 11 Пароль поль зователя name = new TextField (l2) ; pa ss = new TextField (B) ; pass .se tEchoChar ('?'); add (namep ); add ( name) ; add (passp) ; add (pass) ; 11 зарегис трировать приемники событий действия name . addAc tionLi steп er (this) ; pass . addActionLi stener (this) ;
Гл ава 26. П рименение эл ементов управления, диспетчеров ко мпо н овки... 9'5 11 Поль зователь нажал кла вишу <Enter> puЫic void actionPerforme d (ActionEvent ае ) ( repaint () ; puЫic void paint ( Graphics g) ( g.drawString ("Name : " + name .getText () , 6, 60) ; 11 Имя поль зователя g.drawString ("S elected text in name : " + name .getSelectedText (), 6, 80) ; 11 Выд еленный текст в поле име ни поль зователя g.drawString ("P assword : "+pass . getText () , 6, 100) ; 11 Пароль поль зователя Пример ввода имени пользователя и пароля в окне аплета Text FieldDerno при­ веден на рис. 26.8 . Applet V.ewer. тatrll ld Apptel Name: Неrь Name: Herb Schltdl Setecte d text ln name: Schlldl Password: ajfkdlsk Apptet started. Password: j? ?? ?? ?? ? Рмс. 26.8. П ример ввода имени пользователя и пар оля в окне аплета Text FieldDemo Тексто вые обл а сти Иногда одной строки для ввода текста оказывается недостаточно. На этот слу­ чай в библиотеке АWГ предоставляется класс TextArea, реализующий простой редактор многострочного текста . Ниже приведены конструкторы этого класса. TextArea () throws HeadlessException TextArea (int ЖОЛЖV8С!1'ВО С'ZрОЖ , int жomrvec!1'SO С!1'рОЖ) throws Headlesёi:xception - TextArea (String с!1'рожа ) throws Headles sException TextArea (String С!l'рОЖ& , int жomrveC!1'BO С'ZрОЖ , int ЖОЛЖV8С!1'80 CJIIOIOЛ08) throws HeadlessException - - TextArea (Strinq С!l'рОЖа , int ЖОЛЖV8С!1'80 С'ZрОЖ , int ЖОЛЖV8С!1'80 CJl-oлoa , int полОС1 1 _ nрожру!1'юr) throws iieadlessException - Здесь параметр количе ств о_ строкобозначает высотутекстовой области в стро­ ках, а параметр количе ство_ симв олов - ее ширину в символах. Первоначальный текст можно задать в качестве параметра строка . В пятой форме конструктора можно также задать поло сы_ пр окрутки, которые могуг понадобиться для обраще­ ния с текстовой областью. Параметр поло сы_ пр окрутки должен принимать одно из значений следующих констант:
9'6 Часть 11. Б ибл иотека Java SCROLLВARS ВОТВ SCROLLВARS NONE SCROLLВARS BORI ZONТAL ONLY SCROLLВARS VJ:RTICAL ONLY Класс TextArea является производным от IOiacca TextComp onent. Следователь­ но, в нем поддерживаются методы getText (), setText (), ge tSelectedText (), select (), isEditаЫе () и setEditаЫе (), описанные в предыдущем разделе. Класс TextArea предоставляет следующие дополнительные методы: void append (Strinq C!l'pOXa) void insert ( String C!l'pOXa , int JU1Дexc) void replaceRange (Strinq C!l'pOxa , int на vалънuй JU1Дexc , int xaнe'l lADi _ JU1Дexc) - Метод append () вводит заданную строку в ко нце текущего текста. Метод insert ( ) вставляет заданную строку по указанному индексу. Чтобы заменить текст, следует вызвать метод replaceRange ( ) . Он заменяет символы от пози­ ции на чальный_ индекс и до позиции коне чный _ индекс- 1 текстом из заданной строки. Те кстовые области являются практически автономными элементами управ­ ления , а следовательно , для управления ими приЮiадная программа не несет до­ полнительные издержки. Как правило, приЮiадная программа лишь получает текущий текст по мере надобности . Но при желании можно организовать прием и обработку событий типа TextEvent. В следующем примере аплета создается элемент управления типа TextArea: 11 Продемонс триров ать приме нение текстовой области import java .awt.*; import java . applet .*; /* */ <applet code= "TextAreaDemo " width=ЗOO height=250> </app let> puЫ ic class TextAre aDemo extends Applet { puЫ ic void init () { String val = "Java 8 is the latest ve rsion of the mos t\n" + "widely-used compu ter language for Internet programmi ng .\n" + "Building on а rich heritage , Java has advanced both\n" + "the art and science of comput er language de sign .\n\n" + "One of the reasons for Java 's ongoing succe ss is its\n" + "constant , steady rate of evolution . Java has neve r stood\n " + "still . Instead, Java has cons istently adapted to the\n" + "rapidly changing landscape of the networked world .\n" + "Moreover, Java has often led the way, charting the \n" + "course for others to follow. "; TextArea text = new TextAr ea (val, 10, 30) ; dd (text) ; Пример отображения содержимого текстовой области в окне аплета Text AreaDemo приведен на рис. 26.9.
Гл ава 26. П р именение элементов управлени я, диспетчеров ко мпон овки.. . 9'7 Apptet sed computer languaoe for lntemet pr � опаrlel l hentage, Java has ad\lancec nd science of computer language des 1е reasons for Java's ongolng succe s !" � steady rate of evolutlon. Java has ne­ ead, Java has consistentty acnpted !о anging landscape of lhe networked, .r,Javahas oflen ledlheway, Cl l arting • .. . ;-·" .. . ��-- -- -, Applet started. Рис. 26 .9 . П р имер отображения содержимого текстовой области в окне аnл ета TextAreaDemo Ди спетчеры ко мпон овки Каждый из рассмотре нных до сих пор компонентов размещался в окне дис­ петчером компоновки , выбираемым по умолчанию. Как отмечалось в начале этой гл авы , диспетчер ко мпоновки автоматически размещает элементы управления в окне по определенному алгоритму. Если у вас имеется опыт разработки приклад­ ных программ для других сред с ГПИ (например, для Windows) , то вы, возможно, привыкли размещать элементы управления ГПИ вручную. Элементы управления, создаваемые средствами Java, можно также размещать вручную , но делать это не стоит по следующим причинам. Во-первых , размещать вручную большое количе­ ство компонентов - довольно утомительное занятие. И во-вторых, в тот момент, когда требуется разместить какой-нибудь эл емент управления , его ширина и вы­ сота могут быть неизвестны, поскольку еще не готовы компоненты пла'Iформен­ но-ориентированных инструментальных средств. Это напоминает ситуацию, где трудно определить причину и следствие, поскольку неясно, когда можно исполь­ зовать размеры одного компонента для его расположения относительно другого компонента. У каждого объекта класса Container имеется свой диспетчер компонов­ ки , который является экземпляром любого класса, реализующего интерфейс LayoutManage r. Диспетчер компоновки устанавливается с помощью метода set Layout ().Если же метод setLayout ( ) не вызывается, то используется диспетчер компоновки , выбираемый по умолчанию. Всякий раз, когда изменяются разме­ ры контейнера, в том числе и в первый раз, диспетчер компоновки применяется для размещения каждого компонента в контейнере. Метод setLayout () имеет следующую общую форму: void setLayout (Layoutмana9er о6ъеж�_ жонпоно�)
9'8 Часть 11. Б и бл иотека Java где параметр объект_ компоновки обозначает ссьmку на требуемый диспетчер ком­ поновки. Если требуется отменить действие диспетчера компоновки и разместить компоненты вручную, в качестве параметра объект_ компоновки СJiедует передать пу­ стое значение nul l. В таком CJiyчae форму и расположение каждого компонента при­ дется определить вручную с помощью метода setBounds () из класса Component. Но, как правило, компоненты ГПИ размещаются с помощью диспетчера компоновки. Каждый диспетч ер компоновки отслеживает список компонентов, хранящих­ ся под своими именами. Диспетчер компоновки получает уведомление всякий раз, когда компонент вводится в контейнер. А когда требуется изменить размеры контейнера, диспетчер компоновки вызывает для этой цели свои мeтoды minimum Layout Size () и preferredLayo utSize (). Каждый ко мпонент, которым мани­ пулирует дис петчер компоновки , содержит методы getPre ferredSize () и get MinimumS ize (). Они возвращают предпочтительные и минимальные размеры для отображения каждого ко мпонента. Диспетчер ко мпоновки будет учитывать эти размеры, если это вообще возможно, не нарушая правила компоновки. Эти методы можно переопределить в создаваемых подклассах элементов управления, а иначе предоставляются значения по умолчанию. В Java имеется ряд предопределенных классов диспетчеров ко мпоновки , часть из которых описывается далее . Среди них можно выбрать такой диспетчер ком­ поновки , который лучше всего подходит для конкретной прикладной программы. Класс FlowLayou t Класс FlowLayout реализует диспетчер поточной компоновки, выбираемый по умолчанию. Именно этот диспетчер компоновки применялся в предыдущих примерах. Диспетч ер компоновки типа FlowLayout реализует простой стиль компоновки , кото рый напоминает порядок следования слов в редакторе текста. Направление компоновки определяется свойством ориентации компонента в кон­ тейнере , которое по умолчанию задает направление слева направо и сверху вниз. Следовател ь но, по умолчанию компоненты размещаются построчно, начиная с левого верхнего угла. Но в любом случае компонент переносится на следующую строку, если он не умещается в текущей строке. Между компонентами остаются небольшие про межутки сверху, снизу, справа и слева. Ниже приведены конструк­ торы класса FlowLayout . FlovLayout () FlovLayout (int способ) FlovLayout (int способ , int .rорнsон!l'алъно , int вер!l'Хха.пъно) В первой форме конструктора выполняется компоновка по умолч ан ию, т. е . компоненты размещаются по центру, а между ними остается промежуток пять пик­ селей. Во второй форме конструктора можно определить спо соб расположения каждой строки. Ниже представлены допустимые значения параметра спо соб. • FlowLayout . LE FT • FlowLayo ut . CENTER • FlowLayout .RIGHT
Гл ава 26. П рименение эл ементо в управления , диспетчеров компон овки" . 9'9 • FlowLayout . LEADING • FlowLayout.TRAILING Эти значения определяют выравнивание по левому краю, по центру, по право­ му краю , переднему и заднему краю соответственно. В третьей форме конструкто­ ра в качестве параметров гориз онтально и вертикально можно определить про­ межутки между компонентами по горизонтали и по вертикали. Ниже приведен ва­ риант рассматривавшегося ранее примера аплета CheckboxDemo , переделанный для выполнения поточной компоновки с выравниванием по левому краю . // Приме ни ть поточную компоновку с выравниванием по левому краю import java .awt.*; import java .awt . event .*; import java . applet .*; /* */ <applet code= "Fl owLayoutDemo " width=2 40 height=200> </applet> puЫ ic class FlowLayoutDemo extends Applet implements ItemL istener { String msg = ""; Che ckbox wi ndows , android, solaris, ma c; puЫic void init () { // установить поточную компоновку с выра вниванием по левому краю setLayout (new FlowLayout ( Fl owLayout .LEFT ) ); windows new Che ckbox ("Windows ", null, tru e) ; android = new Che ckbox ( "Android" ); solaris = new Che ckbox ("Solaris") ; mac = new Checkbox ("Mac OS ") ; add ( windows ); add ( android) ; add (solaris ); add (mac) ; // зарегис трировать получателя событий от элементов windows .addi temListener (this) ; android. additemListener (thi s) ; solaris . additemListen er (thi s) ; mac . additemListener (this) ; // перерисова ть , когда состояние флажка изменится puЫic void itemStateChanged ( ItemEvent ie) { repaint () ; // отобразить те кущее состояние флажков puЬlic void paint ( Graphics g) msg = "Current state : "; g.drawString (msg, 6, 80) ; msg =" Wi ndows : "+windows .getState () ; g.drawString (msg, 6, 100) ; msg =" Android : "+android .getState ();
950 Часть 11. Библиотека Java g.drawString (msg, б, 120) ; ms g =" Solaris : "+solaris . getState (); g.drawString (msg, б, 140) ; msg =" Мае: " + mac .getState(); g.drawString (msg, б, 160) ; Пример установки флажков в окне aruJeтa FlowLayout Demo приведен на рис.26. 10. Сравните его с результатом выполнения aruJeтa Chec kboxDemo (см. рис. 26.3) . Applet 17Windows ГAndfoidl"f§Olai iS] ); ; f.4ac os Current state: Windows: true Androld: faise Solaris: true Мае: true Applet started. Рис 26.10. Пример уста новки флажков в окне аплета FlowLayout Demo Кnacc вorderLayou t Этот класс реализует общий стиль граничной компоновки д.ля окон передне­ го ruJaнa. Он имеет четыре узких компонента фиксированной ширины по краям и одну крупную область в центре. Четыре стороны именуются по сторонам света: север, юг, запад и восток, а область посредине называется центром. Ниже приве­ дены конструкторы , определяемые в классе Borde rLayout. ВordarLayout () ВorderLayout (int .rора-sан!!'алъио , int вер! !' •жалъно) В первой форме конструктора выполняется граничная компоновка по умолча­ нию. А во второй форме в качестве параметров горизонтально и вертикально уста­ навливается горизонтальный и вертикальный промежутки между компонентами. В классе Borde rLayout определяются следующие константы д.ля указания об­ ластей граничной компоновки : ВorderLayout .CEN'lER ВorderLayout . SOOТB ВorderLayout .DST ВorderLayout . ПST ВorderLayout .NORТВ Эти константы обычно используются при вводе компонентов в компоновку с помощью следующей формы метода add () из класса Container:
Гл ава 26. П р именение эл ементо в управления , диспетчеров ко мпоновки . .. 951 void add (Co11 1p onent ссwrжа_на_.кОNoJОНен!I.' , O:Ь ject oб.irac!l.'.ь) где параметр ссылка_ на_ компонент обозначает ссылку на вводимый компонент, а обла сть - место для ввода компонента в компоновку. Ниже приведен пример граничной компоновки , где в каждой области раз­ мещается отдельный компонент. Пример граничной компоновки в окне аплета BorderLayout Demo приведен на рис. 26.11. // Продемонстрировать приме нение граничной компоновки import java .aw t.*; import java . applet .*; import java .util .*; /* */ <applet code="Borde rLayoutDemo " width=400 height=200> </applet> puЫ ic class Borde rLayoutDemo extends Ap plet puЫ ic void init () { setLayout (new BorderLayout () ); add (new Button ("This is acros s the top.") , BorderLayout .NORTH) ; 11 Кнопка , раэыещаеыа я сверху по всей ширине окна add (new Label (The footer me ssage might go here.") , Borde rLayout .SOUTH) ; // Здесь разме щается нижний колонтитул для вывода сообщений add (new Button ( "Righ t") , BorderLayout .EAST) ; // Кнопка справа add (new Button ("Le ft" ), Borde rLayout .WEST) ; // Кнопка сле ва St ring msg = "The reasonaЫe man adapts " + "himself to the world; \n" + "the unreasonaЫe one persists in " + "trying to adapt the wo rld to himsel f.\n" + "Therefore all progre ss depends " + "on the unreasonaЫe man .\n\n" + - George Be rnard Shaw\n\n"; add (new TextArea (ms g) , Borde rLayout . CENTER) ; c: :�:=-� ��: :- :-�· ·-: :: :- ·:-�·:тti�Js·� ��s�Jtiё i�i>� ��- -"-··--·:·· -::·-�- --":� � - Len rhe reasonaыe man adapts himselfto the world; he unreasonaьte one persists in tryi ng to adapt the wo1 herefore all progress depends оп the unreasonaЫe rr - George Bernard Shaw •r The footer message might go here. Applet stalted. Right Рис. 26.1 1. Пример граничной ком поновки в окне аплета Borde rLayout Demo
952 Ч асть 11. Библ иоте ка Java Вста вки Иногда требуется оставить небольшой промежуток между контейнером с ком­ понентами и тем окном , где он находится. Для этого следует переопределить метод ge tlnsets () из класса Container. Этот метод возвращает объект класса Insets, который содержит верхнюю, нижнюю, левую и правую вставхи, использу­ емые диспетчером компоновки при размещении компонентов контейнера в окне. Ниже приведен конструктор класса Insets. Insets (int саерху, int слева , int снизу, int спра ва) Значения , передаваемые в качестве параметров св ерху, сл ева, сниэу и справа, определяют соответствующие промежутки между контейнером и окном, в кото­ ром он размещается. Метод get lnsets () имеет следующую общую форму: Insets qetinsets () Переопределяя этот метод, следует возвратить новый объект ти па Insets, содержащий требуемый промежуток для вставки. Ниже приведен измененный вариант предыдущего примера, в котором демонстрировалось применение дис­ петчера компоновки типа Borde rLayout. В данном варианте компоненты рас­ ставляются с отступом десять пикселей от каждой гран ицы. Для фона был выбран голубой цвет, чтобы вставки бьvш лучше видны. Пример граничной компоновки со вставками в окне аплета InsetsDemo приведен на рис. 26. 12. // Продемонс трировать приме нение граничной компоновки со вставками import java.awt .*; import java . applet .*; import java .util .*; /* */ <applet code=" IпsetsDemo " width=400 height=2 00> </applet> puЫ ic class InsetsDemo extends Applet { puЫic void init () { // задать цвет фона , чтобы легко различать вставки setBackground (Color . cyan) ; setLayout (new BorderLayout () ); add (new Button ("This is across the top.") , Borde rLayout .NORTH) ; // Кнопка , размещаемая сверху по ширине окна add (new Label ("The footer me ssage might go here.") , // Зде сь разме щается нижний колонтитул для вывода сообщений add (new Button ("Right" ), Borde rLayout .EAST) ; add (new Button ("Le ft" ), BorderLayout .WEST) ; String msg = "The reasonaЫe man adapts " + "himself to the world; \n" + "the unreasonaЫe one persists in " + "trying to adapt the world to hims elf . \n" + "There fore all progress depends " + "on the unreasonaЫe man .\n\n" + - George Bernard Shaw\n\n";
Гл ава 26. П р именение эл ементов упр авл ения, диспетчеров компо н овки." 953 add (new TextArea (ms g) , Borde rLayout .CENTER) ; ) 11 до бавить вставки puЫ ic Insets ge tinsets () return new Insets (lO, 10, 10, 10) ; [·=:::�=:::=:=-: =:=:::: :=� �- i.��!ao��-ii i �!giг-.=: :: :: :: :: ::=:�-= -� �. Len Гhе reasonable man a dapts hims elf lo 11 1 е wortd; he unreasonaЫe one persi sts in lryi ng to adapt \he herefore all progress d e p e n ds оп \h e unreasonaЬI - Georoe Bernard Shaw '" э Th e footer message might go here. Applet started. Righ\ Рис. 26 .12. Пример граничной компоновки со вставка ми в окне аnл ета InsetsDemo Класс GridLayout При использовании класса Gr idLayout компоненты размещаются табличным способом в двухмерной сетке . Реализуя класс Gr idLayout, следует определить количество строк и столбцов. Ниже приведены конструкторы, предоставляемые в классе GridLayout. GridLayout () GridLayout (int ЖOJIJIV8C!l'llO С!l'рОЖ , int ЖOJПlfV8C!J'80 C!l'aJrбцoa) GridLayout (int ЖOJIJIV8C!1'1SO-С!l'рОЖ , int ЖOJПlfV8C!1'80-C!l'aJrбцoa , int ГOJ)Ж.SOJf!l'&IJ'MO, int Jil&p!l'Jr.X&IJWIO) - В первой форме конструктора выполняется сеточная компоновка с одним столбцом, а во второй форме конструктора - сеточная компоновка с заданным количеством строк и столбцов. Тр етья форма позволяет определить в качестве параметров гориз онтально и вертика льно промежутки между компонентами по горизонтали и по вертикали. Любой иэ параметров количе ство_ строк или количе ство_ столбцов может принимать нулевое значение. Та к, если нулевое зна­ чение принимает параметр количе ство_ строк, то ограничение на ширину столб­ цов не накладывается. А если нулевое значение принимает параметр количе ство_ столбцов , то ограничение не накладывается на длину строк. В следующем примере аплета демонстрируется соэдание сетки 4 х4 , заполняе­ мой 15 кнопками, каждая иэ которых обозначается своим индексом: // Продемонс трировать применение сеточной компоновки import java .awt.*; import java . applet .*; /*
95' Часть 11. Библиоте ка Java */ <applet code="GridLayout Demo " width=ЗOO height=200> </applet> puЫ ic class GridLayout Demo extends Ap plet static final int n = 4; puЬlic void init () { setLayout (new GridLayout (n, n) ); setFont (new Font ("SansSeri f", Font . BOLD, 24) ); for(inti=О;i<n;i++){ for(intj=О;j<n;j++){ intk=i*n+j; if(k > 0) add (new Button ("" + k) ); Пример сеточной компоновки кнопок в окне аплета GridLayout Demo приве­ ден на рис. 26. 13. у � J•., ,. ., ..ir.� �1 Applet Viewer: Grid\J J )ЮirtDemo ·· ·- .��·_ ,. .. . - '=-- - ·•·.,. .., ,,. .., , _�_.,,-_.°'Y ':"' r· ·.�,-�.---:·_ Ap plet г-1 · - · - 2 з 4 5 6 7 8 9 101112 131415 Applet started. - Рис. 26 .13 . Пример сеточной ко мпоновки кнопок в окне аплета GridLayout Demo Совет! Этот пример можно взять за основу для написа ния программы игры в пятнашки. Кл асс CardLayou t Особое место среди классов диспетчеров компоновки принадлежит классу CardLayout, поскольку он позволяет хранить разные компоновки . Каждую ко мшг новку можно представить в виде отдел ьной карты из колоды. Карты можно пере­ тасовывать как угодно, чтобы в любой момент наверху колоды находилась какая­ нибудь карта. Карточная компоновка может оказаться уд обной для пользовател ь­ ских интерфейсов с необязательными компонентами, которые можно динами­ чески включать и отключать в зависимости от вводи мых пользователем данных. Имеется возможность подготовить разные виды компоновки и скрыть их до того момента, когда они потребуются.
Гл ава 26. П р именение эл ементов упр авления, диспетч е р ов ко мпоновки ... 955 В классе CardLayout предоставляются следующие конструкторы: CardLayout () CardLayout (int ropirsoн!l'aлънo , int sер!l'.1 11 .калъно) В первой форме выполняется карточная ко мпоновка по умолчанию. А во вто­ рой форме в качестве параметров гориз онтально и вертикально можно указать промежутки между ко мпонентами по горизонтали и по вертикали. Карточная компоновка требует немного бальших затрат труда, чем другие виды компоновки. Карты обычно хранятся в объекте типа Panel. Для этой панели следу­ ет выбраn. диспетчер компоновки типа CardLayout. Карты , составляющие колоду, как правило , также являются объектами ти па Pane l. Следовательно, придется сна­ чала создать панель для колоды карт, а также отдельную панель для каждой карn.1 из этой колоды. Затем следует ввести на соответствующей панели компоненты , форми­ рующие каждую карту. После этого панели отдел ьных карт нужно ввести на гл авной панели с диспетчером компоновки типа Ca rdLayout. И наконец, гл авную панель следует ввести в окно. Как только это будет сделано, нужно предоставить пользова­ телю возможность выбирать каким-нибудь способом карn.1 из колоды. Когда карта вводится на панели, ей обычно присваивается имя. Для этой цели чаще всего употребляется приведенная ниже форма метода add ( ) , где имя обозначает кон­ кретное имя карты , панель которой определяется параметром ссылка_ на _ па нель . void add (C01 11pO nent сС1 1Л жа_на_панелъ , Ob ject .l ll Нl l ) Как только колода карт будет сформирована, отдел ьные карты в ней акти­ визируются с помощью одного из следующих методов, определяемых в классе CardLayou t: void first ( Container пане.па) void last (Container nанЕ!Лlо) void next ( Container nанепъ) void previous (Container naнe.JD,) void вhov (Container панв.пъ , Strinq .1 11 Н1 1 _ .1:ap!1'Jl) Здесь параметр па нель обозначает ссылку на контейнер (обычно панель) , где хранятся карты, а имя_ карты - конкретное имя карты. В результате вызова метода first () отображается первая карта в колоде. Для отображения последней карn.1 в колоде следует вызваn. метод last (),дляотображения следующей карты - метод next ( ) , а для отображения предыдущей карn.1 - метод previous ( ) . Методы next ( ) и previous ( ) автомати чески перебирают колоду карт снизу вверх или сверху вниз соответственно. А метод show () отображает карту по заданному имени_ кар ты. В приведенном ниже примере аплета демонстрируется двухуровневая ко­ лода карт, из которой пользователь может выбрать операционную систе му. Операционные системы типа Windows отображаются на одной карте, а операци­ онные системы Мае OS и Solaris - на другой. // Продемо нс триро вать примен ение карточной компоновки import java .aw t.*; import java . awt . event .*; import java . applet .*; /* */ <applet code= "CardLayout Demo " width=ЗOO height=lOO> < / applet>
956 Часть 11. Библ иоте ка Java puЫic class CardLayoutDemo extends App let impleme nts Ac tionLi stener, MouseListener { Checkbox windowsXP , windows 7, windows6, android , solaris, ma c; Panel озСаrdз ; CardLayout cardLO; Button Win , Other; puЫic void init() { Win = new Button ( "Windows") ; Othe r = new But ton ("Other" ); add (Win) ; add (Other) ; ca rdLO = new CardLayout () ; озСаrdз = new Panel (); osCardз .setLayout ( cardLO) ; // установить компоновку панели 11 для разме ще ния карт windows XP = new Checkbox ("W indows ХР" , nu ll, true) ; windows 7 = new Checkbox ( "Windows 7", null, false) ; windows 6 = new CheckЬox ("Windows 6", null, false) ; android = new Checkbox ( "Android" ); solaris = new Checkbox ("S olaris ") ; mac = new Checkbox ("Mac OS " ); // ввести на панели флажки дл я выбора типа ОС Wi ndows Pane l winPan = new Panel () ; winPan .add (windowзXP ); winPan .add (windows7) ; winPan .add(windows6) ; // ввести на панели флажки дл я выбора других ОС Pane l otherPan = new Panel () ; otherPan .add(android) ; otherPan .add ( solaris ); otherPan .add (ma c) ; 11 ввести панели отдельных карт на панели колоды карт osCardз .add (winPan , "Windows" ) ; osCards .add ( othe rPan, "Other" ) ; // ввести карты на главной панели аплета add ( osCards ); 11 зарегистрировать приемники событий действия Win . addAction Listener (this) ; Other . addActionLi stener (this) ; // зарегистрировать приемники событий от мыши addМouseListener (this) ; 11 перебра ть панели карт puЫ ic void mousePressed (Mouз eEvent me ) { ca rdLO .next ( osCardз ); 11 предоставить пустые реализ ации других методов // из интерфейса MouвeLiвtener puЫ ic void mo useClicked (MouseEvent me ) ) puЫ ic vo id mou seEntered (Mouз eEvent me ) }
Гл ава 26. П р именение эл ементов управления, д испетч еров ком поновки. .. 957 puЫic void mouseExited (Mou seEvent me ) { } puЬlic void mou seReleased (MouseEvent me ) } puЫ ic void actionPerformed (ActionEvent ае ) if (ae.getSource () == Win) { cardLO .show (osCards , "Windows ") ; } else { cardLO .show (osCards , "Othe r" ) ; Пример карточной компоновки элементов управления ГПИ в окне аплета CardLayout Demo приведен на рис. 26. 14. Каждая карта активизируется нажатием ее кнопки . Щелчком кнопкой мыши можно также перебирать карты. Applet Windows 1 Oth er 1 Г Windows ХР Р' Windows 1 P"[W16�o�i!.�) Applet started. Applet Windows 1 [§tijЩ Г Androld Р Solaris Г Мае OS Applet startee1 Рис. 26 .1,. Пример карточной ко мпоновки эл ементов управления ГПИ в окне аnлета CardLayoutDemo Клacc Gr idВagLayou t Рассмотренные выше виды компоновки вполне пригодны для применения во многих аплетах, тем не менее в некоторых из них требуется более то чное управ· ление расположением компонентов в окне. Для этой цел и подходит сеточно·кон· те йнерная компоновка, реализуемая в классе GridBagLayout. Уд обство такой ком· поновки состоит в том, что она позволяет задавать относительное расположение ко мпонентов, указывая его в ячейках сетки . Но самое гл авное, что каждый ком· понент может иметь свои размеры, а каждая строка - свое количество столбцов. Именно поэтому данная разновидность ко мпоновки называется сеточ'Н(}-'1(01tтейнер­ ной и представляет собой совокупность мелких соединенных вместе сеток.
958 Часть 11. Б иблиотека Java Местонахождение и размеры каждого компонента в сеточно-контейнерной ко мпоновке определяются рядом связанных с ним ограничений, которые содер­ жатся в объекте класса GridBagConstraints. В частности , ограничения наклады­ ваются на высоту и ширину ячейки , расположение компонента, его выравнивание и точку привязки в самой ячейке. Общая процедура сеточно-контейнерной ко мпоновки выполняется следую­ щим образом. Сначала создается новый объект типа GridBagLayout в качестве текущего диспетчера компоновки. Затем накладываются ограничения на каждый компонент, вводимый в сеточный контейнер. После этого компоненты вводятся в диспетчер компоновки . Несмотря на то что класс GridBagLayout является бо­ лее сложным по сравнению с другими диспетчерами компоновки , пользоваться им будет нетрудно , если как следует разобраться в принципе его действия. В классе Gr idBagLayout определяется следующий единственный конструктор: Gr idВaqLayout () Кроме того , в этом классе определяется ряд методов, многие из которых яв­ ляются защищенными и не предназначены для общего употребления. Но сре­ ди них имеется один метод , кото рый следует использовать. Это метод setCon­ straints (), общая форма которого приведена ниже. void setCona traints ( Coшponent жонпонен� , Gric\ВaqCons traints orpaнJrveюr•) Здесь параметр компонент обозначает тот компонент, на который накладыва­ ются указанные огра ничения. Этот метод описывает ограничения, накладывае­ мые на каждый компонент в сеточном контейнере. Залогом ус пешного применения класса GridBagLayout является тщатель­ ная установка накладываемых ограничений, которые хранятся в объекте класса GridBagConstraints. В классе GridBagConstraints определяется несколько по­ лей , которые можно устанавливать для управления размерами компонентов, их размещением и промежутками между ними. Эти поля перечислены в табл. 26.2. Некоторые из них подробнее описываются далее. Табл ица 26.2. Поля ограничений, определяемые в классе GridВaqConstraints Попе int anchor int fill Назначение Задает местоположение компонента в ячейке. По умолчанию принимает значение константы GridВаgСоnstrаintа .СВNТВR Задает способ изменения размеров компонента, если они мень- ше размеров ячейки. Допустимыми яаляются значения констант GriclВagConstraints . � (по умолчанию), GridВagConstraints . BORI ZClnAL, GridВagConstraints . VERTICAL и Gric1Вa9Constraints . вотв int qridheight Задает высоту компонента в ячейке. По умол чанию принимает значение 1 int gridwidth Задает ширину компонента в ячейке. По умолчанию принимает значение 1 int gric:lx Задает координату Х ячейки, в которую будет введен компонент. По умол- чанию принимает значение константы Gricf8a9Constraints .RВLAТIVE
Гл ава 26. П рименение элементов управления , диспетче ров компоновки." 959 Попе int gridy Inaets inaete int ipadx int ipady douЫe weiqhtx douЬle weiqhty 0кОН"UlНШi табл. 26. 2 Назначение Задает координату У ячейки , в которую будет введен компонент. По умол­ чанию принимает значение константы GridВaqConstrai.nts . RELA1'IVE Задает всrавки. По умол чанию все всrавки являются нулевыми Задает дополнительный промежуrок, окружающий компонент в ячейке по горизонтали. По умолчанию принимает нулевое значение Задает дополнительный промежуток, окружающий компонент в ячейке по вертикали . По умолчанию принимает нулевое значение Задает весовое значен ие, которое определяет промежутки между ячейка­ ми и краями их контейнера по горизонтали. По умолчанию принимает значен . ие О,О. Чем больше вес, тем больше промежуток. Если все значе­ ния равны О, О, то дополнительные промежутки распределяются равно­ мерно между краями окна Задает весовое значение, которое определяет промежутки между ячейка­ ми и краями их контейнера по вертикали . По умолчанию принимает зна­ чение О,О. Чем больше вес, тем больше промежуток. Если все значения равны О, О, то дополнительные промежутки распредел яются равномерно между краями окна В классе GridBagConstraints определяется также ряд статических полей, которые содержат стандартные значения ограничений, например, значения кон­ стант GridBagConstraints . CENTER и Gr idBagConstraints . VERT I CAL. Если размеры компонента меньше размеров его ячейки , можно воспользовать­ ся полем anchor, чтобы определить место в ячейке , где будет располагаться левый верхний угол компонента. Имеются три типа значений, которые можно присво­ ить полю anchor. Первые из них являются абсолютными значениями. Как можно судить по именам значений приведенных ниже констант, они определяют распо- ложение компонента в соответствующих местах ячейки . Gric:IВaqConstraints .CВNТZR Gric:IВaqConstraints .SOO'l'l l GridВaqConstraints .EAST Gric:IВaqConstrainta .SOU'l'ЯZA S T Gric:IВaqConetrainte . NORTB Gric:IВaqCon strainte . SOU'l'ВWZST Gric:IВaqConstraints . NOR'l'JIEAST Gric:IВaqCon straints .WZST Gric:IВagCon strainta . NORTIПIEST Второй тип значений, которые можно присвоить полю anchor, является относи­ тельн ым, т.е . они указываются относительно ориентации контейнера, которая в вос­ точных языках может быть другой. Огносительные значения констант перечислены ниже. Их имена описывают расположение компонентов относительно ячейки. Gric:IВagCon straints . FIRST LINE END Gric:IВaqCon straints .LINE END Gric:IВaqConstraints . FIRST LINE START Gric:IВaqCon straints .LINE START Gric:IВaqConstraints .LAST LINE END Gric:IВaqConstraints . PAGE END Gric:IВaqConstraints .LAST LINE START Gric:IВaqConatraints . PAGE START Тр етий тип значений , которые могут быть присвоены полю anchor, позволя­ ет размещать компоненты вертикально по отношению к базовой линии строки. Значения констант этого типа перечислены ниже. При горизонтальном располо-
960 Часть 11. Б ибл иоте ка Java же нии центровка может выполняться относительно переднего ( LEADING ) или за· днему (TRAILING) края . GridВaqConatrainta .ВASELINE GridВaqConatrainta . ВASELINE_ LEADING GridВaqConatrainta .ВASELINE ТRAILING GridВaqConatrainta .AВOVE ВASELINE GridВaqConatrainta .AВOVВ _ ВASELINE_ GridВa9Conatrainta .AВOVE _ ВASELINE_ LEADING ТRAILING GridВaqConatrainta .ВELOW _ ВASELINE Gric:IВaqConatrainta .ВELOW _ ВASELINE_ LEADING GridВaqCon atrainta .ВELOW_ВASELINE_ ТRAILING Поля we ightx и weighty очень важны, хотя они и кажугся на первый взгляд непонятными. Их значения определяют, сколько дополнительного пространства будет выделено в контейнере для каждой строки и каждого столбца. По умолча­ нию оба поля имеют нулевые значения. Если все значения в столбце и строке ока­ зываются нулевыми, то дополнительный промежугок распределяется равномерно между краями окна. Увел ичивая вес , можно увеличить распределение свободного пространства для строки или столбца пропорционально остальным строкам ил и столбцам. Самый луч ший способ уя снить назначения этих полей - поэксперимен­ тировать с ними на конкретном примере. В поле gridwidth можно задать ширину ячейки в единицах ячейки. По умолча­ нию эта переменная принимает значение 1. Чтобы компонент использовал свобод­ ное пространство в строке, следует установить значение GridBagConstraints . REМAINDER, а для того чтобы компонент мог использовать предпоследнюю ячейку в строке - значение Gr idBagConstraints . RELAT IVE. Аналогично действует ограни­ чение, накладываемое в поле gridheight, но только в вертикальном направлении. Имеется также возможность определить значение заполнения, чтобы с его по­ мощью увелич ить минимальные размеры ячейки. Для заполнения по горизонта­ ли следует установить соответствующее значение в поле ipadx, а для заполнения по вертикали - в поле idpay. Ниже приведен пример аплета, в котором класс GridBagLayout служит для де­ монстрации только что рассмотренного материала. Пример сеточно-контейнерной компоновки элементов управления ГПИ в окне Gr idBagDemo приведен на рис. 26. 15. // Использовать кл асс GridВa9Layout import java .aw t.*; import java . awt . event .*; import java . applet . *; /* */ <applet code="GridBagDemo " width=2 50 height=2 00> </applet> puЫ ic class GridBagDemo extends Applet implements ItemLi stener { String msg = ""; Chec kЬox windows , android, solaris, ma c; puЫic void init ( ) { GridBa gLayout gbag = new GridBagLayout ();
Гл ава 26. Применение эл ементо в уп равл ени я , диспетчеров ко мпоновки. .. 961 GridBagConstraints gbc = new GridBagConstraints () ; setLayout (gbag) ; 11 определить флажки wi ndows new Checkbox ("Windows ", null, tru e) ; android = new Chec kbox ("Android" ); solaris = new Checkbox ("S olaris ") ; rnac = new Checkbox ("Мае OS ") ; // определить сеточный кон тейнер 11 исполь зовать нулевой вес по умолчанию для первой строки gbc .weightx = 1 .0; // использовать единичный вес для столбца gbc . ipadx = 200; // заполни ть на 200 единиц gbc .insets new Insets (4, 4, О, 0) ; // сделать не большую 11 вставку относи тельно левого верхнего угла gbc . anchor = GridBagConstraints . NORTHEAST ; gbc .gr idwidth = GridBagConstraints . RELAT IVE; gbag .setConstraints (windows , gbc) ; gbc .gridwidth = GridBagConstraints . REМAINDER; gbag .setConstraints ( android, gb c) ; // прида ть второй строке единичный вес gbc .weighty = 1.0; gbc .gridwidth = GridBagConstraints . RELAT IVE; gb ag . setConstraints ( solaris, gbc) ; gbc .gridwidth = GridBagConstraints . REМAINDER; gbag .setConstraints (mac , gbc) ; 11 ввести компоненты add ( windows ); add (android) ; add (solaris ); add (mac) ; 11 заре гистрировать приемники событий от элементов windows .addi temLi stener (this) ; android . add!temListen er (thi s) ; solaris . add!temListener (this) ; ma c.add!temListener (thi s) ; // перерисовать , когда изменится состояние флажка puЫic void itemS tateChanged (ItemEvent ie ) { repaint () ; 11 отобразить текущее состояние флажков puЬlic void paint ( Graphics g) { ms g = "Current state : "; 11 Текуще е состояние g.drawS tring (msg, 6, 80) ; msg =" Windows : "+windows .getState (); g.drawString (msg, 6, 100) ; msg =" And roid : "+android . getState (); g.drawString (msg, 6, 120) ; ms g =" Solaris : "+solaris . getState (); g.drawString (msg, 6, 140) ; msg =" Мае : " + mac .getState (); g.drawString (msg, 6, 160) ;
962 Часть 11. Библиотека Java Applet ГWindows Г�О!.�d� current state: Windows : fa lse Androld: tru e Solarls: fa lse Мае: tru e Applet started. Р Androi c! РMacOS Рис. 26 .1 5. Пример сето чно -ко н те йнерно й ко мпоновки эл ементов управления ГПИ в окне GridBagDerno В данном виде компоновки флажки выбора операцио нных систем размещают­ ся в сетке 2х2. Каждая ячейка имеет заполнение 200 единиц. Каждый компонент вставляется с небольшим отступом (4 единицы) относительно левого верхнего угла ячейки. Вес столбца устанавливается единичным, благодаря чему любой до­ полнительный промежуток по горизонтали распределяется равномерно между столбцами. По умолчанию вес первой строки устанавливается нулевым, а вес вто­ рой строки - единичным. Это означает, что любой дополнительный промежуток по вертикали переносится во вторую строку. Класс GridBagLayout реализует весьма эффективный диспетчер компоновки, и он заслуживает того , чтобы потратить некоторое время на его изучение и экс­ перименты с ним. После того как станет ясно назначение различных устанавлива­ емых компонентов класса GridBagLayou t, им станет легче пользоваться для рас­ положения компонентов с высокой степенью то чности . Меню и строки меню Окно переднего плана может иметь связанную с ним строку меню, которая ото­ бражает список пунктов меню верхнего уровня. Каждый пункт меню связан с вы­ падающим меню. Этот принцип реализован в библиотеке АWГ с помощью классов MenuBar, Menu и Menu item. В общем, строка меню состоит из одного или несколь­ ких объектов класса Me nu. Каждый объект класса Menu содержит список объектов класса Menu item, а каждый объект класса Menu item представляет пункт меню, кото­ рый может бьrгь выбран пользователем. Класс Menu является производным от клас­ са Menuitem, что позволяет создать иерархию вложенных подменю. В меню можно также включать отмечаемые пункты типа CheckboxMenu item. Если пользователь щелкнет на таком пункте, то рядом с ним появится отметка в виде галочки . Чтобы создать строку меню, нужно сначала получить экземпляр класса MenuBar. В этом классе определяется только конструктор по умолчанию. Затем следует по­ лучить экземпляры класса Menu, которые будут определять пункты меню, отобра­ жаемые в строке. Ниже приведены конструкторы класса Menu.
Гл ава 26. Применени е эл ементов упра влени я, д испетчеров ко мпо новки" . 963 Мenu () throws Beadlessli:xception Мenu (Strinq хм. D,)l'Яr�a) throws BeadlessException Мenu (Strinq •- - D,)l'Яr�a , boolean YRaJWeNl lЙ DJ'JU'�) throws BeadlessException - Здесь параметр имя_ пункта обозначает конкретное имя пункта меню. Если па­ раметр уд аляемый _ пункт принимает логическое значение true, то меню можно уд алить, переведя его в плавающий режим, а иначе оно останется присоединен­ ным к строке меню. (Удаляемые меню зависят от конкретной реализации.) В пер­ вой форме конструктора создается пустое меню. Отдел ьные пункты меню относятся к типу Menu ltem. В классе Menu ltem опре­ деляются следующие конструкто ры: мenuitea () throws BeadlessException Nenuite1 11 (Strin9 хм. D.)l'Яr�a ) throws BeadlessException мenuit81 11 (String хм.-п,)l'Яr�а , МenuShortcut rл.-а ,lfoc�yпa) throws BeadlessException - где параметр имя_ пункта обозначает конкретное имя, отображаемое в меню, а параметр кл авиша_доступа - назначаемую клавишу быстрого доступа к данному пункту меню. Пункт меню можно акти визировать или дезактивизировать, вызвав метод setEnaЫed (). Ниже приведена общая форма этого метода. void setEnaЫed (boolean .пpжsяar_ar!l'a'.-.saцig) Если параметр пр иэна к_ активиэации принимает логическое значение true, то пункт меню будет активизирован . А если этот параметр принимает логическое значе ние false, то пункт меню будет дезакти визирован. Состояние пункта меню можно определить, вызвав метод isEnaЬled (). Ниже приведена общая форма этого метода. Ьoolean isEnaЫed () Метод isEnaЫed () возвращает логическое значение true, если активизирован пун кт меню, для которого он вызывается, а иначе - логическое значение fal se. Изменить имя пункта меню можно с помощью метода setLabel (),авыяснить текущее имя пункта меню - с помощью метода getLabel (). Ниже приведены об­ щие формы этих методов. void setLaЬel (Strinq новое хм.) Strinq qetLaЬel () - Здесь параметр но вое_имя обозначает задаваемое новое имя вызываемого пун­ кта меню. Метод getLabe l ( ) возвращает текущее имя пункта меню. Создать отмечаемый пункт меню можно средствами класса CheckЬoxMenultem, производного от класса Menul tem. У этого класса имеются следующие конструкторы: CheckЬoxМenuit- () throws BeadlessException CheckЬoxМenui tea (Strinq хм. D,)l'Яr�a) throws BeadlessException CheckЬoxМenuI� (Strinq - - D,)l'Яr�a , Ьoolean аrлючено) throws Beadles sException где параметр имя_ пункта обозначает задаваемое имя, отображаемое в меню. Отмечаемые пункты меню действуют подобно переключателям. Всякий раз, когда
964 Часть 11. Библиотека Java слева от одного из пунктов ставится отметка, его состояние изменяется. В первых двух формах конструктора отмечаемое поле не имеет метки . А в третьей форме от­ мечаемое поле имеет метку, если параметр включено принимает логическое зна­ чение true. В противном случае это поле остается пустым. Состояние отмечаемого пункта меню можно выяснить, вызвав метод get S tate ( ) . А присвоить пункту меню определенное состояние можно, вызвав метод setState ().Ниже приведены общие формы этих методов. Ьoolean qetS tate О void setState (boolean вжлючено) Если пункт меню отмечен , метод getState () возвращает логическое значение true, а иначе - логическое значение fa lse. Чтобы отметить пункт меню, доста­ точно передать методу setState () логическое значение true в качестве его един­ ственного параметра. Для того чтобы снять отметку с пункта меню, этому методу следует передать логическое значение fal se. После того как пункт меню будет создан, его нужно ввести в объект типа Menu с помощью метода add ( ) , который имеет следующую общую форму: мenui tel ll add (Мenuitein ПJ'НЖ!l') где параметр пункт обозначает вводимый пункт меню. Ввод пунктов в меню про­ изводится в том порядке , в каком осуществлялись вызовы метода add ().Вконеч­ ном итоге возвращается заданный пункт. После ввода всех пунктов в меню типа Menu его можно ввести, в свою очередь, в строку меню с помощью следующей версии метода add ( ) , определенной в классе MenuBar: мenu add (М8nu иеню) где параметр меню обозначает вводимое меню. В ко нечном итоге возвращается задан ное меню. События в меню наступают только при выборе пункта типа Menu ltem и CheckboxMenu I tem. Они не наступают, например , при обращении к строке меню для отображения выпадающего меню. Всякий раз, когда выбирается пункт меню, создается объект класса Act i onEvent. По умолчанию строка с командой действия содержит имя пункта меню. Но если вызвать метод setActionComma nd () для пун· кта меню, то можно определить другую строку с командой действия. Всякий раз, когда отмечается пункт меню или снимается его отметка, создается объект класса ItemEvent. Та ким образом, для обработки этих событий в меню следует реализо­ вать интерфейсы Ac tionListener и ItemListener. Метод ge tltem ( ) из класса ItemEvent возвращает ссылку на пункт меню, сге­ нерировавший данное событие. Ниже приведе на общая форма этого метода. OЬject qe tit81 11 О Ниже приведен пример, в котором ряд вложенных меню вводится во всплыва­ ющее меню. В окне аплета отображается выбранный пункт меню, а также состо­ яние двух отмечаемых пунктов меню. Пример раскрывания меню в окне аплета MenuDemo приведен на рис. 26. 16.
Гл ава 26. П рименение элементов управления, диспетчеров ком пон овки". 965 11 Пример приме нения ме ню import java .aw t.*; import java .awt . eveпt .*; import java . applet .*; /* */ <applet code= "MenuDemo " width=250 height•2 50> </applet> 11 создать подкласс, производный от класса Fraae class MenuFrame extends Frame { String msg = ""; CheckЬoxMenuitem debug , test; MenuFrame (String title ) super ( title) ; 11 создать строку ме ню и ввести ее в обрамляюще е окно MenuBar mЬ ar = new MenuBar (); setMenuBar (mЬar) ; 11 создать пункты меню Menu file = new Menu("File " ); Menui tem iteml , item2 , itemЗ , item4 , item5 ; file . add ( iteml • new Menuitem ("New ...") ); file. add(item2 new Menuitem ( "Open ...")) ; file . add (itemЗ = new Menuitem ("Close" ) ); file .add(item4 = new Menuitem("-") ); file .add ( item5 = new Menuitem ("Qu it ...") ); mЬar .add(file) ; Menu edit = new Menui tem i temб , edit .add(itemб edit .add (item7 edit .add(itemB edit .add(item9 Menu ("Edit ") ; item7 , item8 , item9 ; new Menuitem ("Cut") ); new Menuitem ("Сору") ); new Menui tem ("P aste") ); new Menuitem("-") ); Menu sub = new Menu ("Special") ; Menu!tem itemlO, itemll, item12; sub .add ( iteml O new Menuitem ("First" ) ); sub .add ( iteml l new Menuitem ("Second" )); sub.add(item1 2 new Menuitem ("Thi rd" )); edit .add (subl ; 11 создать отмечаемые пун кты меню debug = new Che ckboxMenuitem ("Debug" ); edi t .add (debug) ; test = new Che ckboxMenuitem ("Testing" ); edit .add(test) ; mЬar.add(edit) ; 11 создать объе кт для обработки событий действия 11 и событий от элементов MyMe nuHandler handl er = new MyMe nuHandler ( this ); 11 заре гис трировать этот объе кт дл я приема событий 11 действия и событий от элементов iteml . addAction Listener ( handler ); item2 . addActi onListener (handl er ); itemЗ . addActionLi stener ( handl er); item4 . addActionLi stener (handl er); item5 . addActionLi stener ( handler); itemб . addActionLis tene r(handler );
966 Часть 11. Библиотека Java } item7 . addActionListener ( handler); item8 . addActionLi stener (handler); item9 . addActionListener ( handler) ; item lO . addActionLi stener (handler ) itemll . addActionLi stener (handler ) iteml2 . addActionListener (handler) debug .additemListener (handl er); test.additemListener ( handler) ; 11 создать объект для обработки оконных событий MyWindowAdapter adapter = new MyWindowAdapter (this) ; 11 заре гистрировать этот объе кт для приема оконных событий addWindowLi stener ( adapter) ; puЫ ic void paint ( Graphics g) { g.drawS tring (msg, 10, 200) ; if (debug .getState () ) еlзе g.drawS tring ("Debug is on .", 10, 220) ; 11 Отладка включена g.drawString ("Debug iз off.", 10, 220) ; 11 Отладка отключена if (test .getState () ) еlзе g.drawS tring ("Testing iз on .", 10, 240) ; 11 Тестирование ВКJl l) чено g.drawS tring ("Testing is off.", 10, 240) ; 11 Тестирование отключено class MyWindowAdapter extends WindowAdap ter Me nuFrame me nuFrame ; puЫ ic MyWindowAdapter (MenuFrame menuFrame ) this .menuFrame = me nuFrame ; puЬlic void windowClosing (Wi ndowEvent we ) { me nuFrame .setVisiЬle (fal s e) ; class MyMenuHandler impleme nts ActionLi stener, ItemLi stener MenuFrame me nuFrame ; puЫ ic MyMenuHandler (MenuFrame me nuFrame ) this .menuFrame = menuFrame ; } 11 обработать события действия puЬlic void actionPe rformed (ActionEvent ае ) String msg = "You selected "; 11 Выбран пункт меню String arg = ae . getActionCoпn n and() ; if(arg.equals("New• • • " )) msg += "New. "; еlзе if (arg.equals ("Open •••") ) msg += "Open."; else if (arg . equals ( "Close" )) ms g += "Сlозе ."; еlзе if (arg .equals ( "Quit ...") ) ms g += "Quit ."; еlзе if (arg . equals ( "Edit") ) msg += "Edit ."; else if (arg.equals ( "Cut") )
Гл ава 26. П р именение эл ементо в уп р авления, диспетч еров ком поновки. .. 967 1 msg += "Cut ."; else if (arg . equals ( "Copy" )) msg += "Сору. "; else if (arg . equals ( "Paste" ) ) msg += "Paste ."; else if (arg . equals ( "Firзt") ) msg += "First . "; else if (arg . equ als ("S econd" )) msg += "Second."; else if (arg . equals ( "Third" )) msg += "Third. "; else if (arg . equals ("Debug " )) msg += "Debug. "; else if (arg . equals ( "Testing" ) ) msg += "Testing."; me nuFrame .msg = ms g; me nuFrame .repaint (); 11 обработать события от элементов puЬlic void itemS tateChanged (ItemEve nt ie ) { me nuFrame .repaint (); 11 создать обр аwляюще е окно puЬlic class MenuDemo extends Applet { Frame f; puЫ ic void init () { f = new Me nuFrame ("Menu Demo " ); 11 Демонстр ационное меню int width = Integer.parseint ( getParameter ( "width" ) ); int height = Integer . parselnt (getParameter ( "height ") ); setSize (new Dime nsion (width, heigh t) ); f. setSize (width, height) ; f. setVi siЬle (true) ; puЬlic void start () { f. setVi siЬle (true) ; puЫic void stop() { f. setVi siЬle (false) ; Testing is off. Рис. 26 .16 . Пример раскрывания меню в окне аплета MenuDemo
968 Ч асть 11. Библиотека Java Имеется также класс PopupMenu, который предстамяет интерес для создания всплывающих или контекстных меню. Этот класс действует подобно классу Me nu, но формирует меню, отображаемое в определенном месте. Класс PopupMenu предо­ стамяет гибкую и уд обную альтернативу в некото рых случаях организации меню. Диал оговые о кн а Нередко возникает потребность размещать ряд связанных вместе элементов упрамения в Оuа.iWговом tЖне. Диалоговые окна служат, в первую очередь, для полу· чения данных, вводимых пользователем. И зачастую они оказываются дочерними окнами по отношению к окну верхнего уровня. У диалоговых окон отсутствует стро­ ка меню, а в остальном они функционируют подобно обрамля ющим окнам. (В них можно, например, вводить элементы упрамения таким же образом, как и в обрам· ляющее окно.) Диалоговые окна могут быть модальными (т.е. режимными) или не­ модальными (безрежимными). Когда активным становится модальное диалоговое окно , то все вводимые данные напрамяются в него до тех пор, пока оно остается открытым. Это означает, что остальные части прикладной программы недоступны до тех пор, пока не будет закрыто диалоговое окно. Если активным становится ш.мо­ дальное диалоговое окно, то фокус ввода может быть передан другому окну приклад· ной программы. Та ким образом, остальные части прикладной программы остаются акти вными и доступными. Диалоговые окна относятся к типу Dialog. Ниже приве· дены два наиболее употребительных конструктора класса Dialog. Dialoq ( Frame ро�!l'елъсжое ожно , Ьoolean ре;ажн) Dialoq (Frame ро�!l'елъсжое:ожно , Strinq saroлosoж, Ьoolean ре;аин) Здесь параметр родитель ское_ окно обозначает владельца диалогового окна. Если параметр режим принимает логическое значение t rue, то создаваемое диало­ говое окно становится модальным, а иначе - немодальным. Заглавие диалогового окна можно указать в качестве параметра за голов ок. Как правило, для создания диалоговых окон сначала получаются подклассы, производные от класса Dialog, а затем они наделяются функциональными возможностями, требующимися для конкретного приложения. Ниже приведен вариант предыдущего примера аплета, переделан ный таким об­ разом, чтобы отображать немодальное диалоговое окно при выборе пункта меню New (Создать) . Обратите внимание на то, что в момент закрытия диалогового окна вызывается метод dispose ().Этот метод определяется в классе Window и освобож· дает все системные ресурс ы, связанные с диалоговым окном. На рис. 26. 17 приведен пример открытия диалогового окна при выполнении аплета DialogDemo . 11 Приме р приме нения диалогового окна import java .aw t.*; import java .awt . event .*; import java . applet .*; /* */ <app let code=" DialogDemo " width=2 50 height=250> </appl et> 11 создать подкласс , производный от класса Dialoq
Гл ава 26. П рименение эл ементов уп равления , диспетче ров ко мпон овки. .. 969 class SampleDialog extends Dialog implements ActionListener Samp l eDialog ( Frame parent , String title ) { �uper (parent , title, false) ; setLayout (new Fl owLayout () ); setSize (ЗOO, 200) ; add (new Label ("P ress thi s but ton:") ); Button Ь; add (b = new Button ("C ancel ") ); b.addActionLi stener (this) ; puЬlic void actionPerformed (ActionEvent ае ) { dispose (); puЬlic void paint ( Graphics g) { g.drawString ("Thi s is in the dialog Ьох" , 10, 70) ; 11 Это сообще ние выводится в диалоговом окне 11 создать подкласс, производный от класса Fraae class MenuFrame extends Frame { String ms g = ""; CheckboxMenui tem debug, test; MenuFrame (String title) supe r (title) ; 11 создать строку меню и ввести ее в обрамляющем окне MenuBar mЬar = new MenuBar () ; setMenuBar (mЬar) ; 11 создать пункты меню Menu file = new Menu ("File" ); Menuitem iteml , item2 , itemЗ , item4 ; file .add(iteml = new Menuitem ("New •..") ); file .add ( item2 = new Menuitem ("Open ...") ); file .add ( itemЗ = new Menuitem ("Close") ); file . add (new Menuitem("- ") ); file .add ( item4 = new Menuitem ( "Quit ...") ); mЬar.add(file) ; Menu edit = new Menu ("Edit" ); Menuitem item5 , itemб , item7 ; edit .add ( item5 = new Menuitem ("Cut" ) ); edit .add ( itemб = new Me nuitem("C opy" )); edit .add ( item7 = new Menuitem ("Paste") ); edit .add(n ew Me nultem("-11) ); Menu sub = new Menu ("Special ", true) ; Menuitem item8 , item9 , iteml O; sub.add (item8 = new Menuitem ("Fi rst") ); sub.add ( item9 = new Menuitem ("Second" )); sub.add ( iteml O = new Menuitem ("Third" )); edit .add (sub) ; 11 создать отмечаемые пункты ме ню debug = new Chec kboxMenui tem ( "Debug " ); edit .add(debug) ; test = new CheckboxMenuitem ("Testing" ); edit .add(t est) ; mЬar. add(edit) ;
970 Часть 11. Библиотека Java ) 11 создать объе кт для обработки событий действия 11 и событий от элементов MyMe nuHandler handl er = new MyMenuHandler(this) ; 11 зарегистрировать этот объе кт для приема событий 11 действия и событий от элементов iternl . addActionLi stener ( handler ); itern2 . addActionLi stener ( handler); iternЗ . addActionLi stener (handler) ; item4 . addActionLi stener (handlerJ; item5 . addActionLi stener (handler) ; iternб . addActionLi stener ( handler); item7 . addActionLi stener (handl er) ; itemB . addActionLi stener (handler); item9 . addAc tionLi stener (handler) ; itemlO . addActionLi stener (handler) ; debug .additemLi stener ( handler) ; test . additemListener (handler) ; 11 создать объе кт для обработки оконных событий MyWindowAdap ter adapter = new MyW i ndowAdapter(this) ; 11 зарегистрировать этот объе кт для приема оконных событий addWindowLi stener ( adap t er) ; puЫ ic void paint ( Graphics g) { g.drawString (msg, 10, 200) ; if (debug .getState () ) else g.drawString ("Debug is on . " , 10, 220) ; 11 Отладка включена g.drawString ("Debug is off.", 10, 220) ; 11 Отладка от ключена if (test .getState () ) else g.drawString ("Testing is on .", 10, 240) ; 11 тестирование включено g.drawString ("Testing is off. ", 10, 240) ; 11 Тестирование отключено class MyWindowAdapter extends WindowAdapter { MenuFrame me nuFrame ; puЫic MyWindowAdapter (MenuFrame menuFrarne ) this .menuFrame = me nuFrame ; ) puЫ ic void windowClosing ( WindowEvent we ) { menuFrame .dispose (J ; class MyMe nuHandler implernents Ac tionListener, ItemListener Me nuFrame rne nuFrame ; puЫic MyMenuHandl er (MenuFrame rnenuFrarne ) this .rnenuFrame = me nuFrame ; ) 11 обработать события действия puЬlic void actionPerformed (ActionEvent ае ) { String msg = "Уои selected "; String arg = ae . getActionCornmand (); 11 активизировать диал оговое окно при выборе пункта меню New if (arg.equals ( "New .•.") ) { msg += "New."; Sampl eDialog d = new SarnpleDialog (menuFrarne , "New Di alog Вох " );
Гл ава 26. Применение эл ементов управления , д испетч еров ко мпоновки .. . 971 d.setVisiЬle (true) ; } // попытаться определить диалоговые окна // для осталь ных пунктов меню else if (arg.equals ("Open ...") ) msg += "Open ."; else if (arg . equals ( "Close") ) msg += "Close ."; else if (arg . equals ("Qui t ...") ) msg += "Quit ."; else if (arg. equals ( "Edi t" ) ) msg += "Edit ."; else if (arg . equals ( "Cu t") ) msg += "Cut ."; else if (arg . equals ( "Copy" )) msg += "Сор у. "; // Копирование else if (arg.equals ( "Paste") ) msg += "Paste ."; // Вставка else if (arg. equals ( "First") ) msg += "First ."; // Первое окно else if (arg . equals ("S econd" )) msg += "Second ."; // Второе окно else if (arg . equals ( "Third" )) msg += "Third."; // Тре тье окно else if (arg . equals ( "Debug " )) ms g += "Debug ."; // Отладка else if (arg. equal s("Testing" )) msg += "Testing. "; / / Тестирование menuFrame .msg = ms g; menuFrame .repaint (); puЬlic void itemS tateChanged ( ItemEve nt ie ) { me nuFrame .repaint (); // создать обрамляющее окно puЫ ic class DialogDemo extends Applet { Frame f; puЫic void init () { f = new MenuFrame ("Menu Demo" ) ; int width = Integer .parseint (getParame t er ( "width") ); int height = Integer .parseint ( getPa rame t er ( "height " )); setSize (width , height ); f.setSize (width , height ); f.setVisiЬle (true) ; puЫic void start () { f.setVi siЬle (true) ; puЫic void stop () { f.setVi siЬle (false) ; Совет! При жел ании можно попробовать определ ить диалоговые окна для других пунктов меню.
972 Часть 11. Библиотека Java Applet Viewer. Дpplet You selected Applet start Debug ls orr. Testin is о New Dialog Вох Press !h is button: Cancel 1 Thls is ln the dialog Ьо)( Рис. 26 .1 7. Пример открытия диалогового окна при выполнении аплета DialogDemo . Диалоговые окна выбо ра фай лов BJava предоставляется встроенный класс Fi leDialog, реализую щий диалого­ вое окно , в котором пользователь может выбрать файл. Чтобы создать диалоговое окно выбора файлов, следует получить экземпляр класса FileDialog. В итоге по­ явится диалоговое окно выбора файлов. Как правило, это стандартное диалоговое окно, предоставляемое операционной системой для выбора файлов. Ниже приве­ дены конструкторы класса FileDialog. FileDialog (Fraшe родя 'l'ель, String заголовок охна ) Fi leDialog (Fraшe родя 'l'ель, String эаголо.ож - охна , int способ) Fi leDialoq (Fraae родя 'l'ель) - Здесь параметр родитель обозначает владельца диалогового окна, а параметр заголов ок_ окна - название, отображаемое в строке заголовка диалогового окна. Если параметр за головок_ окна не указан , то заголовок диалогового окна отобра­ жается. Если параметр спо со б принимает значение константы FileDialog . LOAD, то файл выбирается в диалоговом окне для чтения. А если этот параметр прини­ мает значение константы F i 1еDiа1og . SAVE, то файл выбирается для записи. Если же параметр спо со б не указан , то по умолчанию файл выбирается в диалоговом окне для чтения. В классе Fi leDialog предоставляются методы , позволя ющие определить имя файла и путь к нему, после того как этот файл будет выбран пользователем. Ниже приведе ны два примера объя вл ения этих методов. Эти методы возвращают ката· лог и имя файла соответственно. String ge tDirectory () Strinq qetFile ()
Глава 26. Применение элем ентов управления, диспетчеров компоновки... 973 В следующем примере программы акти визируется стандартное диалоговое окно выбора файлов: /* Продемонстрировать приме нение диалогового окна выбора файлов . Это - прикладн ая программа , а не аnлет . */ import java .awt.*; import java.awt . event .*; 11 создать подкласс , производный от класса Fraвe class SampleFrame extends Frame { SampleFrame (String title) { supe r (title) ; 11 удали ть окно после его закрытия addWi ndowLi stener (new WindowAdapter () { }); puЫ ic vo id windowClosing ( Wi ndowEvent we ) { System. exit (O) ; 11 Продемонс трир овать приме нение кл асса FileDial09 cla ss Fi leDialogDemo { puЫ ic static void main (String args[] ) { 11 созда ть обрамляюще е окно , которому будет 11 принадлежа ть диалоговое окно Frame f = new SampleFrame ("File Dialog Demo " ); f.setVisiЬle (true) ; f. setSi ze (lOO, 100) ; FileDialog fd = new FileDialog (f, "File Dialog" ); fd . setVi siЬle (true) ; На рис. 26. 18 приведен примерный результат, выводимый данной программой. (Конфигурация диалогового окна может отличаться в зависимости от конкретной исполняющей среды.) И последнее замечание: начиная с версииJDК 7 класс FileDialog можно ис­ пользовать для выбора файлов из списка. Соответствующие функциональные возможности обеспечивают методы setMultipleMode (), isMu ltipleMode () и ge tFiles (). О переопредел е н ии метода paint () Прежде чем завершить рассмотрение элементов управления из библиотеки АWГ, следует сказать несколько слов о переопределении метода paint ().Хотя это и не отражено в простых примерах , демонстрирующих применение библиотеки АWГ в этой книге, при переопределении метода paint () иногда необходимо об­ ращаться к реализации метода pa int () в суперклассе. Поэтому в некоторых про­ граммах придется воспользоваться следующим скелетом метода paint ():
971+ Ч асть 11. Библиотека Java puЬl ic void paint ( Graphics g) { 11 код перерисовки данного окна 11 вызвать ме тод paint () из суперкла сса super. paint (g) ; Filt Diьlog Му!Х Nom• D.rtt modifitd Rtctnt Ploc<!S 61 1 mpl<!S 1/14/2014 3:07 РМ U Dirlist.class 1/14/2014 3:06 РМ Dirlist 1/14/2014 3:06 РМ U Exo mplt.c lass 1/14/2014 3:06 РМ Dtslrtop Exompl• 1/14/2014 3:06 РМ МyFil• 1/14/2014 3:06 РМ L) МyFilo2 1/14/2014 3:06 РМ Librari<!S Computor Nttwork rml· Тур• Siz• Fil• fold•r CLASS Fil• JAVA Fil• CLASS Fil• JAVA Fi l• Тext Docum•nt Тext Documtnt ·! Рис. 26 .18. Пример открытия диалогового окна выбора файлов экв 2КВ 1КВ 1кв 1кв 1кв В языке Java имеются два общих типа ко мпонентов: тяжеловеснъr,е и легковеснъ�е. У тяжеловесного компонента имеется свое базовое окно , а легковесный компо­ нент полностью реализуется в коде Java и испол ьзует окно, предоставляемое ро­ дителем. Все элементы управления из библиотеки АWТ, упомянутые в этой гл аве , являются тяжеловесными. Но если контейнер содержит какие-нибудь легковес­ ные компоненты (т.е . имеет дочерние легковесные компоненты), то при перео­ пределении метода paint ( ) для этого контейнера следует вызвать метод sup er . paint ( ) . Вызовом метода super .paint ( ) гарантируется правильность перери· совки любых легковесных дочерних компонентов, например, легковесных эле­ ментов управления. Если неясен тип дочернего компонента, то для его выяснения можно вызвать метод isLightwe ight (), определенный в классе Component. Этот метод возвращает логическое значение true, если компонент является легковес­ ным, а иначе - логическое значение fa lse.
27 Изображения В этой гл аве рассматриваются класс Image из библиотеки АWГ и пакет j ava . awt . image. Совместно они обеспечивают поддержку формирования изображений, которое заключается в отображении графических изображений и манипулирова­ нии ими. Из ображением является обычный прямоугольный графический объект. Изображения являются ключевым компонентом разработки веб-приложений. Когда в 1993 году разработчики из NCSA (National Center fo r Supercomputer Ap plications - Национальный центр по применению суперкомпьютеров) решили включить дескриптор <img> в браузер Mosaic, это способствовало быстрому раз­ витию веб. Этот дескриптор использовался для встраивания изображений в поток гипертекста. Языкjаvа расширяет этот базовый принцип, позволяя управлять изо­ бражениями программным способом. Вследствие этой важной особенности в Java обеспечивается всесторонняя поддержка формирования изображений. Изображения представлены объектами I<Ласса Irnage , который входит в пакет j ava . awt . Манипулирование изображениями осуществляется с помощью классов из пакета j ava . awt . image , который содержит большое количество классов и интер­ фейсов для формирования изображений. Не имея возможности рассмотреть каждый класс и интерфейс в отдел ьности, сосредоточим основное внимание на тех из них, которые участвуют в основном процессе формирования изображений. Ниже пере­ числены классы из пакета j ava . awt . image , которые рассма'Iриваются в этой гл аве. CrOPill lA mtFilter Мuaorviaaaesource Fil terediшaqeSource PixelGr.Ь Ь. r IшaaeFilter RGВIJl la oeFilter В примерах из этой гл авы применяются следующие интерфейсы: 1 IaaqeConsWl l8 r J IшaqeOЬserver JI11 1& geProducer Кроме того , в гл аве рассматривается I<Ласс MediaTracker, который входит впакетjаva.awt. Форматы файл ов Первоначально веб-изображения могли быть представлены только в форма­ те GIF. Формат изображений GIF был разработан специалистами из компании CompuServe в 1987 году для просмо'Iра изображений в оперативном режиме, по-
976 Часть 11. Б ибл иоте ка Java этому он был пригоден и для Интернета. Каждое изображение формата GIF может содержать не более 256 цветов. В связи с этим ограничением в 1995 году ведущие разработчики браузеров включили в них поддержку изображений формата JРЕG. ФорматJPEG был разработан группой специалистов по фотографии для хранения полутоновых изображений с полным спектром цветов. Если правильно сформи· ровать подобное изображение, оно будет воспроизводиться с более высокой сте­ пенью точности и уплотняться более компактно, чем аналогичное изображение формата GIF. Имеется также формат файлов изображений PNG, который явля· ется разновидностью формата GIF. Как правило, в прикладных программах вряд ли придется обращать особое внимание на используемый формат изображений, поскольку классы изображений вJava абстрагируют все отличия в форматах благо· даря ясно определенному интерфейсу. О сновы работы с изображения ми: соэда н ие, загрузка и отображен ие В процессе обработки изображений обычно выполняются в основном три опе­ рации: формирование изображения , его загрузка и воспроизведение. Для обра· щения к изображениям, хранящимся в операти вной памяти , а также к изображе­ ниям, загружаемые из внешних источников данных, вjava предоставляется класс Image. Та ким образом, вjava предоставляются способы создания нового объекта изображения и его загрузки , а также функциональные средства, позволяющие вое· производить изображения. Все эти средства будуг рассмотрены далее по очереди. Соэдание объекта класса Imaqe На первый взгляд может показаться, что для формирования изображения в оперативной памяти требуется примерно такая строка кода: Image test = new Image (200, 100) ; // Ошибка - не сработает! Но на самом деле это не так. Любое изображение предназначено для того , что­ бы его можно было воспроизвести на экране монитора, а класс Image не распола· гает достаточными сведениями о среде для создания подходящего формата дан· ных, выводимых на экран. Поэтому в класс Component из пакета j ava . awt вклю­ чен метод create image (), предназначенный для создания объектов типа Image. (Напомним, что все компоненты из библиотеки АWГ являются производными от класса Component, поэтому все они поддерживают метод create image ().) Метод create Image () имеет следующие общие формы: I11 1 aqe createIJD&qe ( IшaqeProducer пос�а.вщ•х •sображенжJi) Imaqe createIJD&qe (int --.р.-на , int .вuсо�а ) - В первой форме данн ого метода возвращается изображение, сформирован· ное указан ным поста вщиком_ из о бражений, который представляет собой объект класса, реализующего интерфейс Image Produce r. (О поставщиках изображений речь пойдет далее в этой гл аве.) Во второй форме возвращается пустое изображе-
Гл ава 27. Изображения 977 ние, имеющее определенную ширину и высоту. Ниже приведен пример создания объекта пустого изображения. Canvas с=new Canvas (); Image test = c.createlmage (200, 100) ; В данном примере для создания объекта типа Image сначала получается экзем­ пляр класса Canvas, а затем вызывается метод crea te Image ().Полученный объект изображения остается пока еще пусты м. Далее будет показано, как заполнить его данными. Загруз ка изображения Другой способ получить изображение состоит в том, чтобы загрузить его. Для этой цели служит метод get image (), определенный в классе Applet. Он име­ ет следующие общие формы: Iшage qetiшage (URL url) Iшage getiшage (URL url , Strinq .1 1НR _ мsображеюrя) В первой форме данного метода возвращается объект типа Image, инкапсу­ лирующий изображение, находящееся в месте , определяемом параметром url. А во второй форме возвращается объект типа Image, инкапсулирующий изобра­ жение, местонахождение и имя которого определяются параметрами ur 1 и имя_ из о бражения соответственно. Воспроизведение и зоб ражения Итак, получив изображение, можно воспроизвести его с помощью метода draw!mage ( ) , который является членом класса Graphics. У этого метода имеет­ ся несколько форм. Ниже представлена его общая форма, которая употребляется в данной книге. boolean drawimage (Iшage 06'5e.1 1t !1' мsoбpaж81UrR, int слева, int сверху, IшageObserver о65еж!1'_яsображенl lf я) В дан ном случае воспроизводится изображение, передаваемое в качестве па­ раметра о бъект_ из о бражения, а левый верхний угол этого изображения опреде­ ляется параметрами сл ева и св ерху. Параметр о бъект_ изображе ния обозначает ссылку на класс , реализующий интерфейс ImageObserve r. Этот интерфейс реа­ лизуют все компоненты библиотек АWТ и Swing. Наблюдател:ь изображения пред­ ставляет собой объект, который может наблюдать за изображением во время его загрузки. Интерфейс ImageObserve r рассматривается в следующем разделе. Загрузить и воспроизвести изображение методами get!mage ( ) и draw!nage () совсем не трудно. Ниже приведен пример аплета, загружающего и отображающе­ го отдел ьное изображение. В этом аплете загружается файл изображения L i 1 i е s . j pg, но для данного примера можно взять любой другой файл изображения фор­ мата GIF, JPG ил и PNG, при условии, что он будет находиться в том же каталоге , что и НТМL-файл, содержащий аплет. /* * <applet code= "Simp lelmageLoad" width=400 height=345>
978 Часть 11. Б и бл иоте ка Java * <param name=" img " value= "Lilies .jp g"> * </applet> */ import java .awt.*; import java . applet .*; puЫ ic class SimpleimageLoad extends App let { Image img ; puЫ ic void init () { img = getimage ( getDocumentBase () , getParame ter ("i mg ") ); puЫ ic void paint ( Graphics g) { g.drawimage (img , О, О, this ); В методе init ( ) переменной img присваивается изображение, возвращаемое методом get Image ( ) . Символьная строка, возвращаемая в результате вызова ме­ тода getPa rameter ( "img" ) , используется в методе getimage ( ) в качестве имени файла изображения. Это изображение загружается по URL, заданному относитель­ но результата выполнения метода getDocumentBase ( ), т.е . по URL той НТМL­ страницы, на которой находился дескриптор данного аплета. Имя файла, возвраща­ емое в результате вызова метода gеtРа rаmеtеr ( "img"),получается из дескриптора аплета <param name=" img " va lue="Lilies . jpg">. Та кая разметка эквивалентна применению дескриптора НТМL-разметки <img src ="Lilies . jpg" width=400 height=345>, хотя она действует немного медленнее. На рис. 27. 1 представлен ре­ зультат выполнения данного аплета. Рис. 27.1. Воспроизведение загруж енного изображения в окне аплета Simp leimageLoad
Гл ава 27. И зображения 979 Выполнение данного aruieтa начинается с загрузки изображения img в методе init (). Изображение будет появляться на экране монитора по мере его загрузки из сети, поскольку при реализации интерфейса ImageObserve r в классе Applet метод pa int ( ) вызывается всякий раз, когда поступают новые данные изображения. Разумеется, совсем не плохо иметь возможность наблюдать за ходом загруз­ ки изображения, но время ожидания загрузки изображения лучше потратить на параллельное выполнение других задач. Это позволит отобразить полностью сформированное изображение на экране монитора сразу же после его загрузки . Интерфейс ImageObserve r, о котором пойдет речь далее , можно применять для контроля за ходом загрузки изображения в то время , как на экран монитора выводится какая-нибудь другая информация . И нтepфe й cimageObserver Этот интерфейс используется для получения уведомления о формировании изо­ бражения . В нем определяется единственный метод imageUpdate (). Используя наблюдатель изображений типа ImageObserver, можно выполнять ряд других действий, в том числе отображать индикатор выполнения или же переключаться на экран при получении уведомления о ходе выполнения загрузки. Та кой тип уве­ домлений удобен для загрузки изображений по низкоскоростной сети. У метода imageUpdate () имеется следующая общая форма: boolean iшageUpdate (Imaqe о�еж!I' •soбpazeюur , int пр•sна юr, int с.1 1 ева, intсверху,int�,intВl l C�a) где параметр о бъект_ из о бражения обозначает загружаемое изображение, а па­ раметр пр изна ки - целое число , передающее состояние обновляемого отчета. Четыре целочисленных параметра, слева , св ерху, шир ина и высо та , определяют прямоугольник, различные значения которого зависят от значений, передавае­ мых в качестве параметра пр из на ки. Метод imageUpdate () возвращает логиче­ ское значение false, если процесс загрузки завершен, или логическое значение true, если требуется обработать еще одно изображение. Параметр признаки содержит один или несколько поразрядных признаков, опре­ деляемых в качестве статических переменных в интерфейсе ImageObserver. Эти признаки, а также передаваемые с их помощью данные перечислены в табл. 27. 1. Табл ица 27.1 . Поразрядн ые признаки, обозначаемые параметром пр•.з11&Юlf в методе i.JDaqeUpdate () ПрмJнак 1fIDTB ВEIGBT Н аэначенме Параметр -.рхва является дейсгвительным и содержит значение ш ирины и зображения Параметр .вuco!l'a является дейсгвительным и содержит значение высоты и з ображения PROPERT IES Свойства, с вязанные с изображением, можно получить с помощью метода il llgOЬ j . getProperty О
980 Часть 11. Б иблиоте ка Java Ок1жчание mаМ. 27.1 Приэнак Наэначение Seta:BITS П олучены дополнительн ые пиксели, н еобход имые для рисования изобра­ жения. Параметры сле•а, с•ер.х,у, -.р.,.а и 851CO!l'a определяют прямоу­ гольник, содержащий новые пиксели FRAМZBITS Получен весь кадр, являющийся частью ранее нарисованного многока­ дрового изображения. Этот кадр можно отобразить. Параметры ел••• . ca8PXJf,llDlpJUraи81 1 CO!l'a не используются AL LВ ITS Изображение готово. Параметры спеаа, саер.х,у, llDlpXRa и 851CO!l'a не ис­ пользуются ERROR АВОRТ Обнаружена ош ибка в асинхронно отслеживаемом изображении. Изображение не готово и не может быть восп роизведено. Никаких до­ п ол нительных сведений об изображении не получено. Кроме того, будет установлен признак АВОRТ, чтобы указать, что процесс формирования изо­ бражения был прерван Формирование асинхронно отслеживаемого изображения было прервано до полного завершения этого процесса. Но есл и не произошло ош ибки , то при попытке обратиться к какой-нибудь части дан ных изображения нач· нется повторное формирование изображения В классе App let имеется реализация метода irnageUpdate () для инте рфейса IrnageObserver. Этот метод служит для перерисовки изображений во время их загрузки. Та кое поведение можно изменить, если переопределить данный метод в своем классе. Ниже приведен пример применения метода irnageUpdate (). puЫ ic boolean imageUpdate ( Image img , int flags , intх,intу,intw,inth){ if ((flags & ALLBITS) == 0) { System. out . println ("S till proce ssing the image.") ; 11 Изображение все еще обрабатывае тся return true ; else { System. out . println ("D one processing the image.") ; 11 Обработка изображения завершена return false ; Двой ная буфериза ци я Изображения можно использовать не только для хранения фотографий и ри­ сун ко в, как было показано выше, но и в качестве внеэкранных поверхностей ри­ сования. С их помощью можно воспроизвести любое изображение, включая текст и графику во внеэкранном буфере, содержимое которого можно отобразить не­ которое время спустя. Преимущество такого подхода заключается в том , что изо­ бражение можно увидеть лишь после то го , как оно будет полностью готово к вос­ произведению. Для рисования сложного изображения может потребоваться не­ сколько миллисекунд или больше, причем для пользователя этот процесс может выглядеть как серия вспышек или мерцаний.
Гл ава 27. Изображения 981 Подобные эффекты отвлекают внимание и приводят к тому, что пользова­ тель воспринимает воспроизводимое изображение намного медленнее, чем это происходит на самом деле. Процесс использования внеэкранного изображения для уменьшения мерцания называется двой:ной буфеjm.3ацией, поскольку экран мони­ тора принимается в качестве буфера для пикселей воспроизводимого изображе· ния, а внеэкранное изображение является вторым буфером, где можно подготав­ ливать отдельные пиксели к воспроизведению. Ранее в этой гл аве уже пояснялось, как создается пустой объект класса Image. А здесь будет показано, каким образом выполняется воспроизведение изображе­ ния на самом экране. Напомним, для этой цели требуется объект класса Graphics, благодаря которому можно использовать любые методы визуализации, доступные в Java. Он уд обен тем , что доступ к объекту класса Grap hics, который можно при­ менять для воспроизведения изображения, осуществляется с помощью метода ge tGraphics (). Ниже представлен фрагмент кода, в кото ром формируется новое изображение, получается графический контекст и все изображение заполняется пикселями красного цвета. Canva s с=new Canvas () ; Image test = c.create image (200, 100) ; Graphics gc = test . getGraphics (); gc . setColor (Color .red) ; gc . fillRect (O, О, 200, 100) ; После того как внеэкранное изображение будет создано и заполнено, его по· прежнему не будет видно. Чтобы воспроизвести это изображение, следует вызвать метод drawimage (). Ниже приведен пример аплета, где для воспроизведения изо­ бражения требуется немало времени. Этот пример демонстрирует, насколько двой­ ная буферизация оказывает вл ияние на восприятие времени воспроизведения. /* * <applet code=DouЬl eBuffer width=2 50 height=250> * </applet> */ import java .awt.*; import java .awt . event .*; import java . applet .*; puЫ ic class DouЬleBuffer extends App let intgap=3; int mx, ту; boolean flicker = true ; Image buffer = null; int w, h; puЫ ic void init () { Dimension d = getSize () ; w = d.width; h = d.height ; bu ffer = create image (w, h) ; addМouseMo tionListener (new MouseMotionAdapter () puЫic vo id mo useDragged (MouseEve nt me ) { mx = me.getX(); my = me .getY() ; flicker = fal se ; repaint () ;
982 Часп. 11. Библ иотека Java }); ) } puЫ ic vo id mouseMoved (MouseEve nt me ) { mx = те.getX(); my = me .getY(); flicker = true ; repaint () ; puЫ ic vo id paint ( Graphics g) Graphi cs screengc = null; if ( ! flicker) { screengc = g; g = Ьuffer. getGraphics (); g.setColor (Color .Ыu e) ; g.fil!Rect (O, О, w, h) ; g.setColor (Color .red) ; for (int i=O; i<w; i+=gap ) g.drawLine (i, О, w- i, h) ; for (int i=O; i<h; i+=gap ) g.drawLine (O, i, w, h-i) ; g.setColor (Color .Ыack) ; g.drawString ("Press mouse button to douЫe Ьuffer", 10, h/2) ; 11 Ще лкните кноп кой NЬПDИ, чтобы перейти в режим 11 двойной буфериэации g.setColor (Color . ye llow) ; g.fillOval(mx - gap, ту - gap, gap*2+1, gap*2+1); if ( ! flicker) { screengc .dr awimage (buffer, О, О, nul l ); puЫ ic void update(Graphics g) { paint (g) ; Этот простой пример аnлета содержит сложный метод paint (). В этом аnле· те синим цветом окрашивается фон, на котором рисуется красный муар. Далее на фоне этого муара выводится текст черного цвета и вычерчивается желтая окруж· ность, имеющая центр с координатам mx, my. Методы mo useMoved () и mouse­ Dragged ( ) переопределяются для отслежиl)ания положения курсора мыши. Эти методы одинаковы, за исключением логичуской переменной flicker. В методе mo useMoved () переменной flicker присваивается логическое значение true, а в методе mou seDragge r ( ) - логическое значение false. Это равнозначно резуль­ тату вызова метода repaint (), когда переменная flicker содержит логическое значение true, если курсор перемещается без нажатия кнопки мыши, или логи­ ческое значение false, если курсор мыши перемещается при одновременно на­ жатой кнопке мыши.
Гл ава 2 7 . Изображения 983 Если метод paint () вызывается в тот момент, когда переменная flicker содер­ жит логическое значение true , то на экране можно наблюдать вьшолнение каж­ дой операции рисования. Если же бьт произведен щелчок кнопкой мыши, а метод paint ( ) вызывается в тот момент, когда переменная flicker содержит логическое значение false, то на экране можно наблюдать совсем иную картину. Метод paint ( ) заменяет ссьтку g на класс Graphics графическим содержимым внеэкранного хол­ ста buffer, созданного в методе init (),идалее все операции рисования становя'Г" ся невидимыми. А в конце метода paint ( ) просто вызывается метод drawlmage () для одновременного ото�ражения результатов выполнения всех методов рисования. Обратите внимание на то , что теперь методу draw lmage ( ) можно передавать пустое значение nul 1 в качестве четвертого параметра. Этот параметр служит для передачи объекта ти па ImageObserve r, который получает извещение о собы­ тиях от изображения . В данном случае изображение не формируется из сетевого потока ввода-вывода, и поэтому никаких уведомлений не требуется. На момен­ тальном снимке экрана на рис . 27.2, слева, показано, как будет выглядеть резуль­ тат выполнения дан ного аплета, если не будет произведен щелчок кнопкой мыши. Как можно заметить, этот снимок бьт получен именно в тот момент, когда изобра­ жение бьто перерисовано наполовину. А на моментальном снимке экрана на рис. 27.2, справа, показано, что при нажатой кнопке мыши изображение оказывается полностью сформирован ным благодаря двойной буферизации. Рис. 27.2. Резул ьтат выполнения аnлета DouЫeBuffer без двой ной буферизации ( сл ева) и с двойной буферизацией ( справа) Клacc Medi aTracker Объект класса MediaTracker - это объект, который парал л ельно проверяет состояние произвольного количества изображений. Чтобы использовать класс Me diaTracker, нужно создать его новый экземпляр и вызвать его метод add Image ( ) для наблюдения за состоянием загрузки изображения. Метод addlmage ( ) имеет следующие общие формы:
98' Часть 11. Б иблиотека Java void addiшaqe (I11 1&9 e о�еж!l' 11so6paж8Юl l .1 1 , int 11ден!1'11фJlжа!l'ор 11.sображеюr.1 1 ) void addiшaqe (I11 1a qe о�аж!l' - 11.sображенж. 11 , int мден!1'11ф11жа!l'оР.:11sображ8Юl l .1 1 , int 8JIP11Яa , Tnt -.rco!l'a) где параметр объект_ из ображения обозначает отслеживаемое изображение. Его идентификатор передается в качестве параметра ид ентифика тор_ изображения. Идентификаторы не обязательно должны быть однозначными. Один и тот же иде нтификатор можно использовать для обозначения нескольких изображений как части группы. Кроме того , изображения с меньшими идентификаторами при загрузке имеют приоритет над б ll ll ьшими идентификаторами. Во второй форме параметры шир ина и высота определяют размеры объекта при его воспроизве­ де нии. После того как изображение будет зарегистрировано, можно проверить, загру­ же но ли оно, или подождать, пока оно загрузится полностью. Чтобы проверить состояние изображения , следует вызвать метод checkID (). В этой гл аве употре­ бляется следующий вариант данного метода: Ьoolean checkID (int 11ден!1'11ржа2'ор_•sоdражеюr.1 1 ) где параметр ид ентифика тор_ изображения определяет проверяемый идентифи­ ка ционный номер изображения. Метод checkID ( ) возвращает логическое значе­ ние true , если загруже ны все изображения , имеющие указанный идентификатор, или же если процесс загрузки был остановлен вследствие ошибки или прерван пользователем. В противном случае он возвращает логическое значение false. Метод checkAll () можно использовать, чтобы проверить, все ли наблюдаемые изображения были загружены. Класс Me diaTracker следует использовать при загрузке группы изображений. Если все представляющие интерес изображения еще не загружены, можно отобра­ зить что-нибудь, чтобы отвлечь внимание пользователя на время полной загрузки всех изображений. Внимание! Есл и испол ьзовать объект класса Medi атr а с ke r посл е вызова метода addIma ge ( ) , то ссылка на класс Med iaTracker предотвратит процесс сборки "мусора " в системе. Если же требуется, чтобы систе ма могла собирать отслеживаемые изображения в "мусор", сле­ дует обес печить сборку в "мусо р" и экземпляра класса MediaTracker. Ниже приведен пример аплета , в котором загружается семь изображений и отображается привлекательная столбиков<J'я диаграм ма, иллюстрирующая вы­ полнение загрузки. /* * <applet code="TrackedimageLoad " width=ЗOO height=400> * <param name= " img " * value= "vincent+leona rdo+matisse+picasso+renoi r+seurat+ve rmee r"> * </appl et> */ import java .util .*; import java . appl et .*; import java.aw t.*; puЫ ic class TrackedimageLoad extends App let impleme nts Ru nnaЫe { Medi aTracke r tracker;
int tracked; int frame rate = 5; int current img = О; Thread mo tor ; static final int МAX IМAGES = 10; Image img[) = new Image [МAXIМAGE S ]; String name [J = new String [МAXIМAGES J; volatile boolean stopFlag; puЫ ic void init () { tracker = new MediaTracker (thi s) ; StringTokeni zer st = Гл ава 27. Изображения 985 new StringTokeni zer ( getParame t er ("i mg " ), "+") ; while (st.hasMoreToke � () && tracked <= МAX IМAGES ) { name [tr acked] = st . nextToken () ; img [tracked ] = · getimage (getDocumentBase () , name [ tracked] + ".jpg" ) ; tracker . add!mage (img [ tracke d] , tracke d) ; tracked++ ; puЬlic void paint ( Graphi cs g) { String loaded = ""; int donecount = О; for (int i=O; i<tracked; i++) { if (tracker . checkID (i, true) ) donecount++; loaded += name [i] + " "; Dimension d = getSize () ; int w = d.width; int h = d.height ; if (donecount == tracked) frame rate = 1; Image - i = img [current img++ J; int iw = i.getWi dth (null ) ; int ih = i.getHeight (null ); g.drawimage (i, (w - iw) /2, (h - ih )/2, null ); if (current img >= tracked ) current - img=О; else { - int х = w * donecount / tracked; g. setColor (Color .Ыack) ; g.fillRect (O, h/З, х, 16) ; g. setColor (Color .white ); g.fillRect (x, h/3, w-x , 16) ; g.setColor (Color .Ыack) ; g.drawSt ring (lo aded, 10, h/2) ; puЫ ic void start () { motor = new Thread (this ); stopFlag = false; motor .start (); puЫ ic void stop()
986 Часть 11. Библиотека Java stopFlag = true ; puЫic void run () { rnotor .setPriority (Thread .MIN PRIORITY ); while (true ) { - repaint () ; try { Thread .sleep (lOOO/frarne rate) ; catch (Inte rruptedException е ) { Systern.out . println ("Interrup ted" ); re turn ; } if ( stopFlag) return ; В дан ном примере создается новый экземпляр класса MediaTracker в методе init () , после чего с помощью метода add!mage () каждое из указанных изображе­ ний вводится как отслежи ваемое. В методе paint () вызывается метод checkID () для каждого отслеживаемого изображения. После загрузки все изображения будут выведены на экран . В противном случае отображается простая столбиковая диа­ грамма, информирующая о количестве загруженных изображений, а под ней вы­ водятся наименования полностью загруженных изображений. И нтepфe й cimageProducer Интерфейс ImageProducer предназначен для объектов, которые должны предоставить данные для изображений. Объект класса, реализующего интерфейс Image Producer, задает массив целых чисел или байтов, представляющий данные изображений, и формирует объекты типа Image . Как бьuю показано ранее, одна из форм метода create lmage () получает объект типа ImageProduce r в качестве своего параметра. Пакет j ava . awt . image содержит два поставщика изображений в виде классов Memoryima geSource и Filterediyiage Source. Ниже будет рассмо­ трен класс Memoryimage Source и показано соз�ние нового объекта типа Image на основе данных, сформирован ных в аплете . Кnacc мemo ryimageSource Этот класс формирует новое изображение типа Image на основе массива дан­ ных. В нем определяется несколько конструкторов. Ниже приведен один из кон­ структоров, который будет использоваться далее в это й гл аве. Мel l oryI11 1& qeSource (int ll lJIPl lUl a, int вuco!l'a , int пжжселъ[] , int CNEl l/l' eнare, int 11rJl1)Jrl l a_c!rp0юr_pasaep!r.кя) Объект класса MemoryimageSource формируется на основе массива целых чи­ сел, определяемого параметром пиксель , в используемой по умолчанию цветовой модели RGB с целью предоставить данные для объекта типа Image. В этой цвето­ вой модели пиксель обозначается составным целочисленным значением альфа-ка-
Гл ава 27. Изображе ния 987 нала, а также каналов красного , зеленого и синего цвета (OxA AR R GGB B) . Значение альфа-канала обозначает степень прозрачности пикселя . Полностью прозрачному пикселю соответствует нулевое значение, а полностью непрозрачному - значение 255. Значения ширины и высоты готового изображения передаются в качестве параметров ширина и высо та . Исходная точка в массиве пикселей, с которой нач­ нется чтение данных, определяется параметром смеще ни е. Ширина строки раз­ вертки , которая нередко соответствует ширине изображения , обозначается пара­ метром шир ина_ строки_ра звертки. В следующем коротком примере аплета создается объект класса Memoryimage Source с помощью разновидности простого алгоритма (логической операции по­ разрядное исключающее ИЛИ над координатами х и у каждого пикселя), взятого из книги &yoruJ, Photography, The DigiJaJ, Darkro om Джepapдa Дж. Хольцманна (GerardJ. Holz­ mann, Prentice Hall, 1900). /* * <applet code= "MemoryimageGene rator" width=256 height=256> * </applet> */ import java . applet .*; import java.awt.*; import jav a.awt . image .*; puЫ ic class MemoryimageGene rator extends Ap plet { Image img ; puЬlic void init () { Dimension d = ge tSize () ; int w = d.width; int h = d.height; int pixels[J = new int[w * h]; inti=О; for(int у=О; y<h; у++) { for(int х=О; x<w; х++) int r = (xлy) &Oxff; int g = (x*2лy*2) &0xff; int Ь = (x*4лy*4) &0xff; pixels[i++J=(255«24)1(r«16)1(g«8)1Ь; img create image (new Memoryimage Source (w, h, pixels , О, w) ); puЫic void paint ( Graphi cs g) { g.drawimage (img , О, О, thi s) ; Данные для нового объекта типа MemoryimageSource создаются в методе ini t (). Массив целых чисел предназначен для хранения значений пикселей; данные создаются во вложенных циклах for, где значения r, g и Ь оказываются смещенными на оди н пиксель в массиве pixels. В конце данного аплета вызы­ вается метод create image () с новым экземпляром класса Memoryima ge Source , созданным из исходных данных пикселей в качестве его параметра. На рис. 27.3 показано изображение в момент запуска аплета. (В цвете оно выглядит гораздо привлекательнее.)
988 Часть 11. Б и бл иоте ка Java Рис. 27.З . Пример формирования изображения в окне аплета ImageGenerator И нтepфeй cimageConsumer Интерфейс ImageConsume r предназначен для объектов, которые должны полу· чать дан ные пикселей из изображений и предоставлять их в качестве другого вида данных. Та ким образом, этот интерфейс является прямой противоположностью описанного ранее интерфейса Image Р roduce r. Объект класса, реализующего интер­ фейс ImageConsumer, служит для создания массивов типа int или Ьуtе , представля· ющих пиксели из объекта типа Image. Ниже будет рассмотрен класс PixelGrabber, предоставляющий простую реализацию интерфейса ImageConsume r. Клacc Pixe lGraЬber В пакете java . lang . image определен класс P�xe lGrabbe r, который являет­ ся прямой проти воположностью классу MemoryI:n( ageSource. Вместо того чтобы формировать изображение из массива значений пикселей, он принимает суще­ ствующее изображение и захватывает в нем массив пикселей. Чтобы воспользо­ ваться классом PixelGrabber, нужно сначала создать массив целых чисел доста· точ ного размера для хранения в нем данных отдел ьных пикселей , а затем полу· чить экземпляр класса Pixe l Grabber, передав его конструктору прямоугольную область, которую необходимо захватить. И наконец, для этого экземпляра следует вызывать метод grabPixels (). Ниже приведен конструктор класса PixelGrabber, употребляемый в этой гл аве. PixelGraЬЬer (Imaqe �еж!I' 11 1 soбpaжeI01 1 R, int слева , int сверху, int -.ряяа, Tnt .вuco!l'a, int п11 1 жс8Л8(] , int CN81r81Df&, int llDl!pl lЯ B_C!l'J'OЮl l' yasвep!l'юr) Здесь параметр о бъект_ изо бражения обозначает тот объект, пиксели кото­ рого должны быть захвачены; параметры сл ева и св ерху - верхний левый угол
Гn ава 27. Иэображения 989 прямоугольной области; а параметры шир ина и высо та - размеры прямоугольной области , из которой должны быть получены пиксели изображения. Эти пиксели должны храниться в массиве, обозначаемом параметром пиксель, начиная с ука­ занного смещения. Ширина строки развертки, которая нередко соответствует ши· рине изображения, обозначается параметром шир ина_строки_ра звертки. Метод grabPixels () определяется следующим образом: Ьo o l•an qraЬPixela() throwa InterruptedException Ьo o lean qraЬPixela (lonq .-птrсеж.УНд:V) throwe InterruptedJCxoeption В обеих формах возвращается логическое значение true при удачном заверше­ нии этого метода, а иначе - логическое значение false. Во второй форме пара· метр милли секунды определяет промежугок времени, в течение которого метод будет ожидать получения пикселей. В обеих формах генерируется исключение типа InterruptedException, если выполнение данного метода прерывается дру­ гим потоком исполнения. Ниже приведен пример , в котором производится захват пикселей в изобра­ жении с последуJ<'Щ им построением гистограммы яркости пикселей. Ги стограм ма представляет собой простой подсчет пикселей, имеюiЦих определенный уровень яркости в пределах от О до 255. После того как в окне аплета будет воспроизведе­ но изображение, на его фоне выводится гистограмма. На рис. 27.4 приведен при­ мер вывода гистограммы на фоне изображения в окне аплета HistoGrab. /* * <applet code=Hi stoGrab width=400 height=345> * <param name=img value=Lilies .jp g> * </applet> */ import java . applet . *; import java .awt.* ; import java .awt . image .* ; puЫic class His toGrab extends Applet { Dimension d; Image img ; int iw, ih; int pixels [] ; int w, h; int hist [J = new int [256] ; int max_hist = О; puЬlic void init () d getSize (); w = d.width ; h = d.height; try { img = getimage ( getDocumentBase () , getParaJtblJtet ( "img") )I MediaTracker t = new MediaTracker (thiз) r t.add!mage (img , О) ; t.wa itForID(O) ; iw = img.getWidth (null ); ih = img. getHeight (null ) ; pixels = new int[iw * ih]; PixelGrabber pg = new PixelGrabber (img , О, О, iw, ih, pixels, О, iw); pg . grabPixels () ; catch (InterruptedException е) { System.out . println ("Interrupted" );
990 Часть 11. Библ иоте ка Java return ; for (int i=O ; i<iw* ih; i++) int р pixels[i]; } intr Oxff&(р>>16); intg=Oxff&{р>>8); intЬ=Oxff&{р); int y= (in t) (.3 3*r+.56 *g+ .ll *b); hist (y] ++; for (int i=O ; i<256; i++ ) if (hist [i} > max hist} max_hist = hist[i] ; puЫic void update () { } puЫ ic void paint ( Graphics g) { g.drawlmage (img , О, О, null) ; intх=(w-256)/2; int lasty = h - h * hist[OJ / max_hist; for (int i=O ; i<256; i++, х++ ) { int у=h - h * hist[i] / max hist; g. setColor (new Color (i, i, i)); g. fillRect (x, у, 1, h) ; g.setColor (Color .red ) ; g.drawLine (x-1, lasty, x, y) ; lasty = у; Рис. 27.1•. Пример вывода ги сто граммы на фоне изображения в окне аnлета HistoGrab
Кл acc imageFilter Гл ава 27. Изображе ния 991 При наличии пары интерфейсов ImageProducer и ImageConsumer, а также реализующих их конкретных классов MemoryimageSource и PixelGrab ber мож­ но создать произвольный набор фильтров преобразования, которые будут при­ нимать исходные пиксели изображения, видоизменять их и передавать некото­ рому потребителю. Этот механизм аналогичен механизму создания определен­ ных классов на основе абстрактных классов потоков ввода-вывода InputStream, Ou tput Stream, Reade r и Writer, рассматривавшихся в гл аве 20. Такая модель по· токов ввода-вывода изображений завершается внедрением класса Image Fi l ter. Пакетjаv а.аwt . imаgе содержитподклассы, производные от класса imаgеFiltеr. К их числу оmосятся классы AreaAve ragingScaleFilter, Crop imageFilter, Re plicateScaleFilter и RGBima ge Filter. Имеется также реализация интерфей­ са Image Producer в классе Filteredima geSource , который принимает произволь­ ный класс ImageFil ter и заключает его в оболочку интерфейса ImageProducer для фильтрации формируемых им пикселей. Та ким образом, экземпляр класса FilteredimageSource можно использовать в качестве экземпляра интерфейса Image Producer при вызове метода create image () почти так же, как и экземпляр типа BufferedinputStream в качестве потока ввода типа InputStream. Далее в этой гл аве будуг рассмотрены два класса фильтров - Сrор !mаgеFilter и RGBimage Fil ter. Кnacc cropimageFilter Фильтр класса Crop imageFilter выполняет фильтрацию исходного изобра­ жения для извлечения прямоугольной области. Этот фильтр уд обен, например, для обработки нескольких мелких изображений, сформированных из одного крупного исходного изображения. Для загрузки двадцати изображений размером 2 Кбайт потребуется больше времени, чем для загрузки одного изображения раз­ мером 40 Кбайт, составленного из многих кадров анимации. Если каждое составное изображение имеет один и тот же размер, то их можно без особого труда извлечь с помощью фильтра типа Cropimage Filter, расчленив весь блок в самом начале прикладной программы. Ниже приведен пример формирования 16 изображений из одного крупного изображения . Элементы мозаики затем перетасовьmаются 32 раза заменой случайной пары, взятой из 16 изображений. На рис. 27.5 приведено моза­ ичное изображение, составленное из случайно выбранных фрагментов исходного изображения цветков в окне аплета Tile!mage. /* * <applet code=Tileimage width= 400 height=345> * <param name=irng value=Lilies .jp g> * </app let> */ import java . applet .*; import java.awt.*; irnport java .awt . ima ge .*; puЫ ic clas s Tilelrnage extends Applet Irnage img ; Irnage cell [] = new Irnage [4*4J ;
992 Часть 11. Библ иотека Java int iw, ih; int tw, th; puЫ ic void init () { try { img = getimage (getDocumentBase (), getParame t er ("img ") ); Medi aTracker t = new MediaTracker (thi s) ; t.addimage (img , 0) ; t.waitFo rID (O) ; iw = img .getWidth (null ); ih = img .getHeight (null ); tw=iw/4; th=ih/4; Crop imageFilter f; FilteredimageSource fis ; t = new MediaTracker (this) ; for (int у=О; у<4; у++) { for (int х=О; х<4; х++) { f = new CropimageFilter (tw* x, th*y, tw, th) ; fis = new FilteredimageSource (img . getSource () , f) ; int i = у*4+х; cell [i] = createimage (fis) ; t.addimage (cell [i] , i) ; t.wai tForAl l () ; for (int i=O; i<32; i++) { ) int si = (int) (Math. random() * 16); int di = (int) (Math. random() * 16); Image tmp = cell [si] ; cell [si] = cell [di] ; cell [di] = tmp ; catch (InterruptedException е ) { System. o ut . println ("Interrupted") ; puЬlic void update (Graphics g) { paint (g) ; puЫ ic void paint ( Graphicз g) { for (int у=О; у<4; у++) { for (int х=О; х<4; х++) { g.drawimage (cell [y*4+x] , х * tw, у*th, null ); Фильтр класса RGBimaqeFilter Фильтр класса RGB imageFilter служит для попиксельного формирования одного изображения из другого вместе с преобразованием цветов. Этот фильтр можно применить для осветления изображения, повышения его контрастности и даже его преобразования в полуrоновое изображение.
Applet stane cl. Гл ава 27. Иэображения 993 Рис. 27.5 . Мозаичное изображение, составленное в окне аплета Tileima ge Чтобы продемонстрировать применение фильтра класса RGB image Filter, рассмотрим более сложный пример внедрения методики динамического под­ ключения фильтров для обработки изображений. Для обобщения процесса филь­ трации изображения служит специальн ый интерфейс. Это позволяет загружать подключаемые фильтры в аплет, исходя из дескрипторов <param> , но не имея при этом предварительной информации о каждом фильтре типа Image Filter. Данный пример аплета состоит из гл авного класса ImageFilterDemo , интерфей­ са PluglnFilter и служебного класса Loadedlmage, инкапсулирующего некото­ рые методы из класса Me diaTracker, упоминаемые в этой гл аве . В данном при­ мере используются также три класса фильтров, Grayscale, Invert и Contrast, которые просто манипулируют цветовым пространством исходного изображения с помощью фильтров класса RGB imageFilter, а также два дополнительных клас­ са, Blur и Sharpen, позволяющих реализовать более сложные фильтры свертки , изменяющие значения отдел ьных пикселей, исходя из окружающих их пикселей в исходных данных изображения. Классы Blur и Sharpen являются производны­ ми от абстрактного вспомогательного класса Convo 1 ve r. Рассмотрим дан ный при­ мер по частям в силу его сложности. Клacc imageFi l terDemo Этот класс служит каркасом для аплетов в рассматриваемом здесь примере при­ менения фильтров изображений. В нем используется диспетчер граничной компо­ новки типа Borde rLayout и панель типа Pane l на позиции Suuth (Юг) для разме-
99' Ч асть 11. Библиотека Java щения кнопок, которые должны представлять каждый фильтр. Объект типа Labe l занимает позицию Narth (Север) для информационных сообщений о выполнении фильтра. На позиции Center (Центр) размещается описанное ранее изображе­ ние, которое инкапсулируется в подклассе Loadedlmage , производном от класса Canvas. Кнопки выбора фильтров извлекаются из дескриптора filters<param> , где они разделены знаком +, в результате синтаксического анализа средствами класса StringTo kenizer. Метод actionPerforme d () интересен тем, что метка кнопки используется в нем как имя класса фильтра, который он пытается загрузить, вызывая метод (PluglnFilter )Class . forName (a) .newinstance (). Это надежный метод, по­ скольку он выполняет надлежащее действие, если кнопка не соответствует клас­ су, реализующему интерфейс PluginFilter. На рис. 27.6 показано, как выглядит аплет, когда он впервые загружается с помощью дескриптора <applet>, указанно­ го в начале приведенного ниже исходного файла. /* * <applet code= Irnage FilterDerno width=400 height=345> * <pararn name=img value=vincent .jpg> * <param name= filters value= "Grayscale+Inve rt+Contrast+Blur+ Sharpen "> * </applet> */ import java . applet .*; import java .aw t.*; import java .awt . event .*; import java .util .*; puЫ ic class Image Fi lterDemo extends Ap plet implements Ac tionLi stener { Image img ; PluginFilter pi f; Image fimg; Irnage curimg; Loaded!rnage lim; Label lab ; Button reset; puЫic void init () { setLayout (new Borde rLayout () ); Panel р = new Panel (); add (p, Borde rLayout .SOUTH ) ; reset = new Button ( "Reset" ); reset . addActionListener (this) ; p.add (reset) ; StringT okeni zer st = new StringTokeni zer (getPararneter ("filters" ), "+") ; whi le (st.hasMoreTokens () ) { } Button Ь = new Button (st.nextToken () ); b.addActionLi stener (this) ; p.add{b) ; lab = new Label ("") ; add (lab , BorderLayout . NORT H ); img = getirnage ( getDocumentBase (), ge tPa rameter ("i rng ") ); lim = new Loade dimage (img) ; add (lim, Borde rLayout .CENTER) ; puЫ ic void action Performe d (ActionEvent ае ) {
String а = ""; try { а=ae . getActionC011U1 1 and() ; if (a.equals ( "Reset" JJ { lim. set (img ) ; laЬ . setText ( "Norma l") ; } else { Гл ава 27. Изображения 995 pif = (PluginFilter ) Class . forName (a) . newinstance () ; fimg = pif. filter (this, img) ; lim. set (fimg ) ; lab.setText ("Filtered : " + а) ; repaint () ; catch (ClassNotFoundException е) lab . зetText (a + " not found" ); lim. set (img ) ; repaint () ; catch (InstantiationException е) { lab.setText ("c ouldn ' t new " + а) ; catch (IllegalAcce ssException е) { lab . setText ("no access: " + а) ; §;� �� Grэyscale 1 lnvert 1 Contrast 1 Blur 1 Sharpen J Applet stэrtetl. Рис. 27.6 . Пример воспроизведения в обычном режи ме изображения в окне аплета ImageFilterDemo И нтepфeйc PluginFi lter Для абстракции процесса фильтрации изображений служит интерфейс Plugln Filte r, объявление которого приведено ниже. В нем определяется единственный
996 Часть 11. Б иблиотека Java метод filter ( ), принимающий в качестве параметров аШiет и исходное изобра­ жение и возвращающий новое О'Iфильтрованное некоторым образом изображение. interface PluqinFilter { java.awt . Imaqe filter {java . app let .Applet а, java .awt . Imaqe in) ; Кnacc Loadedimage Это служе бный подкласс , производный от класса Canvas. При построении объе кта конструктор этого класса принимает в качестве параметра изображе­ ние и синхронно загружает его с помощью кл асса MediaTracker. Правильное поведе ние класса Loaded!mage в элементе управления типа LayoutControl обеспечивается благодаря то му, что в нем переопределяются методы ge t Pre ferredSize ( ) и getMinimumS ize ( ) . В этом кл ассе имеется также метод set (), позволяющий задать новое изображение типа Image , которое требует­ ся воспроизвести на данном холсте типа Canvas. Именно таким образом от­ фильтрованное изображение воспроизводится по завершении его обработки подключаемым фильтром. Ниже показано, каки м образом определяется класс Loaded!mage. import java . awt .*; puЫic class Loadedimage extends Canvas { Image img ; puЫ ic Loadedimage {Image i) { set(i) ; void set ( Image i) Me di aTracker mt = new Medi aTracker (thi s) ; mt . addimage (i, 0) ; try { mt . waitForAll (); catch (InterruptedException е) { System.out . println ("Interrupted" ); return ; img=i; repaint () ; puЫ ic void paint (Graphi cs g) { if (img == null) { g.drawString ("no image ", 10, 30) ; else { g.drawimage (img , О, О, this ); puЫic Dimension ge tPre ferredSize () { return new Dimension (img . getWidth (this ), img . getHeight (thi s) ); puЬlic Dimension ge tMinimumSize () {
return getPre fe rredSi ze () ; Класс Grayscale Гл ава 27. Изображения 997 Фильтр класса Grayscale является производным от класса RGB image Filter. Это означает, что объект класса Grayscale можно передавать в качестве параметра типа Image Filter конструктору класса Fi lte redimage Source. Нужно лишь пере­ определить метод filte rRGB (), чтобы изменить исходные значения цвета. Этот метод принимает в качестве параметра значение RGB красной, зеленой и синей составляющих цвета и вычисляет яркость пикселя, используя коэффициент преоб­ разования цвета в яркость по американскому стандарту NТSC (National Te levision Standards Committee - Национальный комитет по телевизионным стандартам). Затем он просто возвращает серый пиксель, имеющий такую же яркость, как и у ис­ ходного цвета. Ниже показано, каким образом определяется класс Grays cale. irnport java . applet .*; irnport java . awt .*; irnport java . awt . irnage .*; class Grayscale extends RGBirnage Filter implernents PluglnFilter { puЫic Irnage filter (Applet а, Irnage in) { return a.createlrnage (new Fi lteredlrnageSource (in.getSource () , this) ); puЫ ic int filterRGB (int х, int у, int rgb ) { int r (rgb » 16) & Oxff; intg=(rgb>>8)&Oxff; intЬ=rgb&Oxff; intk=(int)(.56*g+•33*r+.11*Ь); return (OxffOOOOOO 1 k << 16 k << 8 1 k); Класс Invert Фильтр класса Inve rt также достаточно прост. Он разделяет сначала каналы красного, зеленого и синего цвета , а затем обращает значения их цвета, вычитая их из значения 255. Обращенные значения отдел ьных каналов цвета составляют­ ся обратно в значение цвета пикселя и затем возвращаются. Ниже показано, ка­ ким образом определяется класс Inve rt. На рис. 27.7 показано изображение по­ сле обработки фильтром типа Invert. irnport java . applet .*; irnport java . awt .*; irnport java .awt . irnage .*; class Inve rt extends RGBirnage Fi lter irnplernents PluglnFi lter { puЫic Image filter (Ap plet а, Image in ) { return a.create lmage (new Filteredimage Source (in.getSource () , this) ); uЫic int filterRGB (int х, int у , int rgb ) intr=Oxff-(rgb>>16)&Oxff;
998 Часть 11. Библиотека Java intg=Oxff-(rgb>>8)&Oxff; intЬ=Oxff-rgb&Oxff; return(OxffOOOOOO1r<<161g<<81Ь); Ap pi. .t V-. Applet Reset 1 Grayscale / !1nvertj Contrast 1 Blur 1 Sharpen 1 Applel started. Рис. 27.7. Пример обработки изображения фил ьтром типа Invert в окне аплета ImageFilterDemo Класс Contrast Фильтр класса Contrast очень похож на фильтр типа Grayscale, за исклю­ чением того, что метод filterRGB ( ) переопределяется в нем немного сложнее. В этом методе применяется алгоритм, по которому для повышения контрастности значения красной, зеленой и синей составляющих цвета умножаются по отдель­ ности на коэффициент 1 , 2, если уровень их яркости выше 1 28, или же делятся на коэффициент 1 , 2, если уровень их яркости ниже 1 28. Измененные значения яркости ограничиваются в пределах от О до 255 в методе rnu lticlamp (). Ниже показано, каки м образом определяется класс Cont rast. На рис. 27.8 показано изо­ бражение после обработки фильтром типа Cont rast. import java . applet .*; import java .awt.*; import java.awt . image .*; puЫ ic class Contrast extends RGBimage Fi lter implements PluginFilter { puЫic Image filter (Applet а, Image in) { return a.createimage (new FilteredimageSource (in.getSource (),this ) ); private int mul tclamp (int in, douЫ e factor) { in = (int) (in * factor) ;
Глава 27. Изображения 999 return in > 255 ? 255 in; douЫe gain = 1 .2; private int cont {int in) { return {in < 128) ? (int) {in/gain) : mu ltclamp {in, gain) ; puЬlic int filterRGB {int х, int у, int rgb ) { int r = cont((rgb >> 16) & Oxff); int g = cont((rgb >> 8) & Oxff); int Ь = cont(rgb & Oxff); return(OxffOOOOOO1r<<161g<<8\Ь); -- - -- "1�!! Reset 1 Grays cale 1 lnvert 1 [§. .oi i_�-�ПI Blur 1 Sharpen 1 Applet started. Рис. 27 .8. Пример обработки изображения фильтром ти па Cont rast в окне аnлета Image FilterDemo Класс Convolver Абстрактный класс Convo lver выполняет основные функции фильтра сверт­ ки , реализуя интерфейс ImageConsume r для переноса пикселей исходного изо­ бражения в массив imgpixels. А для отфильтрованных данных изображения он создает второй массив newimgpixels. Фильтры свертки выбирают вокруг каждо­ го пикселя в изображении небольшую прямоугольную область пикселей, называ­ емую ядром свертки. В данном примере выбирается прямоугольная область разме­ рами ЗхЗ, чтобы принять решение, каким образом следует изменить центральн ый пиксель в этой области .
1000 Часть 11. Библиотека Java На заметку! Фильтр не может сразу видоизменить массив imgp ixe ls, поскол ьку в следующем пиксел е в строке развертки может быть предп ринята попытка испол ьзовать исходное зна­ чен ие из предыдущего пикселя, которое может быть уже отфильтро вано. В обоих рассматриваемых далее конкреnIЫХ подклассах фильтров представлена довольно простая реализация метода convolver (), в которой массив irngpixels ис· пользуется для хранения исходных данных изображения, а массив newirngp ixels - для хранения результатов его фильтрации. Ниже показано, каким образом определя­ ется класс Convolver. import java . applet .*; import java . awt .*; import java .awt . image .*; abstract class Convolve r implements ImageConsumer, PluginFilter { int wi dth, height ; int imgpixels [] , ne wimgpixels[]; boolean imageReady = false ; ab stract void convolve (); // эдесь следует филь тр ... puЫ ic Image filter (Applet а, Image in) { imageReady = false; in . get Source () .startProduction (this) ; wa itForimage () ; newimgp ixels = new int [width*height ]; try { convolve () ; catch (Exception е) { System.out . println ("C onvolve r failed : "+е) ; e.printStackTrace (); return a.createimage ( new Memo ryimageSource (width, height , ne wimgpixels , О, width ) ); synchroni zed void wa itForimage () try { wh ile (!imageReady ) wait () ; catch (Exception е) { System. out . println ("Interrupted" ) ; puЫ ic voi d setProperties (java .util . HashtaЫe<?, ?> dum m y){1 puЫ ic void setColorModel (ColorModel dummy ) { } puЫ ic void setHints (int dum m y){} puЫ ic synchronized voi d imageComplete (int dummy ) { imageReady = true ; notifyAll (); puЫ ic void setDimensions (int х, int у) { width = х; height = у;
Глава 27. Изображения imgpixels = new int [x*y] ; puЫ ic void setPixels (int xl, int yl, int w, int h, ColorMode l model , byte pixels [] , int off, int scan size) { int pix, х, у, х2, у2, sx, sy; х2 xl+w; у2 = yl+h; sy = off; for(y=yl; у<у2; у++) { SX=sy; for(x=xl; х<х2; х++) { pix = model . ge tRGB (pixels [sx++ ] ); if ( (pix & OxffOOOOOO ) == 0) pix = OxOOffffff; imgp ixels [ y* width+x ] = pix; sy += scansize; puЫic void setPixels (int xl, int yl , int w, int h, ColorMode l model, int pixels [] , int off, int scansize) { int pix, х, у, х2, у2, sx, sy; х2 xl+w; у2 = yl+h; sy = off; for( y=yl; у<у2 ; у++) { sx=sy; for(x=xl; х<х2; х++) { pix = mo del .getRGB (pixels [sx++ ] ); if ( (pix & OxffOOOOOO) == 0) pix = OxOOffffff; imgpi xels [ y*width+x] = pix; sy += scansize; 1001 На заметку! В пакете j ava . awt . image предоста вляется встроенный класс Convo l veOp филь­ тра свертки. Исследуйте его функционал ьные возможности самостоятел ьно. Класс вlur Фильтр класса Blur является производным от класса Convo lver. Он обраба­ тывает каждый пиксель исходного изображения из массива imgpixels, вычисляя среднее значение цвета в окружающей этот пиксель прямоугольной области раз­ мерами ЗхЗ. Вычисленное среднее значение цвета соответствующего пикселя вы­ ходного изображения сохраняется в массиве newimgpixels. Ниже показано , ка­ ким образом определяется класс Blur. На рис. 27.9 показано изображение после обработки фильтром типа Blur. puЫ ic class Blur extends Convolver { puЫ ic void convolve () { for ( int y= l; y<he ight-1; у++ )
1002 Часть 11. Библиотека Java for ( int x= l; x<width- 1; х++) ( int rs О; intgs=О; intbs=О; for(int k=-1; k<=l; k++) { for ( int j=-1; j<=l; j++) { rs/=9; gs/=9; bs/=9; int rgb = imgpixels [(y+k ) *width+x+j ]; int r (rgb >> 16) & Oxff; intg=(rgb>>8)&Oxff; intЬ=rgb&Oxff; rs gs bs += += += r; g; Ь; newimgpixels [ y*width+x ] ( OxffOOOOOO rs<<161gs<<81bs); Reset 1 Grayscale 1 lnvert 1 Contrast l l�lij.i� � Sharpen 1 Applet started. Рис. 27 .9. Пример обработки изображения фильтром ти па Blur в окне аnлета ImageFilterDemo Класс Sharpen Фильтр класса Sharpen также является производным от класса Convo l ve r и в какой-то степени противоположностью фильтру кл асса Blur. Он обрабатыва­ ет каждый пиксель исходного изображения из массива imgp ixels и вычисляет
Гл ава 27. Изображе ния 1003 среднее значение цвета в окружающей этот пиксель прямоугол ьной области раз­ мерами ЗхЗ, не считая центральный пиксель. Значение цвета соответствующего пикселя выходного изображения получается следующим образом: сначала опре­ деляется разность значения центрального пикселя и вычисленного среднего значения окружающих пикселей, а затем эта разность складывается со значени­ ем цвета центрального пикселя и полученный результат сохраняется в массиве newimgpixels. По существу, если пиксель оказывается ярче на 30 единиц, чем окружающие его пиксели, то он становится еще ярче на 30 единиц. А если пик­ сель оказывается темнее на 1 О единиц, то он становится еще темнее на 1 О. В ито­ ге резкие края изображения становятся еще более резкими, а плавные участки изображения остаются без изменения. Ниже показано, каким образом определя­ ется класс Sharpen. На рис. 27.10 показано изображение после обработки филь­ тром типа Sharpen. puЫ ic class Sharpen extends Convolve r private final int clamp (int с) { return(с>255?255:(с<О7О с)); puЫ ic void convolve () { int rO=O, gO=O, ЬО=О; for ( int y=l; y<height-1; у++) { for (int x=l ; x<width-1; х++ ) int rs О; intgs=О; intbs=О; for (int k=-1; k<=l; k++) { for (int j=-1; j<=l; j++) { int rgb = imgpixels [ (y+k) *width+x+j ]; int r (rgb >> 16) & Oxff; intg=(rgb>>8)&Oxff; intЬ=rgb&Oxff; if(j==о&&k==о){ rO r; rs >>= 3; gs >>= 3; gO g; ЬО Ь; else rs+=r; gs+=g; bs+=Ь; bs >>= 3; newimgp ixels [ y* width+x ] (OxffOOOOOO 1 clamp (rO+rO-rs ) << 16 1 clamp (gO+gO-gs ) << В 1 clamp (bO+bO-bs ));
100д Часть 11. Б иблиоте ка Java Reset 1 Grays cale 1 lnvert 1 Contrast 1 Blur 1 [��бarp��JI Applet started. Рис. 27 .10. Пример обработки изображения фильтром ти па Sharpen в окне аплета Image Fi l terDemo Допол нительные кл ассы дл я фор мир ования изображений Помимо описанных в этой гл аве классов для формирования изображений, в па­ кете java . awt . image содержится ряд других классов, обладающих расширенны­ ми возможностями для управления процессом формирования изображений на ос­ нове усовершенствован ных методик. Для формирования изображений имеется также пакет javax . imageio, поддерживающий подключаемые модули для обра­ ботки изображений в разных форматах. Если вас интересуют возможности изо­ щренного вывода графики, вам придется освоить дополнительные класс ы, доступ­ ные в пакетах javax . awt . image и j avax . imageio.
28 Утилиты параллелизма В языкеJava с самого начала бьmа предусмотрена встроенная поддержка многопо­ точности и синхронизации. Например, новые потоки исполнения можно создавать, реализуя интерфейс RunnaЫe или расш иряя класс Thread. Синхронизация потоков исполнения осуществляется с помощью ключевого слова synchronized, взаимодей­ ствие потоков исполнения обеспечивается методами wa i t () и notify (), определен­ ными в классе Ob j ect. В свое время встроенная поддержка многопоточности стала одним из наиболее важных нововведений в языкеJava и до сих пор остается одной из самых сильных его сторон. Но какой бы концептуально безупречной ни бьша первоначальная подцержка многопоточности вJava, она не является идеальной для всех приложений, особенно для тех, где ш ироко применяются многие потоки исполнения. В частности, первона­ чальная поддержка многопоточности лиш ена некоторых высокоуровневых средств, в том числе семафоров, пулов потоков исполнения и диспетчеров, которые способ­ ствуют созданию программ, интенсивно работающих в парал л ельном режиме. Следует сразу же пояснить, что многопоточность применяется во многих про­ граммах нaJava, которые в конечном итоге становятся параллельными. Например, многопоточн ость применяется во многих аплетах и сервлетах. Но в это й гл аве под парал ле.1 1iь1иm подразумевается такая программа, которая в палний юфе использу­ ет параллельно выполняющиеся потоки как ее неотим.л&Чую 'Часть. Примером тому служит программа, в которой отдельн ые потоки исполнения служат для одновре­ менного вычисления частичных результатов более крупн ых расчетов. Други м при­ мером служит программа, координируюtцая активность нескольких потоков испол­ нения , каждый из которых пытается обратиться к информации, хранящейся в базе данных . В этом случае доступ в данным только для чтения может обрабаты ваться отдел ьно от доступа, требую щего не только для чтения, но и записи данных . Сначала для поддержки параллельных программ в версии JDK 5 бьши вн едре­ ны утщитъt пар ал лел изма, которые зачастую называют также парал.лелъным АР/ (прикладным прогр ам м нъ�м интерфейсом). Первоначальный н абор утилит парал л е­ лизма предоставлял немало возможностей, о которых уже давно мечтал и програм­ мисты , занимающиеся разработкой параллельн ых прикладны х программ. В част­ ности, эти утилиты предоставляют такие синхрон изаторы вроде семафоров , пулы потоков исполнения , диспетчеры, блокировки, несколько параллельн ых коллек­ ций , а также рациональное применени е потоков исполнения для получен ия р е­ зультатов вычислений.
1006 Часть 11. Б и бл иоте ка Java Несмотря на то что первоначальные возможности параллельного API были сами по себе впечатляющими, они, тем не менее , были значительно расширены в версии JDK 7. Самым важным дополнением явился каркас Rr r k/joi n Framework, упрости вший создание программ, использующих несколько процессоров (в мно­ гояде рных систе мах) . Та ким образом, этот каркас упростил разработку программ, две или больше частей которых выполняются одновременно на самом деле, а не только путем квантования времени. Нетрудно представить, что парал л ельное вы­ полнение может существенно ускорить некоторые операции. Многоядерные си­ стемы уже стали нормой, и поэтому внедрение каркаса Fork/Join Framework оказа­ лось не только своевременной, но и эффективной мерой. А в версииJDК 8 каркас Fork/Join Framework претерпел дальнейшие усовершенствования. Кроме того , в JDK 8 были внедрены новые средства, связанные с другими ча­ стями параллельного API. Это означает, что параллельный API продолжает разви­ ваться и расширяться , чтобы удо влетворить насущные потребности современной вычислительной среды. Парал л ельный API был довольно крупным с самого начала, а дополнения в вер­ сиях JDК 7 иJDK 8 сделали его еще крупнее. Как и следовало ожидать, большинство вопросов применения утилит параллелизма довольно сложны, поэтому исчерпываю­ щее их обсуждение выходит за рамки данной книги . Те м не менее всем програм ми ру­ ющим нa java следует иметь хотя бы общее представление о парал л ельном API и не­ которые практические навыки его применения . Даже в тех програм м ах, где парал л е­ лизм не используется интенсивно, такие средства, как синхронизаторы, вызываемые потоки исполнения и исполнители, применимы в самых разных случаях. Но важнее всего, вероятно, следующее обстоятельство: в связи с широким распространением многоядерных вычислительных систем все чаще появляются решения , в которых за­ дейсгвован каркас Fork/Join Framework. По указанным выше причинам в этой гл аве дается краткий об:юр утилит парал л елизма и демонстрируется ряд примеров их при­ менения. А завершается глава введением в каркас Fork/Join Framework. П акеты пар аллельного API Утилиты парал л елизма входят в состав пакета java.util . concurrent и двух его подпакетов - jav a.util . concurrent .at omi c и jav a.util . concurrent . locks. Далее следует краткий обзор их содержимого. П a кeт java.util . concurrent В пакете jav a.util . concurrent определяются основные функциональные возможности , которые поддерживают альтернати вные варианты встроенных способов синхронизации и взаимодействия между потоками исполнения. В этом паl<ете определяются следующие основные средства параллелизма. • Синхронизаторы. • Исполнители. • Параллельные коллекции. • Каркас Fork/Join Fгamework.
Гл ава 28. Утил ить1 пар алл елизма 1007 Син хронизатары предоставляют высокоуровневые способы синхронизации вза­ имоде йствия нескольких потоков. В пакете j ava . util . concurrent определен ряд классов синхронизатаров, перечисленных в табл . 28. 1. Табл ица 28.1. Классы синхронизаторов, определен нь1е в пакете java . util . concurrent Класс Описание Semaphore Реализует классический семафор CountDownLatch Ожидает до тех пор, пока не произойдет определенное количество событий CyclicВarri•r Позволя ет группе потоков исполнения войти в режим ожидания в предварительно заданной точке выполнения Zxchanqer Phaaer Осуществляет обмен дан ными между двумя потоками исполнения Синхронизирует потоки исполнения, проходящие через несколько фаз операции Следует иметь в виду, что каждый синхронизатор предоставляет конкретное решение задачи синхронизации. Благодаря этому можно оптимизировать работу каждого синхронизатора. Раньше подобные типы объектов синхронизации необ­ ходимо было создавать вручную. Параллельный API стандартизирует их и делает доступными для всех программирующих нa Java. Исп олнители управляют исполнением потоков. На вершине иерархии испол­ нителей находится интерфейс Executor, предназначенный для запуска пото­ ка исполнения. Интерфейс ExecutorService расширяет интерфейс Executor и предоставляет методы, управляющие исполнением. Имеются следующие три реализации интерфейса Executo rService: классы ThreadPoo lExecutor, ScheduledThreadPoo lExecutor и класс Fo rkJoinPool. В пакете jav a.util . concurrent определяется также служебный класс Executors, содержащий не­ сколько статических методов, упрощающих создание различных исполнителей. С исполнителями связаны также интерфейсы Future и CallaЫe. Интерфейс Fu ture содержит значение, возвращаемое потоком после исполнения. Та ким обра­ зом , это значение определяется "на будущее", когда поток завершит свое исполне­ ние. Интерфейс Cal lаЫе определяет поток исполнения , возвращающий значение. В пакете j ava . util . concurrent определяется ряд классов параллельных кол­ лекций, в том числе ConcurrentHashMa p, ConcurrentLinkedQueue и CopyOnWrite ArrayList. Они предоставляют параллельные альте рнативные варианты для связан­ ных с ними классов, определенных в каркасе коллекций Collections Framework. В каркасе Fork/Join Frarnework поддерживается параллельное программи­ рование. К числу основных в этом каркасе относятся классы Fo rkJoinTask, Fo rk Jo inPoo l, Recur siveTa sk и Re cur siveAction. И наконец, для усовершенство­ ванной синхронизации потоков исполнения в пакете jav a.util . concurrent определяется перечисление т ime Uni t.
1008 Часть 11. Библиотека Java Пaкeт java .util . concurrent . atomi c Средства, предоставляемые в этом пакете, упрощают применение перемен­ ных в параллельной среде . Эти средства эффективно обновляют значения пере­ менных без применения блокировок. Для этой цел и служат такие классы, как At omiclnteger и AtomicLong, а также методы наподобие compareAndSet (), de c remen tAndGe t ( ) и ge tAndSet (). Эти методы действуют в режиме одной не­ прерывно выполняемой операции. Пaкeт java .util . concurrent .locks В этом пакете предоставляется альтернатива применению синхронизирован­ ных методов. В его основу положен интерфейс Lock, определяющий основной механ изм , применяемый для доступа к объекту и отказа в доступе. К основным ме­ тодам из этого пакета относятся lock (), tryLock ( ) и unlock (). Преимущество этих методов заключается в том , что они расширяют возможности управления синхронизацией. Далее в этой гл аве подробно рассматриваются отдел ьные ком­ поненты параллельного API . П рименение объе ктов с инхронизации Объекты синхронизации представлены классами Semaphore, CountDownLatch, CyclicB arrier, Exchange r и Pha ser. Совместно они позволяют без особого тру­ да решать некоторые задачи синхронизации, справиться с которыми ранее было совсем не просто. Их можно также применять в широком ряде программ - даже в тех, где поддерживается только огран иченный параллелизм. Объекты синхро­ низации могуг встречаться практически во всех программах нajava, поэтому оста­ новимся на них более подробно. Класс Semaphore Первым сразу же распознаваемым среди объектов синхронизации является семафор, реализуемый в классе Semaphore. Семафор управляет доступом к обще­ му ресурсу с помощью счетчика. Если счетчик больше нуля , доступ разрешается , а если он равен нулю, то в доступе будет отказано. В действительности этот счетчик подсчитывает разрешения, открывающие доступ к общему ресурсу. Следовател ьно, чтобы получить доступ к ресурсу, поток исполнения должен получить у семафора разрешение на доступ. Как правило, поток исполнения, которому требуется доступ к общему ресурсу, пытается получить разрешение, чтобы воспользоваться семафором. Если значе­ ние счетчика семафора окажется больше нул я, поток исполнения получит разре­ шение, после чего значение счетчика семафора уменьшается на единицу. В про­ тивном случае поток будет заблокирован до тех пор , пока он не сумеет получить разрешение. Если потоку исполнения доступ к общему ресурсу больше не нужен, он освобождает разрешение, в результате чего значение счетчика семафора уве-
Гл ава 28. Утилиты параллелизма 1009 личивается на единицу. Если в это время другой поток исполнения ожидает раз­ решения, то он сразу же его получает. В Java этот механизм реализуется в классе Semaphore. В классе Semaphore имеются два приведенных ниже конструктора: Seшaphore (int -ело) Seшaphore (int v.ело , Ьoolean способ) Здесь параметр число обозначает исходное значение счетчика разрешений. Та ким образом, параметр чи сло определяет количество потоков исполнения , ко­ торым может быть од новременно предоставлен доступ к общему ресурсу. Если па­ раметр число принимает значение 1, к ресурсу может обратиться только один по­ ток исполнения. По умолчанию ожидающим потокам исполнения предоставляет­ ся разрешение в неопределенном порядке. Если же присвоить параметру спо соб логическое значение true, то тем самым можно гарантировать, что разрешения будут предоставляться ожидающим потокам исполнения в том порядке , в каком они запрашивали доступ. Чтобы получить разрешение, достаточно вызвать метод acqui re ( ), который имеет две формы: void acquire () throw• InterruptedException void acquire (int -ело) throw• InterruptedВxception Первая форма запрашивает одно разрешение, а вторая - число разрешений. Обычно используется первая форма. Если разрешение не будет предоставлено во время вызова метода, то исполнение вызывающего потока будет приостановлено до тех пор, пока не будет получено разрешение. Чтобы освободить разрешение, следует вызвать метод release (). Ниже при­ ведены общие формы этого метода. void release () void release (int VJreлo) В первой форме освобождается одно разрешение, а во второй - количество разрешений, обозначаемое параметром число. Чтобы воспользоваться семафором для управления доступом к ресурсу, каж­ дый поток исполнения, которому требуется этот ресурс, должен вызвать метод acquire () , прежде чем обращаться к ресурсу. Когда поток исполнения завершает пользование ресурсом, он должен вызвать метод release (), чтобы освободить ресурс. В приведенном ниже примере программы демонстрируется применение семафора. 11 Простой пример применения сема фора import java.util . concu rrent .*; class SemDemo { puЫic static void ma in (String args[] ) Sernaphore sem = new Semaphore (l) ; new IncThread ( sern, "А" ) ; new DecThread (sem, "В") ;
1010 Часть 11. Библиоте ка.Jаvа 11 Общий ресурс class Shared { static int count О; 11 Поток исполнения, увеличивающий значение сче тчика на единицу class IncThread implements RunnaЫ e { String name ; Semaphore sem; IncThread ( Semaphore s, String n) { sem=s; name = n; new Thread (th is) .start() ; puЫ ic void run() { Syзtem. out . println ("З aпycк потока " + name) ; try { 11 сначала получить разрешение Syзtem.out . println ( "Пoтoк" + name + " ожида ет разрешения ") ; sem. acquire () ; Syзtem.out . println ( "Пoтoк" name + " получает разрешение ") ; 11 а теперь получить доступ к общему ресурсу for(int i=O; i < 5; i++) { Shared . count++; } Syзtem. out .pr intln (name + ": "+Shared . count ); 1 1 разрешить , если возможно, переключение контекста Thread .зleep (10) ; catch (InterruptedException ехс ) { Syзtem.out . println (ex c) ; 1 1 освободить разрешение System. o ut . println ( "Пoтoк" + name + " освобождает разрешение ") ; зem. releaзe () ; 11 Поток исполнения, уме ньшающий значение сче тчика на единицу сlазз DecThread implementз RunnaЬl e { String name ; Semaphore зеm; DecThread (Semaphore s, String n) { зеm=з; name = n; new Thread (this ) .start(); puЫ ic void run() { Syзtem. out . println ("Зaпycк потока " + name) ; try {
Гл ава 28. Ут илиты параллелиэма 11 сначала получи ть разрешение System. o ut . println ("П oтoк" + name + " ожидает разрешения") ; sem. acquire () ; System. out .println ( "Пoтoк" + name + " получает разрешени е") ; 11 а теперь получи ть доступ к обще му ресурсу for(int i=O; i < 5; i++) { Shared . count--; System.out . println (name + "· "+Shared . count ); 11 разрешить , если возможно , пере!(Jlючение контекста Thread .sleep (lO) ; catch (InterruptedException ехс ) { System.out . println (ex c) ; 11 освободить разрешение System. o ut . println ( "Пoтoк" + name + " освобождает разрешени е" ) ; sem. release (); 1011 Ниже приведен примерный результат выполнения данной програымы. (Конкретный порядок следования потоков исполнения может быть иным.) Запуск потока А Поток А ожидает разрешения Поток А получает разрешение А:1 Запуск потока В Поток В ожидает разрешения А:2 А:З А:4 А:5 Поток А освобожда ет разрешение Поток в получает разрешение В:4 В:З В:2 В:1 В:О Пото к В освобождает разрешение Для управления доступом к переменной count, которая является статической переменной класса Shared, в данной програыме используется семафор. Значение переменной Shared . count увеличивается на 5 в методе run ( ) из класса IncThread и уменьшается на 5 в одноименном методе из класса De cThread. Для защиты по­ токов исполнения, представленных этими двумя классами, от одновременного доступа к переменной Shared . coun t такой доступ предоставляется только после того, как будет получено разрешение от управляющего семафора. По завершении доступа к данной переменной как к общему ресурсу разрешение на него освобож­ дается. Та ким образом, только один поток исполнения может одновременно полу­ чить доступ к переменной Shared . count, что и подтверждают результаты выпол­ нения данной программы.
1012 Часть 11. Б и бл иоте ка Java Обратите внимание на то , что в методе run ( ) из классов I ncThread и DecThread вызывается метод sleep (). О н гарантирует, что доступ к переменной Shared . count будет синхронизироваться семафором. В частности , вызов метода sleep () из метода run ( ) приводит к тому, что вызывающий поток исполнения будет при­ останавливаться в промежутках между последовательными попытками доступа к переменной Shared .count. Это , как правило, позволяет исполняться второму пото ку. Но благодаря семафору второй поток испол нения должен ожидать до тех пор, пока первый поток исполнения не освободит разрешение. А это произойдет только после того , как будут завершены все попытки доступа со стороны первого потока исполнения. Та ким образом, значение переменной Shared . count сначала увеличивается на 5 в объекте класса IncThread, а затем уменьшается на 5 в объ­ екте класса DecThread. Ув еличение и уменьшение значения этой переменной про­ исходит строго по поряОку. Если бы в данном примере не использовался семафор, то попытки доступа к переменной Shared . count, производи мые каждым потоком исполнения, осу­ ществлялись бы одновременно, поэтому увеличение и уменьшение значения этой переменной происходило бы не по порядку. Чтобы убедиться в этом, попробуй­ те закомментировать вызовы методов acquire () и release ().Запустив данную программу на выполнение, вы обнаружите, что доступ к переменной Shared. count больше не является синхронизированным, и каждый поток испол нения об­ ращается к переменной Shared . count , как только для него выделяется времен­ ной интервал . Несмотря на то что применение семафора, как правило, не представляет осо­ бой сложности , как демонстрируется в предыдущем примере программы, возмож­ ны и более сложные варианты его применения. Ниже приведен один из таких примеров. Он представляет собой переработанную версию программы, реализую­ щей функции поставщика и потребителя из гл авы 11. В данном варианте использу­ ются два семафора , регулирующие потоки исполнения поставщика и потребителя и гарантирующие, что после каждого вызова метода put ( ) будет следовать соот­ ветствующий вызов метода get (). 11 Реализация поставщика и потребителя , использующа я 11 семафоры для управления синхронизацией import jav a.util . coпcurreпt . Semaphore; class Q int n; 11 начать с недоступно го семафора потребителя static Semaphore semC on = new Semaphore (O) ; static Semaphore semProd = new Semaphore (l) ; void get () ( try { semCon .acquire () ; catch (InterruptedException е) ( System.out . println ( "Перехвачено ис ключение типа InterruptedException") ; System. o ut . println ("П oлyчe нo : "+n) ;
Гл ава 28. Утилить1 па раллелизма semProd .release () ; void put (int n) { try { зemProd .acquire () ; catch (InterruptedExcept ion е) { Syзtem.out . println ( "Перехвачено исключение типа InterruptedException" ); thiз.n = n; Syзtem. out .println ( "Oтпpaвлeнo : "+n) ; зemC on .release (); clas s Produce r impleme nts RunпaЫe { Qq; Producer (Q q) { thiз.q = q; new Thread (this , "Producer") .start() ; puЫic void run () { for (int i=O; i < 20; i++) q.put(i) ; class Consurner implemen ts RunnaЫe { Qq; Consurner (Q q) { this.q = q; new Thread (this , "Consume r") .start(); puЫic void run () { for (int i= O; i < 20; i++) q.ge t(); class ProdCon { puЬlic static void main (String args[] ) { Qq=newQ(); new Conзurner (q) ; new Producer (q) ; Ниже показана ч асть результатов, выводимых данной программой. Отправлено : О Получено : О Отправлено : 1 Получено : 1 Отправлено : 2 Получено : 2 Отправлено : 3 Получено : 3 Отправлено : 4 Получено : 4 Отправлено : 5 1013
Часть 11. Библиотека Java Получено : 5 Как видите, в данном примере синхронизируются вызовы методов put ( ) и ge t ( ) . Это означает, что после каждого вызова метода pu t ( ) следует вызов метода ge t ().ипоэтому ни одно значение не может быть пропущено. Если бы не семафо­ ры , вызовы метода pu t ( ) могли бы происходить несогласованно с вызовами метода ge t (), что привело бы к пропуску некоторых значений. (Чтобы убедиться в этом , удал ите код семафора из данного примера и посмотрите полученные результаты .) Надлежащая последовательность вызовов методов put ( ) и get () соблюдается двумя семафорами: sernProd и sernCon. Прежде чем метод put () сможет предоста· вить значение, он должен получить разрешение от семафора sernProd. Ус тановив значение, он освобождает семафор sernProd. Прежде чем метод get () сможет употребить значение, он должен получить разрешение от семафора sernC on. Уп отребив значение, он освобождает семафор sernC on. Та кой механизм передачи и получения значений гарантирует, что после каждого вызова метода put () будет следовать вызов метода get (). Обратите внимание на то , что семафор sernC on инициализируется без доступ­ ных разрешений. Этим гарантируется, что метод put () выполняется первым. Возможность задавать исходное состояние синхронизации является одной из са­ мых сильных сторон семафоров. Кnacc countDownLatch Иногда требуется, чтобы поток исполнения находился в режиме ожидания до тех пор, пока не наступит одно (или больше) событие. Для этих целей в парал­ лельном API предоставляется класс CountDownLatch, реализующий самоблокиров· ку с обратным отсчетом. Объект этого класса изначально создается с количеством событий, которые должны произойти до того момента, как будет снята самобло­ кировка. Всякий раз, когда происходит событие, значение счетчика уменьшается. Как только значение счетчика достигнет нуля , самоблокировка будет снята. В классе Count DownLatch имеется приведенный ниже конструктор, где пара­ метр число определяет количество событий, которые должны произойти до того, как будет снята самоблокировка. CountDownLatch (int ..,.ело) Для ожидания по самоблокировке в потоке исполнения вызывается метод awa i t (), общие формы которого приведены ниже. void avait () throws Interrupted&xception Ьoolean avai t(lonq OJIJl,lfaнa"e , Til le Unit eдJrIOrцa .8р8Н8Н1 1 ) throws InterruptedJ:xception - В первой форме ожидание длится до тех пор, пока отсчет, связанный с вызы­ вающим объектом типа Count DownLatch, не достигнет нуля. А во второй форме ожидание длится только в течение определенного периода времени, определяе­ мого параметром ожида ние. Время ожидания указывается в единицах, обознача-
Гл ава 28. Утил иты параллелизма 1015 емых параметром единица_ времени, который принимает объект перечисления TimeUnit, рассматриваемого далее в этой гл аве. Метод awa i t ( ) возвращает ло­ гическое значение fa lse, если достигнуг предел времени ожидания , или логиче­ ское значение true, если обратный отсчет достигает нуля. Чтобы известить о событии, следует вызвать метод count Down ( ). Ниже при­ ведена общая форма этого метода. Всякий раз, когда вызывается метод count Down ( ) , отсчет, связанный с вызывающим объектом, уменьшается на единицу. void countDown () В приведенном ниже примере программы демонстрируется применение клас­ са Count DownLa tch. В этой программе устанавливается самоблокировка, которая снимается только после наступления пяти событий. 11 Продемонс трировать приме нение кл асса CountDownLatch import java.util . concurrent . CountDownLatch ; class CDLDemo { puЫ ic static void ma in (String args[] ) { CountDownL atch cdl = new CountDownLatch (S) ; System. o ut . println ( "Зaпycк потока исполнения ") ; new MyThread (cdl) ; try { cdl .await (); } catch (InterruptedExcept ion ехс ) System.out . println (exc) ; } System.out . println ("З aвepweииe потока исполнения") ; class MyThread implements RunnaЬle CountDownLatch latch; MyThre ad ( CountDownLatch с) { latch = с: new Thread (this ) . start () : puЫic void run () { for(int i = О; i<S; i++) { System. o ut . println (i) ; latch . count Down () ; // обратный отсчет Ниже приведен результат выполнения данной программы. Запус к потока исполнения о 1 2 3 4 Завершение потока исполнения
1016 Часть 11. Библ иоте ка Java В теле метода mа in () устанавливается самоблокировка в виде объекта cdl типа Count DownLatch с исходным значением обратного отсчета, равным 5. Затем соз­ дается экземпляр класса MyTh read, который начинает исполнение нового потока. Обратите внимание на то , что объект cdl передается в качестве параметра кон­ структору класса MyThread и сохраняется в переменной экземпляра latch. Далее в гл авном потоке исполнения вызывается метод awai t ( ) для объекта cdl , в резуль­ тате чего исполнение гл авного потока приостанавливается до тех пор. пока обра'Г­ ный отсчет самоблокировки не уменьшится на единицу пять раз в объекте cdl. В теле метода run ( ) из класса MyThread организуется цикл , который повторя· ется пять раз. На каждом шаге этого цикла вызывается метод count Down () для пе­ ременной экземпляра latch, которая ссылается на объект cdl в методе ma in ( ) . По завершении пятого шага цикла самоблокировка снимается, позволяя возобно­ вить гл авный поток исполнения. Класс Count DownLatch является эффекти вным и простым в употреблении средством синхронизации, которое окажется полезным в тех случаях, когда поток исполнения должен находиться в состоянии ожидания до тех пор, пока не про· изойдет одно или несколько событий. Клacc cyclicBarrier В программировании нередко возникают такие ситуации, когда два или больше потока должны находиться в режиме ожидания в предопределенной точке исполне­ ния до тех пор, пока все эти потоки не достигнут данной точки . Для этой цели в па­ рал л ельном API предоставляется класс CyclicBarrier. Он позволяет определить объект синхронизации, который приостанавливается до тех пор, пока определенное кол ичество потоков исполнения не достигнет некоторой барьерной точки. В классе CyclicBar rier определены следующие конструкторы: CyclicBarrier (int жOJD1Jvec�•o по�ожоа) CyclicBarrier (int жOJD1Jчec�•o=no�oжos , Run n aЫe дейс�.вяе) где параметр количе ство_ потоков определяет число потоков, которые должны достигнуть некоторого барьера до того , как их исполнение будет продолже но. Во второй форме конструктора параметр дей ствие определяет поток, который будет исполняться по достижении барьера. Общая процедура применения класса CyclicBarrier следующая. Прежде все· го нужно создать объект класса Cycl icBarrier, указав количество ожидающих потоков испол нения. А когда каждый поток исполнения достигнет барьера, следу· ет вызвать метод awai t ( ) для данного объекта. В итоге исполнение потока будет приостановлено до тех пор, пока метод await ( ) не будет вызван во всех осталь­ ных потоках исполнения. Как только указанное количество потоков исполнения достигнет барьера, произойдет возврат из метода awai t ( ) , и выполнение будет возобновлено. А если дополнительно указать какое-ни будь действие, то будет вы· полнен соответствующий поток. У метода await () имеются следующие общие формы: int await () throws InterruptedException , BrokenВarrierException int awai t(lonq о"даюrе , Ti-Uni t щrюиr.ца .-ренеюr) throwa InterruptedException , BrokenвarrierException , Ti11 1e outException
Гл ава 28. Утил иты параллел изма 1017 В первой форме ожидание длится до тех пор, пока каждый поток исполнения не достигнет барьерной точ ки. А во второй форме ожидание длится только в те­ чение определенного периода времени, определяемого параметром ожид ание. Время ожидания указывается в единицах, обозначаемых параметром единица_ времени. В обеих формах возвращается значение, указывающее порядок, в кото­ ром потоки исполнения будут достигать барьерной точки . Первый поток испол­ нения возвращает значение, равное количеству ожидаемых потоков минус 1, а по­ следний поток возвращает нулевое значение. В приведенном ниже примере программы демонстрируется применение клас­ са Сус l i cBarr i е r. Эта программа ожидает до тех пор, пока все три потока достиг­ нут барьерной точ ки . Как только это произойдет, будет выполнен поток, опреде­ ляемый действием типа BarAction. 11 Продемонс трировать применение кл асса CycliCВarrier import java.util . concurrent .*; class BarDemo { puЫic static vo id ma in (String args[] ) { CyclicBarrier сЬ = new CyclicBarrier (З, new BarAction () ); System.out . println ("З aпycк потоков") ; new MyThread (cb, "А ") ; new MyThread (cb, "В") ; new MyThread (cb, "С") ; 11 Поток исполнени я, использующий барьер типа CycliCВarrier class MyThread implements RunnaЫe { Cycli cBa rrier cba r; String name ; MyThread (Cycl icBarrier с, String n) { cbar с; name n; new Thread (this) .start () ; puЫ ic void run() { System. o ut . println ( name) ; try { cbar. await(); catch (Bro kenBa rrierExcept ion ехс ) System. out . println (exc) ; catch (InterruptedException ехс ) { System.out . println (exc) ; 11 Объе кт этого класса вызывается по достижении 11 барь ера типа CycliCВarrier
1018 Ч асть 11. Библиотека Java class BarAction implementэ RunnaЫe puЫ ic void run() { System.out . println ( "Бapьep достигнут !") ; Ниже приведен примерный результат выполнения данной программы. (Конкретный порядок следования потоков исполнения может быть иным. ) Запуск потоков А в с Барьер достигнут ! Класс Cycl icBarrier можно использовать повторно, поскольку он освобожда­ ет ожидающие потоки исполнения всякий раз, когда метод await ( ) вызывается из заданного количества потоков исполнения. Так, если внести следующие изме­ нения в метод ma in () из предыдущего примера программы: puЫic static void ma in (String args(]) { CyclicBarrier сЬ = new CyclicBarrier (З, new BarAction () ); System. out . println ("З aпycк потоков" ) ; new MyThread (cb, "А") ; new MyThread (cb, "В") ; new MyThread(cb, "С") ; new MyThread (cb, "Х") ; new MyThread (cb, "У") ; new MyThread ( сЬ , "Z") ; то результат ее выполнения будет таким, как показано ниже . ( Конкретный поря­ док следования потоков исполнения может быть иным. ) Запуск потоков А в с Барьер достигнут ! х у z Барьер достигнут ! Как показывает рассмотренный выше пример, класс CyclicBarrier предо­ ставляет изящное решение задачи, которая раньше считалась сложной. Класс Exchange r Вероятно, наиболее интересным с точки зрения синхронизации является класс Exchanger, предназначенный для упрощения процесса обмена данными между двумя потоками исполнения. Принцип действия класса Exchange r очень прост: он ожидает до тех пор, пока два отдел ьных потока испол нения не вызовут его метод exchange (). Как только это произойдет, он произведет обмен данны­ ми, предоставляемыми обоими потоками. Та кой механизм обмена данными не
Гл ава 28. Утилить1 пар аллел изма 1019 только изящен , но и прост в применении. Нетрудно представить, как воспользо­ ваться классом Exchange r. Например, один поток исполнения подготавливает буфер для приема данных через сетевое соединение, а другой - заполняет этот бу­ фер данными, поступающими через сетевое соединение. Оба потока исполнения действуют совместно, поэтому всякий раз, когда требуется новая буферизация , осуществляется обмен данными. Класс Exchange r является обобщенным и объявляется приведенным ниже об­ разом, где параметр V определяет тип обмениваемых данных. Eжchanger<V> В классе Exchange r определяется единственный метод exchange (), имеющий следующие общие формы: V eжchanqe (V буфер) throws InterruptedEжception v eжchange (V буфер, lonq ОЖ1 1 даюrе, TiaeUnit еджюrца арехеюr) throws InterruptedEжception , TiaeoutEжception где параметр буфер обозначает ссылку на обмениваемые данные. Возвращаются дан ные, полученные из другого потока исполнения. Вторая форма метода exchange ( ) позволяет определить время ожидан ия. Гл авная особенность мето­ да exchange ( ) состоит в том, что он не завершится ус пешно до тех пор , пока не будет вызван для одного и того же объекта типа Exchanger из двух отдель­ ных потоков исполнения. Подобным образом метод exchange ( ) синхронизи­ рует обмен данными. В приведенном ниже примере программы демонстрируется применения клас­ са Exchanger. В этой программе создаются два потока исполнения. В одном пото­ ке исполнения создается пустой буфер, принимающий данные из другого потока исполнения. Та ким образом, первый поток исполнения обменивает пустую сим­ вольную строку на полную . 11 Пример приме нения кл асса Eжchanger import java.util . concurrent .Exchanger; class ExgrDemo { puЫic static void ma in (String arg s[] J Exchanger<String> exgr = new Exchange r<String> (); new UseString (exgr) ; new Ma k eString (exgr) ; 11 Поток типа Тhread, формирующий символь ную строку class MakeString implements RunnaЬle { Exchange r<String> ех; String str; Ma keString ( Exchange r<String> с) ех=с; str = new String() ; new Thread (this ) .start() ; puЫic void run() { char ch 'А'; for(inti=О;i<3;i++){
1020 Часть 11. Библиотека Java 11 заполнить буфер for(intj=О;j<5;j++) str += (char ) ch++; try { 11 обменять заполненный буфер на пус той str = ex . exchange (str) ; catch (InterruptedException ехс ) Systern.out . println (exc) ; 1 1 Поток типа Тhread , исполь зующий символ ь ную строку class UseString irnplerne nt s RunnaЫ e { Exchange r<String> ех; String str; UseString ( Exchange r<String> с) ех=с; new Thread (thi s) .start () ; puЫic void run() { for(int i=O; i < 3; i++) { try { 11 обме нять пустой буфер на заполненный str = ex . exchange (new String() ); Systern.out . println ("П oлyч e �o: "+str) ; catch (Interrup tedException ехс ) ( Systern.out . println (exc) ; Ниже показаны результаты выполнения данной программы . Получено : AB CDE Получено : FGHIJ Получено : KLMNO В методе ma in ( ) данной программы сначала создается объект класса Exchanger. Этот объект служит для синхронизации обмена символьными строка­ ми между классами Ma k eString и UseString. Класс Ma ke String заполняет сим· вольную строку данными, а класс UseString обменивает пустую символьную стро­ ку на полную , отображая затем ее содержимое. Обмен пустой символ ьной строки на полную в буфере синхронизируется методом exchange ( ) , который вызывается из метода run ( ) в классах Ma keString и UseString. Кла сс Phaser В версии JDК 7 внедрен новый класс синхронизации под названием Pha ser. Гл авное его назначение - синхронизировать потоки исполнения, которые пред­ ставляют одну или несколько стадий (или фаз) выполнения действия. Например, в прикладной программе может быть несколько потоков исполнения, реализую-
Гл ава 28. Утил иты пар аллелизма 1021 щих три стадии обработки заказов. На первой стадии отдельн ые потоки исполне­ ния используются для того , чтобы проверить сведения о клиенте, наличие товара на складе и его цену. По завершении этой стадии остаются два потока исполнения, где н а второй стадии вычисляется стоимость доставки и сумма соответствующего налога, а на заключительной стадии подтверждается оплата и определяется ори­ ентировочное время доставки . В прошлом для синхронизации нескольких пото­ ков испол нения в такой прикладной программе пришлось бы немало потрудиться. А с появлением класса Phaser этот процесс значительно упростился. Прежде всего следует иметь в виду, что класс Pha ser действует подобно описан­ ному ранее классу CyclicBarrier, за исключением то го , что он подцерживает не­ сколько фаз. В итоге класс Pha ser позволя ет'Ьпределить объект синхронизации, ожидающий завершения определенной фазы. Затем он переходит к следующей фазе и снова ожидает ее завершения. Следует также иметь в виду, что класс Pha ser можно использовать и для синхронизации только одной фазы . В этом отношении он действует подобно классу CyclicBarrier, хотя гл авное его назначение - син ­ хронизация нескольких фаз. В классе Phaser определяются четыре конструктора. В данном разделе упоминаются следующие два конструктора этого класса: Phaser () Phaser (int жолиvес�ао_с�орон) Первый конструктор создает синхронизатор фаз с нулевым регистрационным счетом, а второй устанавливает значение регистрационного счета равным задан­ ному количе ству_ сторон. Объекты , регистрируемые синхронизатором фаз, за­ частую обозначаются термином сторона. Обычно имеется полное соответствие количества регистрируемых объектов и синхронизируемых потоков исполне­ ния, хотя этого и не требуется. В обоих случаях текущая фаза является нулевой. Поэтому когда создается экземпляр класса Phaser, он первоначально находится в нул евой фазе. Обычно класс Phaser используется следующим образом. Сначала создается новый экземпляр класса Phaser. Затем синхронизатор фаз регистрирует одну или несколько сторон, вызывая метод register ( ) или указывая нужное количе­ ство сторон в конструкторе класса Phaser. Синхронизатор фаз ожидает до тех пор, пока все зарегистрированные стороны не завершат фазу. Сторона извещает об этом, вызывая один из многих методов, предоставляемых классом Phaser, на­ пример метод arrive ( ) или arriveAndAwa itAdvance () .Кактолько все стороны достигнут данной фазы, она считается завершенной, и синхронизатор фаз может перейти к следующей фазе (если она имеется) или завершить свою работу. Далее этот процесс поясняется более подробно. Для регистрации стороны после создания объекта класса Pha ser следует вы­ звать метод register (). Ниже приведена общая форма этого метода. В итоге он возвратит номер регистрируемой фазы . int reqi ster () Чтобы сообщить о завершении фазы, сторона должна вызвать метод arr i ve ( ) или какой-нибудь его вариант. Когда количество достижений конца фазы сравня­ ется с количеством зарегистр ированных сторон, фаза завершится и обЪект класса
1022 Часть 11. Б иблиотека Java Phaser перейдет к следующей фазе (если она имеется). Метод arrive () имеет следующую общую форму: int arrive () Этот метод сообщает, что сторона (обычно поток исполнения) завершила не­ которую задачу (или ее часть) . Он возвращает текущий номер фазы. Если работа синхронизатора фаз завершена, этот метод возвращает отрицательное значение. Метод arrive ( ) не приостанавливает исполнение вызывающего потока. Это оз· начает, что он не ожидает завершения фазы. Этот метод должен быть вызван толь­ ко зарегистрированной стороной. Если требуется указать завершение фазы, а затем ожидать завершения этой фазы всеми остальными зарегистрированными сторонами, следует вызвать метод arriveAndAwaitAdva nce ().Ниже приведена общая форма этого метода. int arriveAndAwaitAdvance () Этот метод ожидает до тех пор, пока все стороны не достигнут данной фазы, а затем возвращает номер следующей фазы или отрицательное значение, если синхронизатор фаз завершил свою работу. Метод ri veAndAwaitAdvance () дол­ жен быть вызван только зарегистрированной стороной. Поток исполнения может достигнуть данной фазы, а затем сняться с регистра· ции, вызвав метод arriveAndDeregister ().Ниже приведена общая форма этого метода. int arriveAndDereqister () Этот метод возвращает номер текущей фазы или отрицательное значение, если синхронизатор фаз завершил свою работу. Он не ожидает завершения фазы. Метод arriveAndDeregister () должен быть вызван только зарегистрированной стороной . Чтобы получить номер текущей фазы , следует вызвать метод getPha se ().Его общая форма выглядит следую щим образом: final int getPhase () Когда создается объект класса Phaser, первая фаза получает нулевой номер, вторая фаза - номер 1, третья фаза - номер 2 и т.д . Если вызывающий объект клас· са Phaser завершил свою работу, возвращается отрицательное значение. В приведенном ниже примере программы демонстрируется применение класса Pha ser. В этой программе создаются три потока, каждый из которых имеет три фазы своего исполнения. Для синхронизации каждой фазы применяется класс Phaser. 11 Приме р применения кл асса Phaser impo rt java .util . coпcurreпt .*; class PhaserDemo { puЬlic static vo id maiп (String args[] ) Phaser phsr = пеw Phaser {l); int curPhase; System. out .priпtlп ("запуск потоков ") ; пеw MyTh read (phsr, "А") ;
new MyThread (ph sr, "В") ; new MyTh read (phsr, "С ") ; Глава 28. Утилиты nараnnелиэма // ожидать завершения всеми пото ками исполнения первой фазы curPhase = phsr . getPhase () ; phsr . arriveAndAwai tAdvance () ; System. o ut . println ( "Фaзa " + c urPhase + " завершена" ) ; 11 ожидать завершения всеми потоками исполнения второй фазы curPhase = phsr.getPhase () ; phsr.arriveAndAwaitAdvance (); System. o ut . println ( "Фaзa " + curPhase + " завершена") ; curPhase = phsr . ge tPhase () ; phsr . arriveAndAwaitAdvance (); System. o ut . println ( "Фaзa " + curPhase + " завершена") ; 11 снять осно вной поток исполнения с регистрации phsr . arriveAndDe register () ; if (phsr .is Termi nated ()) System. o ut . println ("Cинxpoнизaтop фаз завершен" ); 11 Поток исполнения , исполь зующий синхронизатор фаз типа Phaaer class MyTh read implements RunnaЫe { Phaser phs r; String name ; MyThread (Pha ser р, String n) { phsr = р; name=n; phsr. regi ster () ; new Thread (this) .start() ; puЬlic void run () { System. o ut . println ( "Пoтoк " + name + " начинает первую фазу" ); phsr.arriveAndAwaitAdvance (); // известить о достижении фазы // Небольшая пауза , чтобы не нарушить порядок вывода . // Только для иллюс трации , но необязательно для правильного 11 функционирования синхронизатора фаз try { Thread .sleep (lO) ; catch (InterruptedException е) { System.out .pr intln (e) ; System. o ut . println ("П oтoк " + name + " начинает вторую фазу" ); phsr.arriveAndAwaitAdvance (); // известить о достижении фазы // Небольшая пауза, чтобы не наруши ть порядок вывода . 11 Толь ко для иллюс трации , но необязательно для прави льного // функционирования синхронизатора фаз try { Thread .sleep (l0) ; catch {InterruptedException е) { System. out . println (e) ; System. o ut . println ( "Пoтoк " + name + " начинает тре тью фазу" ); 1023
102, Часть 11. Библиотека Java phsr . arriveAndDe register () ; // известить о достижении фазы и // снять потоки с ре гистрации Ниже приведен результат, выводимый данной программой. Поток А начинает первую фазу Поток С начинает первую фазу Поток В начинает первую фазу Фаза О завершена Поток В начинает вторую фазу Поток С начинает вторую фазу Поток А начинает вторую фазу Фаза 1 завершена Поток С начинает третью фазу Поток В начинает третью фазу Поток А начинает третью фазу Фаза 2 завершена Синхронизатор фаз завершен Рассмотрим подробнее основные части данной программы. Сначала в методе ma in () создается объект phsr типа Phaser с начальным счетом сторон, равным 1 (что соответствует основному потоку исполнения). Затем создаются три объекта типа MyThread и запускаются три соответствующих потока исполнения. Обратите внимание на то, что объекту типа MyThread передается ссьшка на объект phsr - синхронизатор фаз. Объекты типа MyTh read используют этот синхронизатор фаз для синхронизации своих действий. Затем в методе ma in () вызывается метод ge tPhase (), чтобы получить номер текущей фазы (который первоначально явля­ ется нулевым), а после этого - метод arriveAndAwaitAdvance ().В итоге выпол­ нение метода ma in ( ) приостанавливается до тех пор, пока не завершится нулевая , по существу, первая фаза. Но этого не произойдет до тех пор, пока все объекты ти па MyThread не вызовут метод arriveAndAwa i tAdvance ().Как только это про­ изойдет, метод ma in ( ) возобновит свое выполнение, сообщив о завершении нуле­ вой фазы , и перейдет к следующей фазе. Этот процесс повторяется до завершения всех трех фаз. Затем в методе ma in ( ) вызывается метод arriveAndDe register ( ) , чтобы снять с регистрации все три объекта типа MyThread. В итоге зарегистриро­ ванных сторон больше не остается, а следовательно, перейдя к следующей фазе, синхронизатор фаз завершит свою работу. Те перь рассмотрим объект типа MyThre ad. Прежде всего обратите внимание на то , что конструктору передается ссьшка на синхронизатор фаз, в котором новый поток исполнения регистрируется далее как отдельная сторона. Та ким образом, каждый новый объект типа MyThread становится стороной, зарегистрированной синхронизатором фаз, переданным конструктору этого объекта. Обратите также внимание на то, что у каждого потока исполнения имеются три фазы. В данном примере каждая фаза состоит из метки-заполнителя , которая просто отображает имя потока исполнения и то , что он делает. Безусловно , реальный код выполнял бы в потоке более полезные действия. Между первыми двумя фазами поток испол­ нения вызывает метод arriveAn dAwa itAdvance (). Та ким образом, каждый по­ ток исполнения ожидает завершения фазы всеми остальными потоками, включая и основной поток. По завершении всех потоков исполнения, включая и основной,
Гл ава 28. Утил иты параллелизма 1025 синхронизатор фаз переходит к следующей фазе . А по завершении третьей фазы каждый поток исполнения снимается с регистрации, вызывая метод arriveAnd Deregister ().Как поясняется в комментариях к объектам типа MyThread, метод sleep () вызывается исключительно в иллюстративных целях, чтобы предотвра­ тить нарушение вывода из-за многопоточности , хотя это и не обязательно для пра­ вильного функционирования синхронизатора фаз. Если уд алить вызовы метода s leep ( ) , то выводимый результат может выглядеть немного запутан ным, но фазы все равно будут синхронизированы правильно. Следует также иметь в виду, что в рассматриваемом здесь примере используют­ ся три однотипных потока исполнения , хотя это и не обязательное требован ие. Каждая сторона, пользующаяся синхронизатором фаз, может быть совершенно не похожей на остальные, выполняя свою задачу. Все, что происходит при переходе к следующей фазе , вполне поддается кон­ тролю. Для этого следует переопределить метод onAdva nce ().Этот метод вызы­ вается исполняющей средой, когда синхронизатор фаз переходит от одной фазы к следующей. Ниже приведена общая форма данного метода. protected boolean onAdvance (int фа.за , int жOJDrvec!l!'so_c!ropoв) Здесь параметр фа за обозначает текущий номер фазы перед его приращением, а параметр количе ство_ сторон - число зарегистрированных сторон. Для того чтобы завершить работу синхронизатора фаз, метод onAdvance () должен возвра­ тить логическое значение true. А для того чтобы продолжить работу синхрони­ затора фаз, метод onAdvance () должен возвратить логическое значение false. В версии по умолчанию метод onAdvance () возвращает логическое значение true , чтобы завершить работу синхронизатора фаз, если зарегистрированных сторон больше нет. Как правило, переопределяемая версия данного метода долж­ на следовать этой практике. Метод onAdvance ( ) может быть, в частности , переопределен для того , что­ бы дать синхронизатору фаз возможность выполнить заданное количество фаз, а затем остановиться. Ниже приведен характерный тому пример. В данном при­ мере создается класс MyPha ser, рас ширяющий класс Phaser таким образом , что­ бы выполнять заданное количество фаз. С этой целью переопределяется метод onAdvance () . :Конструктор класса MyPhaser принимает один аргумент, задающий количество выполняемых фаз. Обратите внимание на то , что класс MyPha ser ав­ томатически регистрирует одну сторону. Это удо бно для целей данного примера, но у конкретной прикладной программы могут быть другие потребности. 11 Расширить кл асс Phaaer и переопределить ме тод onAdvance () 11 та ким образом, чтобы было выполнено толь ко определе нное 11 количество фаз import jav a.util . concurrent .*; 11 Расширить кл асс МyPhas er , чтобы выполнить только 11 определенное количество фаз class MyPhaser extends Phaser { int numPha ses ; MyPhaser(i nt parties , int phaseCount ) sup er (parties) ;
1026 Часть 11. Библиоте ка Java numPhaзeз phaseCount - 1; // переопределить метод onAdvance () , чтобы выполнить 11 определенное количество фаз protected boolean onAdvance (int р, int regParties ) { // Следующий оператор println () требуется только для // целей иллюс трации . Как правило , ме тод onAdvance () // не отображает выв одимые данные System. out . println ( "Фaзa "+р +"завершена .\n ") ; // возвратить логическое значение true , // если все фазы завершены if (p == numPhases 11 regParties == 0) return true ; 11 В проти вном случае возвратить логическое значение �alse return fal se ; class PhaserDemo2 { puЫ ic static void ma in (String arg s[] ) MyPhaser phsr = new MyPhaser (l, 4) ; System. o ut . println ("З aпycк потохов \n") ; new MyThread (phsr, "А") ; new MyThread (phsr, "В") ; new MyThread (phsr, "С") ; 11 ожидать завершения определенного количества фаз while (!p hsr.is Terminated {) ) { phsr.arriveAndAwaitAdvance (); System. out . println ("Cинxpoнизaтop фаз завершен" ); // Поток исполнения, использующий синхрониза тор фаз типа Phaser class MyTh read impleme nts RunnaЫ e { Phaser phsr; String name ; MyThread (Phaser р, String n) { phsr = р; name = п; phsr . register () ; new Thread (this ).s tart{); puЫic void run () { while ( ! phsr.isTerminated () ) System.out . println ( "Поток " + name + " начинает фазу " + phsr . getPhase () ); phsr . arriveAndAwaitAdvance (); // Неболь шая пауза, чтобы не нарушить порядок вывода . // Только для иллюс трации , но необязательно дл я правильного // фун кционирования синхронизатора фаз try { Thread .sleep (l0) ;
Гл ава 28. Утилиты параллелизма catch (InterruptedException е) { System.out . println (e) ; Ниже приведен резул ьтат, выводимый данной программой. Запуск потоков Поток В начинает фазу О Поток А начинает фазу О Поток С начинает фазу О Фаза О завершена Поток А начинает фазу 1 Поток В начинает фазу 1 Поток С начинает фазу 1 Фаза 1 завершена Поток С начинает фазу 2 Поток В начинает фазу 2 Поток А начинает фазу 2 Фаза 2 завершена Поток С Beginning Phase 3 Поток В начинает фазу З Поток А начинает фазу 3 Фаза 3 завершена Синхронизатор фаз завершен 1027 В методе ma in ( ) создается один :экземпляр класса Phaser. В качестве аргумен­ та ему передается значение 4. Это означает, что он будет выполняться в течение четырех фаз , а затем завершится. Затем создаются три потока исполнения и на­ чинается следующий цикл, как показано ниже. 11 ожида ть завершения определенного колич ества фаз wh ile (!phsr . isTerminated ()) { phsr .ar riveAndAwaitAdva nce () ; В этом цикле метод arriveAnc!Awa itAdvance ( ) вызывается до завершения работы синхронизатора фаз, а этого не произойдет до тех пор, пока не будет вы­ полнено определенное количество фаз. В данном случае цикл будет продолжать­ ся до тех пор, пока не завершатся все четыре фазы. Следует также иметь в виду, что метод arr i veAndAwa i tAdvance ( ) вызывается из потоков исполнения вплоть до завершения работы синхронизатора фаз в данном цикле. Это означает, что по­ токи исполняются до завершения заданного количества фаз. А теперь рассмотрим подробнее исходный код метода onAdva nce ( ). Всякий раз, когда вызывается метод onAdvance ( ) , ему передается текущая фаза и коли­ чество зарегистрированных сторон. Если текущая фаза соответствует указанной фазе ил и количество зарегистрированных сторон равно нулю, метод onAdvance ( ) возвращает логическое значение true, прекращая таким образом работу синхро­ низатора фаз. Это делается в приведенном ниже фрагменте кода. Как можно за­ метить, для достижения желаемого результата необходимо совсем немного кода.
1028 Часть 11. Библиотека Java 11 возвратить логическое значение true , если все фазы завершены if (p == numPhases 11 regParties == 0) return true ; Прежде чем завершить эту тему, следует заметить, что расширять класс Pha ser совсем не обязательно. Как и в предыдущем примере, для этого достаточно пере­ определить метод onAdvance ( ) . В некоторых случаях может быть создан более компактный код с помощью анонимного внутреннего класса, переопределяющего метод onAdvance (). У класса Phaser имеются дополн ительные возможности , которые моrут ока­ заться полезными в прикладных программах. В частности , вызвав метод awai t Advance ( ) , можно ожидать конкретной фазы, как показано ниже. int awaitAdvance (int фаза) Здесь параметр фаза обозначает номер фазы, в течение которой метод await Advance () находится в состоянии ожидания до тех пор, пока не произойдет пере­ ход к следующей фазе. Этот метод завершится немедленно, если передаваемый ему параметр фаза не будет равен текущей фазе . Он завершится сразу и в том слу­ чае , если синхронизатор фаз завершит работу. Но если в качестве параметра фаза этому методу будет передана текущая фаза, то он будет ожидать перехода к сле­ дующей фазе . Этот метод должен быть вызван только зарегистрированной сто­ роной . Имеется также прерывающая версия этого метода под названием awa i t Advance interruptiЬly(). Чтобы зарегистрировать несколько сторон, следует вызвать метод bul k Reg iste r (); чтобы получить количество зарегистрированных сторон - метод getRegi steredParties (); чтобы получить количество сторон, достигших или не достигших своей фазы, - метод getArri vedParties ( ) или ge tUna rrived Parties () соответственно; а чтобы перевести синхронизатор фаз в завер':l l енное состояние - метод forceTermi nation (). Класс Pha ser позволяет также создать дерево синхронизаторов фаз. Он снаб­ жен двумя дополнительными конструкторами, которые позволяют указать роди­ тельский синхронизатор фаз и метод getParent (). Применение исполнителя В параллельном API поддерживается также средство , называемое испмпителем и предназначенное для создания потоков исполнения и управления ими. В этом отношении исполнитель служит альтернативой управлению потоками исполне­ ния средствами класса Thread. В основу исполнителя положен интерфейс Exe cutor, в котором определяется следующий метод: void execute (Run naЬ le поwож) В результате вызова этого метода исполняется указанный поток. Следовательно , метод execute () запускает указанный поток на исполнение. Интерфейс Exe cutorService расширяет интерфейс Executor, дополняя его методами, помогающими управлять исполнением потоков и контролировать их.
Гл ава 28. Утилиты параллел изма 1029 Например, в инте рфейсе ExecutorService определяется метод shutdown (),об­ щая форма которого приведена ниже. Этот метод останавливает все потоки ис­ полнения , находящиеся в данный момент под управлением экземпляра интерфей­ са ExecutorService. void shutdown () В интерфейсе ExecutorService определяются также методы , которые запу­ скают потоки исполнения , возвращающие результаты, исполняют ряд потоков и определяют состояние остановки. Некоторые из этих методов будут рассмотре­ ны далее. Имеется также интерфейс ScheduledExecutorService, расширяющий интерфейс ExecutorService для поддержки планирования потоков исполнения. Кроме того , в параллельном API имеются три предопределенных клас­ са исполнителей: ThreadPoo lExecutor, ScheduledThreadPoolExecutor и Fo rkJo inPool. Класс ThreadPoolExecutor реализует интерфейсы Executor и ExecutorSe rvi ce и обеспечивает поддержку управляемого пула потоков ис­ полнения . Класс ScheduledThreadPoolExecutor также реализует интерфейс ScheduledExecu torService для поддержки планирования пула потоков исполне­ ния. А класс ForkJoinPool реализует интерфейсы Executor и ExecutorService и применяется в каркасе Fork/Join Framework, как поясняется далее в это й гл аве. Пул потоков предоставляет ряд потоков исполнения для решения разнообраз­ ных задач. Вместо того создавать отдел ьный поток исполнения для каждой зада­ чи, используются потоки из пула. Это позволяет сократить нагрузку, связанную с созданием множества отдел ьных потоков. Хотя классы ThreadPoolExecutor и ScheduledThreadPoolExecutor можно использовать напрямую , исполн итель чаще всего придется получать, вызывая один из следующих статических фабрич­ ных методов, определенных во вспомогательном классе Executors. Ниже приве­ дены общие формы некоторых из этих методов. static ExecutorService newCachedТhreadPool() static ExecutorService newFixedТhreadPool(int жолжvес!l'во no!l'oжoa) static ScheduledExecutorService - newScheduledТhreadPool(int жomrvec!l'ao_no!l'o.a:oa) Метод newCachedThreadPool () создает пул потоков исполнения, который не только вводит потоки исполнения по мере необходимости , но и по возможности повторно испол ьзует их. Метод newFixedThreadPool () создает пул потоков ис­ полнения , состоящий из указанного количества_ по токов . А метод newS chedul ed ThreadPool ( ) создает пул потоков исполнения , в котором можно осуществлять пл анирование потоков исполнения . Каждый из них возвращает ссылку на интер­ фейс ExecutorService, предназначенный для управления пулом потоков испол­ нения. Простой пример исполнителя Прежде чем продолжить обсужде ние данной темы, рассмотрим простой при­ мер применения исполнителя. В приведенной ниже программе создается фикси­ рованный пул , содержащий два потока исполнения. Затем этот пул используется для выполнения четырех задач. Та ким образом, четыре задачи совместно исполь-
1030 Часть 11 . Библ иотека Java зуют два потока исполнения, находящихся в пуле. После того как задачи будут вы­ полнены , пул закрывается и программа завершается. 11 Простой пример применения испОлнителя import java.util . concu rrent .*; class SimpExec { puЬlic static void ma in (String args[]) { CountDownLatch cdl = new CountDownLatch (5) ; CountDownLatch cdl2 = new CountDownLatch (5) ; Count DownLatch cdl З = new CountDownLatch (5) ; CountDownLatch cdl4 = new CountDownLatch (5) ; ExecutorService es = Executors.newFixedThreadPool (2) ; System.out . println ( "Зaпycк потоков" ) ; 11 запустить потоки исполнения es . execute (new MyТhread (cdl , "А") ); es . execute (new MyThread (cdl2, "В" ) ); es . execute (new MyТhread (cdlЗ, "С") ); es . execute (new HyThread (cdl4, "D" ) ); try { cdl . await(); cdl2 • await (); cdl З .await () ; cdl4 .await(); catch (InterruptedExcept ion ехс ) { System. out . println (exc) ; es . shutdown() ; System.out . println ("З aвepшeниe потоков" ) ; cl ass MyThread implements RunnaЫe { String name ; CountDownLatch latch ; MyТhread (CountDownLatch с, String n) { latch = с; name=n; new Thread (this) ; puЫic void run () { for(inti=О;i<5;i++){ System. out .println ( name + "· "+i) ; latch . countDown () ; Ниже приведен результат выпол нения данной программы . Запуск потоков А:О А:1
А:2 А:3 А:4 С:О С:1 С:2 С:3 С:4 D:О D:1 D:2 D:3 D:4 В:О В:1 В:2 В:3 В:4 Завершение потоков Гл ава 28. Утил ить1 пар аллел изма 1031 Судя по приведенным выше результатам , выполня ются все четыре задачи, не­ смотря на то , что в пуле содержится всего два потока исполнения. Но только две задачи могут быть выполнены одновременно, а остальные должны ожидать до тех пор, пока в пуле не освободится один из потоков исполнения , чтобы им можно было воспользоваться . Вызов метода shutdown () очень важен. Если бы его не было в данной програм­ ме, она не смогла бы завершиться , поскольку исполнитель оставался бы активным. Уб едиться в этом можно, закомментировав вызов метода shutdown () и посмо­ трев, что из этого получится. П рименение интерфей сов CallaЬle и Fu ture К числу самых привлекательных средств в параллельном API относится интер­ фейс CallaЬle. Он представляет поток исполнения , возвращающий значение. Объекты интерфейса CallaЫe можно использовать в прикладной программе для вычисления результатов, которые затем возвращаются вызывающему потоку исполнения. И это довольно эффективный механизм, поскольку он облегчает на­ писание кода для самых разных числовых расчетов, в которых частичные резуль­ таты вычисляются одновременно. Его можно использовать и для запуска потока исполнения , возвращающего код состояния , который свидетельствует об ус пеш­ ном выполнении потока. Интерфейс Са l laЫe является обобщенным и объявляется следующим образом: interface Cal laЬle<V> где параметр V обозначает тип данных, возвращаемых потоком исполнения. В ин­ терфейсе Cal laЫe определяется единственный метод call (), общая форма ко­ торого приведена ниже. V cal l () throwв Exception В теле метода call () определяется задача, которую требуется выполнить. Когда она будет выполнена, возвращается результат. Есл и результат нельзя вычис­ лить, метод ca ll () генерирует исключение.
1032 Часть 11. Библиоте ка Java Для выполнения задачи типа CallaЫe вызывается метод submi t (), определен� ный в интерфейсе ExecutorService. У метода submi t ( ) имеются три общие фор­ мы, но для выполнения задачи типа Cal laЫe используется только одна из них: <Т> Future<T> suЬDU t (CallaЬle<T> saдa va) где параметр зада ча обозначает объект типа CallaЫe, который будет выпол· пяться в собственном потоке. Результат возвращается через объект типа Fu ture. Интерфейс Future является обобщенным инте рфейсом и представляет зна­ чение, возвращаемое объектом типа CallaЫe. А поскольку это значение будет получено через некоторое время в будущ ем, то имя интерфейса Future вполне со­ ответствует его назначению. Интерфейс Future объявляется приведенным ниже образом, где параметр V определяет тип результата. interface Future<V> Чтобы получить значение, следует вызвать метод get ( ) из интерфейса Fu ture. Ниже приведены общие формы этого метода. V get() throws InterruptedException , ExecutionException V get (lonq oЖJtдlU Ut e, Ti.Jl le Uni t еджюrца .вренеНJ1) throws InterruptedException , - ExecutionException , Ti.JDeoutException В первой форме ожидание получения результатов длится бесконечно долго. А во второй форме ожидание длится только в течение определенного периода времени, определяемого параметром ожида ние. Время ожидания указывается в единицах , обозначаемых параметром единица_ времени, который принимает объект перечисления Time Unit, рассматриваемого далее в этой гл аве . В приведенном ниже примере программы демонстрируется применение ин­ те рфейсов CallaЫe и Future. В этой программе формируются три задачи, вы· полняющие три разных вида вычислений. Первая задача возвращает суммарное значение, вторая находит длину гипотенузы прямоугольного треугольника с из­ вестными значениями дл ин его сторон, а третья определяет факториал заданного значения. Все три вида вычислений производятся одновременно. 11 Пример приме нения интерфейса CallaЫe import jav a.util . concurrent .*; class CallaЫe Demo { puЫ ic static void ma iп (Striпg args[] ) { Ex ecut orServi ce es = Executors . newFixedThreadPool (З) ; Fu ture<Integer> f; Fu ture<DouЫe> f2 ; Future<Integer> fЗ ; System.out . println ("З aпycк" ); f = es . submit (new Sum(10) ); f2 = es . submit (new Нуроt (З, 4) ); fЗ = es . submit (new Factorial (5)); try { System. out . println (f.ge t()); Syзtem. out . println (f2.ge t());
Гл ава 28. Ут илиты параллел изм а System.out . println (fЗ.get () ); catch (InterruptedException ехс ) System. out . println (exc) ; } catch (ExecutionException ехс ) { System.out . println (exc) ; es . shutdown() ; System. o ut . println ("З aвepшeниe") ; } 11 Три потока исполнения вычислений class Sum implements CallaЫe<Integer> int stop ; Sum(intv){stop=v;} puЬlic Integer call () { intsum=О; for(inti=1;i<=stop;i++){ sum += i; return sum; class Hypot irnpleme nts CallaЫe<DouЫ e> douЫ e sidel, side2 ; Hypot ( douЫe sl, douЫe s2) { sidel = sl; side2 = s2; } puЫ ic DouЫe call () { return Ma th .sqrt ((s idel*sidel) + (side2 *side 2 )); class Fac torial implements CallaЫe<Integer> int stop; Factorial(int v) { stop = v; } puЬlic Integer call () { int fact = 1; for(inti=2;i<=stop;i++){ fact *= i; } return fact ; Ниже приведен результат выполнения данной программы. Запуск 55 5.0 120 Завершение 1033
103, Часть 11. Библиотека Java Переч исление TimeUnit В параллельном API определяются методы , принимающие параметр перечне· лимого типа Tirne Uni t, обозначающий период времени ожидания. Перечисление Tirne Uni t служит для обозн ачения степени разрешения синхронизации. Это пере· числение определено в пакете j ava . util . concurrent и может принимать одно из следующих значений: • DAYS • HOURS • MINUTES • SECONDS • MICROSECON DS • MILLISECON DS • NANOSECONDS И хотя с помощью перечисления Tirne Unit можно определить любое из этих значений в вызовах методо в, принимающих параметр синхронизации, нет пика· кой гарантии того , что система сможет работать с заданным разрешением. Ниже приведен пример , демонстрирующий применение перечисления Tirne Unit. Класс CallaЫeDerno из предыдущего примера был изменен таким об­ разом , чтобы использовать вторую форму метода get () , принимающего параметр типа TirneUni t. В данном варианте ни один из вызываемых метода get () не будет ожидать дольше 1 О мил л исекунд. try { System. out . println (f.get (lO, TimeUnit .MILLISECONDS )); System.out . println (f2.get (10, TimeUnit.MILLISECONDS )); System. out . println (f3.get (10, TimeUnit.MILLISECONDS )); catch (InterruptedException ехс ) { System. out . println (ex c) ; \catch (Execut ionException ехс ) ( System. out . println (exc) ; catch (TimeoutException ехс ) System. out . println (exc) ; В перечислении Tirne Uni t определяются различные методы, выполняющие преобразование единиц. Ниже приведены их общие формы. lonq convert(long .вреЮl , TimeUnit ед.1 1ющ а_.вренеюr) long toМicros ( long .вреюr) lonq toМi llis ( long .l lpeNl l ) lonq toNanos ( long ареа а; ) lonq toSeconds (long .l lpeNl l ) lonq toDays (long .l lpeNl l ) lonq toHours (lonq �) lonq toМinutes (lonq вpeNR) Метод conve rt () преобразует заданное время в единицы времени, обозначае­ мые параметром единица_ времени, и возвращает результат. Методы типа toXXX выполняют указанное преобразование и возвращают результат.
Гл ава 28. Утил ить1 пар аллел и зма 1035 В перечислении Time Un it определяются также следующие методы синхрони­ зации: void sleep ( lonq sадержжа) throws InterruptedExecution void till l&dJ oin ( Тhread DO!l'OЖ , lonq задержжа) throws InterruptedExecution void tiJD&dWait (OЬject о�•ж!l' , long sадерж ж а) throws InterruptedExecution Метод sleep ( ) приостанавливает выполнение на определенный период вре­ мени, который задается в виде вызывающей константы перечислимого типа. Он преобразуется в вызов метода Thread . sleep (). Метод time dJo in () является спе­ циальной версией метода Thread . j oin (), где указанный поток исполнения при­ останавливается на период времени, обозначаемый параметром задержка . Метод timedWa i t ( ) также является специальной версией метода Ob j ect . wait ( ) , где указанный о бъект ожидает в течение периода времени, обозначаемого параме­ тром задержка в вызывающих единицах времени. П араллельн ые колл екц ии Как отмечалось ранее, в параллельном API определяется ряд коллекций, пред­ назначенных для выполнения параллельных операций. К их числу относятся сле­ дующие коллекции: • ArrayBlockingQueue • ConcurrentHashMap • ConcurrentLinkedDeque • ConcurrentLinkedQueue • ConcurrentSkipListMap • ConcurrentSkipListSet • CopyOnWr iteArrayList • CopyOnWriteArraySet • De layQueue • LinkedBlockingDeque • LinkedBlockingQue ue • LinkedTrans ferQueue • PriorityBlockingQueue • SynchronousQueue Эти коллекции служат параллельной альтернативой соответствую щим классам коллекций из каркаса Collections Fгamework. Они действуют таким же образом, как и остальные коллекции, но только поддерживают параллелизм. Программисты , имеющие опыт работы с каркасом Collections Framework, не испытают никаких затруднений, применяя эти парал л ельные коллекции.
1036 Часть 11. Библиотека Java Бл оки р овки В пакете j ava . util . concurrent . locks предоставляется поддержка бликировак, которые являются объектами и служат альтернативой блокам кода synchronized для управления доступом к общему ресурсу. В общем , блокировки действуют следу­ ющим образом. Прежде чем получить доступ к общему ресурсу, запрашивается бло­ кировка, защищающая этот ресурс. По завершении доступа к ресурсу блокировка снимается. Если один поток исполнения попытается запросить блокировку в тот момент, когда она используется каким-нибудь другим потоком исполнения, то пер­ вый поток будет ожидать до тех пор, пока блокировка не будет снята. Благодаря это­ му уд ается избежать конфликтов при доступе к общему ресурсу. Блокировки особенно полезны в тех случаях, когда нескольким потокам испол­ нения требуется получить доступ к значению из общих данных. Например, в при­ кладной программе складского учета может использоваться поток исполнения , в котором сначала подтверждается наличие товара на складе, а затем уменьшается количество доступных товаров после каждой продажи. Есл и будут выполняться два или несколько таких потоков, то без синхронизации может возникнуть ситуация, когда один поток исполнения начнет свою транзакцию в процессе выполнения транзакции другим потоком. Та ким образом, в обоих потоках исполнения будет предполагаться достаточное количество товара на складе , хотя на самом деле это­ го товара хватит только для одной продажи. В подобных ситуациях синхронное исполнение потоков можно орган изовать с помощью блокировок. Блокировка определяется в интерфейсе Lock. В табл. 28.2 перечислены мето­ ды, определенные в интерфейсе Lock. В общем, для получения блокировки сле­ дует вызвать метод lock (). Если блокировка недоступна, метод lock ( ) войдет в состояние ожидания. Чтобы снять бл окировку, следует вызвать метод unlock ( ) , а для того чтобы выяснить, доступна ли блокировка, и если она доступна, полу­ чить ее, - вызвать метод tryLo ck (). Метод tryLock ( ) не будет ожидать блоки­ ровку, если она недоступна. Напротив, он возвращает логическое значение true, если бл окировка получена, а иначе - логическое значение false. Метод new Condi tion () возвращает объект типа Condition, связанный с блокировкой. Объект типа Condi tion позволяет добиться более полного контроля над бло­ кировками с помощью методов awa i t () и signal () , обеспечивающих такие же функциональные возможности , как и методы ОЬ j есt . wait () и Ob ject .notify ( ) . Табл ица 28.2. Методы из инте рфейса Lock М етод void lock () void lockinter- ruptiЬly () throws InterruptedExoeption Condi tion newCondi tion () Описание Ожидает до тех пор , пока не будет получена вызываемая блоки ровка Ожидает до тех пор, пока не будет получена вызываемая блокировка, если только не будет п рервано ожидание Возвращает объект т ипа Condition, связанный с вызы­ ваемой блоки ровкой
Метод Ьoolean tryLock () Ьoolean tryLock (lonq ОJаrдавае , TiJDeUnit eд.l ll l•ц• _8P8Jfe1Ur) throws Interrupt.dException void unlock () Гл ава 28. Утил иты пар аллелизма 1037 Окон-чаниетабл.28.2 Описание Пытается запросить блоки ровку. Этот метод не вход ит в состояни е ожидан ия, если блоки ровка недоступна. Вместо этого он возвращает логи ческое значени е true, если блоки ровка получена, или логи ческое значение false, если в данный момент блоки ровка и спользуется другим потоком исполнения Пытается получ ить блоки ровку. Есл и блоки ровка не­ доступ на, этот метод будет ожидать в течение периода времени , обозначаемого параметром ааrдавае. Время ожидан ия указывается в еди н и цах , обозначаемых пара­ метром ед.1 11 1 •ц•_8J)8N81W. Если блоки ровка получена, то возвращается логи ческое значени е true. А если бло­ ки ровка не была получена в течение заданного периода времени , то возвращается логическое значение fal se Сни мает блоки ровку В пакете j ava . util . concurrent . locks предоставляется класс Re entrantLoc k, реализующий интерфейс Lock. Этот класс реализует ре ен терабел:ьную Wwкироеку, в ко­ торую может входить повторно поток исполнения , удерживающий в данный момент блокировку. Если поток входит в блокировку повторно , все вызовы метода lock () должны быть, разумеется, смещены на равное количество вызовов метода un lock (). В противном случае поток, пытающийся запросить блокировку, перейдет в режим ожидания до тех пор , пока она не будет снята. В приведенном ниже примере программы демонстрируется применение бло­ кировки . В этой программе создаются два потока исполнения, которые обраща­ ются к переменной Shared . count в качестве общего ресурса. Прежде чем поток исполнения сможет обратиться к переменной Shared . count, он должен получить блокировку. После получения блокировки значение переменной Shared . count увеличивается , а далее поток исполнения входит в состояние ожидания, прежде чем снять блокировку. Вследствие этого другой поток исполнения будет пытать­ ся получить блокировку. Но поскольку блокировка все еще уд ерживается первым потоком исполнения, то другой поток будет ожидать до тех пор, пока первый не выйдет из состояния ожидания и не снимет блокировку. Как показывает результат выполнения дан ной программы, доступ к переменной Shared . count синхронизи­ руется с помощью блокировки. 11 Простой пример бло кир овки import java.util . concurrent .locks .*; class LockDemo { puЫ ic static void ma in (String args [] ) { Reent rantLock lock = new ReentrantLock ( ); new LockThread (loc k, "А") ; new LockThread (loc k, "В") ;
1038 Часть 11. Библиотека Java 11 Общий ресурс class Shared { static int count О; 11 Поток исполнени я, инкремен тирующий значение сче тчика class LockThread implements RunnaЫe { String name ; ReentrantLock lock; LockThread (ReentrantLock lk, String n) { lock = lk; name = n; new Thread (this ) .start() ; puЬlic void run () { System. out . println ("З aпycк потока " + name) ; try { 11 сначала заблокировать счетчик System.out . println ( "Поток" + name +"ожид ает блокировки счетчика" ); lock. lock(); System.out . println ( "Пoтoк" name + " блокирует счетчик.") ; Shared . count++ ; System.out . println ( "Пoтoк" + name + ": " + Shared . count ); 11 а теперь переключ ение контекста , если это возможно System. o ut . priпtln ("П oтoк" + name + " ожидает") ; Thread .sleep (lOOO) ; catch (InterruptedException ехс ) { System.out . println (exc) ; finally { 11 снять блокировку System.out . println ( "Поток" + name + " разблокируе т сче тчик" ); lock . unlock (); Ниже приведен примерный результат выполнения данной программы. (У вас порядок выполнения потоков может оказаться иным. ) Запуск потока А Поток А ожида ет блоки ровки счетчика Поток А блокируе т счетчик Поток А: 1 Поток А ожидает Запуск потока В Поток В ожидает блокировки сче тчика Поток А разблокирует счетчик Поток В бл окируе т счетчик Поток В: 2 Поток В ожида ет Поток В разблокирует счетчик В пакете java . util . concurrent . locks определяется также интерфейс Read Wr iteLock, реализующий поддержку отдел ьных блокировок для доступа с целью
Гл ава 28. Утилиты параллелизма 1039 чтения и записи. Это дает возможность предоставлять несколько бл окировок потокам исполнения только для чтения данных из ресурса, но не для их записи. Класс Re entrantReadWr iteLock предоставляет соответствующую реализацию интерфейса Re adWr i teLock. На заметку! В версии JDK 8 внедрен класс Stamp edLock, реал изующий специальную блокиров­ ку. Но этот класс не реализует интерфейс Lock или ReadWriteLock, а вместо этого пре­ доставляет механизм, позволяющий использовать его средства подобно интерфейсу Loc k ил и ReadWriteLock. Ато марн ые операции В пакете jav a.util . concurrent .atomic предоставляется альтернатива дру­ гим средствам синхронизации для чтения или записи значений переменных неко­ торых типов. В этом пакете доступны методы, которые получают, устанавливают или сравнивают значение переменной во время одной непрерывной, т. е . a:moмafr ной, операции. Это означает, что для выполнения такой операции не требуется ни блокировка, ни лtобой другой механизм синхронизации. Атомарные операции выполняются с помощью классов Atom icinteger и At omi cLomg , а также методов get (), set (), compareAndSet (), de cremen tAnd Ge t () и ge tAndS et (), которые реализуют соответственно следующие действия: получение, установку, сравнение и установку, декремент и получение, получение и установку. В следующем примере показано, как синхронизировать доступ к общему ресур­ су с помощью клacca Atom icinteger: 11 Простой пример выполнения атомарных операций import java.util . concurrent .atomic .*; cl ass At omi cDemo { puЫic static void main (String args[]) new AtomThread ("A ") ; new AtomThread ("B ") ; new AtomThread ("C ") ; class Shared { static Atomicinteger ai new Atomicinteger (O) ; 11 Поток исполне ния , в котором инкрементируется значение счетчика class At omThread implements RunnaЫ e { String name ; At omT hread (String n) { name = n; new Thread (this ) .start () ; puЫic void run () { System. o ut . println ("З anycк потока " + name) ;
Часть 11. Библ иоте ка Java for(int i=l; i <= З; i++) System.out . println ("П oтoк" + name + " получено : " + Shared .ai.ge tAndS et (i) ); В данной программе с помощью класса Shared сначала создается статический объект ai типа At om icinteger, а затем три потока исполнения типа AtomThread. В методе run () объект Shared . ai изменяется в результате вызова метода ge tAnd Set ().Этот метод возвращает предыдущее значение и устанавливает то значение, которое было передано в качестве параметра. Благодаря классу Atomi cinteger исключается вероятность того, что два потока будут одновременно осуществлять за пись данных в объект ai. В общем, атомарные операции служат удо бной (а, возможно, и более эффектив· ной) альтернативой другим механизмам синхронизации при обращении к одной переменной в качестве общего ресурса. Начиная с версии JDК 8 в пакете java . ut i l.concurrent . atomic предоставляются также следующие четыре класса, под· держи вающие неблокируемые накопител ьные операции: DouЬ leAccumulator, DouЬ l eAdde r, LongAccumulator и LongAdde r. В накопительных классах поддер· живаются последовательности определяемых пользователем операций. А в сумми· рующих классах накаruшвается нарастающая сумма. Параллел ьное прогр аммирование с р едства ми Fo rk/Join Fra mewo rk В последние годы появилось новое важное направление в разработке программ· ного обеспечения , называемое пара.л.лелъным nрогр ам м ир ованшм. Параллельное программирование - это общее название методик, вы годно использующих вы· числительные мощности многоядерных процессоров. Как известно, ныне ком· пьютеры с многоядерными процессорами уже стали обычным явлением. К пре­ имуществам многопроцессорных систем относится возможность значительно повысить производительность программного обес печения. В итоге заметно воз· росла потребность в механизме, который позволял бы программирующим нajava просто , но эффективно пользоваться несколькими процессорами, без особого труда наращивая вычислительные мощности по мере надобности. В ответ на эту потребность в версии]DК 7 было внедрено несколько новых классов и интерфей· сов для поддержки параллельного программирования. Обычно они упо минают­ ся под общим названием Fork/Jo in Fra mewrлk. Это одно из наиболее важных за по· следнее время дополнений библиотеки классов Java. Каркас Fork/Join Framework определен в пакете java . ut il . concurrent. Каркас Fork/Join Framework усовершенствует многопоточное программирова· ние двумя важными способами. Во-первых, он упрощает создание и использова· ние нескольких потоков исполнения, и во-вторых, автоматизирует использование нескольких процессоров. Иными словами, каркас Fork/Join Frarnework позво­ ляет автоматически наращивать вычислительные мощности в прикладных про­ граммах, увеличивая число задействованных процессоров. Благодаря этим двум
Гл ава 28. Утилиты параллелизма 10'1 усовершенствованиям каркас Fork/Join Framework рекомендуется применять для многопоточного программирования в тех случаях, когда требуется параллель­ ная обработка. Прежде чем продолжить дальше, следует указать на отличие параллельно­ го программирования от традиционного многопоточного программирования . В прошлом большинство компьютеров имело лишь один процессор, и многопо­ точность, прежде всего , позволяла выгодно воспользоваться временем простоя , когда программа ожидает, например, от пользователя ввода данных. При таком подходе один поток может выполняться , в то время как другой ожидает. Иными словами, в системе с одним процессором многопоточность позволяет совместно использовать этот процессор для выполнения двух или более задач. Та кой ти п многопоточности , как правило, поддерживается объектом класса Thread, как по­ яснялось в гл аве 11. И хотя эта разновидность многопоточности останется весьма полезной и впредь, она не совсем подходит для тех случаев, когда имеются два или более процессора, т. е . многоядерный компьютер. Если имеется несколько процессоров, то требуется другой тип многопоточно­ сти , обеспечивающий настоящее параллельное выполнение. На двух или более процессорах программу можно выполнять од новременно отдельными частями. Благодаря этому значительно ускоряется выполнение некоторых видов операций, включая сортировку, преобразование или поиск в крупном массиве. Зачастую та­ кие операции могут быть разделены на меньшие части , например, для обработки массива по частям на отдел ьных процессорах. Нетрудно догадаться , что такое рас­ параллеливани�_операций дает немалый выигрыш в производител ьности , а сле­ довательно, в будущем параллельное программирование станет неотъемлемой частью арсенала средств каждого программиста, поскольку оно открывает путь к значительному повышению производительности программного обеспечения. Основные кл а сс ы Fo rk/Join Fra mewo rk Каркас Fork/Join Framework входит в пакет j ava . util . concurrent. Его ядро составляют следующие классы: P'orkJoinTa•k<V> ForkJoinPool Recursiv&Action RecursiveTask<V> Абстрактный класс, определяющий выполняемую задачу Уп равляет выполнением задач типа ForkJoinTaslt Является производным от класса l!'orltJoinTask<V> для выполне­ ния задач, не возвращающих значения Является производным от класса i'orkJoinTask<V> для вы полне­ ния задач , возвращающих значения Рассмотр им взаимосвязь этих классов. Класс Fo rkJoinPool управляет вы­ полнением задачи, представленной объектом класса Fo rkJo inTa sk. Класс ForkJo inTask является абстрактным и расширяется двумя другими абстрактны­ ми классами: Re curs iveAct ion и Re c ursiveTask. Как правило, эти классы рас­ ширяются в прикладном коде для формирования задачи. Прежде чем перейти к подробному рассмотрению процесса выполнения задачи, сделаем краткий обзор основных особенностей каждого из этих классов.
Ч асть 11. Б иблиоте ка Java На заметку! Класс CountedComplete r, внедрен ный в версии JDK 8, та кже расширяет класс Fo r kJ о i n Та s k. Но его рассмотрение выходит за ра мки данной книги. Клacc Fo rkJoinTask<V> Класс Fo rkJoinTas k<V> является абстрактным и определяет задачу, выпол­ нением которой может управлять объект класса Fo rkJoinPo ol. Параметр типа V определяет тип результата выполнения задачи. Класс For kJoinTas k отличается от класса Thread тем, что он представляет облегченную абстракцию задачи, а не поток исполнения. Задача типа For kJoinTask выполняется потоками, управляе­ мыми из пула потоков типа ForkJo inPoo l. Та кой механизм позволяет управлять выполнением большого кол ичества задач , фактически испол ьзуя небольшое чис­ ло потоков исполнения. Та ким образом, задачи типа Fo rkJoinTa sk оказываются намного эффективнее потоков исполнения типа Thread. В классе Fo rkJoinTask определено немало методов. Основными из них явля­ ются методы fork ( ) и j oin ( ),общие формы которых представлены ниже. final ForkJoinTask<V> fork () final V join () Метод f о r k ( ) передает вызывающую задачу для асинхронного выполнения. Это означает, что поток исполнения , из которого вызывается мс:z,тод fork (), продол­ жает выполняться. Как только задача будет запланирована дл� вьшолнения, метод fork () возвратит ссылку this на объект этой задач и. До версииjDК 8 это можно было сделать только в вычислительной части другой задачи типа Fo rkJoinTask, которая выполнялась из пула потоков типа ForkJo inPool. (Ниже будет показано, каким образом создается вычислительная часть задачи .) Но начиная с версииJDК 8 используется общий пул потоков, если метод fork ( ) не вызывается при выполне­ нии задачи из пула потоков типа Fo rkJoinPool. Метод join ( ) ожидает заверше­ ния задачи, для которой он вызван . В итоге возвращается результат выполнения задач и. Та ким образом, с помощью методов fork ( ) и join () можно запустить на выполнение одну или несколько новых задач и ожидать их завершения. Еще одн им важн ым в классе Fo rkJoinTas k является метод invo ke ().Он объеди­ няет операции вилочного соединения в единый вызов, поскольку запускает сначала задачу на выполнение, а затем ожидает ее завершения. Ниже приведена общая фор­ ма этого метода. Он возвращает результат выполнения вызывающей задачи. final V invoke () С помощью метода invokeAl l () можно вызвать одн овременно несколько за­ дач . Ниже приведены две общие формы этого метода. static void invokeAl l(ForkJoinTask< ?> saдa vaA , ForkJoinTask< ?> sадаvав) static void invok&All (ForkJoinTask<?> . .. сп•сож_sада v) В первой форме выполняются задачи, обозначаемые параметрами зада ча А и зада ча В, а во второй форме - все указанные задачи. В обоих случаях вызыва­ ющий поток исполнения ожидает завершения всех указанных задач. До версии JDK 8 метод invokeAl l ( ) можно было вызвать только в вычислительной части другой задачи типа ForkJoinTa sk, которая выполнялась из пула потоков типа
Гл ава 28. Утилиты параллелизма Fo rkJoinPool. А в версииJDК 8 это ограничение бЫJiо ослаблено благодаря вне­ дрению общего пула потоков. Кnacc RecursiveAction Этот класс является производным от класса For kJoinTask и инкапсулирует за­ дачу, которая не возвращает результат. Как правило, класс Re cursiveAc tion рас­ ширяется в прикладном коде, чтобы сформировать задачу, возвращающую значе­ ние типа vo id. В классе Re cursiveAc tion определены четыре метода, но только один из них обычно представляет какой-то интерес - это абстрактный метод com­ pu te ().Когда класс Re cursiveAction расширяется с целью создать конкретный класс, то код, определяющий задачу, размещается в теле метода compute ().Метод compute ( ) предстамяет въt:чисяитмьную часть задачи. В классе Re cursiveAction этот метод определяется следующим образом: protected aЬstract void co11 1pU te () Обратите внимание на то , что метод compu te () ямяется защищенным. Это означает, что он может быть вызван только другими методами данного класса или производного от него класса. А поскольку этот метод еще и абстрактн ый, то его следует реализовать в производном классе, если только этот класс также является абстрактным. Как правило, класс Re c ursiveAction служит для реализации рекурсивной стратегии выполнения задач, которые не возвращают результаты . Эта стратегия действует по принципу "разделяй и властвуй", как поясняется в соответствующем разделе далее в этой гл аве. Кnacc RecursiveTask<V> Еще одн им производным от класса ForkJoinTask является класс Re c ursive Tas k<V>. Он инкапсулирует задачу, которая возвращает результат. Тип результата определяется параметром типа V. Как правило, класс RecursiveTa sk<V> рас ши­ ряется в прикладном коде, чтобы сформировать задачу, возвращающую значение. Как и в классе RecursiveAction, в данном классе определены четь1ре метода, но обычно используется только абстрактный метод compu te (), представля ющий вы­ числительную часть задачи . Когда класс Re c ursiveTask<V> расширяется для соз­ дания конкретного класса, в теле метода compute () размещается код, представ­ ляющий выполняемую задачу. Этот код также должен возвратить результат вы­ полнения задачи. В классе Re cursiveAction этот метод определяется следую щим образом: protected aЬstract V coшpute () Обратите внимание на то , что метод compu te () является защищенным. Это означает, что он может быть вызван только другими методами данного класса или производного от него класса. А поскольку этот метод еще и абстрактный, то его следует реализовать в производном классе, если только этот класс также являет­ ся абстрактн ым. Будучи реализованным, метод compute () должен возвращать ре­ зультат выполнения задачи.
10,, Часть 11. Библиотека Java Как правило , класс RecursiveTask служит для реализации рекурсивной стра· тегии выполнения задач , которые не возвращают результаты . Данная стратегия действует по принципу "разделяй и властвуй", как поясняется в соответствующем разделе далее в этой гл аве . Кла сс ForkJoinPool Выполнение задач типа Fo rkJoinTask происходит из пула потоков типа Fo rkJoinPool, который управляет также выполнением других задач. Следовательно, чтобы запустить на выполнение задачу типа ForkJo inTa sk, сначала потребуется объ­ ект типа ForkJoinPool. В версии JDК 8 появились два способа получения объекта типа Fo rkJoinPool. Во-первых, этот объект можно создать явным образом, исполь­ зуя конструктор класса Fo rkJoinPool. И во-вторых, можно воспользоваться так назы­ ваемым общим пулам. Общий пул был внедрен в версииJDК 8 и является статическим объектом типа For kJo inPoo l, автоматически доступным для применения. Ниже представлены методы из класса Fo r kJoinPoo l, начиная с построения пула вручную. В классе Fo rkJoinPool определено нес колько конструкторов. Ниже приведе· ны два наиболее употребительных конструктора этого класса. ForkJoinPool () ForkJoinPool (int .vроаенJо_парал л етrsн.а) Первый конструктор создает пул по умолчанию, обеспечивающий уровень па· рал л елизма, равный количеству процессоров, доступных в системе. А второй кон· структор позволяет задать конкретный уровень_ параллелизма . Значение пара· метра уров ень_ параллелизма должно быть больше нуля, но не больше предела реализации. Ур овень параллелизма определяет количество потоков, которые мо­ гут исполняться одновременно. В итоге уровень параллелизма фактически опре­ деляет количество задач, которые могут выполняться одновременно. (Разумеется, количество одновременно выполняемых задач не может превышать количество доступ ных процессоров. ) Однако уровень параллелизма не ограничивает количе­ ство задач , которыми может управлять пул потоков. На самом деле пул пото ко в типа Fo rkJoinPool может управлять намного бальшим количеством задач , чем его уровень параллелизма. Кроме того , уровень параллел изма - это лишь цель, а не средство, дающее какую-то гарантию. Как только будет создан экземпля р класса Fo rkJoinPool, задачу можно за· пустить на выполнение самыми разными способами. Задача, запускаемая на вы· полнение первой, обычно считается основной. Эта задача нередко запускает под· чиненные задачи, которыми также управляет пул потоков. Самый распростра· пенный способ запустить основную задачу - вызвать метод invoke () из класса ForkJoinPool. Ниже приведена общая форма этого метода. <Т> Т invoke (ForkJoinTaak<T> s�ava) Этот метод запускает указанную зада чу и возвращает результат ее выполне­ ния. Это означает, что вызывающий код ожидает завершения метода invoke (). Чтобы запустить задачу на выполнение и не ждать ее завершения, можно вое· пользоваться методом execute ().Ниже приведе на одна из его форм. void execute (ForkJoinTask< ?> saдava)
Глава 28. Утилиты параллелиэма В данном случае указанная зада ча запускается на выполнение, но вызываю­ щий код не ждет ее завершения. Вместо этого вызывающий код продолжает вы­ полняться асинхронно. Начиная с версии JDK 8 строить объект типа Fo rkJoinPool явным образом совсем не обязательно, поскольку для этой цели имеется общий пул. Как правило , если созданный явным образом пул не используется. то вместо него автоматиче­ ски выбирается общий пул. Вызвав метод coпunonPool ( ) , определенный в классе Fo rkJoinPool, можно получить ссылку на общий пул , хотя это и необязательно. Ниже приведена общая форма этого метода. static ForkJoinPool coD1 1D onPool () Этот метод возвращает ссылку на общий пул , обеспечивающий исходный уро­ вень параллелизма. Этот уровень может быть задан с помощью системного свой­ ства. (Подробнее об этом см. в документации на параллельный API.) Как правило , выбираемый по умолчанию общий пул вполне подходит для многих приложений. Разумеется, вам ничто не мешает построить свой пул. Запустить задачу на выполнение из общего пула можно двумя способами. Во­ первых, вызвав метод coпunon Poo l (), можно получить ссылку на пул , а затем вы­ звать по этой ссылке метод invo ke ( ) или execute ( ), как описано выше. И во­ вторых , в любой части задачи, кроме вычислительной, можно вызвать метод fo rk () или invo ke () из класса For kJoinTask. В последнем случае общий пул вы­ бирается автоматически. Иными словами, метод fork () или invoke () запустит задачу на выполнение из общего пула, если задача еще не выполняется в пуле типа Fo rkJoinPool. Пул типа Fo rkJoinPool управляет выполнением своих потоков по принципу перехвата работы. Каждый рабочий поток исполнения поддерживает очередь за­ дач . Если очередь задач одного рабочего потока исполнения окажется пустой, он возьмет задачу из другого рабочего потока исполнения. Та кой принцип спо­ собствует повышению общей производительности и помогает равномерно рас­ пределять нагрузку. (Вследствие того что время ЦП требуется другим процессам в системе, даже два рабочих потока исполнения с одинаковыми задачами в своих очередях могут и не завершиться одновременно.) Следует также иметь в виду, что в пуле типа ForkJoinPool используются п� тиковъtе деманъt. Потоковый демон автоматически завершается вместе со всеми пользовательскими потоками. Та ким образом, нет никакой необходимости явно завершать работу пула типа ForkJoinPool. Те м не менее это можно сделать, вы­ звав метод shutdown (). Впрочем, вызов метода shutdown ( ) не оказывает никако­ го вл ияния на общий пул. Страте гия " разделяй и властвуй " Как правило, пользователи каркаса Fork/Join Framework пользуются страте­ гией "ра.зделяй и властвуй", положенной в основу рекурсии. Именно поэтому оба класса, производных от класса For kJo inTask, называются RecursiveAction и Re cursive Task. Ожидается, что при формировании своей задачи вилочного со­ еди нения программирующие нaJava будут расширять один из этих классов.
Часть 11. Библиотека Java Стратегия "разделяй и властвуй", положенная в основу рекурсии, подразумева· ет разделение задачи на подзадачи до тех пор , пока их объем не станет достаточно мелким для последовательной обработки. Например , задача преобразования каж­ дого из Nэлементов массива целых чисел может быть разделена на две подзадачи, каждая из которых преобразует половину элементов в массиве. Та ким образом, одна подзадача преобразует элементы от О до N/ 2, а другая - элементы от N/ 2 до N. В свою очередь, каждая подзадача может быть сведена к набору подзадач, каждая из которых преобразует половину остальных элементов. Этот процесс де­ ления массива продолжается до тех пор, пока не будет достигнуто пороговое зна­ чение , при котором последовательное решение задачи оказывается быстрее, чем дальнейшее ее разделение на подзадачи. Преимущество стратегии "разделяй и властвуй" заключается в то м, что обра­ ботка может осуществляться парал л ельно. Поэтому части массива могут быть об­ работаны одновременно, вместо того чтобы циклически перебирать весь массив в одном потоке. Безусловно, принцип "разделяй и властвуй" действует во многих случаях и без массива (или коллекции), но наиболее распространенная область его применения подразумевает наличие некоторого типа массива, коллекции или другого группирования данных. Одн им из гл авных условий успешного применения стратегии "разделяй и вла­ ствуй" является правильное определение порогового значения, после которого выполняется последовательная обработка, а не дальнейшее раздел ение задач и. Как правило , оптимальное пороговое значение получается при профилировании характеристик исполнения. Но даже при использовании порогового значения меньше оптимального все равно произойдет весьма существенное ускорение вы­ полнения задачи. Те м не менее лучше избегать чрезмерно крупных или мелких пороговых значений. На момент написания этой книги в документации API Java на класс Fo rkJoinTa sk<T> приводится следующее эмпирически выведенное пра­ вило: задача должна выполняться где-то за 100- 1 0000 этапов вычисления. Важно также иметь в виду, что на оптимальное пороговое значение влияет вре­ мя, которое отнимают вычисления. Если каждый этап вычислений достаточно продолжителен , то предпочтительнее устанавливать малые пороговые значения, и, наоборот, если каждый этап вычислений очень короткий, то большие порого­ вые значения могут обеспечить лучшие результаты. Для прикладных программ, которые должны быть запущены на выполнение в системе с известным количе­ ством процессоров, сведения о количестве процессоров можно использовать для обоснования решения о пороговом значении. Но для прикладных программ, которые будут выполняться в разных системах , возможности которых заранее не­ известны, практически невозможно сделать никаких предположе ний о вычисли· тельных мощностях исполняющей среды. И еще одно замечание: несмотря на то что в системе может быть доступно не­ сколько процессоров, другие задачи (и сама операционная система) будут сопер­ ничать с прикладной программой за время ЦП. Поэтому не стоит особенно пола· гаться на то, что у прикладной программы будет неограниченный доступ ко всем имеющимся в системе процессорам. Кроме того, различные процессы в одной той же программе могут показать разные характеристики времени выполнения из-за отличий в загруженности задачами.
Гл ава 28. Утил иты пар аллелизма П ервый п росто й п ример вилочного с оедине ния А теперь рассмотрим простой пример, демонстрирующий применение карка­ са Fork/Join Framework и стратегии "разделяй и властвуй" на практике. В приве­ денной ниже программе значения элементов массива типа douЫ e преобразуют­ ся в их квадратные корни. Для этой цели служит класс, производный от класса Re cursiveAction. Обратите внимание на то , что в данной программе создается свой пул потоков типа For kJoinPoo l. // Простой пример реализации стратегии "разделяй и властвуй" . // В данном примере применяется кл асс Recurв iveAction import jav a.util . coпcurreпt .*; import java .util .*; 11 Класс ForkJoinTaak преобразует (через класс RecuraiveAction } 11 значения элементов массива типа douЫe в их квадратные корни class SqrtTrans form extends Re curs iveAc tion { // В данном примере пороговое значение произвольно устана вливается 11 равным 1000 . В реальном коде его оптимал ьное значение может 11 быт ь определено в резуль тате профилирования исполняющей системы // или экспериментально fiпal int seqT hreshold = 1000; 11 обрабатываемый массив douЫe [] data; // определить часть обрабатываемых данных int start , end ; SqrtTransform {douЫe [] vals, int s, int е } { data = val s; start = s; end=е; ) 11 Этот ме тод вып олняет параллельное вычисление protected void compute () { // Если колич ество элементов ме ньше порогового значения , // выполни ть дальнейшую обработку последовательно if( (end - start ) < seqThreshold} { } 11 преобразовать значе ние каждого элемента массива 11 в его квадратный корень for(int i = start; i < end; i++} { data [i] = Math .sqrt (data [i] }; else { // в противном случае продолжи ть разделение данных на 11 ме ньшие части 11 найти середину int middle = (start + end} / 2; // запустить новые подзадачи на выполнение , исполь зуя 11 разделенные на части данные invo keAll (new SqrtTrans form (data, start, mi ddle} , new SqrtTrans form (data, middle , end) };
10'8 Часть 11. Библиотека Java 11 продемон стрировать параллельное выполнение class For kJoiлDemo { puЫ ic static void main (Striлg args[] ) { 11 созда ть пул задач Fo rkJo iлPool fjp = леw ForkJoiлPool (); douЬle [] лшns = леw douЬle [lOOOOO] ; 11 присвоить некоторые значения for (int i =О; i < лшns . leлgth; i++) лums [i] = (douЬle) i; System.out . priлtln ( "Чacть исходной последователь ности :"); for (int i=O; i < 10; i++) System. out .print (nums [i] +"") ; System. out . println ("\n ") ; SqrtTransform task = леw Sqrtтraлsform ( лums , О, лums .leлgth) ; 11 запус тить главную задачу типа ForkJoinTask на выполнение fjp . iлvoke (tas k) ; System. o ut . priлtln ("Чacть преобразованной последовательнос'l'и " + "(с точностью до четырех знаков после десятичной точки ) :") ; for(int i=O; i < 10; i++) System. o ut . format ("%.4f ", лums[i ]); Sys tem.out . printlл (); Ниже приведен результат, выводимый данной программой . Как можно заме­ тить, значения элементов массива бьuш преобразованы в их квадратные корни. Часть исходной последовательности : о.о 1.0 2.0 3.0 4.0 5.0 6.0 7.0 в.о 9.0 Часть преобразованной последователь ности (с точностью до че тырех знаков после десятичной точки ) : 0.0000 1.0000 1.4142 1.7321 2.0000 2 .2361 2.4495 2.6458 2.8284 3.0000 Рассмотрим данную программу подробнее. Прежде всего обратите внимание на то, что класс SqrtTrans form расширяет класс Re cursiveAction. Как упоми­ налось ранее , класс Recur siveAction расширяет класс Fo rkJoinTask для выпол­ нения задач, которые не возвращают результаты . Затем обратите внимание на ко­ нечную переменную seqThreshold. Ее значение определяет, когда будет иметь место последовательная обработка. Это значение устанавливается (несколько про извольно) равным 1000. Далее обратите внимание на то , что ссылка на обраба­ тываемый массив сохраняется в переменной data и что поля start и end служат для указания границ доступности элементов массива. Основное действие программы происходит в методе compute (). Оно начина­ ется с проверки соответствия количества обрабатываемых элементов нижнему пороговому значению для последовательной обработки. Если такое соответствие обнаружено , то эти элементы обрабатываются (в данном примере из их значе-
Гл ава 28. Утилит1:�1 пар аллелизма ний извлекается квадратный корень) . А если пороговое значение для последова­ тельной обработки не достигнуго, то вызывается метод invo keAl l () для запуска на выполнение двух новых подзадач. В данном случае в каждой подзадаче обраба­ тывается половина элементов массива. Как пояснялось ранее, метод invo keAl l ( ) ожидает возврата результатов завершения обеих подзадач. По завершении всех рекурс ивных вызовов каждый элемент в массиве будет изменен, причем бальшая часть рассматриваемого здесь действия выполняется параллельно, при усло вии , что в системе имеется несколько процессоров. Как упоминалось ранее, начиная с версии JDК 8 строить пул типа Fo rkJo in Pool явным образом совсем не обязательно, поскольку для этой цели автома­ тически доступен общий пул. Кроме того , пользоваться общим пулом намного проще. В частности , вызвав стати ческий метод common Pool ( ) , определяемый в классе Fo rkJoinPool, можно получить ссылку на общий пул. Следовательно, приведенный выше пример программы можно переделать, чтобы воспользо­ ваться общим пулом, заменив вызов конструктора класса Fo rkJo inPool на вы­ зов метода coпunonPool ( ) : ForkJoinPool fjp = ForkJoinPool . comrnon Pool () ; С другой стороны, получать явным образом ссылку на общий пул нет нужды, поскольку вызов метода invoke ( ) или fork () из класса Fo rkJoinTaskдля задачи, которая уже не является частью пула, приведет к тому, что она будет автомати­ чески выполнена в общем пуле. Та к, из предьщущего примера программы можно полностью исключить переменную fj p и запустить задачу на выполнение, вос­ пользовавшись следующей строкой кода: task. invo ke () ; Как упоминалось выше, общий пул относится к усовершенствованиям, внесен­ ным в каркас Fork/Join Framework в версииJDК 8 с целью упростить его примене­ ние. Более того , применение общего пула оказывается зачастую более предпочти­ тельным, есл и принять во вниман ие, что для этого не требуется совместимость с версиейJDК 7. Влияние уровня параллел изма Прежде чем завершить рассматриваемую здесь тему, важно рассмотреть вли­ яние уровня параллелизма на эффективность выполнения задачи вилочного со­ еди нения, а также взаимосвязь между параллелизмом и пороговым значением. Пример программы, приведенный в этом разделе, позволяет экспериментиро­ вать с различными уровнями параллелизма и пороговыми значениями. Ис пол ьзуя многоядерный компьютер, можно наблюдать в интерактивном режиме результат изменения этих значений. В предыдущем примере использовался уровень параллелизма по умолчанию. Но ничто не мешает задать требующийся уровень параллелизма. Это можно, в частности , сделать, создав объект типа Fo rkJo inPool с помощью следующего конструктора: ForkJoinPool (int ypoвeнi._napaл л emrswa )
1050 Часть 11. Библ иоте ка Java где параметр уров ень_ параллелизма обозначает усганав.ливаемый уровень парал л е­ лизма. Его значение должно бьпъ больше нуля, но меньше предела реализации. В Приведенном ниже примере программы формируется задача вилочного со­ единения, в ходе которой преобразуется массив типа douЫ e. Преобразование вы· полняется произвольно, но оно организовано таким образом, чтобы потреблять несколько циклов процессора. Это сделано для большей наглядности эффекта от изменения порогового значения ил и уровня парал л елизма. Запуская програм· му на выполнение, следует указать пороговое значение и уровень параллелизма в командной строке. В итоге программа запустит задачи на выполнение, отобра· жая время , расходуемое на выполнение каждой задачи. Для этой цели вызывает­ ся метод System . nanoTime (), возвращающий значение высокоточного таймера виртуальной машиныJVМ. 11 Простой пример программы, позволяющий экспериме нтировать с 11 эффектом от изменения порогового значения и уровня параллелизма 11 выполнения задач в кл ассе ForkJoinTask import java.util . concurrent .*; 11 Кл асс ForkJoinTask преобразует (через класс RecursiveAction ) 11 элементы ма ссива типа douЬle class Trans form extends RecursiveAction { 11 Порог последовательного выполнения, 11 устанавливаемый конструктором int seqThreshold ; 11 Обрабатываемый ма ссив douЫe [] data; 11 определи ть часть обрабатыв аемых данных int start, end; Transform (douЬle[] vals, iпt s, int е, int t ) { data = val s; start = s; end=е; seqThreshold t; 11 Этот ме тод выполн яет параллел ьное вычисление protected vo id compu te () { 11 выполнить далее обработку последователь но, 11 если количество элементов ниже порогового значения if ( (end - start ) < seqThreshold) { 11 В следующем фр агменте кода элементу по четному 11 индексу присваивается квадратный корень его 11 первоначаль ного значения, а элементу по нечетному // индексу - кубич еский корень его первоначаль ного значения . 11 Этот код предназначен только дл я потребления времени ЦП , 11 чтобы сделать нагляднее эффект от параллельного выполнения for(int i = start; i < end; i++) { if((data[i] % 2) == 0) data [i] Math. sqrt {data [i] ); else data [i] Math . cbrt {data [i] );
Гл ава 28. Утилить1 параллел изм а else { // В противном случае продолжить разделение данных на 11 ме ньшие части 1 1 найти середину int middle = (start + end) / 2; // запус тить новые подзадачи на выполнение , // исполь зуя разделенные на части данные invo keAll (new Trans form (data, start , mi ddl e, seqThreshold ), new Trans form (data , middl e, end , seqThreshold) ); 11 Продемонстрировать параллельное выполнение class FJExpe riment { puЫic static vo id rna in (String args[] ) { int pLevel; int threshold; if(args . length != 2) { Systern. o ut . println ( "Исполь зование : FJExpe rirnent параллелизм порог ") ; return; pLeve l = Integer.parseint (args [O] ); threshold z Integer .parseint ( args [l] ); // Эти переме нные исполь зуются для измерения // времени выполнения задачи long beginT , endT ; 11 Создать пул задач . // Обратите внимание на установку уровня паралл елизма ForkJoinPool fjp = new Fo rkJoinPool (pLevel) ; douЬle [] nurns = new douЬle [lOOOOOO] ; for(int i = О; i < nums .length; i++) nurns [i] = (douЬl e) i; Trans form task = new Trans form ( nums , О, nurns .length, threshold) ; 11 начать измерение времени выполнения задачи beginT = System.nanoTime () ; 11 запустить главную задачу типа ForkJoinTask fjp.invoke (task); 1 1 завершить измерение времени выполнения задачи endT = System.nanoTime () ; System. o ut . println ( "Ypoвeнь параллелизма : "+pLevel ); 1051
1052 Часть 11. Библиотека Java System.out . println ( "Порог последователь ной обработки : "+threshold); System.out . println ( "Истекшее время : "+ (endT - beginT ) +"нс ") ; System.out . println () ; Чтобы воспользоваться данной программой, следует указать уровень паралле­ лизма и порог. Разные значения каждого из этих параметров можно опробовать экспериментально, наблюдая за результатами. Не следует, однако, забывать, что данную программу нужно запускать на компьютере, по крайней мере, с двумя про­ цессорами. И даже выполнение этой программы два раза подряд на одном и том же компьютере скорее всего приведет к разным результатам из-за наличия в систе­ ме других процессов, потребляющих время ЦП. Чтобы получить некоторое представление о вл иянии парал л елизма на про­ изводительность программы, проделайте такой эксперимент. Сначала запустите программу по следующей команде : java FJExperillent 1 1000 По этой команде устанавливается уровень параллел изма 1 (по существу, после­ довательное выполнение) и пороговое значение 1000. Ниже приведен пример­ ный результат выполнения данной программы на двухъядерном компьютере. Уров ень параллелизма : 1 Порог последовательной обработки : 1000 Истекшее время : 259677487 нс А теперь укажите уровень параллелизма 2 в следующей команде: java FJExperillent 2 1000 Ниже приведен примерный результат выполнения данной программы на том же самом двухъядерном компьютере. Уров ень параллелизма : 2 Порог последовательной обработки : 1000 Истекшее время : 1692 54472нс Совершенно очевидно, что благодаря параллелизму значительно уменьшает­ ся время выполнения, а следовательно, повышается быстродействие программы. Поэкспериментируйте с изменением порога последовательной обработки и уров­ ня параллел изма на своем компьютере. Полученные результаты могут уд ивить вас . Имеются еще два метода, которые могут оказаться полезными для экс пери­ ментирования с временными характеристиками выполнения программы, демон­ стрирующей вилочное соединение. Во-первых, вызвав метод ge tParallelism (), определяемый в классе Fo rkJoinPoo l, можно получить ур овень параллелизма. Ниже приведена общая форма этого метода. int getParallelisш () Этот метод возвращает текущий уровень параллелизма. Напомним, что по умолчанию он будет равен количеству доступных процессоров. (Чтобы по­ лучить уровень параллелизма для общего пула, можно также вызвать метод get
Гл ава 28. Утилиты параллелизма 1053 ComrnonPoolParallelism (), внедренный в версииJDК 8.) И во-вторых, вызвав ме­ тод availaЫe Processors ( ) , определяемый в классе Runt ime , можно получить количество процессоров, доступных в системе. Ниже приведена общая форма это­ го метода. В связи с наличием других системных запросов возвращаемое значение может отличаться после каждого вызова этого метода. int availaЬleProces sors () П ример п рименения кл а сса Re cur siveTask<V> Два приведенных выше примера основаны на классе RecursiveAction, а это означает, что в них одновременно выполняются задач и, которые не возвраща­ ют результаты . Чтобы сформировать задачу, возвращающую результат, следует воспользоваться классом RecursiveTask. В общем, параллельное выполнение задачи, возвращающей результат, организуется так, как было показано выше. Основное отличие состоит в том , что метод compute () возвращает результат. Поэтому нужно накопить результаты , чтобы по завершении первого вызова был возвращен общий результат. Другое отличие состоит в то м, что запуск подзадачи на выполнение обычно делается явно путем вызова метода fоrk ( ) или j оin ( ) , а не неявно путем вызова, например, метода invo keAll (). В приведенном ниже примере программы демонстрируется применение клас­ са Re cur siveTa sk. В этой программе формируется задача Sum, возвращающая сум­ му значений элементов в массиве типа douЫe. В данном примере массив состоит из 5000 элементов. Но значение каждого второго элемента этого массива отрица­ те льное. Та ким образом, первые элементы данного массива будут иметь значения О, - 1, 2, -3, 4 и т.д . (Для совместимости данной программы с версиямиJDК 7 иJDK 8 в ней создается свой пул. В качестве упражнения можете попробовать воспользо­ ваться общим пуло м.) 11 Простой пример применения кл асса RecursiveTask<V> irnport java.util . coпcurreпt .*; 11 кл асс RecursiveTask, исполь зуемый для вычи сления суымы 11 значе ний элементов в ма ссиве типа douЫe class Surn extends Recurs iveTask<DouЫ e> { 11 Пороговое значение последовательного выполнения final int seqThresHold = 500; 11 Обрабатыв аемый ма ссив douЬle [] da ta; 11 определить часть обрабатываемых данных int start , end; Surn(douЬle[] vals, int s, int е ) { data = vals; start = s; end=е; 11 определить сумму значе ний элементов в ма ссиве типа douЬle
Часть 11. Библиоте ка Java protected DouЫe compute () douЫe sum = О; // Если количество элементов ниже порогового значения , 11 то выполнить далее обработку последовательно if ( (end - start ) < seqT hresHold) { ) 11 суммир овать значения элементов в ма ссиве типа douЫe for(int i = start; i < end; i++) sum += data[i]; else { } // В противном случае продолжи ть разделение данных на 11 ме ньшие части 11 найти середину int middle = (start + end) / 2; // запустить новые подзадачи на выполнение , используя // разделенные на части данные Sum subTa skA new Sum (data, start, middle) ; Sum subTa skB new Sum (data, middl e, end) ; // запустить каждую подзадачу путем разве твления subTaskA. fork(); subTaskB. fork() ; // ожидать завершения подзадач и накопить ре зуль таты sum = subTa skA.joi n() + subTas kB. joi n() ; 11 возвратить конечную сумму return sum; // Продемонстрировать параллельное выполне ние class Re curTaskDemo { puЫic static void main (Striпg args[] ) { 11 создать пул задач Fo rkJoinPool fjp = new For kJoinPool () ; douЬle [] nums = new douЬle [5000] ; 11 инициализировать массив nUID8 чередующими ся // положительными и отрица тельными значениями for( int i=O; i < nums .length; i++) nums [i] = (douЬle) (((i%2) == 0) ? i:-i) Sum task = new Sum ( nums , О, nums .length) ; // Запус тить задачи типа ForkJoinTaak. Обратите 11 внимание на то, что в данном случае ме тод invoke () // возвращает ре зуль тат douЫ e summation = fj p.invoke (task) ; System. out . println ("Cyм ми poвaниe " + summation ); Эта программа выводит следующий результат: Суммир ование -2500 .0
Гл ава 28. Утилиты пар алл елизма 1055 У данной программы имеются некоторые интересные особенности. Прежде всего обратите внимание на то , что для выполнения обеих подзадач вызывается метод fork (): subTa skA. fork() ; subTa skB.fork() ; В данном случае метод fork () используется потому, что он запускает подзадачу на выполнение, но не ожидает ее завершения . (Таким образом, он запускает подза­ дачу на выполнение ас инхронно.) А для получения результата выполнения каждой подзадачи вызывается метод join (),как показано ниже. sum = subTaskA.joi n() + subTaskB.joi n() ; В данной строке кода ожидается завершение каждой подзадачи, а затем полу­ ченные результаты суммируются и присваиваются переменной sum. Та ким обра­ зом , результаты выполнения всех подзадач складываются в вычисляемую итого­ вую суммы. И наконец, метод comp u te ( ) завершается, возвращая значение пере­ менной sum, которое станет окончательной итоговой суммой после возврата из первого вызова. Имеются и другие способы асинхронного выполнения подзадач. Например, в следующем фрагменте кода метод fo rk () вызывается для запуска подзадачи subT askA , а метод invo ke () - для запуска и ожидания завершения подзадачи subTas kB: subTa skA. fork() ; sum = subTa skA.joi n() + subTa skB . invoke (); В качестве еще одного варианта можно непосредственно вызвать метод com­ pu te () для подзадачи subTaskB, как показано ниже. subTaskA. fork () ; sum = subTaskA. joi n() + subTaskB.compute (); Асинхронное вып олнение задач Для инициализации задачи в приведенных ранее примерах программ вызвался метод invoke () из класса Fo rkJoinPool. Это общепринятый подход, когда вы­ зывающий поток исполнения должен ожидать завершения задачи (что зачастую и бывает) , поскольку метод invoke () не завершится до тех пор, пока не завершит­ ся задача. Но задачу можно запустить на выполнение асинхронно. При таком под­ ходе вызывающий поток продолжает выполняться. Та ким образом, вызывающий поток и задача выпол няются одновременно. Чтобы запустить задачу на выполне­ ние асинхронно, следует вызвать метод execute (),который также определяется в классе Fo rkJoinPool. Ниже приведены две его общие формы. void execute (ForkJoinTask< ?> saдa va ) void execute (Run n aЫe saдa va ) В обеих формах этого метода задается выполняемая зада ча . Обратите вни­ мание на то , что вторая форма позволяет определить задачу типа RunnaЬle, а не Fo rkJoinTask. Этим наводится своего рода мост между традиционным подходом к многопоточности в Java и новым каркасом Fork/Join Framework. Следует, одна-
1056 Часть 11. Библ иотека Java ко , иметь в виду, что потоки исполнения , используемые пулом типа Fo rkJo inPool, являются потоковыми демонами . Следовательно, они завершаются по окончании основного потока исполнения. Это означает, что основной поток исполнения, возможно, придется поддерживать в активном состоянии до тех пор, пока не за­ вершатся все задач и. Отмена задачи Вызвав метод cancel (), определенный в классе For kJoinTask, можно отме­ нить задачу. Ниже приведена общая форма этого метода. Ьoolean cancel (boolean прершаашrе) Этот метод возвращает логическое значение true, если задача, для которой он был вызван, успешно отменена, или логическое значение false, если задача уже отмене­ на, завершена или не может быть отменена. В настоящее время параметр пр ерыв ание не используется в стандартной реализации. Как правило, метод cance 1 ( ) вызывается из кода за пределами задачи, поскольку задача может легко отменить себя путем воз­ врата. Вызвав метод isCance lled (), можно выясн ить, была ли задача отменена: final Ьoolean isCancelled () Этот метод возвращает логическое значение true, если вызывающая задача была отменена до ее завершения, а иначе -логическое значение false. О предел е ние с остояния заверш ения з адачи Кроме только что описанного метода isCance lled (),класс Fo rkJo inTask со­ держит два других метода, позволяющих определить состояние завершения зада­ чи. Первым из них является метод isCompletedNormally (), общая форма кото­ рого приведена ниже. final boolean isCoшpleteclNo.rmally () Этот метод возвращает логическое значение true , если вызывающая задача завершилась нормально, т. е. не сгенерировала исключение, и не была отменена в результате вызова метода cancel (),аиначе - логическое значение false. Вторым является метод isCompletedAb normally (), общая форма которого приведена ниже. final boolean isC011 1p letedAЬno.rmally () Этот метод возвращает логическое значение true, если вызывающая задача за­ вершилась не нормально, а вследствие отмены или генерирования исключения. В противном случае возвращается логическое значение false. П ереза пус к задачи Обычно перезапустить задачу нельзя . Иными словами, как только задача завер­ шится, она не может быть перезапущена. Те м не менее после завершения задачи
Гл ава 28. Утилиты параллелиэма 1057 ее состояние можно еще раз инициализировать таким образом, чтобы снова за­ пустить ее на выполнение. Для этого достаточно вызвать метод reinitialize ( ) , как показано далее. void reinitialize () Этот метод устанавливает вызывающую задачу в исходное состояние. Но любые изменения, внесенные в какие угодно данные постоянного хранения, обрабатывае­ мые в задаче, не будут отменены. Так , если в задаче изменяется массив, это измене­ ние не будет отменено в результате вызова метода reinitialize (). Предмет дальнейшего изучения Выше бьши затронуты лишь самые основы каркаса Fork/Join Framework и наи­ более употребительные методы. Но Fork/Join Framework - это функционально насыщенный каркас, предоставляющий дополнительные возможности для расши­ ренного управления параллельностью. Обсуждение всех вопросов и особенностей параллельного программирования и применения каркаса Fork/Join Framework выходит за рамки данной книги , тем не менее ниже рассматриваются другие сред­ ства, избранные из этого каркаса. Другие избранные средства из класса ForkJoinTask Как упоминалось выше, такие методы, как invokeAll () и fork (), могут быть вызваны только из класса Fo rkJoinTask. Это правило особенно важно для вер­ сии JDК 7, где общий пул не поддерживается. Зачастую соблюсти это правило не­ трудно, но иногда приходится иметь дело с кодом, способным выполняться как в самой задаче, так и за ее пределами. Поэтому, вызвав метод inForkJoinPool ( ) , можно выяснить, выполняется ли код в пределах задач и. С помощью метода adapt (), определяемого в классе Fo rkJoinTask, можно преобразовать объект типа RunnaЫe или CallaЫe в объект типа For kJoinTask. У этого метода имеются три общие формы: одна - для преобразования объекта типа CallaЬle, другая - для объекта типа RunnaЬle, не возвращающего результат; и третья - для объекта типа RunnaЬle, возвращающего результат. Если это объект типа CallaЫe, то выполняется метод call (),аесли это объект типа RunnaЫe - методrun(). Вызвав метод ge tQueuedTa skCount (), можно получить приблизительное количество задач в очереди вызывающего потока исполнения, а вызвав метод getSurplusQueuedTaskCount () -количество таких задач, превышающее ко­ личество друтих потоков исполнения в пуле , которые могли бы перехватить их. Напомним, перехват работы (в данном случае задачи) в каркасе Fo rk/Join Framework - это единственный способ добиться высокой эффективности. И хотя этот процесс происходит автоматически, иногда сведения о нем могут оказаться полезными для оптимизации производительности . В классе Fo rkJo inTask определяются приведенные ниже варианты методов join () и invoke (), имена кото рых начинаются с префикса quietly. По суще­ ству, эти методы подобны своим упрощенным аналогам , за исключением того, что они не возвращают значений и не генерируют исключений.
1058 Часть 11. Б иблиоте ка Java final void quietlyJoin () Соединяет задачу, но не воз вращает ре зультат и не ге­ нерирует исключение final void quietlyinvoke О Вызывает задачу, но не возвращает результат и не гене­ рирует исключение Вызвав метод tryUn fo rk ( ) , можно попытаться "отменить вызов" задачи. Иными словами , исключить ее из плана выполнения. В версииJDК 8 было внедрено несколько методов, поддерживающих дескрип· то ры, в том числе методы getForkJoinTas kTag ( ) и setForkJoinTas kTag (). Дес крипторы предстамяют собой короткие целочисленные значения , связанные с задачей. Они могут оказаться полезными в особых случаях. Класс ForkJoinTas k реализует интерфейс SerializaЫe. Та ким образом, его объект может быть сериализирован. Но сериализация не употребляется во время выполнения . Другие изб ранные средства из класса ForkJoinPool К числу методов, которые оказываются весьма полезными для настройки при· ложений вилочных соединений, относится метод toString ( ) , переопределя· емый в классе Fo rkJoinPool. Этот метод выводит в уд обной для пользователя форме отчет о состоянии пула. Чтоб ы опробовать его на практике, введите приве· денный ниже фрагмент кода для запуска и последующего ожидания задачи в класс FJExperiment из представленного ранее примера программы экспериментирова· ния с задачами. 11 Запустить главную задачу типа ForkJoinTask асинхронно fj p. execute (task) ; 11 отобразить состояние пула во время ожидания wh ile (!tas k. isDone () ) { System.out . println (fjp) ; Запустив эту программу на выполнение, вы увидите на экране ряд сообщений, описывающих состояние пула. Ниже приведен один из примеров такого вывода. Безусловно, у вас вывод может быть другим из-за отличий в количестве процессо­ ров, пороговых значений, загруже нности задачами и т.д . java.util . concurrent . ForkJoinPool@141d683 [Running , parallelism = 2, size = 2, active = О, running = 2, steals = О, tasks = О, submissions = 1] Вызвав метод isQuiescent (), можно выяснить, не бездействует ли пул в насто­ ящий момент. Эrот метод возвращает логическое значение true, если в пуле отсуг­ ствуют активные потоки, а иначе - логическое значение false. Вызвав метод get PoolSize (),можно получить количество рабочих потоков исполнения, находящих· ся в настоящий момент в пуле, а вызвав вызов метод getActiveThreadCount () - приблизительное количество активных потоков исполнения в пуле. Чтобы закрыть пул , следует вызвать метод shutdown (). Те кущие задачи все еще будут выполняться , но никаких новых задач запущено не будет. Чтобы закрыть пул немедленно, нужно вызвать метод shutdownNow (). В данном случае делается попытка отменить текущие задачи . Следует, однако , иметь в виду, что ни один из
Гл ава 28. Ут илиты параллел изм а 1059 упоминаемых здесь методов не оказывает влияния на общий пул. Вызвав метод isShutdown (), можно выяснить, закрыт ли пул . Этот метод возвращает логиче­ ское значение true, если пул закрыт, а иначе - логическое значение false. И на­ ко нец, чтобы выяснить, закрыт ли пул и все ли задачи завершены, следует вызвать метод isTerminated (). Рекомендации относительно випочноrо соединения В этом разделе даются некоторые рекомендации, помогающие обойти наиболее трудные препятствия, связанные с применением каркаса Fork/Join Framework. Во­ первых, старайтесь не указывать слишком низкое пороговое значение для последо­ вательного выполнения задач. Обычно лучше ошибиться в бальшую, чем в меньшую сторону. Если пороговое значение слишком мало, на формирование и переключе­ ние задач может уйти времени больше, чем на их обработку. Во-вторых, обычно лучше выбирать уровень парал л елизма, устанавливаемый по умолчан ию. Если же указать меньший уровень парал л елизма, это может в значительной степени свести на нет все преимущества, которые дает применение каркаса Fork/Join Framework. Обычно в задаче типа Fo rkJoinTa sk не должны применяться синхронизи­ рован ные методы или блоки кода. Кроме того, метод compu te ( ) обычно при­ меняется вместе с други ми средствами синхронизации, например семафорами. (Тем не менее можно воспользоваться новым классом Phaser, поскольку он со­ вместим с механизмом вилочного соеди нения .) Напомним, что в основу класса Fo rkJo inTask положена стратегия "разделяй и властвуй". Но такой подход обыч­ но не применяется в тех случаях, когда требуется внешняя синхронизация . Кроме того, старайтесь избегать ситуаций, когда ввод-вывод может привести к длитель­ ной блокировке. В подобных случаях класс ForkJoinTask обычно не обслуживает ввод-вывод. Проще говоря, задача должна выполнять вычисление, которое может быть организовано без внешней блокировки ил и синхронизации. Это позволит лучше воспользоваться преимуществами каркаса Fork/Join Framework. И последнее замечание: за исключением необычных обстоятельств не делайте никаких предположе ний относительно среды выполнения, в которой будет рабо­ тать написанный вами прикладной код. Это означает, что вы не должны предпо­ лагать, что будет доступно конкретное количество процессоров или что на харак­ теристики выполнения вашей прикладной программы не будут оказывать вл ияние другие одновременно выполняющиеся процессы. Утилиты параллелизма в сравнении с традиц ионным подходом к многозадачности в Java Принимая во внимание эффективность и гибкость утилит параллелизма, естественно задаться следующим вопросом: заменяют ли они собой традици­ онный подход в многозадачности, принятый в Java? Безусловно, не заменяют!
1060 Ч асть 11. Б и бл иотека Java Первоначальная подцержка многозадачности и встроенные средства синхро­ низации по-прежнему должны применяться во многих прикладных программах нa java , аплетах и сервлетах. Например, ключевое слово synchroni z ed, а также методы wa i t () и notify () предоставляют изящные решения широкого ряда за­ дач многопоточной обработки . Но когда требуется дополнительное упрамение потоками исполнения, на помощь приходят утилиты параллелизма. Кроме того, в каркасе Fork/Join Framework предостамяется эффективный способ , позволяю­ щий интегрировать методики параллельного про граммирования в более сложные прикладные программы.
29 Потоковый API Среди многих новых средств, внедренных в версии JDK 8, особое место, безус­ ловно , принадлежит лямбда-выражениям и потоковому прикладному программно­ му интерфейсу API. Лямбда-выражения бьши описаны в гл аве 15, а в этой гл аве рас­ сматривается потоковый API. Как станет ясно из материала этой гл авы, потоковый API разработан с учетом лямбда-выражений. Более того, потоковый API наглядно демонстрирует, насколько лямбда-выражения повышают эффективностьJava. Несмотря на впечатл яющую совместимость потокового API с лямбда-выраже­ ниями, гл авной его особенностью является способность выполнять очень слож­ ные операции поиска, фильтрации, преобразования и иного манипул ирования данными. Используя потоковый API, можно, например, сформировать последо­ вател ьности действий, принципиально подобных запросам базы данных, состав­ ляемых на языке SQL. Более того, подобные действия, как правило, выполняются парал л ельно, а следовател ьно, они повышают производительность, особенно при обработке крупных массивов данных. Проще говоря , потоковый API предоставля­ ет мощные средства для обработки данных эффективным, но простым способом. Прежде всего следует заметить, что в потоковом API применяются одни из самых развитых языковых средств Java. Поэтому для полного уяснения и приме­ нения этого прикладного программного интерфейса потребуется твердое знание об общений и лямбда-выражений , а также основных принципов параллельного выполнения и применения каркаса коллекций Collections Framework (под робнее об этом см. в гл авах 14, 15, 18 и 28 соответственно) . Ос новные пол ожения о пото ках данных Прежде всего, само понятие потак даннъtх в потоковом API определяется как канал передачи данных. Следовательно, поток данных представляет собой после­ довател ьность объектов. Поток данных оперирует источником данных, например массивом или коллекцией. В самом потоке данные не хранятся, а только переме­ щаются и, возможно, фильтруются, сортируются или обрабатываются иным об­ разом в ходе этого процесса. Но, как правило , действие самого потока данных не видо изменяет их источник. Например, сортировка данных в потоке не изменяет их упорядочение в источнике, а скорее приводит к созданию нового потока дан­ ных, дающего отсортированный результат.
1062 Часть 11. Библиоте ка Java На замепсуl Следует особо подчеркнуrь, что употребляемое здесь понятие потокданных отличает· ся от аналогичного понятия, употребл явшегося при описании классов ввода-вывода ранее в да нной книге. Несмотря на то что обычные потоки ввода-вывода мoryr в принципе действо· вать почти так же, ка к и потоки да нных, определяемые в пакете j ava . u t il . st ream, они не оди наковы. Поэтому под термином поток данных, употребляемым в этой гл аве, подразуме­ ваются объекты, основывающиеся на одном из описываемых здесь типов потоков. Потоковые интерфейсы В потоковом API определяется ряд потоковых интерфейсов, входя щих в со­ став пакета java . util . stream. В основание их иерархии положен интерфейс Ba seStream, в котором определяются основные функциональные возможности всех потоков данных. Интерфейс BaseStream является обобщенным и определя· ется следующим образом: inte�ace BaseStreaa<T , S extends BaseStreaa<T , S>> где параметр Т обозначает тип элементов в потоке дан ных, а параметр S - тип по­ тока данных, расширяющего интерфейс BaseStream. В свою очередь, интерфейс BaseStream расширяет интерфейс Au toCloseaЬle, а следовательно, потоком дан ных можно управлять в блоке оператора try с ресурсами. Но, как правило, за­ крывать приходится только те потоки данных, где источники данных требуют за­ крытия (например, те потоки , которые связаны с файлами). В то же время потоки , источн иками данных для которых обычно служат коллекции, закрывать не нужно. Методы, объявляемые в интерфейсе BaseStream, перечислены в табл. 29. 1 . Та блица 29.1. Методы иэ инте рфейса вaseStreaш. Метод void close () Ьoolean isParallel О Iterator<Т> iterator () S onCloae ( Run n .Ьle об})4б0!1'-.1:) S parallel () S sequential () Описание Закрывает вызывающий поток данных, вызывая люб ые зареги­ стрированные обработчики событий закрытия. (Как пояснялось ранее, закрывать требуется лиш ь немногие потоки данных. ) Возвращает логическое значение true, есл и вызывающий поток данных является парал л ельным, ил и логическое значение fal ae, есл и он является последовательным Получает итератор для потока дан ных и возвращает ссылку на н его. (Это оконе чная операция.) Возвращает новый поток данных с указан ным оСSJ)4бо•-а:он событий закрытия. Ук азанн ый oCSJ)4бo!l'v•a: вызывается при за­ крытии потока данных. (Это промежуrочная операция.) Возвращает парал л ельный поток данных, исходя из вызывающе­ го потока данн ых. Если вызывающий поток дан ных уже является п арал л ельн ым, то именно он и возвращается. (Это промежуrоч­ ная операция.) Возвращает последовательный п оток данных, исходя из вызы­ вающего потока данных. Если вызывающий поток данных уже является последовательным, то именно он и возвращается. (Это промежуrочная операция.)
Глава 29. Потоковый API 1063 Ок1тчанштаб!&. 29.1 Метод Описание Spliterator<T> spli terator ( ) Получает итератор-разделитель для потока данных и возвращает ссылку на него. (Это окон ечная операция .) S unorciered ( ) Возвращает неупорядоченный поток данных, исходя из вызы· вающего потока дан ных. Если вызывающий поток данных уже является неупорядоченным, то именно он и возвращается. (Это промежуточная операция .) Производными от интерфейса BaseStream являются несколько типов интер· фейсов. Наиболее употребительным из них является интерфейс Stream. Он объ· является следующим образом: interface Streaпi<T> где параметр Т обозначает тип элементов в потоке данных. Интерфейс Stream является обобщенным и поэтому пригоден для всех ссылочных типов. Помимо ме· тодо в, наследуемых из интерфейса BaseStream, в интерфейсе Stream определя· ются собственные методы. Некоторые из них перечислены в табл. 29.2. Табл ица 29.2. Иэбраннь1 е методы из инте рфейса stream Метод <R, А> R co11ect (Co11ector<? super т, А, R> Ф.УВхцх. _ вахоп.п.. ._. ) lonq count () Streaia<т> filter ( Predicate<? super Т> пред•х а!I') void forEach (ConsWl l8 r<? super Т> дuc!I'-•) <R> Streaia<R> 11 1& p(Function<? super т, ? extends R> ф,увхцв• _ О!l'Осfражеюrж) Описание Накапливает элементы в изменяемом контей· нере и возвращает этот контейнер. Называется операцией изменяемого сведения. Здесь R обо­ значает тип результирующего контейнера; Т - тип элемента из вызывающего потока данных, тогда как А - внутренний накапливаемый тип. Указанная фуахцв• вахопnев.• обозначает порядок выполнен� процесса накопления (Это оконечная операция .) Подсчитывает количество элементов в потоке дан ных и возвращает полученный результат. (Это оконечная операция .) Производит поток дан ных, содержащий те эле· менты из вызывающего потока данных, которые удовлетворяют указанному npeд•xa!l';y ( Это п ро­ межуточная операция .) Выполняет код, обозначаемый указанным дeJic!l'-ex, для каждого элемента из вызываю· щего потока дан ных. (Это оконечная операция. ) Применяет указанную ф;у.нхц�rю _ о!l'обр.. .. .._. к элементам из вызывающего потока данных , производя новый поток данных , содержащий эти эл ементы. (Это промежуточная операция.)
1064 Ч асть 11. Библиоте ка Java Метод DouЬleStream mapToDouЫe ( ToDouЬlel!'unction<? super Т> фув.1щ1u_о!l.'оdр.. .. ._. . ) IntS tream mapT oint (TointFunction<? super Т> ф,yx.1щ• • _ o !l.'oбpUL'81Ur•) LongStream mapТoLong ( ToLongl!'unction<? super Т> фув а:ц.•_0!1.'Ображев.•) Optional<T> шах( C01 11p arator<? super Т> a:oNDapa !l.'op) Optional<T> ll li n(Co11 1p arator<? super Т> a:ONDapa!l'op) т reduce (T identityVal , BinaryOperator<Т> каа:оп1r!l.'еп.) Stream<T> sorted () Ob ject [] toArray () Окончанш таМ. 29. 2 Описание Применяет указанную ф,уиа:ЦIU)_о!l.'обр.. .. ._. к элементам из вызывающего потока дан- ных, производя новый поток данных типа DouЬleStreua, содержащий эти элементы. (Это промежуточная операция.) Применяет указанную ф,ува:ц.ю_О!l.'ображеш к элементам из вызывающего потока да нных, производя новый поток данных типа IntStream, содержащий эти элементы. (Это промежуточная операция.) Применяет указанную фува:ЦIU)_О!I.'� к элементам из вызывающего потока дан- ных, производя новый поток данных типа LongStreaa, содержащий эти элементы. (Это промежуточная операция.) Обнаруживает и возвращает максимальный элемент в вызывающем потоке данных, исполь­ зуя упорядочение, определяемое указан ным zoNDapa!l.'opoм. (Это оконечная операция.) Обнаруживает и возвращает минимальный элемент в вызывающем потоке дан ных, исполь­ зуя упорядочение, определяемое указанным a:ONDapa!l.'opoм. (Это оконечная операция.) Возвращает результат, исходя из элементов в вы­ зывающем потоке дан ных. Называется операци­ ей сведt'НИЯ (Это оконечная операция. ) Производит новый поток дан ных, содержащий элементы из вызывающего потока данных, отсо­ ртированные в естественном порядке (Это про­ межуточная операция .) Создает массив из элементов в вызывающем по­ токе дан ных (Это оконечная операция.) Обратите внимание на то , что в обеих приведенных выше таблицах методы обозначены как иканечнь�е или промежутачнш операции. Их отличие состоит в то м, что оконечная операция потребляет поток данных и дает конечный резуль­ тат, например, обнаруживает минимальное значение в потоке данных или выпол­ няет некоторое действие , как это делает метод forEach (). Есл и поток данных потреблен, он не может быть использован повторно. А промежуточная операция производит поток данных и служит для создания канвейера для выполнения после­ довател ьности действий. Кроме того , промежуточные операции не выполняются немедленно. Напроти в, указанное действие происходит в том случае, когда око­ нечная операция выполняется в новом потоке данных, созданном промежуточной операцией. Та кой механ изм называется отложенным поведением, а промежуточные
Глава 29. Потоковый API 1065 операции - отложеи'Нъtмu. Благодаря отложенному поведению потоковый API дей­ ствует более эффективно. Еще одна особенность потоков данных состоит в то м, что одни промежуточ­ ные операции выполняются без сохраштия сосmоя'Ния, а другие - с сохра'Нtтием состоя­ ния. В операции без сохранения состояния каждый элемент обрабатывается неза­ висимо от остальных. А в операции с сохранением состояния обработка элемента может зависеть от особенностей остальных элементов. Например, сохранение данных является операцией с сохранением состояния, поскольку упорядочение элемента зависит от значений других элементов. Следовательно, метод sorted () выполняется с сохранением состояния. Но фильтрация элементов на основании предиката, не имеющего состояния, выполняется без сохранения состояния, по­ скольку каждый эл емент обрабатывается отдельно. Следовател ьн о, метод fil­ ter () может (и должен) выполняться без сохранения состояния. Отличие опера­ ций с сохранением состояния от операций без сохранения состояния особенно важно для параллельной обработки потоков данных, поскольку операцию с со­ хранением состояния, возможно, придется выполнить не за один, а за несколько проходов. Интерфейс Stream оперирует ссылками на объекты , и поэтому он не может обращаться непосредственно к примити вным типам данных. Для обработки по­ токов примитивных типов данных в потоковом API определяются следующие ин­ терфейсы: • DouЫeStream • IntStream • LongS tream Все эти интерфейсы расширяют интерфейс BaseStream и обладают теми же функциональными возможностями, что и интерфейс Stream, за исключением того , что они оперируют примитивными, а не ссылочными типами данных. Они предоставляют также служебные методы, например, boxed ( ) , упрощающие их применение. В этой гл аве основное внимание уделяется интерфейсу Stream, по­ скольку потоки объектов употребляются чаще всего. Хотя аналогичным образом могут быть использован ы и потоки примитивных ти пов данных. П олучение потока да нных Получить поток данных можно самыми разн ыми способами. Вероятно, самый распространенный способ получения потока данных из коллекции. В версии JDK 8 интерфейс Collection дополнен двумя методами, специально предназна­ ченными для получения потока данных из коллекции. Первый из них называется stream (),аего общая форма показана ниже. defaul t Stream<E> stream О В реализации по умолчанию этот метод возвращает последовательный поток дан ных. Второй метод называется parallelStream (),аего общая форма выгля­ дит следующим образом:
1066 Часть 11. Библиоте ка Java default Stream<E> paral lelStreaa () В реализации по ум олчанию этот метод возвращает параллельный поток дан­ ных, если это вообще возможно. А если получить параллельный поток данных нельзя , то вместо него может быть возвращен последовательный поток данных. Параллельные потоки данных поддерживают параллельное выполнение пото­ ковых операций. Благодаря тому что интерфейс Col lection может быть реали­ зован в классе каждой коллекции, с помощью упомянугых выше методов можно получить поток из класса любой коллекции, в том числе ArrayList или Ha shSet. Поток данных можно также получить из массива, вызвав метод stream () , вве­ де нный в класс Arrays , начиная с версии JDК 8. Ниже приведена одна из общих форм этого метода. static <Т> Stream<T> atream (Т [ ] иассжа) Этот метод возвращает последовательный поток данных элементов заданного ма ссива. Та к, если имеется массив addre sses типa Addre ss, то в следующей стро­ ке кода получается поток для его элементов: Stream<Address> addrStrm = Arrays .stream ( addresses) ; Определяется также несколько перегружаемых вариантов метода stream( ), в том числе для обработки массивов примитивных типов данных. Они возвраща­ ют поток данных типа IntStream, DouЬleStream или LongStream. Потоки данных можно получить и целым рядом других способов. Например, многие потоковые операции возвращают новый поток данных, а для ввода дан­ ных из источника такой поток можно получить, вызвав метод lines ( ) для буфе­ ризированного потока чтения типа Bu fferedReade r. Но каким бы образом такой поток ни был получен, им можно воспользоваться таким же образом, как и любым другим потоком данных. П ростой пример потока да нных Прежде чем продолжить дальше, рассмотрим пример, в котором применяют­ ся потоки данных. В приведенной ниже программе сначала создается списочный массив myL i s t типа Arr а yLi s t, содержащий коллекцию целочисленных значений, которые автоматически упаковываются в объекты ссылочного типа Integer. Затем в этой программе получается поток данных, использующий массив myList в каче­ стве источника данных. А далее в ней демонстрируются различные потоковые опе­ рации. 11 Продемонс трировать неско лько потоковых операций import java.util .*; import java.util . stream. *; class StreamDemo { puЫ ic static void ma in (String [] args ) { 11 создать списочный ма ссив значений типа Integer ArrayList<Integer> myLi st = new ArrayList<> ( ); myList . add (7) ; myL ist .add(18) ;
myList . add ( 10) myList .add(24) myList . add ( 17) myList .add(5) ; Глава 29. Потоковый API System.out .println ( "Иcxoдный список : "+myList ); 11 получить поток элементов списочного массива Stream<Integer> myS tream = myList . stream() ; 11 получить минимал ьное и максимальное значения , вызвав 11 методы ll lin (), 11 1&Х () , isPresent О и get () Op tional<Integer> minVal = myS tream .min (Integer ::compare ); if (minVal .isPresent () ) System.out . println ( "Минимальное значение : "+minVal .get () ); 11 непременно получить новый поток данных , посколь ку 11 предыдущий вызов ме тода llin () стал оконечной операцией, 11 употребившей поток данных myS tream = myList.stream() ; Optional<Integer> maxVa l = myStream.max (Integer ::compare) ; if (maxVal .is Present () ) System. o ut . println ( "Максимальное значение : "+maxVal .get () ); 11 отсортировать поток данных , вызвав ме тод aorted () Stream< Integer> sortedStream = myList . stream() .sorted() ; 11 отобразить отсортированный поток данных , вызвав ме тод forEach () System.out .print ("O тcopтиpoвaнный поток данных : ") ; sortedStream. forEach ((n) -> System. out .print (n +" ") ); System. out . println () ; 11 вывести толь ко нечетные целочисленные значения, 11 вызвав метод filter () Stream<Integer> oddVa ls = myList . stream() .sorted () .filter ((n) -> (n % 2) 1); System. out .print ( "Heчe тныe значения : ") ; oddVals . forEach ((n) -> System.out . print (п +" ") ); System.out . println () ; 11 вывести только те нечетные целочисленные значения, 11 которые больше 5. Обратите внимание на конвейеризацию 11 обеих операций филь трации oddVa ls = myList .stream( ) .filter ( (n) -> (n % 2) =• 11 .filter((n) -> n > 5); System . out .print ( "Heчe тныe значения больше 5: ") ; oddVals . forEach ((n) -> System.out .print (n + " ") ); System. o ut . println () ; Ниже приведен результат, выводимый да нной программой . Исходный список : [7, 18, 10, 24 , 17, 5] Минимал ьное значение : 5 Максимальное значение : 24 Отсортированный поток данных : 5 7 10 17 18 24 Нече тные значения : 5 7 17 Нече тные значения боль ше 5: 7 17 1067 Рассмотрим каждую потоковую операцию и з данной программы в отдел ьности. После создания списочного массива типа Ar r а yLi s t в данной программе вызыва­ ется метод stream () для получения потока элементов этого массива:
1068 Часть 11. Библиотека Java Stream<Integer> myS tream = myList . stream( ); Как пояснялось ранее, в интерфейсе Collection те перь определяется метод stream () для получения потока данных из вызывающей коллекции. Благодаря тому что интерфейс Collection реализуется классом каждой коллекции, вызвав метод stream (), можно получить поток данных для коллекции любого типа, в том числе и ArrayList. В данном случае ссылка на получаемый поток данных присва­ ивается переменной экземпляра my Stream. Далее в рассматриваемой здесь программе получается минимальное значение, обнаруживаемое в потоке данных (разумеется , оно является минимальным и в ис­ точнике дан ных). Полученное минимальное значение затем отображается, как по­ казано ниже. Op tional<Integer> mi nVal = myS tream.min (Integer ::compare) ; if (minVal .isPresent ()) System. out . println ( "Минимальное значе ние : "+mi nVal .get () ); Как следует из табл. 29.2, метод min ( ) объявляется следующим образом: Optional<T> min (Comparator<? super Т> хонпара тор) Обратите прежде всего внимание на параметр компаратор метода min (). Обозначаемый им компаратор служит для сравнения двух элементов в потоке дан­ ных. В данном примере методу min ( ) передается ссылка на метод compare () из класса I n tege r. Этот метод служит для реализации компаратора типа Comp a r а to r, способного сравнивать два объекта типа Integer. Обратите далее внимание на то , что метод min ( ) возвращает объект типа Opt ional. Класс Op tiona l под­ робно описывается в гл аве 19, а здесь вкратце поясняется принцип его действия. Этот класс является обобщенным, входит в состав пакета j ava . ut il и объявляет­ ся следующим образом: class Optional<T> где параметр Т обозначает тип элемента. Экземпляр класса Op tional может со­ держать значение типа т или же быть пустым. Вызвав метод isPresent (), можно определить, имеется ли значение в данном объекте. Если он содержит значение, то его можно получить, вызвав метод get ().Вданном примере возвращается объект, содержащий минимальное значение из потока данных в виде объекта типа I n teger. И последнее замечание: метод min ( ) выполняет оконечную операцию, употре­ бляющую поток данных. Поэтому по его завершении потоком данных, на который ссылается переменная экземпляра myS trearn, нельзя воспользоваться снова. В следующем фрагменте кода получается и выводится максимальное значение, обнаруживаемое в потоке дан ных: myStream = myL ist . stream() ; Optional<Integer> maxVal = myS tream.max (In tege r ::compare) ; if (maxVal .isPresent ()) System. out . println ( "Максима ль ное значение : "+maxVal .get () ); И в этом случае переменной rnyS trearn присваивается поток данных, возвраща­ емый методом rnyLi st . strearn ( ) . Как пояснялось ранее, это требуется для того, чтобы получить новый поток данных. Ведь предыдущий поток данных употреблен при вызове метода rnin ( ) . И только после этого вызывается метод ma x ( ) , чтобы по-
Гл ава 29. Потоковый API 1069 лучить максимальное значение. Как и метод rnin ( ) , этот метод возвращает объект типа Op tional, значение которого получается в результате вызова метода get (). Далее в рассматриваемой здесь программе получается отсортированный поток данных, как показано в приведенной ниже строке кода. Stream<Integer> sortedSt ream = myLi st . stream() .sorted() ; В этой строке кода метод sorted ( ) вызывается для потока данных, возвраща­ емого в результате вызова метода rnyList . strearn ().Апоскольку метод sorted ( ) выполняет промежуточную операцию, то в ко нечном итоге получается новый поток данных, который присваивается переменной sortedStrearn. Содержимое отсортированного потока данных выводится с помощью метода forEach () следу­ ющим образом: sortedStream. forEach ((n) -> System. out .print (n +"") ); В этой строке кода метод forEach ( ) выполняет операцию над каждым эле­ ментом потока данных. В частности , он вызывает метод Systern.out . print () для каждого элемента из отсортирован ного потока данных, хранящегося в пере­ менной sortedStrearn. Эта операция выполняется лямбда-выражением. Ниже приведена общая форма метода forEach (). void forEach (ConsU1 11 er<? super Т> д&Jic!l'.81 11 &) Здесь Consurne r - это обобщенный функциональный интерфейс , определяе­ мый в пакете java.ut il . function. Он содержит абстрактный метод accept ( ) , объявляемый следующим образом: void accept (Т ccwrжa_на_об-Аеж!l') Лямбда-выражение, указываемое при вызове метода forEach (), предоставля­ ет реализацию ·метода accept ( ) . Метод forEach ( ) выполняет оконечную опера­ цию , и поэтому по его завершении поток данных оказывается употребленным. Далее отсортированный поток данных фильтруется методом filter (),чтобы в нем остались только нечетные значения: Stream<Integer> oddVa ls = myList . stream() .sorted() .filter ((n) -> (n % 2) == 1) ; Фильтрация потока данных методом f i 1 t е r ( ) выполняется по указанному предикату. Этот метод возвращает новый поток данных, содержащий только те элементы из исходного потока, которые удо влетворяют указанному предикату. Ниже приведена общая форма метода filter (). Stream<T> filter (Predicate<? super Т> nредажа !l') Здесь Predicate - это обобщенный функциональный интерфейс, определяе­ мый в пакете jav a.util . function. Он содержит абстрактный метод test (),объ­ являемый следующим образом: Ьoolean te st (Т ccwrжa_на_об-Аеж!l') Этот метод возвращает логическое значение t rue , если указанная ссылка_ на _ о бъект удо влетворяет предикату, а иначе - логическое значение false. А реали­ зуется метод te st () в лямбда-выражении, передаваемом методу filter (). И по-
1070 Часть 11. Библиотека Java скольку метод filter ( ) выполняет промежуточную операцию, то он возвращает новый поток данных, содержащий отфильтрован ные элементы (в данном случае нечетные целочисленные значения). Эти элементы отображаются далее методом forEach ( ) , как и прежде. Метод f i l ter ( ) или любая другая промежуточная операция возвращает новый по­ ток данных. Поэтому отфильтрованный поток может бьпъ отфильтрован еще раз, что и демонстрируется в следующем фрагменте кода, где производится новый П<Угок дан· ных, содержащий только те нечетные целочисленные значения, которые больше 5. Обратите внимание на то, что обоим фильтрам передаются лямбда-выражения. oddVa ls = myList .stream( ) .filter ((n) -> (n % 2) == 1) .filter ((n) -> n>5); Операции с вед ения Рассмотрим подробнее методы m i n () и ma x ( ) из предыдущего примера. Оба метода выполняют оконечные операции и возвращают результат, исходя из эле· ментов в потоке данных. По терминологии потокового API эти методы представ­ ляют собой операции сведения, поскольку каждый из них сводит поток данных к ед инственному значению (в данном случае - максимальному и минимальному соответственно). В потоковом API такие операции сведения называются особ'ЫМи, поскольку они выполняют особую фун кцию. Помимо методов m i n ( ) и max ( ) , име­ ются и другие методы, выполняющие особые операции сведения. Например, ме­ тод count ( ) подсчитывает количество элементов в потоке данных. Но в методе reduce ( ) понятие свед ения обобщается . Вызывая метод reduce ( ) , можно возвра­ тить значение из потока данных по любому произвольному критерию. По опреде­ лению все операции сведения являются оконечными. В интерфейсе Stream определяются три варианта метода reduce (). Ниже при­ ведены общие формы двух из них. Optional<T> reduce ( BinaryOperator<T> нахоn•!l'елъ) Т reduce (Т sиaveюre_•дeн!l'•VROC!l'• , BinaryOperator<T> нахопж!l'ела.) В первой форме возвращается объект типа Op tional, содержащий получен­ ный результат, а во второй форме - объект типа Т, т. е. типа элемента из потока данных. В обеих формах указан ный на копитель обозначает функцию, опериру­ ющую двумя значениями и получающую результат. Во второй форме зна чение_ ид ентично сти обозначает такое значение, что операция накопления, включаtо­ щая зна чение_ ид ентичности и любой элемент из потока данных, дает в итоге тот же самый элемент без изменения. Та к, если выполняется операция сложения, то значение идентичности равно нулю, поскольку О + х = х. А если выполняется операция умножения, то значение идентичности равно 1, поскольку 1 * х = х. Интерфейс BinaryOpe rator является функциональным и объявляется в паке­ те j ava . util . function. Он расширяет функциональный интерфейс BiFunction. В интерфейсе BiFunction определяется следующий абстрактный метод: R apply (Т sиaveRJrel , U sиaveюre2)
Глава 29. Потоковый API 1071 где параметр R обозначает тип результата; параметр Т - тип первого операнда; параметр U - тип второго операнда. Следовательно, метод apply () применяет фун кцию к своим операндам (зна чение] и эна чение2) и возвращает результат. Когда фун кциональный интерфейс В inarуОре r а tor расширяет фун кциональный интерфейс BiFun ct ion, то все параметры типа в нем обозначают один и тот же ти п. Следовател ьно, в интерфейсе BinaryOpe rator метод apply () объявляется следующим образом: т apply (Т sнav-.e1 , т sнav-.e2) По отношению к методу reduce () параметр зна чение] метода apply ( ) будет содержать предыдущий результат, тогда как параметр зна чение2 - следующий эл емент. При первом вызове данного метода параметр зна чение 2 будет содер­ жать значение идентичности или первый элемент в зависимости от применяемо­ го варианта метода reduce ( ) . Следует, однако , иметь в виду, что операция накопления должна уд овлетворять трем огран ичениям. Она должна быть: • без сохранения состоя ния; • без вмешательства; • ассоциативная. Как пояснялось ранее, операция без сохранения состояния означает, что она опи­ рается на сведения о состоянии. Следовательно, каждый элемент из потока дан­ ных обрабатывается отдельно. Операция без вмеша:тел:ьства означает, что источник да нных не видоизменяется самой операцией. И наконец, операция должна быть асс()'Циативной. В данном случае понятие асс()'Циативна.я операция употребляется в его обычном для арифметики значении. Это означает, что если ассоциативный оператор применяется в последовательности операций, то не имеет никакого зна­ чения , какая именно пара операндов обрабатывается первой. Например, вычис­ ление выражения (10*2)*7 дает такой же результат, как и вычисление этого выражения: 10*(2*7) Ассоциативность имеет особое значение для правильного применения опера­ ций сведения в параллельных потоках данных, обсуждаемых в следующем разделе. В следующем примере программы демонстрируется применение рассмотренных выше вариантов метода reduce ( ) : 11 Продемонстрировать приме нение ме тода reduce () import java .util .*; import java.util . stream. *; cla ss StreamDemo 2 { puЬlic static voi d ma in (St ring [J args ) { 11 создать список объе ктов типа Integer
1072 Ч асть 11. Библиотека Java ArrayList<Integer> myList = new ArrayLi st<> ( ); myList .add(7) ; myList .add(18) ; myList .add(lO) ; myList . add (24) ; myList .add(17) ; myList .add(5) ; 11 Два способа получения резуль тата перемноже ния целочисле нных 11 элементов списка шyLi st с помо щью ме тода reduce () Op tional<Integer> productObj = myLi st . stream() .reduce ((a,b) -> а*Ь) ; if ( productObj .is Present (} ) System. o ut . println ( "Пpoиэвeдeниe в виде объе кта типа Op tion al : " + produ ctObj .ge t()); int produ ct = myblst .stream( ) .reduce (l, (а,Ь) -> а*Ь) ; System.out .println ( "Произведение в виде значения типа int : "+product ); Как показан о ниже, в обоих вариантах применения метода reduce ( ) получает­ ся оди наковый результат. Произведение в виде объе кта типа Op tion al : 2570400 Произведение в виде значения типа int : 2570400 Сначала в данной программе применяется первый вариант метода reduce ( ) с лямбда-выражением для получения произведения двух числовых значений. В связи с тем что поток в данном примере содержит объекты типа Integer, они автоматически распаковываются перед операцией умножения и снова упаковы· ваются перед возвратом результата. Оба перемножаемых значения представля ют те кущий результат и следующий элемент в потоке данных. А конечный результат возвращается в виде объекта типа Optional. Выводимое значение получается в результате вызова метода ge t () для возвращаемого объекта. Далее в данной программе применяется второй вариант метода reduce ( ) , при вызове которого значение идентичности указывается явным образом: для опера· ции умножения оно равно 1. Обратите внимание на то , что результат возвраща· ется в виде объекта, тип которого соответствует типу элемента из потока данных (в данном случае - Integer). Столь простые операции сведения, как умножение, удобны в качестве приме­ ров, но эти м применение операций сведения, конечно, не ограничивается. Так , если обратиться к предыдущему примеру программы, то получить произведение только четных целоч исленных значений можно следующим образом: int evenProduct = myList .stream() .reduce (l, (а,Ь) -> { if (b%2 == 0) return а*Ь; else return а; }); Обратите особое внимание на лямбда-выражение. Если параметр Ь имеет чет­ ное числовое значение, то возвращается произведение а * Ь, а иначе - значение параметра а. И это вполне допустимо, поскольку параметр а содержит текущий результат, а параметр Ь - следующий элемент из потока данных, как пояснялось ранее.
Гл ава 29. Потоковь1 й API 1073 Паралл ельные потоки да нных Прежде чем продолжить рассмотрение потокового API , следует остановить­ ся на параллельных потоках данных. Как отмечалось ранее в данной книге , па­ раллельное выполнение кода на многоядерных процессорах позволяет добиться значительного повышения производительности. Вследствие этого параллельное программирование стало важной частью современного арсенала средств про­ граммистов. Но в то же время парал л ельное программирование - дело непростое и чреватое ошибками. Поэтому одно из преимуществ библиотеки потоков данных заключается в том, что она позволяет просто и надежно организовать параллель­ ное выполнение некоторых операций. Запросить параллельную обработку потока данных совсем не трудно . Для это­ го достаточно воспользоваться параллельным потоком дан ных. Как упоминалось ранее , чтобы получить парал л ельный поток данных, можно , в частности , вызвать метод parallelStream ( ), определенный в классе Collection. С другой сторо­ ны, можно вызвать метод parallel ( ) в последовательном потоке данных. Метод parallel () определяется в интерфейсе BaseStream следующим образом: s parallel О Этот метод возвращает параллельный поток данных, исходя из вызывающего последовательного потока данных. (Если же он вызывается в потоке данных, кото­ рый уже является параллельным, то возвращается вызывающий поток. ) Следует, однако, иметь в виду, что даже при наличии параллел ьного потока данных иско­ мый парал л елизм достигается только в том случае, если его поддерживает испол­ няющая среда. Как только будет получен параллельный поток данных, последующие операции в этом потоке могут выполняться параллельно, при условии, что параллелизм под­ держивается исполняющей средой. Например, первая операция сведения, выпол­ няемая методом reduce () в предыдущей программе , может быть распараллелена, если вызов метода stream () замен ить на вызов метода parallelStream (), как показано ниже. Результат окажется тем же самым, но операция ум ножения может быть распараллелена в разных потоках исполнения . Op tion al<Integer> productObj = my Li st . parallelStream() .reduce ((a,b) -> а*Ь) ; Как правило , любая операция в параллельном потоке дан ных должна выпол­ няться без сохранения состоя ния. Она должна быть также ассоциативной и без вмешательства. Этим гарантируется, что результаты выполнения операций в па­ раллельном потоке данных остаются такими же , как и результаты выполнения ана­ логичных операций в последовательном потоке данных. Для применения параллельных потоков данных особенно полезной может ока­ заться приведенный ниже вариант метода reduce ( ) . Этот вариант позволяет ука­ зать порядок объеди нения частичных результатов. <U> U reduce (U .эначею.rе яд eн!l'Jlrr.iнoc!l'Jlr , BiFunction<U , ? super Т, U> нaжonя�eлi.-BinaryOperator<U> о6ъеджи.�ель)
107, Часть 11. Библиоте ка Java В данной версии параметр о бъединитель обозначает фун кцию, объединяю­ щую два значения , получаемые функцией, определяемой параметром на копитель. Если обратиться к предыдущему примеру программы, то вычисление произведе­ ния элементов из списочного массива myList можно организовать в параллель­ ном потоке данных следующим образом: int parallelProduct =myList . parallelStream () .reduce (l, (а, Ы -> а*Ь, (а,Ь) -> а*Ь) ; Как видите, накопитель и объединитель выполняют одну и ту же функцию. Но иногда действия накопителя моrуг отличаться от действий объединителя. В качестве примера рассмотрим приведенную ниже программу. В этой программе списочный массив myLi s t содержит список значений типа douЫe, а для вычисления произведе­ ния квадратных корней каждого элемента этого списка вызывается метод reduce ( ) . 11 Продемон стрировать приме нение объединяюще го 11 варианта ме тода reduce ( ) import java .util .*; import java.util . stream.*; clas s StreamDemoЗ { puЫic static vo id main (String [) args ) { 11 Теперь это список числовых значе ний типа douЫe ArrayList<DouЫe> myList = new ArrayList<> ( ); myL ist .add(7.0) ; myL ist .add(l8.0) ; myL ist.add(lO.O) ; myList.add(24.0) ; myL ist .add(l7.0) ; myList .add(5.0) ; douЫe prod uctOfSqrRoots System.out . println ( myList . parallelStream() .reduce ( 1.О, ); (а,Ь) -> а*Math .sqrt (b) , (а,Ы ->а*Ь "Произведение квадратных корней : "+productOfSqrRo ots) ; Обратите внимание на то , что фун кция накопителя умножает квадратные кор­ ни двух элементов, тогда как функция объединителя умножает частичные резуль­ таты. Следовател ьно, обе фун кции выполняют разные действия. Более того , обе фун кции должн:ы отличаться, чтобы вычисления выполнялись правильно. Та к, если попытаться получить произведение квадратных корней элементов списка, используя приведенное ниже выражение, то результат окажется ошибочным. 11 Не сработает ! douЫe productOfSqrRoots2 = myList .parallelStream() .reduce ( 1.О, (а,Ь) -> а*Math .sqrt (b) );
Глава 29. ПотоковыйдРI 1075 В данном варианте метода reduce ( ) функции накопителя и объединителя оди­ наковы. И это приводит к ошибке, ведь когда объединяются два частичных резуль­ тата, перемножаются их квадратные корни, а не сами частичные результаты. Любопытно, что если изменить парал л ельный поток данных на последователь­ ный в предыдущем вызове метода reduce ( ) , то выполнение данной операции даст правИJiьный резул ьтат, поскольку в этом случае объединять оба частичных результата не нужно. Ошибка возникает лишь в том случае, если применяется па­ раллельный поток данных. Вызвав метод sequent ial (), определяемый в интерфейсе BaseStrearn, можно заменить парал л ел ьный поток данных последовательным. Ниже приведена общая форма этого метода. Как правило, параллельный поток данных заменяется после­ довательным, и наоборот, по мере надобности . s sequential О Организуя парал л ельное выполнение в потоке данных, следует также прини­ мать во внимание порядок следования элементов. Потоки данных могут быть как упорядоченными , так и неупорядоченными. Если источник данных упорядочен , то, как правИJiо, упорядочен и поток данных. Но иногда можно добиться повыше­ ния производительности , если применяемый поток данных неупорядочен. В этом случае каждую часть потока данных можно обрабатывать отдел ьно, не согласуя ее с остальными частями. В тех случаях, когда порядок следования операций не имеет особого значения, можно выбрать режим работы без упорядочения, вызвав метод unordered (): S unordered О И наконец, при выполнении метода forEach ( ) упорядоченность парал л ельно­ го потока может не сохраняться. Если же при выполнении операции над каждым элементом в параллельном потоке данных требуется сохранить его упорядочен­ ность, то лучше воспользоваться методом forEachOrdered ( ).Этот метод приме­ няется таким же образом, как и метод forEach (). Отображе н и е Нередко элементы одного потока данных требуется отобразить на элементы другого потока. Например, из базы данных, содержащей имя, номер телефона и адрес электронной почты в одном потоке данных, требуется отобразить только имя и адрес электро нной почты в другом потоке. А в качестве другого примера мож­ но привести преобразование, которое требуется выполнить только над некоторы­ ми элементами в потоке данных. Операции отображения весьма распространены, и поэтому в потоковом API предоставляется их встроенная поддержка. Самое общее отображение выполняется методом map ( ) . Ниже приведена его общая форма. <R> Streaш<R> 11 1ар (Function< ? super Т, ? e][tenda R> фуижЦJrR_ �odpazei.-•) Здесь параметр R обозначает тип элементов из нового потока данных; па­ раметр Т - тип элементов из вызывающего потока данных; параметр функция_ отображения - экземпляр функционального интерфейса Function, выполняю-
1076 Часть 11. Библиотека Java щий отображение. Фун кция отображений должна выполнять операцию без сохра­ нения состояния и без вмешательства. А метод map ( ) выполняет промежугочную операцию, поскольку он возвращает новый поток данных. Интерфейс Function является функциональным и объявляется в пакете j ava . util . func tion следущим образом: Function<T , R> где параметр Т по отношению к методу map ( ) обозначает тип элемента, а пара­ метр R - результат отображения. В функциональном инте рфейсе Function объ­ является следующий абстрактный метод: R apply (T sяаvен.е) где параметр зна чение обозначает ссылку на отображаемый объект. В итоге воз­ вращается результат отображения. Ниже приведен простой пример применения метода map ( ) . Это переделанная версия программы из предыдущего примера. Как и прежде, в программе вычис­ ляется произведение квадратных корней элементов из списочного массива типа ArrayList. Но в данной версии программы квадратные корни элементов сначала отображаются на новый поток данных, а затем для вычисления их произведения вызывается метод reduce ( ) . 11 Отобразить один поток данных на другой import java .util .*; import java.util . stream. *; clas s StreamDemo 4 ( puЫic static vo id ma in (String [] args ) { 11 Список числовых значений типа douЫe ArrayList<DouЫ e> myLi st = new ArrayList<> ( ); myL ist .add(7.0) ; myL i st .add(l8.0) ; myL ist .add(l0.0) ; myL ist .add(24.0) ; myList .add(l7.0) ; myL ist .add(5.0) ; 11 Отобразить квадратные корни элементов из списка шyLi st 11 на новый поток данных Stream<DouЫe> sqrtRootStrm = myList . stream() .map( (a) -> Math .sqrt (a) ); 11 получить произведение квадратных корней douЫ e productOfSqrRoots = sqrtRootStrm. reduce (l.O, (а,Ь) -> а*Ь) ; System.out . println ( "Произведение квадратных корней равно " + productOfSqrRoots.) ; Эта версия программы выводит такой же результат, как и предыдущая ее версия. А отличается она от предыдущей версии лишь те м, что преобразование
Гл ава 29. Потоковый API 1077 (т.е . вычисление произведения квадратных корней) выполняется во время ото­ бражения, а не во время сведения. Благодаря этому для вычисления произведения можно воспользоваться формой метода reduce ( ) с двумя параметрами, поскольку для этого больше не требуется предоставлять отдельную функцию объединителя. Ниже приведен пример программы, где для создания нового потока дан­ ных, содержащего только избранные поля из исходного потока, использует­ ся метод map ( ) . В данном примере исходный поток содержит объекты типа Name PhoneEma i l, состоящие из имен , номеров телефонов и адресов электронной почты. На новый поток данных объектов ти па Name Phone отображаются только имена и номера телефонов, тогда как адреса электронной почты отбрасываются. 11 Приме ни ть ме тод шар () для со здания нового потока данных , 11 содержащего только избранные элементы из исходного потока import java .util .*; import java.util . stream.*; class Name Phone Ema il String name ; String phonenum; String email; Name PhoneEmail ( String n, String р, String е) { name = n; phonenum = р; email = е; class Name Phone { String name ; String phonenum; Name Phone (St ring n, String р) { пате = n; phonenum = р; class StreamDemo5 { puЬlic static void ma in (String [] args ) { 11 Список имен, номеров телефонов и адресов электронной почты ArrayLi st<Name PhoneEmail> myLi st = new ArrayL ist<> ( ); myLi st . add ( new Name PhoneEmail ( "Лappи", "555-5555 ", "Larry@HerbSchildt .com ") ); myLi st .add ( new Name PhoneEmail ( "Джeймc" , "555-4444", "James@HerbSchildt .com") ); myList . add (new Name PhoпeEmail ( "Mэpи", "555-3333" , "Ma ry@HerbSchildt .com ") ); System.out . println ( "Иcxoдныe элементы из списка myList : ") ; myList . stream( ) .forEach ( (а) -> { System.out . println (a.name +" "+a.phonenum +" "+a.email ); }); System.out . println () ;
1078 Часть 11. Библиотека Java 1 1 отобразить на новый поток данных 11 только име на и номера телефонов Stream<NamePhone> nameAndPhone = myList .stream() .map ( (а) -> new Name Phone (a .name, a.phonenum ) ); System. out . println ( "Cпиcoк име н и номеров телефонов : ") ; nameAndPhone .forEach ( (а) - > { System.out . println (a.name +" "+a.phonenum) ; }); Ниже приведен результат, выводимый дан ной программой. Он позволяет све­ рить исходные элементы списка с отображаемыми. Исходные элементы из списка myLi st : Ларри 555-5555 Larry@He rbSchildt .com Джеймс 555-4444 James@HerbSchildt .com Мэри 555-3333 Ma ry@HerbSchildt .com Список имен и номеров телефонов : Ларри 555-5555 Джеймс 555-4444 Мэ ри 555-3333 Несколько промежуrочных операций можно объединить в единый конвейер, что позволяет составлять довольно эффективные действия. Например, в следую­ щей строке кода для получения нового потока дан ных, содержащего только эле­ менты имени и номера телефона, совпадающие с именем "Джеймс", сначала вы­ зываетсяметодfi1ter(),азатемметодmap(): Stream<Name Phone> nameAndPhone = myList.stream() . filter ((a) -> a.name .equals ( "Джeймc " )). map( (a) -> new Name Phone (a.name, a.phonenum) ); Такая операция фильтрации весьма характерна для составления запросов базы дан ных. Приобретя некоторый опыт обращения с потоковым API, вы непремен­ но обнаружите, что из подобных цепочек операций можно составлять довольно сложные запросы, объединения и выборки данных в потоке данных. Помимо описанной выше формы, имеются еще три формы метода map ( ) . Все они возвращают поток данных примитивного типа и приведены ниже. IntStreaa aapToint (TointFunction<? вuper Т> fJ'НIЩl llR O!J'OбpaJir&НILV) LonqStrea1 11 aapToLonq (ToLonqJ!'unction<? вuper Т> fyюcfiж• О!!'�•) DouЬleStreaa aap'roDouЬle (ToDouЫeFunction<? вuper Т> tУНи:ча-_О!l'ображеюu) Каждая задаваемая функция_ отображения должна реализовывать абстракт­ ный метод, определяемый в заданном интерфейсе и возвращающий значение ука­ занного типа. Например, в интерфейсе ToDouЬleFunc tion определяется метод app lyAs DouЫe (Т зна чение ) , который должен возвращать значение своего па­ раметра как douЫe. В приведенном ниже примере программы применяется поток данных при­ митивного типа. Сначала в этой программе создается списочный массив типа ArrayList, состоящий из объектов типа DouЫe. Затем для создания потока дан­ ных типа IntStream, содержащего максимально допустимый предел каждого чис­ лового значения, вызывается метод stream (),адалее - метод mapTolnt ().
Гn ава 29. Потоковы й API 11 Отобразить поток данных типа Streaa на поток данных типа IntStreaa import java .util .*; import java.util . stream. *; class StreamDemo б { puЬlic static vo id ma in (String [] args ) { 11 Список значений типа douЫe ArrayList<DouЫe> rnyLi st = new Ar rayList<> ( ); rnyList .add(l.l) ; myList .add(3.6) ; myL i st .add(9.2) ; rnyList .add(4.7) ; rnyL ist .add(l2.l) ; rnyList .add(5.0) ; Systern.out . print ( "Иcxoдныe значения из списка rnyLi st : ") ; rnyList .stream() .forEach ( (а) -> { Systern. out .print (a +" ") ; }); Systern.out . println () ; 11 Отобразить максимал ьно допустимый предел каждого 11 значения из списка шyL ist на поток данных типа IntStre81 11 IntStrearn cStrrn = myList . stream() .rnapTolnt ((a) -> (int ) Math .ceil (a) ); Systern.out . print ( "Maкcимa льнo допустимые пределы значений " + "из списка myLi st : ") ; cStrrn. forEach ( (а) -> { Systern. out .print (a +" ") ; }); 1079 Ниже приведен результат, выводимый данной программой. Поток дан ных, по­ лучаемый методом mapToint (), содержит максимально допустимые пределы ис­ ходных значений элементов из списка myLi s t. Исходные значения из списка myLi st : 1.1 3.6 9 .2 4.7 12 .1 5.0 Максимально допустимые пределы значений из списка myLi st : 2 4 10 5 13 5 Завершая обсуждение темы отображения , следует заметить, что в потоковом АР! предоставляются также методы, поддерживающие плоское отображение. К их чис­ лу относятся методы flatMap (), flatMapTo int (), flatMapToLong () и flatMap ToDouЫe ( ) . Методы плоского отображения предназначены для выхода из тех си­ туаций, когда каждый элемент в исходном потоке данных отображается на более чем один элемент в итоговом потоке данных. Накоплен ие В приведенных выше примерах можно получить поток данных из коллекции, что, как правило , и делается. Но иногда требуется совсем противоположное: полу-
1080 Част" 11. Библ иоте ка Java чить коллекцию из потока данных. Для выполнения подобного действия в пото­ ковом API предоставляется метод collect ().Уэтого метода имеются две общие формы. Ниже приведена одна из них, употребляемая в этом разделе. <R , А> R collect (Collector< ? super Т, А, R> фун.1щJ1R_нажоп.1 1 еюr•) Здесь параметр R обозначает тип получ аемого результата; параметр Т - тип элемента из вызывающего потока данных; параметр А - тип накапливаемых вну­ три данных; параметр функция_ на копления - порядок обработки коллекции. Метод collect ( ) выполняет оконечную операцию. Инте рфейс Collector объявляется в пакете j ava . ut il . stream, как показано ниже . interface Collector<T , А, R> Параметры Т, А и R имеют описанное выше назначение. В интерфейсе Co llector определяется ряд методов, но в этой гл аве не демонстрируется их ре­ ализация . Вместо этого испол ьзуются два предопределенных накопителя , предо­ ставляемых в классе Collectors, входящем в состав пакета java.util . stream. В классе Collectors определяется ряд статических методов накопления, ко­ то рыми можно воспользоваться в готовом виде. Ниже приведены общие формы двух употребляемых далее методов toList () и toSet (). static <Т> Collector<T , ?, List<T>> toList () static <Т> Collector<T , ?, Set<T>> toSet () Метод toList () возвращает накопитель, предназначенный для накопления элементов в списке типа List, а метод toSet () - накопител ь, предназначенный для накопления элементов во множестве типа Set. Например, для накопления эле­ ментов в списке ти па List можно вызвать метод collect () следующим образом: col lect (Collectors .toList () ) В приведенном ниже примере программы все сказанное выше о накоплении демонстрируется на практике. Это версия программы из предыдущего приме­ ра, переделанная таким образом, чтобы накапливать имена и номера телефонов в списке типа List и множестве типа Set. 11 Использовать ме тод collect () для создани я списка типа List 11 и множества типа Set из потока данных import java .util .*; import java.util . stream. *; class NamePhoneEmail String name ; String phonenшn; String emai l; NamePhone Email ( String n, String р, String е) { name = n; phonenum = р; email = е; class Name Phone {
String name ; String phonenum; Name Phone (String n, String р) { name = n; phonenum = р; class StreamDemo7 { puЫ ic static void ma in (String [] args ) { Гл ава 29. П отоко вый дРI 11 Список имен, номеров телефонов и адресов электронной почты ArrayList<Name PhoneEmail> myList = new ArrayList<> ( ); myList .add (new Name Phone Email ( "Лappи ", "555-5555", "Larry@HerbSchildt . com" )); myList . add (new Name PhoneEmail ( "Джeймc ", "555-4444", "James@HerbSchildt .com ") ); myList . add (new Name PhoneEmail ( "Mэpи ", "555-3333" , "Mary@HerbSchildt .com") ); 1 1 отобразить только име на и номера телефонов на новый поток данных Stream<Name Phone> nameAndPhone = myList .stream() .map ( (а) -> new Name Phone (a.name , a.phonenum) ); 11 вызвать ме тод collect () , чтобы составить список типа Liat 11 из име н и номеров телефонов List<NamePhone> npList = nameAndPhone .collect (Collectorз .toLi st () ); System. o ut . println ( "Имe нa и номера телефонов в списке типа List:") ; for ( Name Phone е : npList) System.out . println (e.name + ": "+e.phonenum) ; 1 1 получить друг ое отображе ние имен и номеров телефонов nameAndPhone = myList . зtream() .map ( (а) -> new Name Phone (a.name, a.phonenum) ); 11 а теперь создать множе ство типа Set, выз вав ме тод collect () Set<NamePhone> npSet nameAndPhone .collect ( Collectors . toSet () ); Syзtem.out . println ( "\nИме на и номера телефонов в множестве типа Set:") ; for(NamePhone е : npSet) Syзtem.out . println (e.name + ": "+e.phonenum) ; Эта программа выводит следующий результат : Име на и номера телефонов в списке типа List : Ларри : 555-5555 Джеймс : 555-4444 Мэри : 555-3333 Име на и номера телефонов в множе стве типа Set : Джеймс : 555-4444 Ларри : 555-5555 Мэри : 555-3333 1081
1082 Часть 11. Библиоте ка Java В следующей строке кода из данной программы накаrtливаются имена и номера телефонов в списке типа List с помощью метода toList (): List<Name Phone > npLi st = nameAndFhone .collect (Collectors .toList () ); После выполнения это й строки кода коллекция npList может быть использо­ вана аналогично любой другой коллекции типа List. Например, ее можно пере­ брать в цикле for в стиле for each, как показано в следующей строке кода: for ( Name Phone е : npLi st) System. out . println (e.name + ": "+e.phonenwn) ; Аналогичным образом осуществляется и создание множества типа Set в резуль­ тате вызова метода collect (Collectors . toSet () ) . Возможность перемещать дан­ ные из коллекции в поток, а затем обратно в коллекцию является одной из самых сильных сторон потокового API. Это позволяет оперировать колле кцией через по­ ток данных, а затем реорганизовывать его в виде коллекции. Более того, потоковые операции могут при соответствующих условиях выполняться парал л ел ьно. Форма метода collect () , использованная в предыдущем примере , достаточно уд обна и нередко удо влетворяет требованиям программистов. Но имеется и дру­ гая, приведенная ниже форма этого метода, предоставляющая больше возможно­ стей управлять процессом накопления. <R> R collect ( Supplier<R> адреса !l', BiConsuшer<R , ? super Т> нажоп•!l'елs. , BiConsWl l& r <R, R> ��.-н.!l'апь) Здесь параметр адреса т обозначает порядок создания объекта, содержащего результат. Например, чтобы воспользоваться связным списком типа LinkedList в качестве результи рующей колле кци и, следует указать ее конструктор. Функция накопителя добавляет элемент к результату. а функция о бъединителя служит для объ­ единения двух частичных результатов. Следовательно, эrn функции действуют таким же образом, как и в методе reduce ( ) . Но обе функции должны выполняться без со­ хранения состояния и без вмешательства, а также должны быть ассоциативными. Следует замеrnть, что параметр адре са т относится к типу Sup plier. Это функ­ циональный интерфейс, объявляемый в пакете j ava . util . function. В этом фун кциональном интерфейсе определяется единственный метод get ( ) без пара­ метров, и в да нном случае он возвращает объект типа R. Следовательно, по отно­ шению к методу collect () метод get ( ) возвращает ссылку на изменяемый объ· ект хранения , например коллекцию. Следует также иметь в виду, что параметры на копитель и о бъединитель от­ носятся к типу BiConsurner. Это функциональный интерфейс, определяемый в па­ кете java.ut il . function. В этом фун кциональном интерфейсе определяется абстрактный метод accept (): void accept (T �ex!l'l , U о�еж!1'2) Этот метод выполняет определенного рода операцию над указан ными о бъектомl и о бъектом2 . По отношению к на копителю указанный о бъектl обо­ значает целевую коллекцию, а о бъект2 - две объединяемые коллекции. Используя описанную только что форму метода collect (), можно указать связный список типа LinkedList в качестве целевой коллекции в предыдущем примере программы следующим образом:
Гл ава 29. Потоковый API LinkedList<Name Phone > npLi st nameAndPhone .collect ( () -> new LinkedLi st<> () , (list , eleme nt ) -> list .add ( element) , (listA, listB ) -> listA. addAl l (listB) ); 1083 Обратите внимание на то , что первым аргументом в методе co llect () явля­ ется лямбда-выражение, возвращающее новый связный список типа LinkedList. В качестве второго аргумента указывается выражение со стандартным методом коллекции add ( ) , вводя щим эл емент в список, а в качестве третьего аргумента - выражение с методом addAll (), объединяющим два связных списка. Любопытно , что для ввода элемента в список можно воспользоваться любым методо м, опре­ деленным в классе LinkedList. Например, для ввода эл ементов в начале списка можно вызвать метод addFirst ( ) следующим образом: (list, eleme nt ) -> list . addFi rst ( element ) Нетрудно догадаться , что в качестве аргументов метода collect ( ) далеко не всегда нужно указывать лямбда-выражение. Зачастую достаточно указать ссьтку на метод или конструктор. Если снова обратиться к предыдущему примеру про­ граммы, то в ее исходный код можно ввести приведенную ниже строку, в которой создается множество типа HashSet, содержащее все элементы из потока name And Phone. Обратите внимание на то , что в качестве первого аргумента указыва­ ется ссылка на конструктор класса Ha shSet, а в качестве второго и третьего аргу­ ментов - ссылки на методы add ( ) и addAl l () из класса HashSet соответственно. HashSet<Name Phone > np Set = nameAndPhone .collect (HashSet ::n ew, HashSet : : add , HashSet ::addAll ); И последнее замечание: в те рминологии потокового API метод collect () вы­ полняет операцию, называемую изменяем ым сведеиием. Дело в том, что в результате сведения получается изменяемый объект хранения, например колле кция . Итераторы и потоки да нных Несмотря на то что поток данных не является объектом их хранения , для пере­ бора его элементов можно воспользоваться итератором почти так же , как и для перебора элементов коллекции. В потоковом API поддерживаются два типа ите­ раторов. Первым из них является традиционный итератор типа Iterator, а вто­ рым - итератор-разделитель типа Spl iterator, внедренный в версии JDK 8. Последний обеспечивает немало преимуществ в тех случаях, когда он применяет­ ся в парал л ельных потоках данных. П рименени е итерато ра в п отоке да нных Как упоминалось выше, итератор применяется в потоке данных почти так же, как и в коллекции. Подробнее итераторы рассматриваются в гл аве 18, но здесь все же стоит сделать их краткий обзор. Итераторы являются объектами классов, реализующих интерфейс Iterator, объявляемый в пакете j ava . util. В этом ин-
108, Часть 11. Библиоте ка Java терфейсе имеются два метода - hasNext () и next ().Если для перебора имеется следующий элемент, то метод hasNext ( ) возвращает логическое значение true, а иначе -логическое значение false, тогда как метод next ( ) возвращает следую­ щий эл емент в итерации. На заметку! В версии JDK 8 внедрены следующие до пол н ител ьные ти пы итераторов для обработ· ки пото ков примити вных ти пов да нных: Primitiveiterator, Primitiveiterator . OfDouЬle, Primitiveiterator . OfLong и Primit iveiterator.Ofint. Все эти итераторы расширяют инте рфейс Iterator и в целом действуют та ким же образом, ка к и итераторы, непосредствен но опирающиеся на интерфейс Iterator. Чтобы получить итератор для потока данных, следует вызвать метод i terator ( ) для этого потока. Ниже приведена общая форма этого метода, употребляемая в потоке дан ных типа Stream. Iterator<T> iterator () Здесь параметр Т обозначает тип элемента. (Потоки примитивных типов дан­ ных возвращают данные соответствующего примитивного типа. ) В приведенном ниже примере программы демонстрируется перебор элемен­ тов потока с помощью итератора. В данном примере перебираются символьные строки в списочном массиве ArrayLi st, но этот процесс аналогичен для потока данных любого типа. 11 Приме нить итератор в потоке данных impo rt java.util .*; impo rt java.util . stream. *; class StreamDemoB { puЫ ic static void ma in (String [] args ) { 11 создать список символь ных строк ArrayLi st<String> myLi st = new ArrayList<> ( ); myList .add ( "Aльфa" ) ; myLi st . add ("Бeтa " ); myList . add ("Гaммa" ) ; myList.add( "Дeльтa") ; myList .add ( "Kcи" ); myList .add ( "OМeгa" ); 11 получить поток данных для списочного ма ссива Stream<String> myS tream = myList . stream( ); 11 получить итератор для потока данных Iterator<String> itr = myS tream. iterator () ; 11 перебрать элементы в потоке данных whi le (itr . hasNext () ) System.out . priпtln (itr .next () ); Ниже приведен результат, выводимый данной программой.
Ал ьфа Бета Гамма Дельта Кси Оме га Г.n ава 29. Потоковый API П рименение итерато ра- раздел ител я 1085 Итератор-разделитель типа Spliterator служит альте рнативой обычному итератору типа Iterator, особенно для параллел ьной обработки. В общем , ите­ ратор-разделитель сложнее, чем обычный итератор. Более подробно он рассма­ тривается в гл аве 18, но здесь все же стоит напомнить вкратце его особенности . В интерфейсе Sp literator определяется несколько методов, но на практике при­ меняются только три их них. Первым из них является метод tryAdvance (), вы­ полняющий действие над следующим элементом и продвигающим итератор даль­ ше. Ниже приведе на общая форма этого метода. Ьoolean tryAdvance (ConsUJ1 1& r<? super Т> д&Jic!l'a.e) Здесь параметр действие обозначает конкретное действие, выполняемое над следующим элементом в итерации. Метод tryAdvance ( ) возвращает логи­ ческое значение true, если имеется следующий элемент, или логическое значе­ ние false, если эл ементов больше не осталось. Как пояснялось ранее в этой гл а­ ве , в функциональном интерфейсе Consurner объявляется единственный метод accept (), принимающий элемент типа Т в качестве своего аргумента и возвраща­ ющ ий значение типа vo id, а по существу, ничего не возвращающий. Благодаря тому что метод tryAd vance () возвращает логическое значение false, если больше не остается элементов для обработки , не составляет особого труда орган изовать цикл итерации, как показано в следующем примере: whi le (splititr .tr yAdvance ( // выполнить зде сь действие ); Заданное действие выполняется над следующим эл ементом до тех пор , пока метод tryAd vance () возвращает логическое значение true. Итерация завершает­ ся , когда метод tryAdvance () возвращает логическое значение false. Обратите внимание, как в одном методе tryAdvance () сочетаются функции, выполняе­ мые методами hasNext () и next (), предоставляемыми интерфейсом Iterator. Благодаря этому повышается эффективность процесса итерации. Ниже приведена переделанная версия программы из предыдущего примера. В этой версии вместо обычного итератора типа Iterator применяется итератор­ разделитель ти па Spliterator. Эта версия программы выводит такой же резуль­ тат, как и предыдущая ее версия. 11 Приме нить итератор-разделитель в потоке данных irnport java .util .*; irnport java.util . strearn. *; class StrearnDerno 9 { puЬlic static void rna in (String [] args ) {
1086 Часть 11. Библ иотека Java 11 создать список симв оль ных строк ArrayList<St riпg> myList = пеw ArrayLi st<> ( ); myList .add ( "Aльфa") ; myLi st .add ( "Бeтa" ); myL ist .add ( "Гaммa") ; myList .add ( "Дeльтa") ; myLi st .add("K cи" ); myList .add ("Oмe г a" ) ; 11 получи ть поток данных для списочного массива Stream<String> myS tream = myList . stream() ; 11 получи ть итератор-разделитель Spliterator<String> splitltr = myS tream. spliterator (); // перебрать элементы в потоке данных whi le (spl i titr . tryAdvance ((n) -> System. o ut . println (n))); Иногда некоторое действие требуется выполнить над всеми элементами сразу. а не над каждым из них по очереди. На этот случай в интерфейсе Spliterator предоставляется метод forEachRemaining ().Ниже приведена его общая форма. default void forEachRel lla ininq (ConsWl l8 r< ? super Т> .zrdc�-e) Этот метод выполняет заданное действие над каждым необработанным эле­ ментом и затем производит возврат. Та к, если обратиться к предыдущему примеру программы, то оставшиеся в потоке символьные строки можно вывести следую­ щим образом: splititr . forEachRemaining ((n) -> System. o ut . println (n) ); Обратите внимание на то , что этот метод вообще исключает потребность в ци­ клическом переборе каждого элемента по очереди. И это еще одно преимущество ите ратора-разделителя типа Spliterator. В интерфейсе Spli terator имеется еще один метод, называемый trySplit () и представляющий особый интерес. Этот метод разделяет итерируемые элементы на две части , возвращая новый итератор-разделитель типа Sp literator для од­ ной из этих частей, тогда как другая часть остается доступной по исходному итера­ тору-разделителю. Ниже приведена общая форма метода trySplit (). Spliterator<T> trySplit () Если разделить вызывающий итератор-разделитель типа Spliterator не уда­ ется , то возвращается пустое значение null, а иначе - ссылка на возвращаемую часть. В качестве примера ниже приведена другая версия программы из предыду­ щего примера, где демонстрируется применение метода trySplit (). 11 Продемонс трировать приме нение ме тода trySplit () import java .util .*; import java.util . stream. *; class StreamDemo lO { puЫic static void main(String[] args) {
Глава 29. Потоковь1й API 11 создать список символь ных строк ArrayLi st<String> rnyLi st = new ArrayLi st<> ( ); myList .add ( "Aльфa" ) ; myList .add ( "Бeтa" ); myList.add ( "Гaммa" ) ; myList.add ( "Дeль т a" ) ; myList .add("K cи" ); rnyList. add ( "Омега") ; 11 получить поток данных для списочного ма ссива Strearn< String> rnyS trearn = rnyList . strearn() ; 11 получить итератор-разделитель Spliterator<String> splititr = rnyS trearn.spliterator () ; 11 а теперь разделить первый итератор Spliterator<String> splititr2 = split itr . trySplit () ; 11 использовать сначала итератор splititr2 , если 11 нельзя разделить итератор spl ititr if(splititr2 != пull) { System. out . println ("P eзyль тaт , выводимый итератором splititr2 : ") ; split itr2 . forEachRema ining ((n) -> System. out . println (n)); 11 а теперь восполь зоваться итератором splititr Systern. out .println ("\nPeзyль тaт , выв одимый итератором splititr : ") ; splititr . forEachRemaining ((n) -> System.out . println (n)); Ниже приведен результат, выводимый данной программой. Результат , выводимый итератором spl ititr2 : Ал ьфа Бета Гамма Результат , выводимый итератором spl ititr : Дельта Кси Оме га 1087 В данном простом примере разделение итератора-разделителя типа Sp literator не имеет особого смысла, но оно может иметь бол ьшое значение при параллельной обработке крупных массивов данных. На практике все-таки лучше воспользоваться каки м-нибудь другим методом из инте рфейса S t ream вместе с па­ раллельным потоком данных, чем разделять вручную итератор-разделитель типа Sp literator. Последнее следует делать лишь в тех случаях, когда не подходит ни один из предопределенных методов. Дал ьнейшее изуч ение пото кового API В этой гл аве были рассмотрены лишь некоторых из основных средств потоко­ вого API и продемонстрированы приемы из применения, хотя этих средств на­ много больше в данном прикладном программном интерфейсе. Прежде всего сле­ дует упомя нуть ряд других методов, предоставляемых в интерфейсе Stream. Эти методы могут оказаться полезными в следующих случаях.
1088 Ч асть 11. Библ иотека Java • Чтобы выяснить, уд овлетворяет ли один или больше элементов указанному предикату, следует воспользоваться методом allMatch (), anyMatch () или noneMatch (). • Чтобы получить количество элементов в потоке , следует вызвать метод count (). • Чтобы получить поток, содержащий только однозначные элементы , нужно вызвать метод distinct (). • Чтобы создать поток, содержащий указанный ряд элементов, следует вое· пользоваться методом о f ( ) . И последнее замечание: потоковый API является эффекти вным дополнением Java. Этот прикладной программный интерфейс, скорее всего, будет со временем расширен дополнительными фун кциональными возможностями. Поэтому реко­ мендуется периодически заглядывать в документацию на потоковый API.
30 Регулярные выражения и другие пакеты Первоначально язык Java включал восемь пакетов, называемых баз овым АР/. С каждым последующим выпуском этот API пополнялся новыми пакетами. Ныне API Java содержит большое количество пакетов. Многие из них являются специ­ ализированными и не рассматриваются в данной книге. Те м не менее в этой гла­ ве будуr рассмотрены пакеты java . util . regex, j ava . lang . reflect, j ava . rmi и j ava . text, поддержи вающие обработку регулярных выражений, рефлексию, уд аленный вызов методов и форматирование текста соответственно. А в конце гл авы будет представлен новый прикладной программный интерфейс API даты и времени, входящий в пакет jav a.time , и подчиненные ему подпакеты. Пакет реzулярн'Ых вь�ражений позволяет выполнять сложные операции сопо­ ставления с шаблонами. Он подробно рассматривается в этой гл аве на многочис­ ленных примерах его применения. Рефлек сией называется способность программ­ ного обеспечения к самоанализу. Рефлексия является неотъемлемой частью тех­ нологии Jаvа Beans , которая рассматривается в гл аве 37. Удаленный вызов методов (Remote Method Invocation - RМI) позволяет создавать на Java прикладные про­ граммы, распределяемые на нескольких машинах. В этой гл аве представлен про­ стой пример построения архитектуры "клиент-сервер" с использован ием уд ален­ ного вызова методов. Возможности форматирования текста, доступные в пакете java . text, находят немало примеров применения . И наконец, новый API даты и времени предлагает современный подход к обработке даты и времени. П акеты из базового API В табл . 30.1 перечислены все пакеты из базового API, доступные на момент на­ писания данной книги в пространстве имен java, с указанием их назначения. Таблица 30.1 . Пакеты иэ базового API Пакет java.applet java.awt java.awt.color java.awt.datatransfer Основное назначение Поддерживает построение аплетов Предоставляет средства для построения ГПИ Поддерживает цветовые пространства и профили Осуществляет обмен данными через системный буфер обмена
1090 Часть 11. Библиотека Java Пакет java.awt.dnd java .awt . event java.awt. font java.awt.qeoa java.awt.ila java .awt.ila. spi java.awt.i.mag& java .awt . ila age .renderaЫe java.awt.print java.Ьeans java.Ьeans.Ьeancontext java .io java.lanq java.lanq.an n otation java.lanq.inetru.ant java.lang. invoke java.lanq.ll l&Da.g8 88n t java.lanq.ref java.lanq.retlect java.шath java.net java .nio java .nio . chan n els java.nio . chan n els .spi java .nio . chareet java.nio . charset .spi java.nio . file java .nio . file . attri.Ьute java.nio . file .spi java .rmi. java.rmi .activation java.rmi. .dqc ПродолжениетаШ. 30.1 Основное назначение Поддерживает операции перетаскивания Организует обработку собьrгий Представляет различные типы шрифгов Позволяет работать с геометрическими qюрмами Позволяет вводить японские, китайские и корейские символы в компоненты редактирования текста Поддерживает альтернативные устройства ввода Организует обработку изображений Поддерживает независимое воспроизведение изображений Поддерживает общие возможности печати Позволяет создавать компоненты программного обеспечения Предоставляет исполняющую среду для компонентов Java Beans Организует ввод-вывод данных Предоставляет базовые фун кциональные возможности Поддерживает аннотации (метаданные) Поддерживает инструментальные средства для разработ­ ки программ Поддерживает динамические языки Поддерживает управ.ление исполняющей средой Позволяет взаимодействовать с системой сборки ммусора" Анализирует код во время выполнения Организует обработку больших целых и десятичных чисел Поддерживает работу в сети Пакет верхнего уровня для классов системы ввода-вывода NIO. Инкапсул ирует буфера Инкапсулирует каналы, применяемые в системе ввода-вы­ вода NЮ Предостав.ляет поставщики услуг для каналов Инкапсулирует наборы символов Предоставляет поставщики услуг для наборов символов Поддерживает систему ввода-вывода NIO для файлов Поддерживает атрибуты файлов в системе ввода-вывода NIO Предоставляет поставщики услуг системы ввода-вывода NIO для файлов Обеспечивает удал енный вызов методов Активизирует постоянные объекты Управляет распределенной сборкой ммусора"
Глава ЗО. Регулярн ые вы ражен ия и другие пакеты 1091 Пакет java.rшi.regiat:жy java.rшi .иner java.вecurity java.security . acl java. security .cert java.нc:urity . intedacea java. security.spec java. sql java.text java.text.spi java.tiшe java.tiшe.chrono java. ti.JDl l .foX1 1& t java.tiJDe.teaporal java.tiшe.zone java.util java.util . concurrent java.util .oonou.r ren t.ataaic java.util . conaur ren t.lockв java.util . jar java.util . logqing java.util .prefв java.util .regex Java.util.spl java.util . zip Окончаниета6А.30.1 Основное наэначение Оrображает имена на ссылки, кагорые делаются на уда­ ленные объекты Поддерживает удаленный вызов методов Организует обрабагку сертификатов, ключей, сверток, подписей и выполнение других функций, связанных с со­ блюдением безопасности Поддерживает списки управления доступом Выполняет синтаксический аналиэ и управление сертифи­ катами Определяет интерфейсы для ключей DSA (Digital Signature Algorithm - алгоритм цифровой подписи ) Определяет параметры ключей и алгоритмов Организует взаимодействие с базой данных SQL (Structured Query Language - яэык структурированных эа­ просов) Организует форматирование и поиск текста, а таюке мани­ пулирование им Предоставляет поставщики услуг для классов форматиро­ вания текста иэ пакета java. tezt Обеспечивает основную поддержку нового API даты и вре­ мени (добавлен в версии JDK 8) Подцерживает другие календари, кроме григорианского (добавлен в версии JDK 8) Поддерживает форматирование даты и времени (добав­ лен в версииJDК 8) Поддерживает расширенные функции обработки даты и времени (добавлен в версииJDК 8) Поддерживает часовые пояса (добавлен в версииJDK 8) Содержит общие утилиты Поддерживает утилиты парал л елизма Поддерживает атомарные (т.е . неделимые) операции над переменными, не прибегая к блокировкам Поддерживает синхрониэированные блокировки Создает и читает архивные JАR-файлы Поддерживает прагоколирование сведений о выполнении программы Инкапсулирует сведения о п0ЛЬ30вательских настройках Поддерживает обрабагку регулярных выражений Предоставляет поставщики услуг для вспомогательных классов из пакета java.util Читает и записывает сжатые и несжатые архивные файлы форматаZIР
1092 Часть 11. Библиоте ка Java Обра ботка р е гул ярных выр ажений В пакете java . util . regex Поддерживается обработка регулярных выраже­ ний. Употребляемый здесь термин регулярное въtражение обозначает строку, описы­ вающую последовательность символов. Это общее описание называется шаtiлоном и может быть впоследствии использовано для поиска совпадений в других после­ довательностях символов. В регулярных выражениях допускается определять ме­ тасимвол ы, наборы символов и различные кванторы. Та ким образом, можно за­ дать регулярное выражение, представляющее общую форму, совпадающую с раз­ ными конкретными последовательностями символов. Обработка регулярных выражений поддерживается двумя классами: Pattern и Mat che r. Эти классы действуют совместно. Регулярное выражение определяет­ ся в классе Pa ttern, а сопоставление последовательности символов с шаблоном осуществляется средствами класса Ma tcher. Класс Pattern В классе Pa ttern конструкторы не определяются . Вместо этого для составле­ ния шаблона вызывается фабричный метод compile (). Ниже приведена одна из общих форм этого метода. static Pattern coшpile ( Strinq •аблон) Здесь параметр ша бл он обозначает регулярное выражение, которое требуется использовать. Метод compile () преобразует в шаблон символьную строку, опре­ деляемую параметром ша бл он, для сопоставления средствами класса Ma tcher. Этот метод возвращает объект типа Pa ttern, содержащий шаблон. Как только объект типа Р а t t е rn будет получен, его можно использовать для соз­ дания объекта типа Ма tcher. Для этого вызывается фабричный метод ma tcher (), определяемый в классе Pattern. Ниже приведена его общая форма. Мatcher matcher ( CharSequence C!l'p()Xli) Здесь параметр строка обозначает последовательность символов, сопостав­ ляемую с шаблоном и называемую входной последователъ11,остъю. В интерфейсе CharSequence определяется набор символов, доступных только для чтения. Среди прочих, он реализуется в классе String. Та ким образом , мeтoдy ma tcher () можно передать символьную строку. Класс Маtcher У этого класса отсутствуют конструкторы. Вместо этого для создания объекта класса Ма tcher вызывается фабричный метод ma tcher (), определяемый в классе Pattern, как пояснялось выше. Как только объект класса Ma tche r будет создан , его методы можно использовать для выполнения различных операций сопостав­ ления с шаблоном.
Гл ава 30. Р е гул ярные выражения и другие пакеты 1093 Самым простым для сопоставления с шаблоном является метод ma tches (), который просто определяет, совпадает ли последовательность символ ов с шабло­ ном. Общая форма этого метода приведена ниже. boolean шatches О Этот метод возвращает логическое значение true, если последовательность символов совпадает и шаблоном, а иначе - логическое значение false. Следует, однако , иметь в виду, что с шаблоном должна совпадать вся последовательность символов, а не только ее часть (т.е . подпоследовательность) . Чтобы определить, совпадает ли с шаблоном подпоследовательность из вход­ ной последовательности символов, следует вызвать метод find ().Ниже приведе­ на одна из его общих форм. Ьoolean find О Этот метод возвращает логическое значение true, если подпоследователь­ ность совпадает с шаблоном, а иначе - логическое значение false. Метод find ( ) можно вызывать неоднократно, чтобы находить все совпадающие подпоследова­ те льности . При каждом вызове метода find () сравнение начинается с того места, где было завершено предыдущее сравнение. Символьную строку, содержащую последнюю совпавшую последовательность , можно получить, вызвав метод g roup ( ) . Ниже приведена одна из его общих форм. String group () Этот метод возвращает совпавшую символьную строку. Если ни одного совпа­ дения не обнаружено, генерируется исключение типа IllegalStateException. Вызвав метод start ( ), можно получить индекс текущего совпадения во входной последовательности символов, а индекс , следующий после текуще­ го совпадения, - вызвав метод end ( ) . Ниже приведены общие формы этих ме­ тодо в. В отсутствие совпадений оба эти метода генерируют исключение типа IllegalStateExcept ion. int start () int end () Каждую совпавшую последовательность символов можно за менить другой по­ следовательностью, вызвав метод replaceAl l (). Его общая форма выглядит сле­ дую щим образом: Strinq replaceAll ( String нова.v_с!rроха) где параметр нов ая_ строка определяет новую последовательность символов, ко­ торая будет заменять последовательности , совпавшие с шаблоном. Обновленная входная последовательность будет возвращена в виде символ ьной строки. Синта кс ис регуля рных выражений Прежде чем продемонстрировать применение классов Pattern и Matche r на практике , следует пояс нить, каким образом составляется регулярное выраже­ ние. Хотя ни одно из правил составления регулярных выражений нельзя назвать
109, Часть 11. Библиотека Java сложным, их очень много, и поэтому описать полностью все эти правила в одной гл аве просто невозможно. Те м не менее ниже описываются некоторые из наибо­ лее распространенных синтаксических конструкций регулярных выражений. В общем, регулярное выражение состоит из обычных символов, классов симво­ лов (наборов символов), метасимволов и кванторов. Обычный символ сопостав­ ляется в исходном виде. Та к, если шаблон содержит пару символов 11 ху11 , то с этим шаблоном может совпасть только входная последовательность 11 ху11 • Символы вроде новой строки и табуляции указываются с помощью стандартных управля­ ющих последовательностей, начинающихся со знака обратной косой черты (\). Например, символ новой строки обозначается управляющей последовательно­ стью \n. В терминологии регулярных выражений обычный символ иначе называ­ ется литералом. Класс символов является набором символов. Класс символов можно задать, заключив символы этого класса в квадратные скобки. Например, класс символов [wxyz] совпадает с символами w, х, у или z. Чтобы задать обратный набор симво­ лов, перед ними следует указать знак ". Например, класс символов [ лwxyz ] совпа­ дает с любым символом, кроме w, х, у и z. Диапазон символов указывается с помо­ щью дефиса. Та к, класс символов [ 1-9] совпадает с цифрами от 1 до 9. Метасимволом служит знак точки (.), совпадающий с любым символ ом. Та ким образом, шаблон 11 • 11 , состоящий только из знака точки , будет совпадать с любой из следующих (и других) входных последовательностей: 11А ", 11 а", 11 х" и т.д. Квантор определяет, сколько раз совпадает выражение. В табл. 30.2 перечисле­ ны кванторы, применяемые в регуля рных выражениях. Та блица 30.2. Кванторы реrу11ярных выражений Квантор Описание + * ? Обозначает совпадение один раз или больше Обозначает совпадение нуль или раз больше Обозначает совпадение нуль или один раз Например, шаблон 11 х + 11 будет совпадать с последовательностями симво­ лов 11 х11, 11 хх 11, 11 ххх " и т. п. И наконец, следует иметь в виду, что если регуляр­ ное выражение составлено неверно, то будет сгенерировано исключение типа PatternSyntaxException. Примеры, демо нстрирующие совпадение с ша бл оном Чтобы стало понятнее, каки м образом происходит сопоставление с шаблоном в регулярном выражении, рассмотрим несколько примеров. В первом приведен­ ном ниже примере осуществляется поиск совпадения с литеральным шаблоном. 11 Пример простого сопоставления с шаблоном import jav a.util . regex .*; class Re gExpr { puЬlic static void main (St ring args[] ) { Pat tern pat ;
Глава 30. Регулярные выражения и другие пакеты Ma tcher ma t; boolean found ; pat = Pattern . compile ("Java " ); mat = pat .matcher ( "Java" ) ; found = ma t .matches () ; // про верить на совпадение System. out . println ("Пpoвepкa совпадения Java с Java:") ; if ( found ) System.out . println ( "Coвпaдaeт ") ; else System. out . println ("He совпадает ") ; System. o ut . println (); System. out .println ("Пpoвepкa совпадения Java с Java 8:") ; ma t = pat .matcher ("Java 8") ; // создать новый сопоставитель 11 с шаблоном found = ma t .matches () ; // проверить на совпадение if ( found ) System.out . println ( "Coвпaдaeт ") ; else System. out .println ("He совпадает ") ; Ниже приведен результат, выводимый дан ной программой. Пр оверка совпадения Java с Java : Совпадает Пр оверка совпадения Java с Java 8: Не совпадает 1095 Проанализируем эту программу. Сначала в ней создается шаблон, состоящий из последовательности символов "Java ". Затем для данного шаблона создается объект ти па Ma tcher с входной последовательностью "Java ". Далее вызывается мeтoд ma tches (),спомощью которого определяется, совпадает ли входная после­ довательность с шаблоном. Входная последовательность совпадает с шаблоном, и поэтому метод ma tche s () возвращает логическое значение true. После этого создается новый объект типа Ma tcher с входной последовательностью "Java 8 " и снова вызывается метод ma tche s ().В этом случае входная последовательность отличается от шаблона, и поэтому совпадение не обнаруживается. Напомним, что метод ma tche s ( ) возвращает логическое значение true только в том случае, если входная последовательность полностью совпадает с шаблоном. Он не возвратит логическое значение true только потому, что с шаблоном совпадает подпоследо­ вател ьность, а не вся входная последовательность в целом. Чтобы выяснить, содержит ли входная последовательность подпоследователь­ ность , совпадающую с шаблоном, достаточно вызвать метод f ind ( ) . Рассмотрим следующий пример программы: 11 Исполь зовать ме тод find () дл я нахожд ения подпоследовательности import java.util . regex .*; class RegExpr2 { puЫ ic static vo id ma in ( String args[] ) { Pattern pat Pattern . compile ("Java " ); Matcher ma t = pat .matcher ("Java 8") ; System. o ut . println l"П oиcк Java в Java 8:") ;
1096 Часть 11. Библ иоте ка Java if (mat .find() ) System. o ut . println ( "Подпоследовательность найдена" ) ; else System.out . printlп ( "Coвпaдeния отсутст вую т") ; Ниже приведен результат, выводи мый данной программой. В данном случае метод find ( ) осуществляет поиск подпоследовательности "Java " во входной по­ следовательности символов. Поиск Java в Java В: Подпоследовательность найдена Метод find ( ) можно использовать для поиска во входящей последователь· ности повторяющихся совпадений с шаблоном, поскольку каждый вызов метода find () начинается с того места, где был завершен предыдущий. Та к, в следующем примере программы обнаруживаются два совпадения с шаблоном "test ": 11 Исполь зовать ме тод find () для нахождения несколь ких 11 подпоследовательностей симв олов irnport java.util . regex .*; class RegExp rЗ 1 puЬlic static void main (String args[] ) { Pat tern pat = Pattern . compile ("test") ; Matcher mat = pat .matcher ( "test 1 2 3 test" ); while (mat . fiпd ()) { System. out .println ( "Подпоследовательность test найдена по индексу " + mat .start () ); Ниже приведены результаты, выводимые дан ной программой . Подпоследовательность test найде на по индексу О Подпоследовательность test найде на по индексу 11 Как следует из результатов, в данной программе обнаружены два совпадения. Для получения индекса каждого совпадения в ней используется метод start () . При менение метасимвопов и кванторов В предыдущем примере программы была продемонстрирована общая методика применения классов Pa ttern и Ma tcher, но она не раскрывает полностью их воз· можности. Подлинное преимущество , которое дает обработка регулярных выра· жен ий, невозможно ощуrить, не применяя метасимволы и кванторы. Рассмотрим сначала следующий пример, где квантор + применяется для сопоставления с лю­ бой произвольной последовательностью символов W: 11 Приме нить квантор import java.util . regex .*; class RegExp r4 1 puЫ ic static void ma in (String args[] ) {
Гл ава ЭО. Регулярные выраже н ия и другие пакеты Pattern pat = Pattern . compile ("W+ ") ; Matche r mat = pat .matcher ("W WW WWW " ); while (mat .find() ) System. out . println ("C oвпaдeниe : "+ma t .group() ); 1097 Ниже приведе ны результаты , выводимые данной программой. Как следует из результатов, шаблон "W+ " в регулярном выражении совпадает с последовательно­ стью символов W любой длины. Совпадение : W Совпадение : WW Совпадение : WW W В приведенном ниже примере программы метасимвол используется для состав­ ления шаблона, который должен сопоставляться с любой последовательностью, начинающейся с символа е и оканчивающейся символом d. Для этой цели в шабло­ не указывается не только метасимвол точ ки, но и квантор +. 11 Приме нить ме тасимвол и квантор import jav a.util . regex .*; class RegExpr5 { puЬlic static void ma in (String arg s[] ) { Pattern pat = Pattern . compile ("e.+d ") ; Matcher mat = pat .match er ("e xtend cup end tаЫ е") ; while (mat .find() ) System. out . println ("C oвпaдeниe : "+mat . group()); Приведенный ниже результат выполнения этой программы может показаться неожиданным. Совпадение : extend cup end При сопоставлении входной последовательности с шаблоном было обнаружено только одно совпадение. И это самая длинная последовател ьность, которая начи­ нается с символа е и оканчивается символом d, хотя предполагалось обнаружить два совпадения: "extend " и "end". Более длинная последовательность была обна­ ружена потому, что по умолчанию метод f ind ( ) обнаруживает совпадение с шабло­ ном самой длинной последовател ьности. Та кое совпадение называется стр огим. Но можно задать и нестрогое совпадение, если ввести в шаблон квантор ? , как показано в приведенной ниже версии программы из предыдущего примера. В итоге получа­ ется шаблон для сопоставления с самой короткой входной последовательностью. 11 Приме нить квантор ? import java.util . regex .*; cl ass RegExp rб { puЬl ic static vo id ma in (String args [] ) { 11 составить шаблон для нестрогого совпадения Pattern pat Pattern . compile ("e.+?d") ; Matcher mat = pat .matcher ("e xtend сир end tаЫ е") ;
1098 Часть 11. Библиотека Java whi le (mat .find() ) System. out . println ( "Coвпaдeниe : "+ma t . group() ); Ниже приведены результаты , выводимые данной программой. Совпадение : eкtend Совпадение : end Как следует из результато в, шаблон " е.+?d" совпадает с более короткой вход­ ной последовательностью, которая начинается с символа е и оканчивается симво­ лом d. Та ким образом, обнаруживаются два совпадения. Обращение с классами символов Иногда возникает потребность обнаружить совпадение с последовательно­ стью , состоящей из одного или нескольких произвол ьно расположенных сим­ волов, являющихся частью определенного набора символов . Например, чтобы обнаружить совпадение с целыми словами, придется искать совпадение с любой последовательностью букв алфавита. Это проще всего сделать с помощью класса символ ов, определяющего их набор. Напомним, чтобы создать класс символов, следует закл ючить сопоставляемые символы в квадратные скобки. Например, для сопоставления со строчными буквами от а до z служит класс символов [а- z ] . В следующем примере программы демонстрируется применение класса символ ов: 11 Применить класс симв олов import java.util . regex .*; class Re gEкp r7 { puЫ ic static void ma in (St ring args[] ) { 11 составить шаблон для сопоставления со словами , 11 набранными строчными буквами Pattern pat = Pattern . compile ("[a-z] +" ) ; Matcher mat = pat .matcher ("t his is а test.") ; wh ile (rnat .find() ) System. out .println ( "Coвпaдeниe : "+mat . group()); Ниже приведены результаты выполнения данной программы. Совпадение : this Совпадение : is Совпадение : а Совпадение : test П ри менени е метода replaceAl l () Метод replaceAl l (), определяемый в классе Ma tche r, позволяет выполнять полноценные операции поиска и замены, в которых используются регуля рные выражения. Та к, в следующем примере программы каждая входная последователь­ ность "Jon " заменяется выходной последовательностью "Eric": 11 Приме нить ме тод replac&All () import java.util . regex .*;
Гл ава 30. Реrулярные в ыраже н ия и другие пакеты class RegExpr8 { puЬl ic static void ma in {String args[] ) { String str = "Jon Jonathan Frank Ken Todd" ; Pattern pat = Pattern . compile {"Jo n.*? ") ; Matcher mat = pat .matcher (str) ; System.out . println { "Иcxoднaя последовательность : "+str) ; str = mat . replaceAll { "Eric ") ; System. out .println { "Иэмeнeннaя последователь ность : "+str) ; Ниже приведены результаты выполнения данной программы. Исходна я последователь ность : Jon Jonathan Frank Ken Todd Измененная последовательност ь: Eric Eric Frank Ken Todd 1099 Регулярное выражение 11 Jon . * ? 11 выполняет сопоставление с любой символь­ ной строкой, начинающейся с последовательности символов Jon, после которой следует нуль или больше символов, и оканчивающейся пробелом, поэтому его можно применить для сопоставления и замены обоих именjоn иjonathan именем Eric. Та кую замену нельзя было бы произвести без сопоставления с шаблоном. Применение метода split () С помощью метода split (), определяемого в классе Patte rn, можно свести входную последовательность к отдел ьным лексемам. Ниже приведена одна из об­ щих форм этого метода. Strinq [] split (CharSequence с�.а:а) Метод split () обрабатывает входную последовательность, обозначаемую па­ раметром строка , сводя ее к отдел ьным лексемам с разделителями, указываемыми в шаблоне. Та к, в следующем примере программы обнаруживаются лексемы, раз­ деляемые пробелами, запятым и, точками и знаками восклицания: 11 Исполь зовать ме тод split () import java.util . regex .*; class RegExpr9 { puЫ ic static void ma in (String args[] ) { 11 составить шаблон дл я сопоставления со словами , 11 набранными строчными буквами Pattern pat = Pattern .compi le (" [ ,. !]") ; String strs [] = pat . split ( "one two , alpha9 12 !done.") ; for (int i=O; i < strs. length; i++) System.out . println ( "Cлeдyющaя лексема : "+strs[i] ); Ниже приведены результаты выполнения данной программы.
1100 Ч асть 11. Библиотека Java Следующа я ле ксема : one Следующа я лексема : two Следующа я ле ксема : alpha9 Следующа я ле ксема : 12 Следующа я ле ксема : done Как следует из результатов, входная последовательность сводится к отдель· ным лексемам. Обратите внимание на отсутствие раздел ителей в выводимых ре· зультатах. Два варианта сопоста вл е ния с ш аблоном Несмотря на то что описанные способы сопоставления с шаблонами отлича· ются высокой степенью гибкости и высокой производительностью, имеются два других варианта, которые могут оказаться полезными в определенных обстоя· тельствах. Та к, если требуется только одноразовое сопоставление с шаблоном, то для этой цели можно воспользоваться методом ma tche s (),определяемым в клас· се Ра t tern. Ниже приведена общая форма этого метода. static boolean шatches (Strinq •аб.лон , CharSequence C!l'pOJCa) Метод ma tches () возвращает логическое значение true, если заданный ша блон совпадает с указанной строкой, а иначе - логическое значение false. Этот метод автомати чески компилирует заданный ша блон, а затем ()существляет поиск на совпадение с ним. Для неоднократного применения шаблона вызов ме· тoдa ma tche s () оказывается менее эффективным , чем отдел ьная компиляция ша· блона и сопоставление с ним с помощью методов, определяемых в классе Ма tche r, как пояснялось ранее. Сопоставление с шаблоном можно также выполнить с помощью метода ma tche s (),реализуемого в классе String. Ниже приведена общая форма этого метода. Если вызывающая строка совпадает с регулярным выражением, обозна· чаемым параметром ша блон, то метод ma tche s () возвращает логическое значе· ние true , а иначе - логическое значение false. Ьoolean ma.tches (Strinq 1rаб.лон) Дальней шее изучение регулярных выражений Представленный выше краткий обзор регулярных выражений лишь отчасти раскрывает их подлинный потенциал. Синтаксический анализ, манипул ирование и разбиение текста на лексемы является неотьемлемой частью программирования, и поэтому подс истема регулярных выражений вJava может стать уд обным и полез· ным инструментальным средством. В связи с этим рекомендуется уд елить время дальнейшему изучению свойств регулярных выражений. Поэкспериментируйте с различными видами шаблонов и входных последовательностей. Уя снив, каким образом осуществляется сопоставление с шаблонами в регулярных выражениях, вы найдете подобный прием вполне пригодным для решения многих задач про· граммирования.
Глава 30. Регулярн ые выражения и другие пакеть1 1101 Рефлекси я Рефлексия - это способность программного обеспечения к самоанализу. Та кая способность обеспечивается пакетом j ava . lang . re f lect и эл ементами класса Class. Рефлексия является важным языковым средством, особенно для компо­ нентов Java Beans. С ее помощью можно динамически анализировать компоненты программного обеспечения и описывать их свойства во время выпол нения , а не компиляции. Например, с помощью рефлексии можно определить, какие мето­ ды, конструкторы и поля поддерживаются в конкретном классе. Обсужде ние темы рефлексии было начато в гл аве 12, а в этой гл аве оно будет продолжено. В состав пакета j ava . lang . re flect входит несколько инте рфейсов. Особый интерес представляет интерфейс MemЬer, в котором определяются методы, позво­ ляющие получать сведения о поле, конструкторе или методе отдел ьного класс а. В этом пакете имеется также восемь классов. Все они перечислены в табл. 30.3 . Табл ица 30.З. Кл ассы из пакета java . lanq . reflect Класс Основное назначение Acc:eaai.ЬleOЬjec:t Позволяет обходить стандартные проверки управления доступом Array Позволяет динамически создавать массивы и манипулировать ими Cons truc:tor Предоставляет сведения о конструкторе Field Предоставляет сведения о поле Мethod Предоставляет сведения о методе Мodifier Предоставляет сведения о модификаторах доступа к классу и его членам Paraшeter Предоставляет сведения о параметрах (добавлен в версииJDК 8) Proxy Поддерживает динамические прокси-классы Reflec:tPeraiasion Разре ш ает рефлексию закрытых или защищенных членов класса В приведенном ниже примере прикладной программы демонстрируется про­ стой вариант применения свойств рефлексии в Java. Эта программа выводит на экран конструкторы, поля и методы из класса j ava . awt . Dimens ion. Сначала в этой программе вызывается метод forName () из класса Class для получения объекта класса j ava . awt . Dimension. Как только он будет получен, вызываются методы getConstructors (), ge tFileds () и getMethods () для анализа объекта этого класса. Они возвращают массивы типа Constructor, Field и Me thod, содер­ жащие сведе ния об этом объекте. В классах Constructor, Field и Me thod определяется ряд методов, которые можно использовать для получения сведений об объекте. Вам придется изучить эти классы самостоятел ьно, но в каждом из них поддерживается метод toString (). Как показано в данной программе , указывать объекты типа Constructor, Field и Me thod в качестве параметров метода println () совсем не трудно. 11 Пр одемонстрировать приме нение рефлексии import java .lang . re flect .*; puЫ ic clas s Re flectionDemo l { puЫic static void ma in (String args[] )
1102 Часть 11. Библиоте ка Java try Clas s<?> с=Class . forName ("java . awt . Dimension") ; System.out . println ("Koнcтpyктopы: ") ; Constructor constructors [J = c.getConstructors () ; for (int i = О; i < constructors . length; i++) { System. out . println (" "+constructors [i] ); System. out . println ("П oля :"); Field fields [J = c.getFields () ; for(int i = О; i < fields.length; i++) { System.out . println (" "+fields[i] ); System.out . println ( "Me тoды: ") ; Method me thods [J = c.getMethods () ; for(int i = О; i < methods .length; i++) { System. out . println (" "+me thods [i] ); } catch ( Exception е) { System.out . println ( "Иcключ eниe : " + е) ; Ниже приведен примерный результат, выводимый данной программой (у вас он может оказаться иным) . Конструкторы : puЫ ic java.awt . Dimension (int , int ) puЫ ic java .awt . Dimension () puЫic java.awt . Dimension (java .awt . Dimension) Поля : puЫ ic int java .awt . Dimension . width puЫic int java . awt . Dime nsion . height Ме тоды : puЫic int java .awt . Dimension . hashCode () puЫ ic boolean java.awt . Dimension . equals (java . lang .Obj ect ) puЫic java.lang . String java .awt . Dimension .toString () puЬl ic java.awt . Dimension java .awt . Dimension.getSize () puЬl ic vo id java . awt . Dimension .setSize (douЫe , douЬle) puЫic void java.awt . Dimension .setSize (java .awt . Dimension ) puЫic void java.awt . Dimension .setSi ze (int, int ) puЫ ic douЫ e java .awt . Dimension . getHe ight () puЫic douЫ e java . awt . Dime nsion . getWidth () puЫ ic java .lang.Obj ect java.awt . geom.Dimension2D . clone () puЬlic void java.awt . geom . Dimension 2D. setSize (java . awt . geom. Dimension2D) puЫ ic final native java.lang .Class java.lang .Obj ect . getClass () puЫic final native void java . lang .Obj ect .wait ( long ) throws java . lang .In terruptedExcept ion puЫic final void java . lang .Object .wa it () throws jav a.lang .In terruptedException puЫic final void java . lang.Obj ect .wait ( long , int ) throws java.lang .InterruptedException puЫ ic final native void java.lang .Object .notify ( ) puЬl ic final native vo id java .lang .Obj ect . noti fyAll () В следующем примере программы возможности рефлексии в Java используют­ ся для получения открытых методов класса. Сначала в этой программе реализует­ ся класс А. Метод getClass () вызывается по ссылке на объект этого класса и воз-
Глава 30. Регулярные выражения и другие пакеты 1103 вращает объект типа Class для описания класса А. Метод ge tDeclaredMe thods ( ) возвращает массив объектов типа Method, описывающий только методы , объяв­ ленные в классе А. В этот массив не включаются методы, унаследованные от супер­ классов, например от класса Ob j ect. После этого обрабатывается каждый элемент массива me thods. В частности , метод ge tModi fiers () возвращает значение типа int, содержащее признаки , описывающие модификаторы доступа, применяемые к этому элементу. В клас­ се Modi fier предоставляется ряд методов типа isX, перечисленных в табл . 30.4 и предназначенных для проверки заданного значения . Например, статический метод isPuЫ ic () возвращает логическое значение true, если в его аргумент включается модификатор доступа puЫ ic, а иначе - логическое значение fal se. Табл ица 30.4. Методы из класса Мodifier, определяющие модификатор доступа Методы static Ьoolean isAЬs tract (int sвaverure) •tatic Ьoolean is!'inal (int sвaveвJre) static Ьoolean isinterface (int .sвavar r жe) static Ьoolean isNative (int эваvевже) static Ьoolean isPrivate (int sваvеаже) static Ьoolean isProteoted (int sвaveвJre) static Ьoolean isPuЫic (int sваvеаже) static Ьoolean isStatic (int sваvевже) static Ьoolean isStrict (int sвaverure) statio Ьoolean isSynchronized (int sиаvевже) Описание Возвращает логическое значение true, если заданное sвaverure имеет установленный признак aЬstraot, а иначе - логическое значение false Возвращает логическое значение true , если задан ное .эваvевJrе и меет установленный признак final, а ина­ че - логическое значение fal se Возвращает логическое значение true, если заданное .эваvевже имеет установленный признак interface, а иначе - логическое значение false Возвращает логическое значение true, есл и заданное эваvеюrе имеет установленный признак na tive, а ина­ че - логическое значение false Возвращает логическое значение true, если заданное .эвaverure имеет установленный признак private, а ина­ че - логическое значение false Возвращает логическое значение true , если заданное sваvевже имеет установленный признак proteoted, а иначе - логическое значение false Возвращает логическое значение true, если заданное .эваvевJrе имеет установленн ый признак puЫic, а ина­ че - логическое значение false Возвращает логическое значение true, если заданное sваvевже имеет установленный признак static, а ина­ че - логическое значение fal se Возвращает логическое значение true, если заданное sваvевже имеет установленный признак strict, а ина­ че - логическое значение false Возвращает логическое значение true , если заданное sваvевже имеет установленный признак synchronized, а иначе - логическое значение fal se
110д Часть 11. Библ иотека Java Окончание таtИ. JQ. 4 Методы Описани е static Ьoolean isTran sient (int .sвav.. . e) Возвращает логическое значение true , если заданное sвav.. . e имеет установленный признак transient, а иначе - логическое значен ие false static Ьoolean isVolatile (int sвav.. . •) Возвращает логическое значение true, если заданное sвaveвJre имеет установленный признак vo latile, а иначе - логическое значение false Если метод оказывается открытым, то его имя получается с помощью метода getName () и затем выводится на экран. Ниже приведен исходный код из данного примера программы. 11 Показать открытые ме тоды import java .lang . reflect .*; puЫ ic class Re flectionDemo2 puЫ ic static vo id ma in (String args[] ) try { Аа=newА(); Clas s<?> с= a.getClass () ; System.out . println ("O ткpытыe ме тоды: ") ; Method me thods [] = c.getDeclaredМethods () ; for (int i = О; i < methods .length; i++) { } int modi fiers = me thods [i] .getModi fiers (); if (Modi fier. isPuЫ ic (modi fiers )) { System. out . println (" "+me thods [i] .getName()); catch ( Exception е) { System.out . println ("И cключeниe : " + е) ; class А { puЫic void al () } puЫic vo id а2 () } protected void аЗ () } private void а4 () { } Эта программа выводит следующий результат: Открытые ме тоды : al а2 Класс Modifier содержит также ряд статических методо в, возвращающих тип модификаторов, которые могут быть применены к определенному типу элемента программы. Ниже приведены общие формы этих методов. stati.c int classНodifiers () static int constructorNodifiers () static int fieldМodifiers ()
Гл ава 30. Регулярные выражения и другие пакеть1 static int interfaceМodifiers () static int шethodМodifiers () static int parameterМodifiers () (добавлен в версии JDK 8.) 1105 Например, метод me thodModifiers () возвращает модификаторы, кото рые могут быть применены к методу. Каждый метод возвращает признаки , скомпо­ нованные в виде значения типа in t и указывающие допустимые модификато­ ры. Значения модификаторов определяются константами PROTECTED, PUBL IC, PRIVAT E, STATIC, FINAL и прочими, определенными в классе Modifier. Удал енный вызов методов Механизм уд аленного вызова методов (RМI) позволяет объекту Java, выполня­ ющемуся на одной машине, вызывать метод объектаjаvа, выполняющегося на дру­ гой машине. Это очень важный механизм, поскольку он позволяет разрабатывать распределенные приложения. Подробное обсуждение механ изма RМI выходит за рамки данной книги , но в приведенном ниже упрощенном примере демонстриру­ ются основные принципы его действия . П ростое приложение ·· кл и ент-сервер" , использующее механизм RMI В этом разделе представлена пошаговая процедура разработки простого при­ ложение "клиент-сервер" с использован ием механизма RМI. Сервер получает запрос от клиента, обрабатывает его и возвращает результат. В данном примере приложения запрос состоит из двух чисел. Сервер суммирует их и возвращает по­ лученный результат. Шаг 1.Ввод и компиляция исходного кода В рассматриваемом здесь приложении испол ьзуются четыре исходных файла. В первом файле , именуе мом AddS erverintf. java , определяется удал енный ин­ терфейс, предоставляемый сервером. Он содержит один метод, принимающий два параметра типа douЫe и возвращающий их сумму. Все удаленные интерфей­ сы должны расширять интерфейс Remote, входящий в пакет j ava . rmi . Никакие члены в интерфейсе Remo te не определяются. Его основное назначение - указать на то , что в инте рфейсе испол ьзуются удал енные методы. Все удаленные методы могут ге нерировать исключение типа RemoteException, как показан о ниже. import java.rmi.*; puЬlic interface Ad dServe rintf extends Remo te { douЫe add ( douЫe dl , douЫ e d2 ) throws RemoteException; Во втором исходном файле, AddServerimpl . java, реализуется удале нный ин­ терфейс . Реализовать метод add ( ) несложно . Уд аленные объекты обычно отно­ сятся к классам , расширяющим инте рфейс Un icastRemoteObj ect, как показано ниже . Этот интерфейс обеспечивает функциональные возможности, требующие­ ся для того , чтобы сделать доступными объекты на удал енных машинах.
1106 Часть 11. Библиоте ка Java import java . rmi .*; import java . rmi . server .*; puЫ ic cl ass Ad dServe rimpl extends Un icastRemoteObject implements AddSe rve rintf ( puЫ ic AddS erve rimpl () throws RemoteException ( } puЬlic douЫe add (douЫ e dl , douЫe d2 ) throws RemoteException return dl + d2; Тр етий исходный файл , AddServer . j ava, содержит гл авную программу для серверной машины. Ее основное назначение - обновлять реестр RМI на этой машине, как показано ниже. Это делается с помощью метода reЬind ( ) из клас­ са Naming, входя щего в пакет java . rm i. Этот метод связывает имя со ссылкой на объект. В качестве первого параметра в методе reЬind ( ) указывается символь­ ная строка, присваивающая серверу имя "AddS erv er", а в качестве второго пара­ метра - ссылка на экземпляр класса AddS erverlmpl. import java .net .*; import java.rmi.*; puЫ ic class AddServe r puЫ ic static vo id ma in ( String args[] ) { try { AddServerimpl addSe rve rimpl = new AddServerimpl (}; Nami ng .reЬind ( "AddServer ", addServe r lmpl ) ; catch (Exception е} { System.out . println ( "Иc ключeниe : "+е) ; В четвертом исходном файле , AddClient . java, реализуется клиентская часть распределенного приложения . Этому файлу требуются три аргумента командной строки. В первом из них указывается IР-адрес или имя серверной машины, а во втором и третьем параметрах - два суммируемых числа. Приложение начинается с формирования символьной строки в соответствии с синтаксисом URL. В этом URL используется протокол rmi . Символьная строка включает в себя IР-адрес или Имя сервера и подстроку "AddS erv er". Затем вызы­ вается метод loo kup ( ) из класса Naming. Этот метод принимает URL по протоко­ лу rmi в качестве единственного параметра и возвращает ссылку на объект типа Add Serverlntf. Все последующие вызовы удал енных методов могут быть направ­ лены этому объекту. Далее выводятся аргументы данной прикладной программы и вызывается уд аленный метод add ( ) , возвращающий результат суммирования , который затем выводится на экран , как показано ниже . Введя весь исходный код, воспользуйтесь компилятором javac, чтобы скомпилировать четыре созданных вами исходных файла. import java . rmi.*; puЫic class AddClient { puЫic static vo id main (String args[]) try ( String addSe rverURL = "rmi://" + args [OJ + "/AddServer"; AddSe rve rintf addServerintf = (AddServ erintf) Naming .loo kup ( addServerURL) ;
Глава 30. Реrулярные выражения и другие пакеты Systern.out . println ( "Пepвoe число : "+args[l] ); douЫ e dl = DouЫe . va lueOf (args[l]).douЫeValue (); Systern. o ut . println ("B тopoe число : "+args[2] ); douЫe d2 = DouЫe .valueOf (args[2] ).d ouЫeValue () ; Systern. out . println ("Cyм м a: "+addServerintf . add (dl, d2 ) ); catch ( Exception е) { Systern. out .println ( "Иcключeниe : "+е) ; Шаг 2. Соэда ние, если требуется, заглушки вручную 1107 В контексте RMI заглушка представляет собой объект J ava, находящийся на кли­ ентском машине. Назначение заглушки - представить те же самые интерфейсы, что и на удал енном сервере. Вызовы удал енных методов, инициируемые клиен­ то м, направляются заглушке, которая взаимодействует с остальными частями си­ стемы RМI для составления запроса, направляемого дистанционной машине. Уд аленный метод может принимать в качестве параметров значения прими­ ти вных типов или объекты. В последнем случае указываемый объект может иметь ссылки на другие объекты. Вся эти данные должны быть отпраалены удал енной машине. Следовательно, объект, передаваемый в качестве арrумента при вызове уд аленного метода, должен быть сериализирован и отправлен уд аленной маш ине. Как пояснялось в главе 20, при сериализации рекурсивно обрабатываются также все объекты, на которые делаются ссылки. Если клиенту требуется возвратить ответ, весь процесс происходит в обратном порядке. Следует иметь в виду, что сериализация и десериализация выполняются и при возвращении объектов клиенту. До версииJаvа 5 заглуш ки приходилось со:щавать вручную, используя компиля­ тор rmic, а в современных версиях Jаvа эта стадия уже необязател ьна. Но если вы работаете в устаревшей среде, то для создания заглуш ки можете воспользоваться компилятором rmic следующим образом: rai.cAd dS erveril lp l По этой команде создается файл AddS erver lmpl_Stub . class. Используя ком­ пилятор rmic, включите текущий каталог в переменную окружения CLAS SPAT H. Шаг 3. Установка файлов на серверной и клиентской машинах Скопируйте файлы AddClient .class, AddServerlmpl_Stub .class (если требуется) и AddS erver!ntf. class в каталог на клиентской машине, а файлы AddSe rver!ntf . class, AddS erver!mpl . class, AddServer !mpl_Stub .class (если требуется) и AddS erve r . class - в каталог на серверной машине. На заметку! Механизм RMI поддерживает технологии динамической загрузки классов, но они не применяются в рассматриваемом эдесь примере. Вместо этого все файлы, испол ьзуемые в приложениях "клиент-сервер", должны быть установлены вручную на соответствующих машинах.
1108 Часть 11. Библиоте ка Java Шаг 4. Запуск реестра RMI на серверной машине В комплектJDК входит угилита rшireg i s try , которая выполняется на стороне сервера. Она преобразует имена в ссылки на объекты. Сначала проверьте , вклю­ чен ли каталог, где находятся ваши файлы, в переменную окружения CLAS SPAT H. Затем запустите реестра RМI из командной строки по следующей команде : start rшireqi •try По завершении этой команды на экране появится новое окно. Это окно долж­ но быть открыто на весь период экспериментирования с рассматриваемым здесь примером приложения , использующего механизм RМI. Шаг 5. Запуск сервера Серверная часть рассматриваемого здесь приложения запускается из команд­ ной строки по следующей команде : java Ad d.S erver Напомним, что вклассеАddSеrvеrполучается экземплярклассаАddSеrvеr imрl, который регистрируется под именем " AddS erver" . Шаг 6. Запуск клиента Для выполнения клиентской части AddClient рассматриваемого здесь приложе­ ния требуется указать следующие три аргумента: имя или IР-адрес серверной маши­ ны и два числа, которые должны быть просуммированы. Клиентскую часть данного приложения можно вызвать из командной строки в одном из следующих форматов: java Ad d. Client eerverl 8 9 java Ad d Client 11 .12.13.14 8 9 В первом формате указывается имя сервера, а во втором формате - его IР­ адрес (11 .12 .13 .14). Расс матриваемое здесь приложение можно попытаться выполнить и без удал енного сервера. Для этого достаточно установить все части этого приложения на одном и том же компьютере и запустить сначала уrилиту rшiregistry , затем серверную часть AddS erver, а после этого клиентскую часть AddC lient следующим образом: java Ad d Client 127.0 .0.1 В 9 где 127 . О.О .1 обозначает адрес обратной связи локальной машины. Благодаря этому адресу можно испытать весь механизм RМI, не устанавливая сервер на уда­ ленной машине. Но в любом случае результат выполнения данного приложения будет следующим: Первое число : 8 Второе число : 9 Сумма: 17.О На заметку! Применяя механизм RMI на практике, вероятнее всего , придется уста новить на сер­ вере диспетчер безопасности.
Глава 30. Регулярные вь1ражен ия и другие пакеты Форматирование даты и времени средствами пакета java . text 1109 В пакете java . text предоставляются средства для форматирования, синтакси­ ческого анализа, поиска и манипулирования текстом. В этом разделе рассматри­ ваются два наиболее употребительных класса из этого пакета , предназначенных для форматирования даты и времени. Следует, однако , сразу же заметить, что в рассматриваемом далее новом API даты и времени предлагается современный подход к обработке даты и времени, который поддерживает также форматирова­ ние. Разумеется , рассматриваемые здесь классы будут еще некоторое время при­ меняться в унаследо ванном коде. Класс DateFormat Класс Da te Fo rma t является абстрактным и позволяет форматировать и ана­ лизировать дату и время . В частности , метод ge tDateinstance () возвращает экземпляр клас са Da teFormat, способный форматировать информацию о дате. Имеются следующие общие формы этого метода: static final DateFormat qe tDateins tance () stati c final DateForшat qetDateins tance (int ст•.1 18 ) static final DateForшat qe tDateins tance (int ст•лт. , Locale регианалыwе_настроi i юr) Параметр стиль может принимать значение одной из следующих констант: DE FAULT, SHORT , ME DI UM, LONG или FULL. Это константы типа int, определяемые в классе Da te Fo rma t. Они позволяют представить различные подробности , касаю­ щиеся даты. Параметр региональные_ на стройки принимает одну из статических ссылок, определяемых в классе Lo са 1 е. как поясняется в гл аве 19. Если параметры стиль и/или региона ль ные_ на стройки не указаны, то испол ьзуются значения , устанавливаемые по умолчанию. К числу наиболее употребительных из класса Da te Forma t относится метод forma t ().Унего имеется несколько перегружае мых форм, одна из которых при­ ведена ниже . final Strinq forшat (Date d) В качестве аргумента этого метода указывается объект типа Da te, представля­ ющий отображаемую дату. Метод forma t ( ) возвращает символьную строку. содер­ жащую отформатированную информацию о дате. В приведенном ниже примере программы демонстрируется форматирование сведений о дате. Сначала в этой программе создается объект класса Da te. В этом объекте хранятся сведе ния о текущих дате и времени. Затем сведения о дате вы­ водятся отформатированными в разных стилях и с учетом региональных особен­ ностей представления дат. 11 Продемонстрировать различные форма ты дат import java .text .*; impo rt java .util .*;
1110 Часть 11. Библиотека Java puЫ ic class Date Fo rma t Demo { puЫ ic static void ma in (String args []) { Date date = new Date (); DateForma t df; df = DateFo rmat . getDatelnstance ( DateFormat .SHORT , Locale . JAPAN) ; System.out . println ( "Яnoния : "+df . format (date) ); df = DateForma t .getDatelnstance ( DateFormat .MEDIUM, Locale . KOREA ) ; System.out . println ( "Kopeя : "+df . format (date) ); df = DateFormat . getDa telnstance ( DateFormat . LONG, Locale .UK) ; System.out . println ( "Beликoбpитaния : "+df . format (date) ); df = DateFormat .getDatelnstance ( DateForma t .FULL, Locale .US) ; System.out . println ("CШA : "+df . format (date )); Ниже приведен примерный результат, выводимый данной программой. Япония : 14/01/01 Корея: 2014. 1. 1 Великобритания : 01 Janua ry 2014 США: Saturday, January 1, 2014 Meтoд ge tTimelnstance () возвращает зкземnляркласса Dа tеFоrmа t, способный форматировать сведения о времени. Этот метод имеет следующие общие формы: static final DateFor.at 91 1 tTi.8e:tnstance () static final DateForiu.t qetTUleins tance (int с-.а.) static f'inal DateForшat qetTUleins tance (int с!!'ЖЛЪ , Loca1e per.OН&/U.Нl l' e_114C!l'p0Jix.) Параметр стиль может принимать значение одной из следующих констант: DE FAU LT, SHORT , ME DI UM, LONG и FU LL. Это константы типа int, определяемые в классе Da teFo rmat. Они позволяют представить различные подробности, ка· сающиеся времени. Параметр региональ ные_ на стройки принимает одну из ста­ тических ссылок, определяемых в классе Locale, как поясняется в гл аве 19. Если параметры стиль и/или региональные_ на стройки не указаны, то используются значения , устанавливаемые по умолчанию. В приведенном ниже примере программы демонстрируется форматирование сведений о времени. Сначала в этой программе создается объект класса Da te. В этом объекте хранятся сведения о текущих дате и времени. Затем сведения о вре­ мени выводятся отформатированными в· разных стилях и с учетом региональных особенностей представления времени. 11 Продемонстрировать различные форма ты времени import java .text .*; import java.util .*; puЫ ic class TimeForma tDemo { puЫ ic static void ma in (String args []) { Date date = new Date (); DateForma t df; df = DateFo rma t .getTimelnstance ( DateFo rmat .SHORT , Locale . JAPAN) ; System. o ut . println ( "Яnoния : "+df . format (date) ); df = DateFo rmat . getTimelnstance (DateFormat . LONG , Locale .UK) ; System.out . println ("B enикoбpитaния : "+df . format (date )); df = DateFormat . getTimelnstance ( Da teFo rma t .FULL, Locale.CANADA) ;
Гл ава 30. Ре гуляр ные в ыражения и другие пакеты System. out . println ("Kaнaдa : "+df . format (date) ); Ниже приведен примерный результат, выводимый данной программой. Япония : 13: 06 Великобритания : 13 :06:53 CST Канада : 1:06:53 o ' clock РМ CST 1111 В классе Da teForma t имеется также метод getDateTimeinstance (), способ­ ный форматировать сведения о дате и времени. Можете поэкспериментировать с ним самостоятел ьно . Кnacc Simp ledateFormat Класс SimpleDateFo rma t является конкретным подклассом, производным от класса Da teFormat. Он позволяет определять собственные шаблоны форма­ тирования , чтобы использовать их для отображения сведений о дате и времени. Ниже показан один из конструкторов этого класса. SimpleDateFormat(Strinq фopнa !l'JWP.YDJra-«_c!l.'poжa) Параметр форма тирующа я_ строка обозначает способ отображения даты и вре­ мен и. В следующей строке кода демонстрируется один из примеров применения конструктора класса SimpleDateFormat: SimpleDateForma t sdf = SirnpleDateFormat ( "dd ММ М уууу hh :rnm: ss zzz") ; Символы, указываемые в форматирующей строке, определяют порядок ото­ бражения сведений о дате и времени. Все эти символы и их описание приведены в табл. 30.5. Табл ица 30.5. Символ ы форматирующей строки для класса SimpleDateFo rmat Символ а d h k ID 8 w у z D Е F Описание АМ (до полудня) или РМ (пoCJie полудня ) День месяца (1-3 1) Часы в формате АМ/РМ (1-12 ) Часы в формате суток (1-2 4) Минуты (0-59) Секунды (0-5 9) Неделя в году (1-52) Год Часовой пояс День в году (1-366) День недели (например, четверг) День недели в месяце
1112 Часть 11. Библ иотека Java Оr.:ончанш таМ. 30 ,5 Символ G в к и з w х у z Описание Эра (AD - после Рождества Христова, или нашей эры, ВС - до Рождества Христова, или до нашей эры) Часы в сугках (О-23) Часы в формате АМ/РМ (О-11) Месяц Миллисекунды в секунде Неделя в месяце (1-5) Часовой пояс в формате по стандарту ISO 860 1 Неделя в году Часовой пояс в формате по стандарту RFC 822 Как правило , количество повторений символа определяет способ представле­ ния даты. Те кстовая информация отображается в сокращенной форме, если буква шаблона повторяется меньше четырех раз. В противном случае используется не­ сокращенная форма. Например, шаблон zzzz позволяет отобразить часовой пояс Pacific Daylight lime (тихоокеанское время), а шаблон zzz - этот же часовой пояс в сокращенной форме PDT. Количество повторений буквы шаблона определяет количество цифр в число­ вом представлении времени. Например, шаблон hh : ll lDL : ss позволяет представить время в формате О 1 : 51 : 15, тогда как шаблон h : а: s отображает то же самое время в формате 1:51 : 15. И наконец, символы М или ММ обозначают отображение месяца в виде одной или двух цифр. Тр и или больше повторения символа М обусловливают отображе­ ние месяца в виде текстовой строки. В следующей программе демонстрируется Simpledate Fo rmat: 11 Продемонс трировать применение класса Sil llp leDatei'orшat import java . text .*; import java .util .*; puЫ ic class SimpleDateFormat Demo { puЬlic static void ma in (String args[)) Date date = new Date (); SimpleDateForma t sdf; sdf = new SimpleDateForma t ("hh :mm: ss ") ; System.out . println (sdf . forma t (date) ); применение sdf = new SimpleDateFormat ( "dd ММ М уууу hh :mm: ss zzz") ; System.out . println (sdf . format (date ) ); sdf = new SimpleDateFormat ("E ММ М dd уууу" ); System.out . println (sdf . format (date ) ); Ниже приведен примерный результат, выводимый данной программой. 01 :30:51 01 Jan 2014 01 :30:51 CST Wed Jan 01 2014 класса
Глава 30. Реrулярные выражен ия и другие пакеты 1113 API даты и времени, внедренный в версии JDK8 В гл аве 19 был представлен давно устоя вшийся в Java подход к обработке даты и времени средствами классов Calendar и GregorianCalenda r. На момент на­ писания данной книги этот традиционный подход был по-прежнему широко рас­ пространен, и поэтому он должен быть известен программирующим нa java. Но в версииJDК 8 в - Java был предложен другой подход к обработке даты и времени, который определен в пакетах, перечисленных в табл . 30.6. Табл ица 30.6. Пакеты нового API даты и времени Па кет java . tiшe java .tiJl le . chrono java .ti11 1& .fora a t java.ti.Jнl.temporal java .till l8 .zone Описание Предостамяет классы верхнего уровня для по.цдержки даты и времени Поддержи вает другие календари , кроме гри горианского Поддерживает формати рование даты и времени Поддерживает расш и ренные функциональные возможности об работки даты и времени Поддерживает часовые пояса В этих новых пакетах определяется большое количество классов, интерфейсов и перечислений, обеспечивающих обширную и подробную поддержку операций об­ работки даты и времени. Та кое большое количество элементов, составляющих новый API даты и времени, поначалу выглядит немного пугающе. Но этот API хорошо ор­ ганизован и логически структурирован. Его размеры отражают степень детализации и гибкость управления, предоставляемые для обработки даты и времени. В рамках данной книги просто невозможно описать каждый элемент этого обширного API, по­ этому ниже будут рассмотрены лишь самые основные его классы. В ходе обсуждения этих классов станет ясно, что их достаточно для применения во многих случаях. О с новные кл а ссы даты и времени В пакете j ava . time определяется несколько классов верхнего уровня , упро­ щающих доступ к дате и времени. К их числу относятся три класса: LocalDate, Loca lTime и LocalDateT ime . Как следует из имен этих классов, они инкапсулируют локальную дату, локальное время , локальные дату и время соответственно. С помо­ щью этих классов нетрудно получить текущие дату и время , отформатировать дату и время , сравнить даты и время , а также выполнить над ними другие операции. Класс LocalDa te инкапсулирует дату, для представления которой используется выбираемый по умолчанию григорианский кал ендарь, как определено в стандар­ те ISO 8601. Класс LocalTime инкапсулирует время по стандарту ISO 8601, а класс Loca lDateTime инкапсулирует дату и время . Эти классы содержат большое коли­ чество методов, предоставляющих доступ к составляющим даты и времени, позво­ ляющих сравн ивать даты и время , складывать и вычитать составляющие даты или времени и выполнять прочие операции над ними. Ус воив один из этих методов,
1114 Часть 11. Б иблиотека Java нетрудно овладеть остальными благодаря принятым для них общими условными обозначениями. В классах LocalDate, LocalTime и LocalDateTime открытые конструкторы не применяются. Вместо этого для получения экземIVIяра соответствующего класса служит фабричный метод. Одним из удо бных для этих целей является метод now (} , определяемый во всех трех классах. Он возвращает текущую системную дату и/или время. В каждом из трех рассматриваемых здесь классов определяется несколько форм этого метода, но здесь будет использована самая простая из них. Та к, ниже приведена общая форма метода now (}, определенная в классе LocalDate. static LocalDate now () Форма этого метода определяется в классе Loca1 т ime следующим образом: etatic Loca1Till l8 now () А его форма в классе LocalDateTime определяется так: static LocalDateTi11 1e now() В каждом случае метод now ( } возвращает соответствующий объект, который может быть отображен в своей исходной удобочитаемой форме, например мето­ дом println (}.Но в то же время имеется возможность полностью управлять про­ цессом форматирования даты и времени. В приведенном ниже примере программы демонстрируется применение клас­ сов LocalDate и LocalTime для получения и отображения текущих даты и времени. Обратите внимание на вызов метода now ( } для изв.лечения текущих даты и времени. 11 Простой пример приме нения классов LocalDate и LocalTiJl le import java . time. *; class DateTime Demo { puЬl ic static void ma in (String args[] ) LocalDate curDate = Loca lDate .no w( ); System. out .println (curDate ); LocalTime curTime = LocalTime.no w( ); System. out . println (curTime) ; Ниже приведен примерный результат, выводимый данной программой. Полученный результат отражает стандартный формат, который получают дата и время . (В следующем разделе будет показано , как указать другой формат. ) 2014-01-01 14 :03:41 .436 В предыдущем примере программы отображаются текущие дата и время , но это было бы проще сделать средствами класса LocalDateTime . В этом случае по­ требовалось бы создать единственный экземпляр данного класса и сделать един­ ственный вызов его метода now ( } , как показано ниже. LocalDateTime curDateTime = LocalDateTime .no w( ); System.out . println ( curDateTime) ;
Гл ава 30. Ре гул ярные в ыражения и другие пакеты 1115 При таком подходе дата и время выводятся по умолчанию вместе. Ниже при­ веден пример такого вывода. 2014-01-01Т14 :04:56. 799 Следует также заметить, что из экземпляра класса Loca lDateTime можно так­ же получить ссылку на составляющую даты или времени, вызвав метод toLoca 1- Date ( ) или toLocalTime ( ) соответственно. Ниже приведены общие формы этих методов. Каждый из них возвращает ссылку на указанную составляющую. LocalDate toLocalDate () LocalTi.Jae toLocalTime () Форматирование даты и времени Стандартные форматы даты и времени, продемонстрированные в предыдущих примерах, оказываются пригодными лишь в некоторых случаях, а зачастую тре­ буется указывать другой формат. Правда, сделать это совсем не трудно , поскольку в классах LocalDate, LocalTime и Loca lDateTime для этой цели предоставля­ ется метод forma t (). Ниже приведена общая форма этого метода, где параметр форма тирующий_ объект обозначает экземпляр класса DateTime Formatter, пре­ доставляющий требуемый формат. String forшat (DateTimeForinatter фopнa�.1 11p yiairJdi_oбiъex�) Класс DateTime Formatter входит в состав пакета j ava . time . format. Для по­ лучения экземпляра класса DateTime Formatter, как правило, вызывается один из фаб ричных методов. Ниже приведены общие формы трех таких методов. static DateTilDeFormatter ofLocalizedDate (ForinatStyle форма � да ТJ J ) static DateTi.JaeForшatter ofLocali zedTime (ForшatStyle фориа� - .вреиеюr) static DateTi.JDeFormatter ofLocali zedDateTiиle (ForшatStyle ФоРиа� да�u, ForшatStyle фориа �_.вреиеюr ) - Безусловно, тип средства форматирования даты и времени, создаваемого в виде экземпляра класса DateTime Formatter, будет зависеть от типа того объек­ та, которым он оперирует. Та к, если требуется отформатировать дату, хранящую­ ся в экземпля ре класса Local Date, то следует вызвать метод ofLocalizedDate ( ) . Конкретный формат указывается в виде параметра типа Fo rmatStyle. Перечисление Fo rmatStyle входит в состав пакета java . time . format. В нем определяются приведенные ниже константы . l'UL L LONG NEDIUМ SRORT Эти константы обозначают уровень детализации отображаемых даты и време­ ни. Следовательно, данная форма класса Da teTime Fo rmatter действует аналогич­ но классу Da te Fo rma t из пакета jav a.text, описанному ранее в этой гл аве. В следующем примере программы демонстрируется применение класса Da teTime Forma t ter для отображения текущих даты и времени: 11 Продемонстрировать приме нение кл асса DateTilleForшatter import java .time.*;
1116 Часть 11. Библиотека Java impo rt java . time . forma t .*; class DateTime Demo2 { puЬlic static void ma in (String args [] ) LocalDate curDate = LocalDate .no w() ; System. out . println ( curDate . format ( DateTime Formatter . ofLocalizedDate ( FormatStyle .FULL ) )); LocalTime curTime = LocalTime.no w() ; System. out . println (curTime .forma t ( DateTime Formatter .ofLocal izedT ime (FormatStyle .SHORT) )); Ниже приведен примерный результат, выводимый данной программой. Wedne sday, Janua ry 1, 2014 2:16 РМ Иногда требуется формат, отличающийся от тех, что указываются с помощью констант из перечисления FormatStyle. Для этой цели можно, в частности , вое· пользоваться предопределенным средством форматирования, например, I SO_ DAT E или ISO_T IME из класса DateT ime Formatter. Ac дpyгoй стороны, можно соз­ дать специальный формат, указав шаблон. Для этого достаточно вызвать фабрич­ ный метод ofPa ttern () из класса DateTime Formatter. Ниже приведена одна из общих форм этого метода. static DateTimeForшatter ofPattern (Strinq ша блон_ форна �ироаан.я) Здесь параметр ша бл он_ форма тирования обозначает символьную стро ку, со­ держащую шаблон, требующийся для форматирования даты и времени. Этот ме­ тод возвращает объект типа Da teTime Formatter для форматирования по задан­ ному шаблону с учетом региональных настроек по умолчан ию. В общем, шаблон состоит из спецификаторов формата, называемых иначе бук­ вами шawuma. Каждая буква шаблона заменяется той составляющей даты или вре­ мени, которую она обозначает. Полный перечень букв шаблона приведен в опи­ сании метода ofPattern () из документации на АРI даты и времени, а в табл. 30.7 перечислены лишь некоторые их них. Следует иметь в виду, что буквы шаблона указываются с учетом регистра. В общем , конкретный выводимый результат зависит от количества повторений буквы в шаблоне. (Следовательно, класс Da teT ime Fo rmatter действует в какой-то степени аналогично классу SimpleDateForma t из класса java . text, описанного ранее в этой гл аве.) Например, следующий шаблон для вывода даты в апреле месяце: ммммм м мм мм позволяет вывести отформатированную дату, как показано ниже. Откровенно го­ воря , самый лучший способ уяснить назначение каждой буквы шаблона и воздей­ ствие различных повторений на вывод - экспериментирование. 4 04 Apr April Если букву шаблона требуется вывести как текст, ее следует заключить в оди­ нарные кавы чки . Та ким образом, все символы , не относящиеся к шаблону, реко­ мендуется заключать в одинарные кавычки , чтобы избежать осложнений при из­ менении букв шаблона в последующих версияхjаvа.
Глава 30. Регул ярнь1е выражения и другие пакеты Табл ица 30.7. Иэбраннь1е буквы шаблона Буква шаблона а d Е h в м ш в у Наэначение Обозначает время до полудня (АМ) ил и после полудня (РМ) День месяца День недели Час в 12-часовом формате Час в 24-часовом формате Месяц Минуты Секунды Год 1117 В следующем примере программы демонстрируется применение шаблона даты и времени: 11 Создание специального форма та даты и времени import java . time.*; import java .time.format .*; cla ss DateTimeDemoЗ { puЬlic static vo id ma in ( String args []) { LocalDateTime curDateTime = Local DateTime .no w() ; System.out . println ( curDateTime .format ( DateTime Forma tter . ofPattern ("ММ ММ d',' ууууh':'mmа"))); Ниже приведен примерный результат, выводимый дан ной программой . January 1, 2014 2:22 РМ В отношении создания специального формата вывода даты и времени следу­ ет также заметить следующее: в классах LocalDate, LocalTime и LocalDateTime определяются методы, позволяющие получать различные составля ющие даты и времени. Например, метод getHour () возвращает час в виде целочисленного значения типа int, метод ge tMon th () - месяц в виде значения перечислимого типа Month, а метод getYear () - год в виде целочисленного значения типа int. Используя эти и другие методы, можно сформировать вывод даты и времени вруч­ ную . Получаемыми в итоге значениями можно воспользоваться и в других целях, например, при создан ии специальных таймеров. Синта кс ичес ки й анализ с имвольных строк даты и врем ени В классах Loca lDate, LocalTime и LocalDateTime предоставляются средства для синтаксического анализа символьных строк даты и/или времени. С этой це­ лью вызывается метод parse () для экземпляра одного из упомянутых классов. У этого метода имеются две формы . В первой форме применяется выбираемое по умолчанию средство форматирования и выполняется синтаксический анализ даты и/или времени, отформатированных по стандарту ISO, например, времени в формате 03 : 31 и даты в формате 2014-08 -02 . Ниже приведе на общая форма
1118 Ч асть 11. Б иблиотека Java метода parse () для класса LocalDateTime. (Его общие формы для других классов аналогичны, за ис ключением типа возвращаемого объекта.) static LocalDateTiшe parse ( CharSequence с!!'р()жа_да !Rl_времеюr) Здесь параметр строка_да ты_ времени обозначает символьную строку, содер­ жащую дату и время в надлежащем формате. Есл и же указан недействительный формат, возникнет ошибка. Если требуется синтаксический анализ символьной строки даты и/или време­ ни в другом формате , а не по стандарту ISO, то для этой цели можно воспользо­ ваться второй формой метода parse (), позволяющей указать собственное сред­ ство фор матирования . Ниже приведена общая форма этого метода для класса Loca lDateTime , где средств о_ форма тирова ния_да ты_ времени обозначает тре­ буемое средство форматирования. (Его общие формы для других классов анало­ гичны, за исключением типа возвращаемого объекта.) stati c LocalDateTime parse (CharSequence с!!'р()жа дa !Rl вренеюr, DateTiшeForшatter средс�во_ форна �вроааЮrя_да!l.'М_вренеюr) В следующем простом примере демонстрируется синтаксический анализ символь­ ной строки даты и времени с помощью специального средства форматирования: 11 Пример син таксического анализа даты и времени import jav a.ti me.*; import java . time . format .*; class DateTime Demo 4 { puЫ ic static vo id ma in (String args []) ( 11 nолучить объе кт типа LocalDateTime , выполнив 11 синта ксический анализ симв оль ной строки даты и времени LocalDateTime curDateTime = LocalDateTime .parse ("June 21, 2014 12 :01 АМ" , DateTime Fo rmatter.ofPattern ("ММ ММ d',' ууууhh':'mmа")); 11 а теперь отобразить проанализированные дату и время System.out . println (curDateTime .format ( DateTime Format ter .of Pattern ("ММ ММ d', ' уууу h':'mm а"))); Ниже приведен примерный результат, выводимый данной программой. June 21, 2014 12 :01 АМ Дальнейшее изучение пакета java . time Начать доскональное изучение всех пакетов даты и времени лучше всего с паке­ та j а va . t ime , который содержит немало полезных средств. Начните его изучение с методов, определенных в классах LocalDate, LocalTime и LocalDateT ime . В ю�ж­ дом из этих классов имеются методы, позволяющие среди прочего складывать, вычи­ тать, корректировать по заданной составляющей или сравнивать даты и/или время , а также создавать экземпляры, исходя из составляющих даты и/или времени. В чис­ ле других классов из пакета j ava . time особый интерес могут представлять Instant, Duration и Period. В частности , класс Instant инкапсулирует момент времени; класс Dur а t ion - протяженность времени; класс Period - протяженность даты.
ЧАС ТЬ 111 ГЛАВА З1 Введение в библиотеку Swi ng ГЛ АВА З2 Исследовани е библиоте ки Swing ГЛ АВА ЗЗ Введение в меню Swi ng Введение в програ ммирование ГПИ средствами Swi ng
31 Введение в библ и отеку Swing Как было показано в части 11 дан ной книги, с помощью классов из библиоте­ ки АWТ можно создавать графические пользовательские интерфейсы (ГП И). Несмотря на то что библиотека АWТ по-прежнему является важной частью Java, ее компоненты применяются для создания ГПИ уже не так широко. Ныне многие программирующие наJava пользуются для этой цели библиотеками Swing иJavaFX. Подробнее библиотека JаvаFХ рассматривается в части IV данной книги, а в этой части представлено введение в библиотеку Swing. В этой библиотеке предостав­ ляются более эффективные и гибкие компоненты ГПИ, чем в библиотеке АWТ. Именно поэтому библиотека Swing уже более десятка лет широко применяется д.ля построения ГПИ прикладных программ нajava. Рассмотрению библиотеки Swi ng посвящены три гл авы части 111. В этой гл аве представлено введение в библиотеку Swing. Сначала в ней поясняются основные принципы построения библиотеки Swing. Затем демонстрируется общая форма прикладных программ на основе Swing, включая приложения и аплеты. А в кон­ це гл авы поясняется , каким образом выполняется рисование графики средствами библиотеки Swi ng. Во второй гл аве из этой части будет рассмотрен ряд наиболее употребительных компонентов библиотеки Swing, а в третьей гл аве - меню, соз­ даваемые на основе Swing. Следует, однако , иметь в виду, что количество классов и интерфейсов в пакетах Swing довольно велико, поэтому просто невозможно рас­ смотреть каждый из них подробно в данной книге. (В действительности потребу­ ется отдел ьная книга, чтобы полностью осветить библиотеку Swing. ) Те м не менее в трех гл авах этой части даются основы того , что нужно знать о библиотеке Swing. На заметку! Более содержател ьное описание библиоте ки Swi ng можно найти в другой книге автора - Swing: А Beginner's Guide, издател ьство McGraw-Hill Professional, 2007 г. В русском переводе книга вышла nод названием Swing. Руководство для начинаю­ щих в Ид "Вильяме", 2007 г. П роисхождение библ иоте ки Swin g Библиотека Swing появилась в Java не с самого начала. Причиной разработки классов библиотеки Swing стали недостатки, обнаружившиеся в библиотеке АWТ - исходной подсистеме Java для построения ГПИ. В библиотеке АWТ определяется базовый набор элементов управления , обычных и диалоговых окон , поддержи-
1122 Часть 111. Введение в программирование ГПИ средствами Swing вающий пригодн ый для применения ГПИ, но имеющий ограниченные возмож­ ности. Одна из причин ограниченности библиотеки АWТ состоит в том, что она преобразует свои визуал ьные компоненты в соответствующие им платформенно­ зависимые эквиваленты , называемые равноправн:ы.ми компонентами. Это означает, что внешний вид ко мпонента определяется конкретной платформой , а не Java. Компоненты библиотеки АWТ используют ресурсы платформенно-ориентирован­ ного кода, и поэтому они называются тяжеловесными. Использование платформенно-зависимых равноправных компонентов порож­ дает ряд затруднений. Во-первых, в силу отличий в операционных системах компо­ нент может выглядеть или даже вести себя по-разному на разных платформах. Такая потенциальная изменчивость шла вразрез с гл авным принципомjаvа: "написано од­ нажды, работает везде". Во-вторых, внешний вид каждого компонента бьш фикси­ рованным, поскольку все зависело от конкретной платформы, а изменить его бьшо очень трудно, если вообще возможно. И в-третьих, применение тяжеловесных ком­ понентов влекло за собой новые неприятные ограничения. Например, тяжеловес­ ный компонент всегда имеет прямоугольные очертания и является непрозрачным. Вскоре после выпуска первоначальной версии Java стало очевидно, что огра­ ничения , присущие библиотеке АWТ, оказались настолько серьезными , что тре­ бовался более совершенный подход к построению ГПИ. Поэтому в 1997 году по­ явилась библиотека Swing как часть набора библиотек классов Java Foundation Classes OFC) . Первоначально они бьши досrупны в виде отдел ьной библиотеки , входи вшей в состав версии jаvа 1.1. А в версии jаvа 1.2 библиотека Swi ng, а также все компоненты , входи вшие в составJFС, были полностью интегрированы вjava. Построение библиотеки Swing на основе библиотеки AWT Прежде чем продолжить дальше, нужно сделать следующее важное замечание: несмотря на то , что библиотека Swing снимает некоторые ограничения, прису­ щие библиотеке АWТ, она не заменяет ее. Напротив, библиотека Swing построена на основе библиотеки АWТ. Именно поэтому библиотека АWТ до сих пор является важной составной частью Java. Кроме того, в библиотеке Swing применяется тот же самый механизм обработки событий, что и в библиотеке АWТ. Поэтому, пре­ жде чем воспользоваться библиотекой Swing, следует усвоить хотя быть основные принципы работы библиотеки АWТ. (Библиотеке АWТ посвящены гл авы 25 и 26, а обработке событий - гл ава 24.) Главные о собенности библ иоте ки Swi ng Как упоминалось выше , библиотека Swing бьша создана с целью преодолеть ограничения, присущие библиотеке АWТ. Этого уд алось достичь благодаря двум гл авн ым ее особенностям: легковесным компонентам и подключаемому стилю оформления. Совместно они предлагают изящное и простое в употреблении ре-
Глава 31. Введение в библиотеку Swing 1123 шение недостатков библиотеки АWГ. Именно эти две особенности и определяют сущность библиотеки Swing. Каждая из них рассматривается ниже в отдел ьности . Л егко вес ные ко мпоненты Swi ng За редким исключением, компоненты библиотеки Swing являются легковесными. Это означает, что они написаны исключительно нajava и не преобразуются в рав­ ноправные компоненты для конкретной платформы. Следовательно, легковесные компоненты являются более эффективными и гибкими. Более того, легковесные компоненты не преобразуются в равноправные платформеннО-Qриентированные компоненты , поэтому внешний вид каждого компонента определяется библиоте­ кой Swing, а не базовой операционной системой. Это означает, что каждый компо­ нент будет действовать одинаково на всех платформах. П одкл ю чаемый сти п ь оформпения В библиотеке Swing поддерживается подключаемый стиль оформления (PlAF) . Каждый ко мпонент библиотеки Swing воспроизводится кодом, написанным на Java, а не платформеннО-Qриентированными равноправными компонентами, поэтому стиль оформления ко мпонента находится под управлением библиотеки Swing. Это означает, что стил ь оформления можно отдел ить от логики компонен­ та, что и делается в библиотеке Swing, что дает следующее преимущество: изме­ нить способ вос произведения компонента, не затрагивая остальные его свойства . Иными словами, новый стиль оформления любого ко мпонента можно "подклю­ чить" без побочных эффектов в коде, использующем данный компонент. Более того, можно определить наборы стилей оформления , чтобы представить разные стили оформления ГПИ. Чтобы воспользоваться определенным стилем оформле­ ния , достаточно "подключить" его. Как только это будет сделано , все компоненты автоматически будут вос производиться в подключенном стиле оформления. У подключаемых стилей оформления имеется ряд важных преимуществ. Например, можно определить такой стиль оформления, который будет одинако­ вым для всех платформ. С другой стороны, можно определить стиль оформления , характерный для отдельной платформы. Например, если заранее известно, что прикладная программа будет эксплуатироваться только в среде Windows, для ее ГПИ можно определить стиль оформления, характерный для Windows. Кроме того, можно разработать специальный стиль оформления. И наконец, стиль оформления можно динамически изменять во время работы прикладной программы. В версииjаvа 8 предоставляются стили оформления metal, Motif и Nimbus, ко­ торые доступны всем пользователям библиотеки Swing. Стиль оформления metal (метал л ический) называется также cmUJ!JrМ оформленuя]аvа. Он не зависит от Шiат­ формы и доступен во всех исполняющих средах Java. Кроме того , этот стиль оформления выбирается по умолчанию. В среде Windows доступен также стиль оформления Windows. Здесь и далее используется стиль оформления metal, по­ скольку он не зависит от конкретной платформы.
Часть 111. Введение в п р огр аммир ование ГПИ ср едствами Swi ng Связь с архитектурой MVC В общем, визуальный компонент определяется тремя отдельными составля- ющими. • Внешний вид компонента при воспроизведении на экране. • Взаимодействие с пользователем. • Сведения о состоянии компонента. Независимо от того , какая именно архитектура испол ьзуется для реализации компонента, она должна неявно включать в себя эти три его составляющие. В те­ чение многих лет свою исключительную эффективность доказала архитектура MVC - "модель-представление-контроллер". Ус пех архитектуры МVС объясняется тем , что каждая часть этой архитектуры соответствует отдел ьной составляющей компонента. Согласно терминологии МVС , моделъ соответствует сведениям о состоянии компонента. Та к, для флажка модель со­ держит поле, которое показывает, установлен ли флажок. Пр едставленш определяет порядок отображения компонента на экране , включая любые составляющие пред­ ставления , зависящие от текущего состояния модели. Контрол лер определяет поря­ док реагирования ко мпонента на действия пользователя. Та к, если пользователь щелкает на флажке, контроллер реагирует на это действие, изменяя модель, чтобы отразить выбор пользователя (установку или сброс флажка) . В итоге представление обновляется. Разделяя компонент на модель, представление и контроллер, можно изменять конкретную реализацию любой из этих составляющих архитектуры МVС , не затрагивая остальные. Например, различные реализации представлений могут воспроизводить один и тот же компонент разными способами, но это не будет ока· зывать никакого влияния на модель или контроллер. Хотя архитектура МVС и положенные в ее основу принципы выглядят вполне благоразумно, высокая степень разделения представления и контроллера не дает никаких преимуществ компонентам библиотеки Swing. Поэтому в библиотеке Swing применяется видоизмененный вариант архитектуры MVC, где представле­ ние и контроллер объединены в один логический объект, называемый представи­ телем полъзовател:ьского интерфейса. В связи с этим подход, применяемый в библи­ отеке Swi ng, называется архитектурой "моделъ-представителъ ", или архитектурой "р азделяемая модел:ь ". Та ким образом, в архитектуре компонентов библиотеки Swing не используется классическая реализация архитектуры МVС, несмотря на то, что первая основывается на последней. Подключаемый стиль оформления стал возможным в библиотеке Swi ng благо­ даря архитектуре "модель-представител ь". Представление и контроллер отделе­ ны от модели, поэтому стиль оформления можно изменять, не оказывая влияния на то , как компонент применяется в программе. И наоборот, модель можно на­ строить, не оказывая влияния на то , как компонент отображается на экране или реагирует на действия пользователя. Для поддержания архитектуры "модель-представитель" большинство компонен­ тов библиотеки Swing содержит два объекта. Один из них представляет модель, а дру­ гой - представитель пользовательского интерфейса. Модели определяются в интер­ фейсах. Например, модель кнопки определяется в интерфейсе Bu t tonMode 1. А пред-
Глава 31. Введение в библиотеку Swing 1125 сгавители пользовательского интерфейса являются классами , наследующими от клас­ са ComponentU I. Например , представителем пользовательского интерфейса кнопки является класс ButtonUI. Как правило, прикладные программы не взаимодействуют непосредственно с представителями пользовательского интерфейса. Компоненты и ко нтейнеры ГПИ, создаваемый средствами Swing, состоит из двух основных элементов: ком­ пошттов и контейиеров. Но это , по существу, концептуальное разделение, посколь­ ку все контейнеры также являются компонентами. Отличие этих двух элементов заключается в их назначении: компонент является независимым визуальным элементом управления вроде кнопки или ползунка, а контейнер содержит груп­ пу компонентов. Та ким образом, контейнер является особым типом компонента и предназначен для хранения других компонентов. Более то го , компонент должен находиться в контейнере, чтобы его можно было отобразить. Так, во всех ГПИ, создаваемых средствами Swing, имеется как минимум один контейнер. А посколь­ ку контейнеры явля ются компонентами, то один контейнер может содержать дру­ гие контейнеры. Благодаря этому в библиотеке Swi ng можно определить иерархию вложенности, на вершине которой должен находиться контейиер верхнего уровня. А теперь рассмотрим подробнее компоненты и контейнеры. Компоненты В общем, компоненты библиотеки Swing происходят от класса JComponent. (Исключением из этого правила являются четыре контейнера верхнего уров­ ня, о которых речь пойдет в следующем разделе.) В классе JC omponent предо­ ставляются функциональные возможности , общие для всех компонентов . Та к, в классе JC omponent поддерживается подключаемый стиль оформления. К.ласе JC omponent наследует классы Container и Component из библ иотеки АWГ. Следовател ьно , компонент библиотеки Swing построен на основе компонента би­ блиотеки АWГ и совместим с ним. Все компоненты Swi ng представлены классами, определенными в пакете j а - vax . swing. Ниже поименно перечислены классы компонентов Swing (включая компоненты, используемые в качестве контейнеров). JADolet JВutton JCheckВox JCheckВoxNenuit&Jn JColorChoo ser JColl lЬ oВox JComoonent JDesktopPane JDialoa JEditorPane JFileChooser JFormatt8dTextField JFraae Jinternal!'raDl8 JL&Ьel JLayer JLayeredPane JList JМenu JМenuВar JМenuiteш JODtionPane JPanel JPasswordField JPoouoМ&nu JProare ssвar JRadioButton JRadioButtonМenuitea JRootPane JScrollВar JScrollPane JSeoarator JSlider JSpinner JSplitPane JТаЬ ЬеdР аnе JТаЬlе JТextArea JТextField JТextPane JТoaa l.Ьutton JТoolВar JТoolTip JТree JViewport JWindow
1126 Часть 111. Введение в п рограммирование ГПИ средствам и Swing Обратите внимание на то, что все классы компонентов начинаются с буквы J. Например, класс для создания метки называется JLabe l, класс для создания кноп­ ки - JButton, а класс для создания ползунка - JS crol lBar. Контейнеры В библиотеке Swing определены два типа контейнеров. К первому типу отно­ сятся контейнеры верхнего уровня , представленные классами JFrame , JApp let, JW indow и JDi а 1 og. Классы этих ко нтейнеров ненаследуют от класса JComponent, но они наследуют от классов Component и Container из библиотеки АWГ. В от­ личие от остальных компонентов Swing, которые являются легковесными, ком­ поненты верхнего уровня являются тяжеловес ными. Поэтому в библиотеке Swing ко нтейнеры являются особым случаем компонентов. Судя по названию, контейнер верхнего уровня должен находиться на вершине иерархии контейнеров. Контейнер верхнего уровня не содержится ни в одном из других контейнеров. Более того, каждая иерархия вложенности должна начинать­ ся с контейнера верхнего уровня. Та ким контейнером в прикладных программах чаще всего является класс JFrame, а в аплетах -класс JApp let. Ко второму типу контейнеров, поддерживаемых в библиотеке Swing, относятся легковесные контейнеры. Они наследуют от класса JComponent. Примером легко­ весного контейнера служит класс JPane l, который представляет контейнер обще­ го назначения. Легковесные контейнеры нередко применяются для орган изации и управления группами связанных вместе компонентов, пос кольку легковесный ко нтейнер может находиться в другом контейнере. Следовател ьно , легковесные контейнеры вроде класса JPane 1 можно применять для создания подгрупп связан­ ных вместе элементов управления, содержащихся во внешнем ко нтейнере. Панели ко нтейнеро в верхнего уровня Каждый контейнер верхнего уровня определяет ряд панелей. На вершине иерар­ хии панелей находится корневая панель в виде экземпляра класса JRootPane. Класс JRo otPane представляет легковесный контейнер, предназначенный для управле­ ния остальными панелями. Он также помогает управлять дополнительной, хотя и не обязател ьной строкой меню. Панели, составляющие корневую панель, называ­ ются прозр ачний панелью, панелью содержимого и многослойний панелью соответственно. Прозрачная панель является панелью верхнего уровня. Она находится над все­ ми панелями и покрывает их полностью. По умолчанию эта панель представлена прозрачным экземпляром класса JPanel. Прозрачная панель позволяет управлять событиями от мыши, оказывающими влияние на весь контейнер в целом, а не на отдел ьный элемент управления , или, например, рисовать поверх любого друго­ го компонента. Как правило, обращаться к прозрачной панели непосредственно не требуется, но если она все же понадобится , то ее нетрудно обнаружить там, где она обычно находится. Многослойная панель представлена экземпляром класса JLaye redPane. Она позволяет задать определенную глубину размещения компонентов. Гл убина опре­ деляет степень перекрытия компонентов. (В связи с этим многослойные панели
Гл ава 31 . Введение в библиоте ку Swi ng 112 7 позволяют задавать упорядоченность ко мпонентов по координате Z , хотя это тре­ буется не всегда.) На многослойной панели находится панель содержимого и до­ полнительно , хотя и не обязательно, -строка меню. Несмотря на то что прозрачная и многослойная панели являются неотъемле­ мыми частями контейнера верхнего уровня и служат для разных целей, бальшая часть их возможностей скрыта от пользователей. Прикладная программа чаще всего будет обращаться к панели содержимого, поскольку именно на ней обычно располагаются визуальные компоненты . Иными словами, когда компонент (на­ пример, кнопка) вводится в ко нтейнер верхнего уровня , он оказывается на пане­ ли содержи мого. По умолчанию панель содержимого представлена непрозрачным экземпляром класса JPane l. Пакеты библ иоте ки Swi n g Библиотека Swing - это довольно крупная подсистема, в которой задействова­ но бол ьшое количество пакетов. Ниже перечислены пакеты , определенные в би­ блиотеке Swi ng на момент написания данной книги. javax . swing javax .swing .plaf .Ьasic javax .swinq .text javax .swing .Ьorder javax .swinq .plaf . 11 18 tal javax .awinq .text .htlDl javax .swinq . colorchooser javax . swinq .plaf .шulti javax . awinq .text .htlDl. paraer javax .swinq . event javax . swinq .plaf .nill lЬ us javax . swinq .text . rtf javax .swinq . filechooser javax . swinq .plaf . aynth javax .awinq . tree javax . swinq .plaf javax .swinq . t.Ьle javax .swinq . und.o Самым гл авным среди них является пакет javax . swing. Его следует импорти­ ровать в любую прикладную программу, пользующуюся библиотекой Swing. В этом пакете содержатся классы, реализующие базовые ко мпоненты Swing, в том числе кнопки , метки и флажки. Простое Swing-npиnoжeниe Swing-приложения отличаются от консольных программ и АWГ -приложений, демонстрировавшихся ранее в данной книге. Например, в них используются ком поненты и иерархии контейнеров не из библиотеки АWГ. Кроме того , Swing­ приложения предъявляют особые требования, связанные с многопоточной обра­ боткой. Уя снить структуру Swing-приложения лучше всего на конкретном приме­ ре . Имеются две разновидности программ нajava, в которых обычно применяется библиотека Swi ng: настольные приложения и аплеты. В этом разделе будет рас­ смотрен пример создан ия Swing-приложения. А создание Swing-aплeтa обсуждает­ ся далее этой гл аве . Несмотря на вс ю краткость расс матриваемого здесь примера программы, он наглядно демонстрирует один из способов разработки Swing-приложения , а также ос новные средства библиотеки Swing. В дан ном примере используются два ком-
1128 Ч асть 111. Введение в программирование ГПИ средства ми Swing понента Swing: JFrame и JLabel. Класс JFrarne представляет контейнер верхне­ го уровня, который обычно применяется в Swing-приложениях, а класс JLabe l - компонент Swing, создающий метку для отображения информации. Метка явля­ ется самым простым компонентом Swing, поскольку это пассивный компонент. Это означает, что метка не реагирует на действия пользователя. Она служит лишь для отображения выводимых данных. В данном примере контейнер типа JFrame служит для хранения метки в виде экземIUIЯра класса JLabel. Метка отображает короткое текстовое сообщение. 11 Приме р прос того Swing-приложе ния import javax .swing.*; class SwingDemo SwingDemo () 11 создать новый контейнер типа JFraDe JFrame jfrm = new JFrame ("A Simple Swing Application") ; 11 Простое Swing-приложе ние 11 задать исходные размеры фрейма jfrm .setSize (275, 100) ; 11 завершить работу , если поль зователь закрывает приложе ние jfrm .setDefaultCloseOperation ( JFrame .EXIT_ON_CLOSE) ; 11 создать ме тку с текстом сообщения JLabel jlab = new JLabel ("S wing me ans powerful GUi s .") ; 11 Swing - это мощные ГПИ 11 ввести метку на панели содержимого jfrm .add(jlab ) ; 11 отобразить фрейм jfrm .se tVisiЬle (true) ; puЬlic static vo id ma in (St ring args[]) ( 11 создать фрейм в потоке диспетчеризации событий SwingUtilities . invokeLater (new RunnaЬle () { puЫic void run() { new SwingDemo () ; )); Swing-приложения компилируются и выполняются таким же образом, как и остальные приложения Java. Поэтому, чтобы скомпилировать данное Swing­ приложение, нужно ввести в командной строке следующую команду: javac SwingDeao .java А для того чтобы запустить Swing-п риложение на выполнение, нужно ввести в командной строке такую команду: java SvinqDeao
Гл ава 31 . Введение в библиоте ку Swing 1129 Когда Swing-приложение начнет выполняться, в нем будет создано окно, пока­ занное на рис. 31 .1. -�. - . -- 1! А SimpLe Swi 11g App[icatio •1 1g (Q]� Swing rneans powerful GUls. Рис . 31 .1. Окно, создаваемое приложением SwingDemo Пример приложения SwingDemo демонстрирует ряд основных понятий би­ блиотеки Swi ng, поэтому рассмотрим этот пример построчно. Данное Swing­ приложение начинается с импорта пакета j avax . swing. Как упоминалось ранее, этот пакет содержит компоненты и модели, определяемые в библиотеке Swing. Та к, в пакете j avax . swing определяются классы, реализующие метки , кнопки , текстовые элементы управления и меню. Поэтому этот пакет обычно включается во все программы, пользующиеся библиотекой Swing. Затем объявляются класс SwingDemo и его конструктор, в котором выполняет­ ся большинство действий данной программы. Сначала в нем создается экземпляр класса JFrame , как показано ниже. JFrarne jfrrn = new JFrarne ("A Sirnple Swing Application" ) ; В методе setSi ze (),наследуемом классом JFrame от класса Component из би­ блиотеки АWГ, задаются размеры окна в пикселях. Ниже приведена общая форма этого метода. В данном примере задается шир ина окна 275 пикселей, а высота - 100 пикселей. void setSize (int 1DtpRНa , int .1 11 1 со�а) Когда закрывается окно верхнего уровня (например, после то го, как пользо­ ватель щелкнет на кнопке закрытия) , по умолчанию окно уд аляется с экрана, но работа приложения не прекращается. И хотя такое стандартное поведение иногда оказывается полезным, для большинства приложений оно не подходит. Чаще все­ го при закрытии окна верхнего уровня требуется завершить работу всего прило­ же ния. Это можно сделать двумя способами. Самый простой из них состоит в том , чтобы вызвать метод setDefaultCloseOpe ration ( ) , что и делается в данном приложении следующим образом: jfrrn .se tDe faultCloseOpe ration ( JFrame .EXIT_ON_CLOSE ); В результате вызова этого метода приложение пол ностью завершает свою ра­ боту при закрытии окна. Общая форма метода setDefaultCloseOpe ration () вы­ гл ядит следую щим образом: void setDefaul tCloseOperation (int v�o) Значение константы, передаваемое в качестве параметра что определяет, что именно происходит при за крытии окна. Помимо значения константы JFrame . EX IT_ON _CLOSE, имеются также следующие значения:
1130 Ч асть 111. Введение в программирование ГПИ средствами Swi ng DISPOSE ON CLOSE HIDE ON - CLOSE DO нОтн!нG ON CLOSE - - - Имена этих констант отражают выполняемые действия. Эти константы объяв­ ляются в интерфейсе WindowC onstants, который определяется в пакете j avax . swing и реализуется в классе JFrame . В следующей строке кода создается компонент типа JLabe 1 из библиотеки Swing: JLabel jlab = new JLabel (" Swing me ans powerful GU!s ."); Класс JL abe 1 определяет метку - самый простой в употреблении компонент, поскольку он не принимает вводимые пользователем данные, а только отображает информацию в виде текста, значка или того и другого. В дан ном примере создается метка, которая содержит только текст, передаваемый конструктору класса JLabe l. В следующей строке кода метка вводится на панели содержимого фрейма: jfrm .add(jlab ) ; Как пояснялось ранее , у всех контейнеров верхнего уровня имеется панель со­ держимого , на которой размещаются отдел ьные компоненты . Следовательно, что­ бы ввести компонент во фрейм, его нужно ввести на панели содержимого фрейма. Для этого достаточно вызвать метод add () по ссылке на экземпляр класса JFrame (в данном случае j frm) . Ниже приведена общая форма метода add (). Метод add () наследуется классом JFrame от класса Container из библиотеки АWТ. Coшponent add (C01 11pO nent ЖОНDОНSН!l') По умолчанию на панели содержимого, связан н ой с компонентом типа JFr ame , применяется граничная компоновка. В приведенной выше форме метода add ( ) мет­ ка вводится по центру панели содержимого. Другие формы метода add ( ) позволяют задать одну из граничных областей. Когда компонент вводится по центру, его разме­ ры автоматически подгоняются таким образом, чтобы он разместился в центре. Прежде чем продолжить дальше , следует заметить, что до версииJDК 5 при вво­ де компонента на панели содержимого метод add ( ) нельзя бьuю вызывать непо­ средственно для экземпляра класса JFrame . Вместо этого метод add () приходилось вызывать для панели содержимого из объекта типа JFrame . Панель содержимого можно было получить в результате вызова метода getContentPane () для экземпля­ ра класса JFrame . Ниже приведена общая форма метода ge tContent Pane (). Container getContentPane () Класс Container получает ссылку на окно содержимого . И по этой ссьmке делается вызов метода add ( ) , чтобы ввести компонент на панели содержимого. Следовател ьно , чтобы ввести метку j lab во фрейм j frm, раньше приходилось пользоваться следующим оператором: jfrm . getContent Pane () .add(jlab) ; // старый стиль В этом операторе сначала вызывается метод getContentPane (), получающий ссьmку на панель содержимого, а затем метод add ( ) , вводящий указанный компо­ нент (в данном случае метку) в контей нер, связанный с этой панелью. Эта же про­ цедура потребовалась бы и для вызова метода remo ve ( ) , чтобы удалить компонент,
Глава 31. Введение в библиотеку Swing 1131 или д.ля вызова метода setLayout (), чтобы задать диспетчер компоновки для окна содержимого. Именно поэтому в коде, написанном нa java до версии 5.0, нередко встречаются вызовы метода getContent Pane (). Но теперь вызывать этот метод больше не нужно. Вместо этого достаточно вызвать методы add (), remo ve () и set Layout ( ) непосредственно для объекта типа JFrame , поскольку они были специаль­ но изменены, чтобы автоматически оперировать панелью содержимого. Последний оператор в конструкторе класса SwingDemo требуется для то го, что­ бы сделать окно видимым, как показано ниже. jfrm .se tVisiЬle (true) ; Метод setVisiЫe () наследуется от класса Component из библиотеки АWГ. Если его аргумент принимает логическое значение true, то окно отобразится, а иначе оно будет скрыто . По умолчанию контейнер типа JFrame невидим, поэто­ му нужно вызвать метод setVi siЫe ( true ) , чтобы показать его. В методе ma in ( ) создается объект типа SwingDemo , чтобы отобразить окно и метку. Обратите внимание на то , что конструктор класса SwingDemo вызывается в следующих трех строках кода: SwingUtilities . invokeLate r(new RunnaЬle () { puЬlic void run() { new SwingDemo (); }); Выполнение этой последовательности кода приводит к создан ию объекта типа SwingDemo в потоке диспет'ЧеjJи1ации событий, а не в гл авном потоке испол­ нения данного приложения, потому что Swing-приложения обычно выполняются под управлением событий. А событие происходит, например, когда пользователь взаимодействует с компонентом. Событие передается приложению при вызове обработчика событий, определенного в этом приложении. Но обработчик выпол­ няется в потоке диспетчеризации событий, предоставляемом библиотекой Swing, а не в гл авном потоке исполнения данного приложения. Та ким образом, обработ­ чики событий вызываются в потоке исполнения, который не был создан в прило­ же нии, хотя они и определены в нем. Чтобы избежать осложнен ий, в том числе вероятной взаимной блокировки , все компоненты ГПИ из библиотеки Swing следует создавать и обновлять из потока ди спетчеризации событий, а не из гл авного потока исполнения данного приложе­ ния. Но метод ma in ( ) выполняется в гл авном потоке исполнения. Следовательно, метод ma in () не может напрямую наследовать объект типа SwingDemo . Вместо этого нужно создать объект типа RunnaЫe, который выполняется в потоке дис­ петчеризации событий, и с помощью этого объекта построить ГПИ. Чтобы построить ГПИ в потоке диспетчеризации событий, следует вызвать один из следующих двух методо в, определенных в классе SwingUt ilities: invokeLater () и invo keAndWa i t ().Ниже приведены общие формы этих методов. static void invokeLater (RurmaЫe об�еж�) static void invokeAndWait (RurmaЫe обо.еж�) throvs InterruptedException , InvocationTarqetException Здесь параметр объект обозначает объект типа RunnaЬ le, метод run () кото­ рого должен вызываться в потоке дис петчеризации событий. Единственное отли-
1132 Часть 111. Введение в программирова ние ГПИ с р едствами Swi ng чие этих двух методов заключается в том, что метод invo keLater () выполняет возврат немедленно, а метод invo keAndWa i t () ожидает возврата из метода obj . run ( ) . Эти ми методами можно пользоваться для вызова метода, строящего ГПИ конкретного Swing-приложения, или вызывать их всякий раз, когда требуется из­ менить состояние ГПИ из кода, не выполняющегося в потоке диспетчеризации событий. Для этих целей обычно вызывается метод invo ke Later (), как демон­ стрируется в рассматриваемом здесь примере. А при построении исходного ГПИ для аплета потребуется метод invo keAndWa it (). Обработка событий В предыдущем примере была продемонстрирована основная форма Swing- при­ ложения , но ей недостает одной важной части: обработки событий. Компонент типа JLabe l не принимает данные, вводимые пользователем, и не ге нерирует со­ бытия, и поэтому обработка событий в данном примере не требовалась. Но осталь­ ные компоненты библиотеки Swing реагируют на вводимые пользователем данные , а следовател ьно, требуется каким-то образом обработать события, наступающие в результате подобных взаимодействий. Событие происходит, когда, например, срабатывает тай мер. Так или иначе , обработка событий занимает большую часть любого Swiпg-приложения. Механизм обработки событий, применяемый в библиотеке Swi ng, ничем не от­ личается от аналогичного механизма из библиотеки АWТ, называемого моделъю де­ легирования событий, как пояснялось в главе 24. Как правило , в библиотеке Swing используются те же самые события , что и в библиотеке АWТ, и эти события опре­ делены в пакете java . awt . event. А события , характерные только для библиоте­ ки Swi ng, определены в пакете javax . swing . event. Несмотря на то что события обрабатываются в библиотеке Swing таким же об­ разом , как и в библиотеке АWТ, этот процесс лучше рассмотреть на простом при­ мере. В приведенной ниже программе обрабатывается событие, генерируемое после щелчка на экранной кнопке, определяемой соответствующим компонентом Swing. Результат выполнения данной программы приведен на рис. 31 .2. '-- - ��-• •• • -��"-.,. . •.. .,. .._.,.- �.. .,. � ••----•-r•..,• ' 11А11 [vent Exampte �LQJ� [AlphaJ\ Beta J Alpha was pre ssed. Рис. 31 .2. Резул ьтат выполнения программы Event Demo 11 Обработка события в Swing-приложе нии impo rt java.aw t.•; impo rt java.awt . event .*; import javax . swing .*; class EventDemo {
JLabel jlab ; EventDemo () Глава 31. Введение в библиотеку Swing 11 создать новый контейнер типа JFr aиie JFrame jfrm = new JFrame ("An Eve nt Examp le") ; 11 Пример обработки событий 11 определить диспе тчер поточной компоновки типа FlowLayout jfrm . setLayout (new Fl owLayout () ); 11 установить исходные размеры фрейма jfrm .setSize (220, 90) ; 11 завершить работу приложе ни я, если пользователь 11 закрывает его окно jfrm . setDefaultCloseOpe ration (JFrame .EXIT _ ON_CLOSE) ; 1 1 создать две кнопки JButton jbtnAlpha = new JButton ("Alpha" ) ; JButton jЬtnBeta = new JButton ("Beta" ); 11 ввести nриемник действий от кнопки Alpha jЬtnAlpha . addAct ionLi stener (new Act ionLi stener () }); puЬlic void actionPe rformed (ActionEvent ае ) { jlab.setText ( "Alpha was pressed. ") ; 11 Нажата кнопка Alpha 11 ввести приемник действий от кнопки :веtа jЬtnBeta . addActionListener (new ActionLi stener () puЬlic void act ionPerformed (ActionEvent ае ) jlab.se tText ("Beta wa s pressed.") ; 11 Нажата кнопка :веtа }); 11 ввести кнопки на панели содержимого jfrm . add (jЬtnAlpha) ; jfrm . add (jbtnBeta ); 11 создать текстовую ме тку jlab = new JLabel ( "Press а button.") ; 1 1 Метка "Нажми те кнопку ." 11 ввести ме тку на панели содержимо го jfrm .add(j lab) ; 11 отобразить фрейм jfrm . setVi siЬle (true) ; puЫic static void ma in (String args[] ) { 11 создать фрейм в потоке диспе тчеризации событий SwingUtilities . invo keLater (new RunnaЬle () { puЫic void run () { new EventDemo () ; }); 1133
113, Часть 111. Введение в програ ммирование ГПИ средства ми Swing Прежде всего обратите внимание на то , что в данном примере программы те­ перь импортируются пакеты java . awt и java.awt . event. Пакет java . awt тре­ буется потому, что в нем содержится класс Fl owLa yout, поддерживающий стан­ дартный диспетчер поточной компоновки , который применяется для размеще­ ния компонентов во фрейме. (Описание диспетчеров компоновки см. в гл аве 26.) А пакет jav a.awt . event требуется потому, что в нем определяются интерфейс Ac tionLi stener и класс ActionEvent. Сначала в конструкторе класса Event Demo создается контейнер j frm типа JFrame , а затем устанавливается диспетчер компоновки типа FlowLayout для па­ нели содержимого контейнера j frm. Напомним, что по умолчанию для размеще­ ния компонентов на панели содержимого применяется диспетчер компоновки типа BorderLayout, но для данного примера больше подходит диспетчер компо­ новки типа Fl owLayout. Обратите внимание на то, что диспетчер компоновки типа FlowL ayout назначается с помощью следующего оператора: jfrm . setLayout (new Fl owL ayout (} }; Как упоминалось выше, раньше приходилось явным образом вызывать метод get­ ContentPane (),чтобы задать диспетчер компоновки для размещения компонентов на панели содержимого. Но начиная с версииJDК 5 этого больше не нужно делать. После определения размеров и стандартной операции, выполняемой при за­ крытии окна, в конструкторе класса Event Demo создаются две экранные кнопки, как показано ниже . JButton jЬtnAlpha = new JButton ( "Alpha" } ; JButton jbtnBeta = new JBut ton ("Beta " }; Первая кнопка будет содержать надпись "Alpha " , а вторая - надпись "Beta" . Экран ные кнопки из библиотеки Swing являются экземплярами класса JButton. В классе JButton предоставляется несколько конструкторов. Здесь используется приведенный ниже конструктор, где параметр сообщение обозначает символьную стро ку, которая будет отображаться в виде надписи на экранной кнопке. JВutton (Strinq сооб•енJr•) После щелчка на экранной кнопке наступает событие типа ActionEvent. Поэтому в классе JButton предоставляется метод addAc tionListener (), кото­ рый служит для ввода приемника подобн ых событий. (В классе JBu t ton предостав­ ляется также метод removeActionListene r () для удаления приемника событий, но в рассматриваемом здесь примере программы он не применяется .) Как поясня­ лось в гл аве 24, в инте рфейсе ActionListener определяется единственный метод actionPerforme d ().Ради уд обства ниже еще раз приводится его общая форма. void actionPerformed (ActionEvent соб.u�••_д8'с�В1 1 R) Этот метод вьIЗывается после щелчка на экранной кнопке. Иными словами, это обработчик, который вызывается , когда наступает событие нажатия экранной кнопки . Далее вводится приемник событий от двух экранных кнопок, как показано ниже. В данном случае используются анонимные внутренние классы, чтобы предо­ ставить обработч ики событий от двух экранных кнопок. Всякий раз, когда пажи-
Глава 31. Введение в библиотеку Swing 1135 мается экранная кнопка, символьная строка , отображаемая на месте метки j lab, изменяется в зависимости от того , какая кнопка была нажата. 11 ввести приемник событий действия от кнопки Alpha jbtnAlpha .addActionListener (new ActionLi stener () { }); puЫic void actioпPerformed (ActionEvent ае ) { jlab. setText ( "Alpha was pressed. "J ; 11 Нажата кнопка Alpha 11 ввести приемник событий действия от кнопки Веtа jbtпBeta . addActioпListener (new ActionLi stener () { puЫ ic void actioпPe rformed {ActioпEvent ае ) { jlab . setText ("B eta was pressed."J ; 11 Нажата кнопка Beta }); Начиная с версии JDK 8 для реализации обработчиков событий можно также воспользоваться лямбда-выражениями. Например, обработчик событий от кноп­ ки Alpha можно было бы написать следующим образом: jЬtnAlpha . addAction Listener ( (ае) -> jlab . setText ( "Alpha was pressed. " )); Как видите , этот код более краткий. В интересах тех читателей, которые поль· зуютс яjаvа до версии JDК 8, в последующих примерах лямбда-выражения не при· меняются. Но при написании нового кода следует непременно найти возможно· сти воспользоваться ими. Затем кнопки вводятся на панели содержимого следующим образом: jfrm . add (jЬtnAlpha) ; jfrm . add (jЬtnBeta ); И наконец, на панели содержимого вводится метка j lab, и окно приложения становится видимым. Если после запуска данного Swing-приложения щелкнугь на любой из двух экранных кнопок, то на месте метки отобразится сообщение, из· вещающее, какая именно кнопка была нажата. И еще одно, последнее замечание: не следует забывать, что все обработчики событий вроде метода actionPerforme d () вызываются в потоке диспетчериза· ции событий. Следовательно , возврат из обработчика событий должен быть про· изведен быстро , чтобы не замедлить выполнение Swing-приложения. Если же при наступлении некоторого события в приложении требуется выполнить операции, от нимающие много времени, то для этой цели следует организовать отдел ьный поток исполнения. Соэда ние Swi n g - anneтa Еще одной разновидностью прикладной программы, где обычно применяется библиотека Swing, является аплет. Аплеты, разработанные на основе библиотеки Swing, подобны аплетам , построенным на основе библиотеки АWГ, но у них есть одно существенное отличие: Swing-aплeт расширяет класс JApp let, а не класс Ap plet. Та ким образом, класс JApp let включает в себя все функциональные
1136 Часть 111. Введение в п рограммирова ние ГПИ ср едст ва ми Swi ng возможности класса Applet, дополняя их поддержкой библиотеки Swing. Класс JApp let служит контейнером Swi ng верхнего уровня, а это означает, что он не на­ следует от класса JComponent, но в то же время включает в себя различные опи­ санные ранее панели. Следовател ьно , все компоненты вводятся на панели содер­ жимого контейнера типа JApp let таким же образом , как и компоненты на панели содержимого контейнера типа JFrame . В Swing-anлeтax применяются те же самые методы обеспечения жизненно­ го цикла аплета, которые были описаны в гл аве 23, а именно: init (), start (), stop () и de stroy (). Естественно, переопределить следует только те методы, которые потребуются в аплете. Рисование графики в библиотеке Swing осущест­ вляется иначе, чем в библиотеке АWГ, и поэтому метод paint () обычно не пере­ определяется в Swing-anлeтe . (О рисовании средствами Swing речь пойдет далее в этой главе.) Следует также иметь в виду, что все взаимодействия с компонентами Swing должны происходить в потоке диспетчеризации событий, как пояснялось в пре­ дыдущем разделе. Это относится ко всем Swing-приложениям. Ниже предстамен пример Swing-anлeтa. Он обладает теми же функциональны­ ми возможностями, что и Swing-приложение из предыдущего примера, но только является аплетом. На рис. 31.3 показан результат выполнения Swing-aплeтa из дан­ ного примера в окне утилиты appleteviewer. � -с.. .. .. �·• •_,,-- - ���- 11Applet Viewer: М... �@� Applet Alpha 1 \ Beta Alpha was pressed. Applet started. Рис. 31 .З . Резул ьтат выполнения примера Swi пg-аплета 11 Пример простого Swing-anлeтa irnport javax . swing .*; irnport java .awt.*; import java .awt . event .*; /* */ Этот код HTML можно исполь зовать для запуска аплета : <applet code= "My SwingApplet " width=220 height=90> </applet> puЫ ic class MySwingApplet extends JApplet { JButton jbtnAlpha ; JButton jbtnBe ta;
JLabel jlab; // инициализировать аплет puЫ ic void init () { try { Гл ава 31 . Введение в библиоте ку Swi ng SwingUtilities . invo keAndWa it (new Runn aЫe () puЫic void run () { ma keGUI () ; // инициализировать ГПИ }); catch ( Exception ехс ) { System. o ut . println ("Can ' t create because of "+ ехс ) ; // Нель зя создать из-за исключения указанного типа // В этом аплете не нужно переопределять ме тоды 11 start() , stop () или des troy () // настроить и инициализировать ГПИ priva te void ma keGUI () { // установить дл я аплета ди спетчер поточной компоновки setLayout (new FlowLayout () ); 11 создать две кнопки jЬtnAlpha = new JButton ("Alpha" ) ; jЬtnBeta = new JB utton ("Beta" ); // ввести приемник действия от кноп ки Alpha jЬtnAlpha .addActionLi stener (new Ac tionLi stener () { puЫic void actionPer formed (ActionEvent le ) { jlab . setText ( "Alpha was pressed. ") ; 11 Нажата кнопка Alpha }); // ввести приемник действия от кнопки Веtа jЬtnBeta . addAc tionListener (new Ac tionListener () puЫ ic void actionPerformed (ActionEvent le ) jlaЬ . setText ( "Beta was pressed.") ; 11 Нажата кнопка Веtа }); // ввести кнопки на панели содержимого add (jbtnAlpha ); add (jЬtnBeta ) ; 11 создать текстовую ме тку jlab = new JLabel ( "Press а Ьutton . ") ; 11 Метка "Нажми те кнопку ." 11 ввести ме тки на панели содержимого add (jlab) ; 1137 В отношении аплетов следует сделать два важных замечания. Во-первых, класс MySwingApp let расширяет класс JApplet. Как пояснялось ранее , все аплеты, по­ строенные на основе библиотеки Swing, расширяют класс JApplet, а не класс Applet. И во-вторых, метод init () инициализирует компоненты Swing в пото-
1138 Часть 111. Введение в программир ование ГПИ ср едствами Swing ке диспетчери3аЦии событий, устанавливая вызов метода ma keGUI (). Обратите внимание на то , что для этой цели служит метод invo keAndWa it (), а не invo ke Later (). Метод invo keAndWa i t () следует применять в аплетах потому, что возврат из метода ini t ( ) не должен происходить до тех пор , пока не завершится весь про­ цесс инициализации. По существу, метод start () нельзя вызывать до завершения инициали3аЦии, а это означает, что прежде нужно полностью построить ГПИ. В методе ma keGUI () создаются две кнопки и метка, а также вводятся прием­ ники действий от этих кнопок. И наконец, компоненты вводятся на панели со­ держи мого. Несмотря на всю простоту данного примера, демонстрируемый в нем принцип следует применять при построении любого ГПИ для Swiпg-anлeтa. Рисование средствами Swing Какими бы эффективными ни были компоненты Swing, построение ГПИ не ограничивается только ими, поскольку библиотека Swing позволяет также выводить информацию непосредственно в область отображения фрейма, панели или одного из компонентов Swing вроде метки типа JLabel. Как правило, рисование не выпол­ няется непосредственно на поверхности компонента Swing, тем не менее это мож­ но делать в тех приложениях , где требуется нечто подобное. Чтобы вывести дан­ ные прямо на поверхность компонента Swing, следует вызвать один или несколько методов рисования, определенных в библиотеке АWТ, например drawL ine () или drawRect (). Та ким образом, большинство приемов и методов, описанных в гл аве 25, распространяются и на библиотеку Swing. Но в подходе к рисованию средствами Swing имеется ряд очень важных отличий, о которых и пойдет речь в этом разделе. Основы рис ования Подход к рисованию средствами Swing основывается на исходном механизме из библиотеки АWТ, но библиотека Swing позволяет более точно управлять этим процессом. Прежде чем приступить к обсужде нию особенностей рисования сред­ ствами Swing, целесообразно напомнить особенности механизма рисования в биб­ лиотеке АWГ. В классе Component из библиотеки АWГ предоставля.ется метод paint (), пред­ назначенный для рисования выводимых данных прямо на поверхности компонен­ та. Как правило, метод paint () не вызывается для этого из прикладной програм­ мы. (В действительности вызывать этот метод из прикладной программы прихо­ дится в очень редких случаях. ) Вместо этого метод paint () вызывается испол­ няющей средой в процессе воспроизведения ко мпонента. Такая ситуация может возникнуть по нескольким причинам. Например, окно, в котором отображается компонент, может быть перекрыто другим окном, а затем появиться снова на экра­ не, или же оно может быть свернуто , а затем восстановлено. Метод paint () вы­ зывается и в тех случаях, когда программа начинает выполняться. Если разрабаты­ вается приложение на основе библиотеки АWТ, метод paint () переопределяется в нем всякий раз, когда дан ные требуется вывести прямо на поверхности компо­ нента.
Гл ава 31 . Введение в библиоте ку Swi ng 1139 Класс JComponent наследует от класса Соmро nеnt , и поэтому метод раint () до­ ступен вс ем легковесным ко мпонентам Swing. Но переопределять его с целью вы­ водить информацию непосредственно на поверхности ко мпонента не придется . Дело в том, что при рисовании средствами библиотеки Swing применяется более изощренный подход с помощью трех разных методов : paintComp onent (),paint Border () и paintChi ldren ().Эти методы рисуют указанную часть компонента, разделяя процесс рисования на три разных логических действия. В легковесном компоненте исходный метод paint () из библиотеки АWГ просто вызывает эти методы в упомянугом выше порядке. Чтобы нарисовать что-нибудь на поверхности компонента Swing, сначала при­ дется создать подкласс компонента, а затем переопределить его метод paint Component ().Этот метод отвечает за прорисовку внутренней области компонен­ та. А два других метода рисования , как правило, переопределять не требуется . Переопределяя метод paintComponent (), нужно сначала вызвать метод sup er . paintComponent (), чтобы задействовать часть процесса рисования из суперклас­ са. (Этого делать не нужно лишь в том случае, если управление отображением ком­ понента осуществляется вручную.) После этого можно вывести отображаемые дан ные. Ниже приведена общая форма метода paintComp onent (),где параметр g обозначает графическое содержимое выводимых данных. protected void paintCoшponent ( Graphics g) Чтобы нарисовать на поверхности компонента под управлением программы, следует вызвать метод repaint (). Этот метод действует в библиотеке Swing та­ ким же образом, как и в библиотеке АWГ. Метод repaint () определен в классе Compo nent. Вызов этого метода приводит к тому, что исполняющая система вызо­ вет метод paint (),как только для этого представится возможность. А поскольку процесс рисования отнимает немало времени, то данный механизм позволяет ис­ полняющей системе мгновенно задерживать рисование до тех пор, пока, напри­ мер, не завершится выполнение другой задачи, имеющей более высокий приори­ тет. Вызов метода paint () в Swing, безусловно, приводит к вызову метода pa int Component (). Следовател ьно, дан ные, выводимые на поверхность компонента, должны сохраняться прикладной программой до тех пор, пока не будет вызван метод paintComponent ().А рисование сохраняемых данных выполняется в пере­ определяемом методе paintComp onent (). В ычисление области р и сова ния Во время рисования на поверхности компонента нужно аккуратно ограничить область рисования выводимых данных в пределах ко мпонента. Несмотря на то что любые выводимые данные, выходящие за границы компонента, автоматиче­ ски отсекаются средствами Swing, вполне возможно, что какой-нибудь рисуемый участок окажется прямо на границе, а при ее перерисовке он может быть стерт. Во избежание этого следует вычислить область fтсования в пределах компонента. Эта область определяется следующим образом: из текущих размеров компонента вычитается пространство, занятое его границами. Следовательно, прежде чем ри­ совать на поверхности компонента, следует сначала выяснить ширину границы, а затем откорректировать соответстве нно рисование.
Часть 111. Введение в п рогра ммирование ГПИ средствами Swing Чтобы выяснить ширину границы компонента, следует вызвать метод ge t Insets ().Ниже приведена общая форма этого метода. Insets qetinsets () Этот метод определяется в классе Container и переопределяется в классе JC omponent. Он возвращает объект типа Insets, содержащий размеры границ по сто ронам ко мпонента . Значения этих размеров можно получить из следующих полей: int top ; int bot tom; int left ; int right ; Впоследствии эти значения применяются для вычисления области рисования с учетом ширины и высоты компонента. Ширину и высоту компонента можно вы­ яснить, вызвав методы getWidth ( ) и getHe ight ( ) соответственно. Ниже приве­ дены общие формы этих методов. int getWidth () int qetHeiqht () Вычитая соответствующие значения размеров границ из ширины и высоты компонента, можно получить ширину и высоту области , в которой предполагает­ ся рисование. П рим ер рис ования Рис. 31 ·'· Примерный резул ьтат выполнения программы Paint Pane l 11 Рисовать линии на панели import java.awt.*; import java .awt . event .*; import javax . swing .*; impo rt java .util .*; А теперь рассмотрим пример програм­ мы, где демонстрируются упомянутые выше методы рисования . В этой программе соз­ дается кл асс PaintPane l, расширяющий кл асс JPane l. Объект данного кл асса слу­ жит для отображения линий, конечные точ­ ки которых формируются случайным обра­ зом. Результат выполнения данной програм­ мы приведен на рис. 31.4 . 11 Этот кл асс расширяет кл асс JPanel . В нем переопределяется 11 ме тод paintCo!l lpO nent () , чтобы произвольно рисовать линии на панели class PaintPanel extends JPanel ( Insets ins ; // служи т для хранения разме ров границ панели Random rand ; // служит для генерирования случайных чисел 11 созда ть панель
Гл ава 31 . Введение в библиоте ку Swing PaintPanel () { 11 разместить рамку вокруг панели , определив ее гр аницы setBorder ( BorderFactory .createLineBorder (Color .RED, 5) ); rand = new Random() ; 11 переопр едели ть ме тод paintCoшponent () protected void paintComp onent ( Graphics g) 11 вызывать всегда первым ме тод из суперкласса sup er . paintComp onent (g) ; int х, у, х2, у2; 11 получить высоту и ширину коылонента int height = getHeight () ; int width = getWidth () ; 11 получить размеры границ панели ins = get insets () ; 11 нарисовать десять линий , конечные точки которых 11 формируются произвольно for(int i=O; i < 10; i++) { 11 получи ть произволь ные коорди наты, определя1О1 11И е 11 конечные точки каждой линии х = rand .next int (width-ins .left) ; у=rand .next int (height -ins .bottom) ; х2 = rand .nextlnt (width-ins .left ) ; у2 = rand .nextint (height-ins . bottom) ; 11 нарисовать линию g.drawLine (x, у, х2, у2 ); 11 продемонстрировать рисовани е непосредственно на панели clas s PaintDemo { JLabel jlab ; PaintPanel рр ; PaintDemo () { 11 создать новый контейнер типа JFr81 118 JFrame jfrm = new JFrame ("P aint Demo") ; 11 задать исходные размеры фрейма jfrm .setSize (200, 150) ; 11 завершить приложение , если пользователь 11 закроет его окно jfrm . setDefaultCloseOperation (JFrame .EXIT _ ON_CLOSE ); 11 создать панель для рисования рр = new Paint Panel () ; 11 Ввести эту панель на панели содержимого . В данном 11 случ ае применяется граничная коылоновка , поэтому 11 размеры панели будут автома тически подгоняться таким 11 образом, чтобы она заняла централь ную область jfrm . add (pp) ; 11 отобразить фрейм jfrm . setVisiЬle (tru e) ; ,,,,
11,2 Часть 111. Введе ние в п рограммирование ГПИ средства м и Swing puЫ ic static vo id ma in ( String args[]) { }); 11 создать фрейм в потоке ди спетчеризации событий SwingUtilities . invo keLater (new RunnaЬle () { puЫic void run() { new PaintDemo (); А теперь рассмотрим данную программу подробнее. Класс PaintPane l расши­ ряет класс JPane l, представляющий один из легковесных контейнеров Swing, т.е. компонент, который можно вводить на панели содержимого JFrame . Для рисова­ ния в классе Paint Pane l переопределяется метод paintComponent () .Этопозво­ ляет рисовать прямо на поверхности компонента средствами класса PaintPane l. Размеры панели не определены, поскольку в данной программе по умолчанию ис­ пользуется граничная компоновка, а панель вводится по центру. В итоге панель получает такие размеры, которые позволяют разместить ее по центру. При изме­ нении размеров окна соответственно подгоняются размеры панели. Обратите внимание на то , что в конструкторе класса PaintPane l определяет­ ся также красная граница толщиной 5 пикселей. Для этого вызывается метод set Bo rde r (),общая форма которого показа.на ниже . void setвorder (ВOrder граюrца) Интерфейс Border из библиотеки Swing инкапсулирует границу, которую мож­ но получить, вызвав один из методов, определенных в классе Borde rFactory. В данной программе применяется один из таких методо в, называемый create LineBorder () . Он создает простую линейную границу, как показано ниже , где па· раметр цв ет обозначает цвет рамки , а параметр ширина - ее ширину в пикселях. static Border createLineВOrder (Color цв� , int .-.ржн а) В переопределяемом варианте метода paintComponent () следует обратить внимание на то , что сначала в нем вызывается метод sup er . paintComponent () . Как пояснялось ранее, это требуется для обеспечения надлежащего рисования. Затем вычисляется ширина и высота панели, а также размеры границ. Эти значе· ния используются для того , чтобы рисуемые линии не выходили за пределы об­ ласти рисования на панели. Область рисования определяется вычитанием разме­ ров границ из общей ширины и высоты компонента. Вычисления организованы таким образом, чтобы учитывать разные размеры области рисования и границ. Чтобы убедиться в этом, попробуйте изменить размеры окна. Линии по-прежнему будуг рисоваться , не выходя за границы панели. В классе PaintDemo сначала создается панель рисования типа PaintPane l, а за· тем она вводится на панели содержимого. При первом отображении вызывается переопределяемый метод paintComp onent ( ) и рисуются линии. Всякий раз , ког­ да изменяются размеры окна или же оно сворачивается и восстанавливается , ри­ суется новый ряд линий. Но в любом случае линии не будут выходить за пределы заданной области рисования.
32 Иссл едован ие библ и отеки Swi ng В предыдущей гл аве были рассмотрены некоторые основные принципы по­ стро ения библиотеки Swing и показана общая форма приложений и аплетов, соз­ даваемых на основе библиотеки Swi ng. В этой гл аве продолжается исследование библиотеки Swing кратким обзором ее компонентов, в том числе кнопок, флаж­ ко в, деревьев и таблиц. Компоненты Swing обладают богатыми фун кциональными возможностями и допускают специальную настройку в широких пределах. К со­ жалению, в рамках данной книги невозможно описать все особенности и свойства компонентов Swing, поэтому рассмотрим лишь некоторые из них. Ниже перечислены классы компонентов Swing, рассматриваемых в настоящей гл аве. Все эти компоненты являются легковесными. Это означает, что все они про­ исходят от класса JC omponent. JВutton JCheckВox JСо..ЬОВох JLaЬel JLi at JRadioButton JS�ollPan• JТ.Ь Ь. dРаnе JТаЫе JТextField JТ\Sq9l8Вutton JТr" В этой гл аве рассматривается также класс ButtonGroup, инкапсулирую щий вза­ имоисключающий ряд кнопок Swing, а также класс Image Icon, инкапсулирую щий графическое изображение. Каждый из этих классов определяется в библиотеке Swing и входит в пакет j avax . swing. С'. . ледует также иметь в виду, что в этой гл аве применение компонентов Swing демонстрируется на примерах аплетов, поскольку код аплета более компактный, чем код настольного приложения. Но и на те и на другие в одинаковой степени распространяются расс матриваемые здесь методики построения ГПИ . Классы JLaЬel и Imageicon Класс JLabel представляет метку - самый простой в употреблении компонент Swing. Этот компонент уже рассматривался в предыдущей гл аве , и вам должно быть уже известно в общих чертах, как с его помощью создается метка. А в этой главе компонент типа JLabel рассматривается более подробно. С помощью ком­ понента типа JLabel можно отображать текст и/или значок. Этот компонент яв­ ляется пассивным в том отношении, что он не реагирует на данные, вводимые
,,,, Ч асть 111. Введение в п рогра ммирование ГПИ средствами Swing пользователем. В классе JLabe1 определяется несколько конструкторов. Ниже приведены три из них. JLaЬel (Icon .sнavoa:) JL&Ьel (Strinq C'J)Oa:a) JLaЬel (Strinq C'J)Oa:a , Icon .sнavoa: , int .вuра.-в аюr е) Здесь параметры строка и зна чок обозначают соответственно текст и значок, которые будут использоваться в качестве метки, а параметр выра внив а ние - вид выравнивания текста и/или значка по горизонтали в пределах метки . Этот па­ раметр должен принимать одно из значений следующих констант: LEFT, RIGHT, CENTER, LEAD ING или TRAILING. Наряду с рядом других констант, испол ьзуе­ мых в классах из библиотеки Swing, эти константы определяются в интерфейсе SwingCons tant s. Обратите внимание на то, что значки определяются с помощью объектов типа Icon, относящегося к интерфейсу, определяемому в библиотеке Swi ng. Получить значок проще всего средствами класса Image lcon, реализующего интерфейс Icon и инкапсулирующего изображение. Следовательно, объект типа Image lcon можно передать в качестве параметра типа Icon конструктору класса JLabel. Предоставить изображение можно нескол ькими способами, включая чтение изо­ бражения из файла или его загрузку по указанному URL. Ниже приведен конструк­ тор класса Image lcon, используемый в примере из этого раздела. Этот конструк­ тор получает изображение из файла, обозначаемого параметром имя_ файла . Imaqeicon (Strinq ЯХl l _ ф�ла) Значок и текст, связанные с меткой, можно получить с помощью следующих методо в: Icon qe ticon О Strinq qetText () Значок и текст, связанные с меткой, можно установить, вызывая приведенные ниже методы. void se ticon (Icon sнavoa:) void setText (Strinq C!l'pOA:a) Здесь параметры зна чок и строка обозначают указываемый значок и текст соответственно. Та ким образом, с помощью метода setText () можно изменить текст метки во время выполнения программы. В приведенном ниже примере аплета демонстрируется порядок создания и отображения метки , состоящей из значка и символьной строки. Сначала в дан­ ном аплете создается объекта типа Imageicon для отображения песочных часов из файла изображения hourglass . g i f. Этот объект указывается в качестве второго параметра при вызове конструктора класса JLabel. А первый и последний пара­ метры этого конструктора представляют текст метки и его выравнивание. И нако­ нец, созда нная метка вводится на панели содержи мого. На рис. 32.1 показано, ка­ ким образом метка в виде значка отображается в окне аплета из данного примера. 11 Продемонстрировать приме нение компоне нтов типа JLaЬel и Imaqeicon import java .aw t.*; import javax . swing .*;
/* */ Гл ава 32. Иссл едо вание библиотеки Swi ng <applet code= " JLabelDemo " width=250 height=2 00> </applet> puЫic class JLabelDemo extends JApp let ( puЫic void init () ( try { SwingUtilities . invo keAndWait ( new RunnaЫe () ( )i puЬlic void run () ma keGUI (); catch (Exception ехс) { System. o ut . println ("Can't create because of " + ехс) ; // Нель зя создать по указ анной причине private vo id ma keGUI () ( // создать значок Image icon ii = new Image icon ( "hourglass .png" ); 11 создать ме тку JLabel jl = new JLabel ( "Hourglas s", ii, JLabel . CENTER) ; 11 ввести ме тку на панели содержимо го add(jl) ; Applet Applet started. Hourglass Рис. 32 .1 . Отображение метки в окне аплета JLabelDemo Кла сс JТextField 11,5 Класс JT extField представляет простейший текстовый компонент из библио­ теки Swi ng. Это , пожалуй , наиболее употребительный текстовый компонент. Этот
11д6 Часть 111. Введение в п рограммирование ГПИ средствами Swing компонент позволяет отредактировать одну строку текста. Класс JT extField яв­ ляется производным от класса JT extComponent, наделяющим функциональны­ ми возможностями текстовые компоненты Swing. В качестве своей модели класс JT e xtField использует интерфейс Document. Ниже приведены три конструктора класса JT extField. JТextl'ield (int С!l'олб�) JТextField (Strinq С!l'рожа , int Ц88 !1'& ) JТextField (String C!l'J'Ox•) Здесь параметр строка обозначает первоначально предоставляемую символь­ ную строку, а параметр столбцы - количество столбцов в текстовом поле. Если па­ раметр строка не задан , то текстовое поле оказывается исходно пустым. А если не задан параметр столбцы, то размеры текстового поля выбираются таким образом , чтобы оно могло уместиться в указанной символьной строке. Компонент типа JT e xtField генерирует события в ответ на действия пользо­ вателя. Например, событие типа Act ionEvent наступает при нажатии пользова­ телем клавиши <Enter> , а событие типа CaretEvent - при каждом изменении по­ зиции каретки (т.е . курсора) . (Событие типа CaretEvent определяется в пакете j avax . swing . event.) Возможны и другие события . Как правило, эти события не нужно обрабатывать в прикладной программе. Вместо этого достаточно получить символьную строку, находящуюся в данный момент в текстовом поле. Для ее полу­ чения следует вызвать метод getText (). В приведенном ниже примере аплета демонстр ируется применение компо­ нента типа JT extField. В дан ном аплете сначала создается компонент типа JTe xtField, который затем вводится на панели содержимого. Когда пользователь нажимает клавишу <Enter>, наступает соответствующее событие действия. В ре­ зультате обработки этого события отображается текст в окне состо яния , как по­ казана рис. 32.2. // Продемонс трировать приме нение компонента JТextl'ield import java . awt .*; import java.awt . event .*; import javax .swing. *; /* */ <applet code="JTextFieldDemo " width=ЗOO height=SO> </appl et> puЫic class JTextFieldDemo extends JApp let { JText Field jtf; puЫic void init () { try { SwingUti lities . invo k eAndWa it ( new RunnaЬle () { puЫ ic void run() makeGUI( ) ; ); catch (Exception ехс ) { System.out .println ("Can ' t create because of " + ехс ) ; // Нель зя создать по ука занной причине
Гл ава 32. Иссл едование библиоте ки Swi ng private void ma keGU I () { 11 изменить поточную компоновку setLayout (new FlowLayout () ); 11 ввести текстовое поле на панели содержимо го jtf = new JTextField (15) ; add(jtf) ; j tf. addActionLi stene r (new Ac tionListener () { }); puЫic void actionPe rformed (ActionEvent ае ) 11 отобразит ь текст , когда поль зователь 11 нажима ет кл авишу <Enter> showS tatus (jtf. getText () ); . . - 1! App[et Viewe r: JText Fie LdDeшo []�([! Ap plet jтhis is а test.I Thls ls а test. Рис. 32 .2 . Отображение те кста в те ксто вом поле окна аплета JT ext FieldDerno Кнопки иэ библиотеки Swing В библиотеке Swi ng определены четыре класса кнопок: JBu tton, JT oggleButton, JCheckBox и JRad ioButton. Все они являются производными от класса AЬ stractButton, расширя ющего класс JComponent. Та ким образом, у кнопок имеются общие характерные черты. Класс Ab stractButton содержит немало методов, позволяющих управлять по­ веде нием кнопок. С их помощью можно, например, определить различные знач­ ки , которые будут отображаться на месте кнопки , когда она отключена, нажата или выбрана. Другой значок можно использовать для динамической подстановки , чтобы он отображался при наведении указателя мыши на кнопку. Ниже приведе­ ны общие формы методов, с помощью которых можно задавать эти значки. void aetDieaЫedicon (Icon di) void setpreeaedi con (Icon pi ) void setSelectedicon (Icon si) void eetRollovericon (Icon ri ) Параметры di, pi, s i и r i определяют значки , используемые для обозначения различных состояний. Те кст, связанный с кнопкой, можно прочитать и записать с помощью приведенных ниже методов, где параметр строка обозначает текст надписи на кнопке.
,,,8 Часть 111. Введение в п рограммирование ГПИ с редствами Swi ng Strinq qetтext О void setText (Strinq с�рожа ) Модель, применяемая во всех кнопках, определяется в интерфейсе ButtonModel. Кнопка генерирует событие действия, когда ее нажимает пользова­ тель. Возможны и другие события . В последующих разделах будут подробнее рас­ смотрены классы отдел ьных кнопок. Класс JВutton В классе JBut ton определяются функциональные возможности экранной кноп­ ки. Простая форма конструктора этого класса была представлена в предыдущей гл аве. Компонент типа JButton позволяет связать с экранной кнопкой значок, символьную строку или же и то и другое. Ниже приведены три конструктора клас­ са JButton, где параметры зна чок и строка обозначают соответственно значок и строку, используемые для кнопки. JВutton (Icon .sнаvож) JВutton (Strinq С!l'р()жа) JВutton (Strinq С!l'р()Жа , Icon .sна vож) Когда пользователь щелкает на экран ной кн опке, наступает событие типа Ac tionEvent. Используя объект ти па АсtiоnЕvеnt, передаваемый методу act ion Performed () зарегистрированного приемника действий типа Ac tion Listener, можно получить символьную строку с командой действия, связанной с данной кнопкой. По умолчанию эта символьная строка отображается в пределах кноп­ ки. Но команду действия можно также задать, вызвав метод setA ctionCom­ mand () для кнопки. А получить команду де йствия можно, вызвав метод getAction Command () для объекта события. Этот метод объявляется следующим образом: Strinq qetActionCommand () Команда действия обозначает кнопку. Та к, если в одном приложении использу­ ются две кнопки или больше , команда действия позволяет легко определить, ка­ кая именно кнопка была нажата. В предыдущей гл аве был представлен пример применения текстовой кнопки. А в приведенном ниже примере аплета демонстрируется кнопка в виде значка. В дан­ ном примере отображаются четыре экранные кнопки и одна метка. Каждая кнопка отображает значок, представляющий разновидность часов. Когда пользователь щел­ кает на кнопке, в метке появляется название часов, как показано на рис. 32.3. 11 Продемонс трировать применение компонента типа JВutton в виде значка import java.aw t.*; import java.awt . event .*; import javax . swing .*; /* */ <applet code="JBut tonDemo " width=2 50 he ight=750> </applet> puЫic class JButtonDemo extends JApplet implement s Ac tionLi stener JLabel jlab ;
Гл ава 32. И сследование библиоте ки Swi ng puЫ ic void init () { try { SwingUtilities .invokeAndWait ( new RunnaЬle () { ); puЫic void run() rna keGUI () ; catch (Exception ехс ) { Systern.out . println ("Can't create because of " + ехс) ; 11 Нельзя создать по указанной причине private void rna keGUI () { 11 изме нить поточ ную компоновку setLayout (new Fl owLayout()); 11 ввести кнопки на панели содержимого . Irnage icon hourglass = new Irnageicon ( "hourglass .png") ; JButton jb = new JButton (hourglas s) ; jb. setAc tionCornrnand ("Hourglass") ; // Песочные часы jb.addActionListener (this) ; add(jb) ; Irnage !con analog = new Irnage icon ("analog . png " ); jb = new JButton (analog) ; jb.setAc tionCornrnand ("An alog Clock" ); 11 Аналоговые часы jb.addActionLi stener (this) ; add(jb) ; Irnage icon digital = new Irna ge!con ("digital .png" ); jb = new JButton (digital ); jb.setAc tionComrnand ("D igital Clock" ); // Цифровые часы jb.addActionLi stener (this) ; add(jb) ; Irnage icon stopwa tch = new Irnageicon ("stopwa tch .png") ; jb = new JBu tton (stopwa tch ); jb. setActionComrnand ("Stopwatch" ); // Секундомер jb.addActionLi stener (this) ; add(jb) ; 11 создать метку и ввести ее на панели содержимого jlab = new JLabel ( "Choose а Tirnepiece ") ; // выбрать часы add (jlab ) ; 11 обработать события от кнопок puЫ ic vo id actionPerforrned (ActionEvent ае ) { jlab . setText ("You selected " + ae . ge tActionCornrnand () ); 11 Выбр аны указанные часы
1150 Часть 111. Введение в п р огр аммир ование ГПИ с редства ми Swi ng Applet 12:24 РМ .-. -- - -�:-,,,. .- :-: : !!1! !1 11 1 �х ' f -·--.. .. .. .. .. .. .. . - Choose е Тimepiece Ap plet st arted. Рис. 32 .3 . Кнопки выбора часов в окне аплета JBut tonDemo Кnacc JТoggleButton Полезной разновидностью экранной кнопки является переключаr rи и:ь. Эта кноп­ ка похожа на обычную кнопку, но действует иначе, поскольку может находиться в двух состоя ниях: нажатом и отпущенном. После щелчка на переключателе он остается нажатым, а не отпускается, как обычная экранная кнопка. Если после это­ го щелкнуть на переключателе еще раз, он отпускается. Та ким образом , всякий раз, когда пользователь щелкает на переключателе, он переходит в одно из двух ВОЗМОЖНЫХ СОСТОЯНИЙ. Переключатели определяются объектами класса JToggleButton, производно­ го от класса Ab stractButton. Помимо создания стандартных переключателей, класс JT oggkeButton служит суперклассом для двух других компонентов Swing, которые также представляют элементы управления, имеющие два состояния. Это классы JChe ckBox и JRadi oButton. Та ким образом, класс JT oggleButton опреде­ ляет базовые фун кции всех компонентов, имеющих два состояния. В классе JT oggleButton определяется несколько конструкторов. Ниже приве­ ден один из конструкторов, применяемых в примере из этого раздела. JТoqqleButton (Strinq C!l'pOxa ) Этот конструктор создает переключатель с текстом надписи, задаваемым в ка­ честве параметра строка . Стандартным является отпущенное состояние переклю-
Глава 32. Исследование библиотеки Swing 1151 чателя. Остальные конструкторы данного класса позволяют создавать переключа­ тели с изображением или текстом надписи и изображением. В классе JT o ggleButton применяется модель, определяемая во вложенном классе JTo ggleButton . ToggleBut tonMode l. Как правило , обращаться непосред­ стве нно к этой модели не требуется, чтобы воспользоваться стандартным пере­ ключателем. Как и компонент типа JButton, компонент типа JT oggleButton генерирует событие действия всякий раз , когда пользователь щелкает на переключателе. Но, в отличие от класса JB utton, компонент типа JT oggleButton генерирует также событие от элемента. Это событие используется теми компонентами, которые действуют по принципу выбора. Если переключатель типа JT oggleButton нажат, он считается выбранным. Если пользователь отпускает переключатель, выбор от­ меняется. Для обработки событий от элементов следует реализовать интерфейс ItemListener. Как пояснялось в главе 24, всякий раз, когда ге нерируется собы­ тие от элемента, оно передается методу i temStateChanged (), определяемому в интерфейсе ItemListener. Из метода itemS tateChanged () может быть вызван метод get !tem () для объекта типа ItemE vent , чтобы получить ссылку на экзем­ пляр класса JT oggleButton, сгенерировавш ий дан ное событие. Ниже приведена общая форма метода ge tltem (). OЬject geti tell l () Этот метод возвращает ссылку на кнопку. Эту ссылку следует привести к типу JT o ggleBut ton. Чтобы определить состояние переключателя, проще всего вызвать метод is Selected (), наследуемый из класса Ab stractButton, для кнопки , сгенерировав­ шей событие. Ниже приведена общая форма метода isSelected (). Этот метод возвращает логическое значение true , если кнопка выбрана, а иначе - логиче­ ское значение false. Ьoolean isSelected () В приведенном ниже примере аплета демонстрируется применение переклю­ чателя. Обратите внимание на приемник событий, который просто вызывает ме­ тод isSelected (), чтобы определить состояние кнопки. На рис. 32.4 показано окно аплета из данного примера с переключателем в нажатом состоянии. 11 Продемонстрир овать применение компонента типа JТoggleВUtton import java .awt.*; import java .awt . event .*; import javax . swing. *; /* */ <applet code= "JToggleButtonDemo " wi dth=2 00 hei ght=BO> </applet> puЬlic class JToggleButtonDemo extends JApplet { JLabel jlab ; JToggl eButton jtbn;
1152 Часть 111. Введение в программирование ГПИ средствами Swing puЫic vo id init () { try { SwingUtilities . invokeAndWait ( new RunnaЬle () {' ); puЫic vo id run () ma keGUI (); catch (Exception ехс ) ( System.out . println ("C an ' t create because of " + ехс) ; // Нельзя создать по указанной причине private void ma keGUI () { // измени ть поточную коъmоновку setLayout (new Fl owLayout () ); // создать ме тку jlab = new JLabel ( "Button is off."} ; // кнопка отпуще на // создать переключатель jtbn = new JT oggleButton ("On /Off") ; 11 ввести приемник событий от переключателя jtbn . addi temLi stener ( new ItemListener () { puЫ ic void itemS tateChanged (ItemEve nt ie ) if (jtbn . isSelected () ) }); jlab . setText ("Button is on . ") ; // Кнопка нажата else jlab.зetText ( "Button is off . ") ; // Кнопка отпущена // ввести переключатель и ме тку на панель содержимо го add(jtbn ) ; add(jlab ) ; -- , - - - - � 8 Applet Viewe r: JToggleB utto 11De 11 1 0 ��� Ap plet Ap plet sta rte d. On/Of f :JBut t on is on. Рис. 32 ., . Нажатие переключателя в окне аплета JT oggleButtonDerno
Гл ава 32. Исследование библ иоте ки Swi ng 1153 Флажк и Класс JC heckBox определяет функции флажка. Его суперклассом служит класс JTogg leButton, поддерживающий кнопки с двумя состояниями, как пояснялось выше. В классе JC heckBox определяется ряд конструкторов. Один из них выгля­ дит следующим образом: JCheckВox (Strinq С!l'рОЖВ) Этот конструктор создает флажок с текстом метки , определя емым в качестве параметра строка . Остальные конструкторы позволяют определить исходное со­ стоя ние выбора флажка и указать значок. Если пользователь устанавливает или сбрасывает флажок, генерируется со­ бытие типа IternE vent. Чтобы получить ссылку на компонент типа JC heckBox, сгенерировавший событие, следует вызвать метод get!tern () для объекта типа IternE vent, который передается в качестве события от элемента методу i tern StateChanged (), определяемому в интерфейсе IternListener. Определить вы­ бранное состояние флажка проще всего, вызвав метод isSe lected ( ) для экзем­ пляра класса JCheckBox. В приведенном ниже примере аплета демонстрируется применение флажков. В окне этого аплета отображаются четыре флажка и метка. Когда пользователь щелкает кнопкой мыши на флажке , наступает событие типа IternEvent. Из ме­ тода i ternStateChanged () вызывается метод ge t!tern( ) для получения ссьшки на объект типа JC heckBox, сгенерировавший событие от эл емента. Далее вызы­ вается метод isSelected ( ) с целью определить установленное щи сброшенное состояние флажка. А метод getText () вызывается с целью получить текст, выво­ димый на месте метки дан ного флажка. На рис. 32.5 показано окно аплета из дан­ ного примера с одним из установленных флажков. // Продемонс трировать приме нение компонента JCheckЬox irnport java .aw t.*; irnport java.awt . event .*; irnport javax . swing .*; /* */ <applet code=" JCheckBoxDerno " width=270 height=SO> </applet> puЬlic class JCheckBoxDerno extends JApplet irnplernent s IternLi stene r { JLabe l jlab; puЫic vo id init () { try { SwingUtilities . invokeAndWa it ( new RunnaЬle () { ); puЫ ic void run () rna keGUI () ; catch (Exception ехс ) { Systern.out . println ("Can't create because of " + ехс ) ;
Часть 111. Введение в программирование ГП И средств ами Swi ng 11 Нель зя создать по указанной причине private voi d makeGUI () { 11 изменить поточную компо новку se tLayout (new FlowLayout () ); 11 ввести флажи на панели содержимо го JCheckBox сЬ = new JCheckBox ( 11С 11 ) ; cЬ . additemLi stener (thiз) ; add(cЬ) ; сЬ = new JCheckBox ("C++" ); cЬ . additemLi stener (thiз) ; add (сЬ) ; сЬ = new JCheckBox ( "Java11 ) ; cЬ . additemLi stener (this) ; add (сЬ) ; сЬ = new JCheckBox ("Perl 11) ; cb . additemLi stener (thi s) ; add(cb) ; 11 создать ме тку и ввести ее на панели содержимо го jlaЬ = new JLaЬel ("S elect languages") ; 11 Выбор языка программи рования add(jlaЫ ; 11 обработать событие от флажка puЫ ic void itemS tateChanged (ItemEvent ie) { JCheckBox сЬ = (JCheckBox) i e.getitem() ; if (cb.isSelected () ) jlab . setText (cb.getText () +"iз selected" ); 11 Выбр ан ука занный язык программирования else jlaЬ . setText (cb.getText () + 11 iз cleared" ); 11 Указанный флажок сброшен . . . 11 App[et Viewer: JC heckВox:Deпю [J[QJ[8] Ap plet ОС ОС++ [aJava 0Perl Java is seleded Apple1 started. Рис. 32 .5 . Уста новка флажка в окне аnлета JCheckBoxDemo
Гл ава 32. Исследование библиоте ки Swing 1155 Кнопки - пере кп ю чатеп и Кнопки-переключатели образуют группу взаимоисключающих кнопок, из кото­ рых можно выбрать только одну. Они поддерживаются в классе JRadioButton, расширяющем класс JT oggleButton. В классе JRadioButton предоставляется не­ сколько конструкторов. Ниже приведен конструктор, используемый в примере из этого раздела. Jl la dioВutton (Strinq стража) Здесь параметр строка обозначает метку кнопки-переключателя. Остальные конструкторы позволяют определить исходное состоя ние кнопки-переключателя и указать для нее значок. Кнопки-переключатели следует объединить в груп пу, где можно выбрать толь­ ко одну из них. Та к, если пользователь выбирает какую-нибудь кнопку-переключа­ тель из группы, то кнопка-переключатель, выбранная ранее в этой группе, автома­ тически выключается. Для создания группы кнопок-переключателей служит класс Bu ttonGroup. С этой целью вызывается его конструктор по умолчанию. После этого в группу можно ввести отдел ьные кнопки-переключатели с помощью при­ веденного ниже метода, где параметр аЬ обозначает ссьmку на кнопку-переключа­ тель, которую требуется ввести в группу. void add (AЬstractвutton аЬ) Компонент типа JRadi oBut ton генерирует события действия , события от эле­ ментов и события изменения всякий раз, когда выбирается другая кнопка-пере­ ключатель в группе. Зачастую обрабатывается событие действия, а это, как прави­ ло, означает необходимость реализовать интерфейс ActionListener, в котором определяется ед инственный метод actionPerforme d ().Вэтом методе можно не­ сколькими способами выяснить, какая именно кнопка-переключатель бьmа выбра­ на. Во-первых, можно проверить ее команду действия , вызвав метод ge tAct ion Comma nd ( ) . По умолчанию команда действия аналогична метке кнопки, но, вы­ звав метод setAc tionCommand () для кнопки-переключателя, можно задать какую­ нибудь другую команду действия. Во-вторых, можно вызвать метод ge tSource ( ) для объекта типа ActionEvent и проверить ссылку по отношению к кнопкам-пере­ ключателя м. И наконец, для каждой кнопки можно вызвать свой обработчик со­ бытий действия , реализуемый в виде анон имного класса или лямбда-выражения . Не следует, однако, забывать, что всякий раз, когда наступает событие действия, оно означает, что выбранная кнопка-переключатель бьmа изменена и что бьmа вы­ брана одна и только одна кнопка-переключатель. В приведенном ниже примере аплета демонстрируется применение кнопок-пе­ реключателей. В этом а.плете создаются и объединяются в группу три кнопки-пере­ ключателя. Как пояснялось ранее, это требуется для того, чтобы они действовали, взаимно исключая друг друга. При выборе кнопки-переключателя наступает собы­ тие действия, которое обрабатывается методом actionPer formed (). В этом о&­ работчике событий метод ge tAc tionCommand ( ) получает текст, связанный с кноп­ кой-переключателем, чтобы отобразить его на месте метки. На рис. 32.б показано окно аплета из данного примера с одной из выбранных кнопок-переключателей.
1156 Часть 111. Введение в программирование ГПИ с р едствами Swing // Продемонстрировать приме нение компонента JR.adioВUtton import java .aw t.*; import jav a.awt . event .*; import javax . swing .*; /* */ <applet code="JRadioButtonDemo " width=ЗOO height=50> </applet> puЫ ic class JRadioButtonDemo extends JApp let implements Ac tionLi stener { JLabel jlab ; puЫic void init () { try { SwingUtilities . invo keAndWait ( new Ru nnaЫe () { ); puЫ ic void run {) ma keGUI () ; c atch (Exception ехс ) { System.out . println ("Can't create because of " + ехс) ; 11 Нельзя создать по указанной причине private void makeGUI {) { } // изменить поточную компоновку setLayout {new FlowLayout () ); // создать кнопки-переключатели и ввести // их на панели содержимого JRadioButton Ы = new JRadioBut ton ("A ") ; Ы.addActionLiзtener (this) ; add{Ь l) ; JRadioButton Ь2 = new JRadioButton ("B ") ; b2 . addActionListener (this) ; add {b2) ; JRadioButton ЬЗ = new JRadioButton ("C ") ; bЗ . addActionLi stener (this) ; add (b3) ; 11 определить группу кнопок ButtonGroup bg = new ButtonGroup () ; bg .add(Ь l) ; bg. add(b2); bg .add(b 3) ; // создать метку и ввести ее на панели содержимого jlab = new JLabel ( "Select One" ); 11 ВыОор одной из кнопок-переключ ателей add(jlaЫ ; // оОраОотать соОытие выОора кнопки-переключателя puЫic void actionPerformed (ActionEvent ае ) { jlab.setText ("You зelected " + ae . getAc tionCornmand () ); // ВыОр ана указанная кнопка - переключатель
Глава 32. Исследование библиотеки Swing .-..'... .. .. .. ,. .. . ...... �- ... .. . .·--·.-�"_� ...,.-. 11 Арр[е1 Viewe r: JRadioButto 11De11 1 0 �LQ.11:8] Applet ОА @В ОС YouselectedВ Applet started. Рис. 32 .6. Выбор кноп ки-переключател я в окне аплета JRadioBut tonDemo Кл а сс JТаЬЬеdРаnе 1157 Класс JTabbedPane инкапсулирует панелъ с 8'КЛадками. Он управляет рядом ком­ понентов, связывая их с помощью вкладок. При выборе вкладки связанный с ней ко мпонент выступает на передний план . Панели с вкладками широко применяют­ ся в ГПИ современных приложен ий, и вам, без сомнения, придется не раз употре­ блять их в своих прикладных программах. Несмотря на сложную структуру панели с вкладками , создавать и пользоваться ею очень просто. В классе JTabbedPane определяются три конструктора. В примере из этого раз­ дела используется конструктор по умолчан ию, создающий пустой элемент управ­ ления с вкладками, располагаемыми вдоль верхнего края панели. Два других кон­ структора позволяют определить расположение вкладок вдоль од ной из четырех сторон окна. В классе JTabbedPane применяется модель, определяемая в классе SingleSelectionMode l. Для ввода вкладок на панели вызывается метод addTab (). Ниже приведена одна из общих форм этого метода. void addTa.Ь (Strinq JU U1 , Component хо.мпо.нент) Здесь параметр имя обозначает указанное имя вкладки , а параметр компонент - вводимый на вкладке компонент. Обычно на вкладке вводится ко мпонент ти па JPane l, содержащий группу связанных вместе компонентов. Благодаря этому вкладка может содержать ряд компонентов. В общем, процедура употребления панели с вкладками сводится к следующему. 1. Создать объект класса JT abbedPane. 2. Вызвать метод addT ab (), чтобы ввести каждую вкладку по отдельности . 3. Ввести панель с вкладками на панели содержимого. В приведенном ниже примере аплета демонстрируется процесс создания пане­ ли с вкладками. Первая вкладка озаглавлена как Cities (Города) и содержит четыре кнопки . Каждая кнопка отображает название города. Вторая вкладка озаглавлена как Colors (Цвета) и содержит три флажка. Каждый флажок отображает название цвета. И наконец, третья вкладка озаглавлена как Flavors (Ароматы) и содержит один ко мбинированный список. Из этого списка пользователь может выбрать
1158 Часть 111. Введение в програ ммирование ГП И средствами Swi ng один из трех ароматов. На рис. 32.7 показано содержимое трех вкладок, выбирае­ мых на панели в окне аплета из данного примера. // Продемонстрировать приме нение компоне нта JТаЬ ЬеdР аnе import javax . зwing.*; /* */ <applet code= "JТabbedPaneDemo " width=400 height=lOO> </applet> puЫ ic class JТabbedPaneDemo extends JApplet puЬl ic void init () { try { SwingUtilities . invokeAndWa it ( new RunnaЫe () { ); puЫic void run () ma keGUI (); catch (Exception ехс ) { Syзtem.o ut . println ("Can ' t create Ьесаuзе of "+ехс) ; 11 Нель зя создать по указанной причине private void ma keGUI () { JTabbedPane jtp = new JТ abbedPane () ; jtp.addTab ("Citieз", new CitieзPanel () ); // вкладка городов jtp.addTab ("Colors ", new ColorsPanel () ); // вкладка цветов jtp.addTab ("F lavorз ", new FlavorsPanel () ); // вкладка ароматов add(jtp) ; // создать панели , которые будут введены на панели с вкладками class CitieзPanel extendз JPanel { puЫ ic CitieзPanel () { JButton Ы new JВutton ("New York" ); // Нью- Йорк add (Ьl ); JВu tton Ы new JВutton ("London") ; // Лондон add(Ы) ; JВutton ЬЗ new JButton ("Hong Kong " ); // Гонконг add(b 3) ; JButton Ь4 new JВutton ("Tokyo" ); // Токио add (b4 ); clasз ColorsPanel extendз JPanel { puЫ ic ColorзPanel () { JCheckBox сЫ new JChe ckBox ("Red" ); // Кр асный add (cЬl ); JCheckBox сЫ new JCheckBox ("Green" ); // Зеленый add (cЫ) ; JCheckBox сЬЗ new JChe ckBox ("Blue " ); //Синий add (cbЗ); class FlavorзPanel extendз JPanel 1
Гл ава 32. Иссл едование библиотеки Swing 1159 puЫ ic FlavorsPanel () { JComЬoBox< String> jcb = new JComЬoBox<String> () ; jcb.addltern ("Vanilla" ); // Ванильный арома т jcb.additern ("Chocolate ") ; // Шо коладный арома т jcb.addl tern ("Strawberry") ; // КлуОничный аромат add(jcb) ; '�'_" "� •� •' �·- ,"_-�""'""w•-�- _. .. .. . .. .,. .. •? ..,. ••' • • ,. ., ",. . • 1! Apptet Viewer: JTabbe dPa11e Oeino r:;J[QJ� Applet l9!_ies f Colors 1' Flavors 1 . . NewYork JI London J1 Hong Kong 11 Tokyo Ap plet sta rted. • '·' 1 . ·-- . 1 1 8 Applet Viewer: JTabbe dPa 11eOeino GJ�L'8J Applet Cities Г Colors Г Flavors 1 '- - ��-- -L �������������__.,. 0Red �Green 0Blue Applet sta rted. � . . . " ' 1 f! App!et Viewe r: JTabbedPa11eOeшo ��[g] Applet Cities Colors Flavors Applet sta rted. Chocolate anilla hocolate Рис. 32.7. Выбор каждой из трех вкладок на панели в окне аплета JTabbedPaneDemo
1160 Часть 111. Введение в п рограммирование ГПИ средствами Swi ng Кл а сс JS crollPane Класс JScrollPane представляет легковесный контейнер, автоматически вы­ полняющий прокрутку другого компонента. Прокручиваться может как отдель­ ный компонент (например, таблица) , так и группа компонентов, содержащихся в другом легковесном контейнере, например JPane l. Но в любом случае прокру­ чиваемый компонент дополняется горизонтальной и/или вертикальной полосой прокрутки, если он больше области просмотра, что позволяет прокручивать ком­ понент в пределах панели. Класс JScrollPane автоматизирует процесс прокрут­ ки , избавляя от необходимости управлять отдел ьными полосами прокрутки. Просматриваемая область панели с полосами прокрутки называется сжном npQ­ cмompa. Это окно, в котором отображается прокручиваемый компонент. Та ким образом , в окне просмотра будет показана видимая часть прокручиваемого ком­ понента. Полосы прокрутки служат для прокручивания компонента в окне про­ смотра. По умолчанию класс JS crollPane динамически добавляет или удал яет полосу прокрутки по мере надобности . Та к, если компонент оказывается больше по высоте, чем окно просмотра , то оно дополняется вертикальной полосой про­ крутки . А если компонент полностью размещается в окне просмотра, то полосы прокрутки исключаются. В классе JS crollPane определяется несколько конструкторов. Ниже приведен ко нструктор, используемый в примере из этого раздела. JScrollPane ( Coшponent жонпанен�) Прокручиваемый компонент указывается в качестве параметра компо нент. Полосы прокрутки автоматически отображаются, если содержимое панели пре­ вышает размеры окна просмотра. Чтобы воспользоваться панелью с полосами прокрутки , достаточно выпол­ нить следующие действия . 1. Создать прокручиваемый компонент. 2. Создать экземпляр класса JScrollPane, передав ему прокручиваемый ком­ понент как объект. 3. Ввести панель с полосами прокрутки на панели содержимого. В приведенном ниже примере аплета демонстрируется применение панели с полосами прокрутки. Сначала в этом аплете создается панель в виде объекта типа JPanel, а затем на этой панели вводится 400 кнопок, размещаемых в 20 столбцах. Далее эта панель вводится на панели с полосами прокрутки, а последняя - на пане­ ли содержимого. Эта панель оказывается больше окна просмотра, поэтому она ав­ томатически дополняется вертикальной и горизонтальной полосами прокрутки. Полосы прокрутки служат для прокручивания кнопок в окне просмотра. На рис . 32.8 показан вид панели с полосами прокрутки в окне аплета из данного примера. // Продемонстрировать приме нение компонента JScrollPane import java . awt .*; import javax .swing. *; /* <applet code="JScrollPaneDemo" width=ЗOO height=2 50> </appl et>
Гл ава 32. Иссл едование библ иоте ки Swi ng */ puЫ ic class JScrollPaneDemo extends JApplet puЫ ic void init () ( try ( SwingUtilities . invo keAndWait ( new RunnaЬle () ( ); puЫic void run() makeGUI () ; catch (Except ion ехс ) ( System. out . println ("Can't create because of " + ехс) ; 11 Нель зя создать по указанной причине private void ma keGUI () ( // ввести 400 кнопок на панели JPanel jp = new JPanel () ; jp. setLayout (new GridLayout (20, 20) ); intЬ=О; for(inti=О;i<20;i++)( ) for(intj=О;j<20;j++)( jp.add (new JBut ton ("B utton " + Ь) ); ++Ь; // создать панель с полосами прокрут ки JScroll Pane jsp = new JScrol lPane (jp) ; // Ввести панель с полосами прокрутки на панели содержимого . 11 По умолчанию выполняется граничная комп оновка, и поэтому 11 панель с полосами прокрут ки вводи тся по центру add(jsp, Borde rLayout .CENTER) ; Класс JList 1161 Базовым для составления списков в Swing служит класс JL ist. В этом классе поддерживается выбор одного или нескольких элементов из списка. Зачастую спи­ сок состоит из символьных строк, но ничто не мешает составить список из любых объектов, которые только можно отобразить. Класс JL ist настолько часто при­ меняется вJava, что он скорее всего встречался вам в прикладном коде. Раньше элементы списка типа JL ist были представлены ссьтками на класс Obj ect. Но в версии JDК 7 класс JL ist стал обобщенным и теперь объявляется приведенным ниже образом, где параметр Е обозначает тип элементов в списке. class JLi st<E> В классе JL ist предоставляется несколько конструкторов. Ниже приведен один из наиболее употребительных конструкторов данного класса. Этот конструк­ тор создает список типа JLi st, содержащий элементы в массиве, обозначаемом в качестве параметра элементы. JLis t (Е [] эленен!l'Ы)
1162 Часть 111. Введение в программир ование ГПИ средствами Swi ng But t on 109 But t on 129 But t on 149 But t on 168 But t on 169 But t on 188 But t on 189 But t on 208 But t on 200 But t on 220 But t on 220 But t on 248 But t on 249 But t on 268 ВЩ!оn 269 111 Рис. 32 .8 . Вид панели с полосами прокрутки в окне аплета JScrollPaneDemo Класс JL ist основывается на двух моделях. Первая модель определяется в ин­ терфейсе Li s tMode 1 и устанавливает порядок доступа к данным в списке. Вторая модель определяется в интерфейсе Lis tSelectionMode l, где объявляются мето­ ды , позволяющие выявить выбранный из списка элемент (или элементы). Несмотря на то что компонент типа JL ist вполне способен действовать само­ стоятел ьно , он обычно размещается на панели типа JScrollPane. Благодаря это­ му длинные списки становятся автоматически прокручиваемыми. Этим упроща­ ется не только построение ГПИ , но и изменение количества записей в списке, не требуя изменять размеры компонента типа JL ist. Компонент типа JL ist генерирует событие типа ListSelectionEvent, когда пользователь выбирает элемент или изменяет выбор элемента в списке. Это событие наступает и в том случае, если пользователь отменяет выбор эле­ мента. Оно обрабатывается приемником событий, реализующим интерфейс ListSelectionListener, в котором определяется еди нственный метод value­ Changed (): void valueChan9ed (ListSelectionИvent coбu�••_CJDrcжa) где параметр со бытие_ списка обозначает ссылку на событие. И хотя в классе ListSelectionEvent предоставляются свои методы для выяснения событий, на­ ступающих при выборе элементов из списка, как правило , для этой цели достаточ­ но обратиться непосредственно к объекту типа JLi st. Класс ListSelectionEvent и интерфейс Lis tSelectionLi stener определе ны в пакете javax . swing . event.
Гл ава 32. Исследование б и бл иоте к и Swing 1163 По умолчанию компонент типа JL ist позволяет выбирать несколько элемен­ тов из списка, но это поведение можно изменить, вызвав метод setSe lection Mo de l (), определяемый в классе JL ist. Ниже приведена его общая форма. void aetSelectionМode (int pez!Df) Здесь параметр режим обозначает заданный режим выбора. Этот параметр дол­ жен принимать значение одной из следующих констант, определенных в интер­ фейсе ListSelect ionModel: SINGLE SELECTION SINGLE - INТERVAL SELECTION NULT IPLE_INТERVAL_SELECTION По умолчанию выбирается значение последней константы , позволяющее вы­ бирать несколько интервалов элементов из списка. Если же задан режим выбора в одном интервале (константа SINGLE_ INTERVAL_SELECTION), то выбрать можно тол ько один ряд элементов из списка. А если задан режим выбора одного элемента (константа SINGLE_SELECT ION), то выбрать можно только один элемент из спи­ ска. Разумеется , один элемент можно выбрать и в двух других режимах, но эти ре­ жимы позволяют также выбирать несколько элементов из списка. Вызвав метод ge tSelectedindex (), можно получить индекс первого выбран­ ного элемента, который оказывается также индексом единственного выбранно­ го элемента в режиме SINGLE_S ELECT ION. Ниже приведена общая форма метода getSelectedinde x (). int qetSelectedindex () Индексация элементов списка начинается с нуля. Та к, если выбран первый эле­ мент, метод getSelectedinde x () возвращает нулевое значение. А если не выбра­ но ни одного элемента, то возвращается значение -1 . Вместо того чтобы получать индекс выбранного элемента, можно получить зна­ чение, связанное с выбранным элементом , вызвав метод ge tSe lectedVa lue (). Ниже приведена общая форма этого метода. Е qetSelectedValue () Этот метод возвращает ссылку на первое выбранное значение. Если не выбра­ но ни одного значения, то возвращается пустое значение nul l. В приведенном ниже примере аплета демонстрируется применение просто­ го компонента типа JLi st, содержащего список городов. Всякий раз, когда из этого списка выбирается город, наступает событие типа ListSelectionEvent , обрабатываемое методом va lueChanged (), определяемым в интерфейсе List SelectionLi stener. Этот метод получает индекс выбранного элемента и отобра­ жает имя выбранного города на месте метки. На рис. 32.9 показано, как город вы­ бирается из списка в окне аплета из данного примера. // Продемонс трировать применение компонента JList import javax . swing .*; import javax .swing . event .*; import java .awt.*; import java.awt . event .*; /*
116' Часть 111. Введение в п рограммирование ГПИ средствами Swi ng */ <applet code= "JL istDemo " width=200 height=l20> </apple t> puЫic class JListDemo extends JApplet { JLi st<St ring> jlst; JLabel jlab; JScroll Pane jscrlp; 11 создать ма ссив из названий городов String Cities [] = { "New York" , "Chicago" , "Hous ton" , "Denver", "Los Angeles", "Seattle ", "London ", "Paris", "New Delhi ", "Hong Kong ", "Tokyo" , "Sydney" } ; puЫic void init () { try { SwingUtilities .invokeAndWa it ( new RunnaЬle () { ); puЬlic void run() makeGUI (); catch (Exception ехс } { System.out . println ("Can't create because of " + ехс) ; 11 Нель зя создать по ука занной причине private void makeGUI () { 11 изме нить поточную компоновку setLayout (new FlowLayout () ); 11 создать список на основе компонента типа JList jlst = new JLi st<String> (Cities }; 11 задать режим выбора единс твенного элемента из списка jlst .se tSelectionMode (ListSelectionМodel .SINGLE_S ELECT ION) ; 11 ввести список на панели с полосами прокрутки jscrlp = new JScroll Pane (jlst) ; 11 задать предпочтительные размеры панели с полосами прокрутки jscrlp . setPreferredSize (new Dimension {120, 90) ); 11 создать ме тку для отображения выбранного города jlab = new JLabel ( "Choose а City" ); // Выбор города 11 ввести прие114Ник событий выбора из списка jlst . addListSelectionListener (new ListSelectionListener (} puЫ ic void valueChanged ( ListSelectionEveпt le) { 11 получить индекс измененного элемента int idx = jlst . ge tSelectedindex () ; 11 отобразить сделанный выбор , если элеме нт 11 был выбран из списка if (idx != -1) jlab.setText ( "Curreпt selection : "+Cities [idx] ); 11 Текущий выбор : ука занный город else // В противном случае еще раз предложить 11 выбр ать город из списка jlab.setText ( "Choose а City") ; // Выбор города
Гл ава 32. Иссл едова ние библиоте ки Swi ng }); 11 ввести список и ме тку на панели содержимого add(jscrlp ) ; add(jlab ) ; .��� .. - ' 11 Applet Viewe r: JListDe11 1 0 �(g]L8J Applet .. .. 1-ondo ft � Paris ,. .. .,. New Delhi - .. ..;. .. Hong Kong - " Current selection: London Ap plet sta rte d. Рис. 32 .9. Выбор го рода из списка в окне аплета JL istDerno Кла сс JComЬoBox 1165 С помощью класса JComЬoBox из библиотеки Swing определяется компонент, называемый комбu:нированнъtм списком и сочетающий текстовое поле с раскрываю­ щимся списком. Как правило , комбинированный список отображает одну запис ь, но он может отображать и раскрывающийся список, позволяющий выбирать дру­ гие элементы. Имеется также возможность создать комбинированный список, по­ зволяющий вводить выбираемый элемент в текстовом поле. Раньше элементы комбинированного списка на основе компонента типа JComЬoBox были представлены ссылками на класс Obj ect. Но в версииJDК 7 класс JComЬo Box был сделан обобщенным и теперь объявляется приведенным ниже об­ разом, где параметр Е обозначает тип элементов комбинированного списка. class JСошЬоВох<Е> Ниже приведен конструктор класса JComЬoBox, используемый в примере из этого раздела. JСошЬоВох (Е [ ] алам8Н!П1) Здесь параметр элементы обозначает массив, инициализирующий комбиниро­ ван ный список. В классе JC omЬoBox имеются и другие конструкторы. В классе JC omЬoBox применяется модель, определяемая в интерфейсе ComЬ oBoxMode l. Для создания изменяемых комбинированных списков (т.е . таких списков, элементы которых могут изменяться) применяется модель, определяе­ мая в интерфейсе MutaЬ leComЬ oBoxMode l.
1166 Часть 111. Введе н ие в программировани е ГПИ ср едствами Swing Кроме передачи массива элементов, которые должны быть отображены в рас­ крывающемся списке , элементы можно динамически вводить в список с помощью метода addl tem ( ) . Ниже приведена общая форма этого метода. void additem ( Е o65er!1') Здесь параметр объект обозначает объект, вводи мый в комбинированный спи­ сок. Метод addItem () должен применяться только к изменяемым комбинирован­ ным спискам. Компонент типа JC omЬoBox генерирует событие действия, когда пользователь выбирает элемент из ко мбинированного списка. Этот компонент генерирует так­ же событие от элемента, когда изменяется состояние выбора, что происходит при выборе или отмене выбора элемента. Та ким образом, при изменении выбора происходят два события : одно - от того элемента, выбор которого был отменен, другое - от выбранного элемента. Нередко оказывается достаточно принимать со­ бытия действия , но обрабатывать можно оба типа событий. Вызвав метод getSelectedItem (), можно получить элемент, выбранный из комбинирован ного списка. Ниже приведена общая форма этого метода. OЬj ect qetSelecteditem () Значение, возвращаемое этим методом, следует привести к типу объекта, хра­ нящегося в списке. В приведенном ниже примере аплета демонстрируется приме­ нение комбинированного списка. Этот список содержит элементы "Hourglass" (Песочные часы) , "Ana log" (Аналоговые часы) , "Digital" (Цифровые часы) и " Stopwatch " (Секундомер). Есл и пользователь выберет часы, метка обновит­ ся, отображая значок данной разновидности часов. Обратите внимание, как мало кода требуется, чтобы воспользоваться этим сложным компонентом. На рис. 32. 10 показан комбинированный список в окне аплета из данного примера. 11 Продемонстрировать применение компонента типа JComЬoBox import java .awt.*; import java .awt . event .*; import javax . swing.*; /* */ <applet code=" JComЬoBoxDemo " width=ЗOO height=200> </appl et> puЫ ic class JComЬoBoxDemo extends JApp let { JLabe l jlab ; Image !con hourglass , analog , digital , stopwatch; JComЬoBox<String> jcb; String tirne pieces [] = { "Hourglass", "Analog", "Digital ", "Stopwatch" } ; puЫic void init {) { try { SwingUtilities . invokeAndWa it ( new Runn aЬle () { puЫic void run() makeGUI (); );
Гл ава 32. Исследование библиоте ки Swing catch (Exception ехс } { System. o ut . println ("Can't create because of " + ехс) ; 11 Нель зя создать по указанной причине private vo id ma keGU! (} { 11 изменить поточную компоновку setLayout (new FlowLayout (} }; 11 получить экземпл яр объе кта комбинированного списка и 11 ввести его на панели содержимого jcb = new JComЬoBox<String> (timepieces} ; add (jcb) ; 11 обработать события выбора элементов из списка jcb. addAc tionLi stener (new ActionListener (} ( puЫic void actionPerformed (ActionEvent ае ) { String s = (String } jcb. getSelecteditem( ); jlab . setlcon (new Imagelcon (s + ".png"} }; } }}; 11 создать ме тку и ввести ее на панели содержимого jlab = new JLabel (new Image lcon ("h ourglasз .png"} }; add(jlab } ; Деревья Applet Applet started. Рис. 32.10. Выбор часов из ко мбинированного списка в окне аплета JComЬoBoxDemo 1167 Дерево - это компонент для иерархического представления данных. В таком представлении пользователь может развертывать или свертывать отдел ьные узлы. В библиотеке Swing деревья реализуются в классе JT ree. Ниже приведе ны некоторые из конструкторов этого класса.
1168 Часть 111. Введе н ие в программирование ГПИ средства ми Swing JТr- (OЬject о6.еж!1' [] ) JТr- (Vector< ?> v) JТree (TreeNode tn) В первой форме конструктора дерево создается из элементов массива объект, во второй форме - из эл ементов вектора v, в третьей - на основании корневого узла tn. Несмотря на то что класс JT ree входит в пакет javax . swing, он поддер­ живает классы и интерфейсы, определенные в пакете j avax . swing . tree. Дело в том, что количество классов и интерфейсов, требующихся для поддержки ком­ понента типа JT ree, слишком велико. Класс JT ree основывается на двух моделях типа TreeMode l и TreeSelection Mode l. Компонент типа JT ree генерирует различные события , но к деревьям не­ посредственное отношение имеют лишь три типа событий: Т re е Expans i onEvent, TreeSelectionEvent и TreeModelEvent. Событие типа TreeExpans ionEvent наступает при разворачивании или сворачивании узла дерева, событие типа TreeSelectionEvent - пpи выборе пользователем узла дерева или отмене его вы­ бора, а событие типа Т reeMode lEvent - при изменении данных или структуры де­ рева. Эти события отслеживаются приемниками типа TreeExpansionListener, TreeSelectionListener и TreeModelListene r соответственно. Классы со­ бытий, происходящих в дереве, а также интерфейсы приемников этих событий определены в пакете j avax . swing . event. В примере aWJeтa из этого раздела обрабатывается событие типа Tree SelectionEvent. Чтобы принять это событие, следует реализовать интерфейс TreeSelectionListener. В этом интерфейсе определяется еди нственный метод va lueChanged (),получающийсобытие в виде объекта типа TreeSelectionEvent. Вызвав метод ge tPath (), можно получить путь к выбранному объекту события . Ниже приведена общая форма этого метода. TreePath qetPath () Метод getPath () возвращает объект типа TreePath, описывающий путь к из­ мененному узлу. Класс TreePa th инкапсулирует сведения о пути к определенному узлу дерева. В нем предоставляется ряд методов и конструкторов. В примере из этого раздела используется только метод toString (), возвращающий символь­ ную строку, описывающую искомый путь. В интерфейсе TreeNode объявляются методы , получающие сведения об узле дерева. В частности , можно получить ссылку на родительский узел или список порожденных им узлов. Интерфейс Mu taЫeTreeNode расширяет интерфейс TreeNode . В нем объявляются методы, позволяющие вводить и уд алять порожден­ ные узлы или изменять родительский узел дерева. Класс De faultMutaЫeTreeNode реализует интерфейс Mu taЫeTreeNode . Он представляет узел дерева. Ниже приведен один из его конструкторов. DefaultмutaЬleTreeNode (OЬject о65еж!1') Здесь параметр объект обозначает тот объект, который требуется заключить в данном узле дерева. У нового узла дерева отсутствует родительский или порож­ денный узел.
Гл ава 32. Исследован ие библ иоте ки Swing 1169 Чтобы создать иерархию из трех узлов дерева, достаточно вызвать метод add ( ) из класса De faultMutaЫeTreeNode . Ниже приведена общая форма этого метода, где параметр потомок обозначает изменяющийся узел дерева, который требуется ввести в качестве порожденного от текущего узла. void add (!61taЬleTreeNode по�оиож) Сам компонент типа JT ree не предоставляет никаких возможностей для про­ крутки. Поэтому этот компонент обычно размещается в контейнере типа JScrollPane . Та ким образом, большое дерево можно прокрутить в окне просмо­ тра меньших размеров. Ниже перечислены действия , которые требуется выполнить, чтобы воспользо­ ваться деревом. 1. Создать экземпляр класса JTree. 2. Создать экземпляр класса JScrollPane и определить дерево в качестве про- кручиваемого объекта. 3. Ввести дерево на панели с полосами прокрутки. 4. Ввести панель с полосами прокрутки на панели содержимого . В приведенном ниже примере аплета демонстрируется создание дерева и об­ работка событий выбора его узлов. В данном аплете создается изменяемый узел дерева в виде экземпляра класса De faultMutaЫeTreeNode с заголовком Options (Варианты выбора) . Это самый верхний узел в иерархии дерева. Затем создаются дополнительные узлы дерева и вызывается метод add ( ) для присоединения этих узлов к дереву. Ссьmка на верхний узел дерева передается в качестве параметра конструктору класса JT ree. После этого дерево передается в качестве параметра конструктору класса JS cr о 11Р ane. Получаемая в итоге панель с полосами прокр}Т" кн вводится на панели содержимого. Затем создается и вводится метка на панели содержимого. Эта метка отображает выбор узла в дереве. Для получения событий выбора узлов в дереве регистрируется приемник типа TreeSelectionListener. В методе va l ueChanged () получается и отображается путь к текущему месту вы­ бора в дереве. 11 Продемон стрировать приме нение компонента типа JТree import java .aw t.*; import javax .swing . event .*; import javax . swing .*; import javax . swing .tree.*; /* */ <applet code="JTreeDemo " width=400 height=200> </applet> puЫic class JT reeDerno exteпds JApp let { JT ree tree ; JLabel jlab; puЫic void init () try { SwingUtilities . invo keAndWa it ( new RunnaЬle () { puЫ ic void run () rna keGUI ();
1170 Часть 111. Введение в про гра ммирование ГПИ средствами Swing ); catch (Exception ехс ) � System.out . println ("Can ' t create because of "+ехс) ; 11 Нель зя создать по указанной причине private void makeGUI ( ) { 11 создать с аы ый верхний узел дерева De faultMut aЬleTreeNode top new De faultMu t.aЬleTreeNode ( "Options ") ; 11 создать поддерево " А " De faultMutaЫeTreeNode а=new De faultMutaЫeTreeNode ("A ") ; top.add(a) ; De faultMutaЫeTreeNode al new De faultMut aЫeTreeNode ("A l") ; a.add (al) ; De faultMutaЬleTreeNode а2 new DefaultMut aЫeTreeNode ("A2") ; a.add (a2) ; 11 создать поддерево "В" De faultMutaЬleTreeNode Ь = new De faultMutaЫ eTreeNode ("B ") ; top.add(b) : De faultMutaЬleTreeNode Ы new De faultMutaЫeTreeNode ("Bl") ; b.add (Ьl) ; De faultMutaЫeTreeNode Ь2 new De faultMutaЫ eTreeNode ("B2 " ); b.add (b2) ; De faultMutaЬleTreeNode ЬЗ new De faultMutaЫeTreeNode ("BЗ ") ; b.add (b3) ; 11 создать дере во tree = new JT ree (top) ; 11 ввести дере во на панели прокрутки JScrollPane jsp = new JScrollPane (tree) ; 11 ввести панель с полосами прокрутки на панели содержимого add(jsp) ; 11 ввести ме тку на панели содержимо го jlab = new JLabel (); add (jlab, Borde rLayout .SOUTH) ; 11 обработать события выбора узлов дерева tree . addTreeSelectionLi зtener (new TreeSelectionLi stener () puЫ ic void valueChanged ( TreeSelectionEvent tse ) { jlab.setText ( "Selection is "+tse . getPath () ); 11 Выбран узел дерева по указанному пути }); На рис. 32. 11 показано дерево, развернугое в окне аплета из данного примера. Символьная строка, представленная в текстовом поле , обозначает пугь от верхне­ го узла дерева к выбранному узлу.
Гл ава 32. Исследование библиоте ки Swi ng Options tL]A DA1 DA2 'LIB Di81 D2 Dвз election is [Options, в, 82) Applet sta rtea . Рис. 32 .1 1 . Дерево, развернутое в окне аnлета J'l'reeDemo Класс JТаЬlе 1171 Класс JТ аЫе представляет компонент, отображающий дан ные в виде строк и столбцов таблицы . Чтобы изменить размеры столбцов, достаточно перетащить их границы мышью. Кроме того, весь столбец можно перетащить в другое место. В зависимости от конфигурации можно выбрать символьную строку. столбец или ячейку в таблице, а также изменить данные в ячейке. Компонент типа JТаЫ е до­ вольно сложный, он предостааляет немало вариантов выбора и средств, рассмо­ треть которые полностью просто невозможно в одном разделе. (Это едва ли не самый сложный компонент Swi ng.) Но в своей стандартной конфигурации компо­ нент типа JT аЫ е предоставляет простые в употреблении средства, которых долж­ но быть достаточно для представления дан ных в табличном виде. Приведенный ниже краткий обзор позволяет составить ясное представление о возможностях этого сложного компонента. Как и с компонентом типа JT ree, с компонентом типа JТ аЫе связаны многие классы и интерфейсы. Все они входят в состав пакета j avax . swing . tаЫе. В своей основе компонент типа JТаЫ е очень прост. Он состоит из одного или нескольких столбцов с данными. Вверху каждого столбца находится заголовок. Кроме описания данных в столбце, заголовок предоставляет механизм, с помо­ щью которого пользователь может изменять размеры столбца или его местопо­ ложение в таблице. Компонент ти па JТ аЫе не предоста аля ет никаких возмож­ ностей для прокругки , поэтому он , как правило, размещается в контейнере типа JScrollPane. В классе JТ аЫе предоставляется несколько конструкторов. Ниже приведен один из них, где параметр да нньrе обозначает двухмерный массив дан ных, пред-
1172 Часть 111. Введение в программирование ГПИ с редствами Swi ng ставляемых в табличном виде , а параметр заголовки_ столбцов - одномерный массив, содержащий заголовки столбцов. JТaЬle (OЬject дан ны е[] [] , Object sа.голо.1 1 1Q'_С!1'0Лбцоа ( ]) Компонент типа JТаЫе основывается на трех моделях. Первой из них являет­ ся модель таблицы, определяемая в интерфейсе TaЬleMode l. Эта модель опреде­ ляет все , что связано с отображением данных в двухмерном формате. А второй яв­ ляется модель столбца таблицы, определяемая в интерфейсе TaЬleCo lumnModel. Компонент типа JТ аЫе обозначает столбцы, а модель типа TaЬleColumnMode l - характеристики столбцов. Обе эти модели входят в состав пакета j avax .swing . tаЫе. И наконец, третья модель обозначает порядок выбора элементов и опре­ деляется в интерфейсе ListSelectionMode l (она упо миналась выше при рассмо­ трении класса JLi st) . Компонент типа JT аЬ 1 е может генерировать целый ряд разных событий. К са­ мым основным относятся события типа L i s t Sе1 е с t i оnЕvеnt и ТаЫеМоdе 1 Еvеnt. Событие ListSelectionEvent наступает, когда пользователь выбирает что­ нибудь в таблице. По умолчан ию компонент типа JТаЫе позволяет выбрать полностью одну строку таблицы или больше, но это поведение можно изменить, чтобы пользователь мог выбрать один или несколько столбцов или одну или не­ сколько отдел ьных ячеек таблицы. Событие типа TaЬleMode lEvent наступает, когда данные каким-нибудь образом изменяются в таблице. Обработка таких со­ бытий требует больших затрат труда, чем обработка событий в описанных ранее компонентах, а ее рассмотрение выходит за рамки дан ной книги. Но если компо­ нент типа JTаЫе требуется лишь для отображения данных в табличном виде , как в приведенном ниже примере, то никаких событий обрабатывать не придется. Чтобы создать простой компонент типа JТ аЫе для отображения данных в та­ бл ичном виде , достаточно выполнить следующие действия. 1. Создать экземпляр класса JТ аЫе. 2. Создать экземпляр класса JScrollPane, определив таблицу в качестве про­ кручиваемого объекта. 3. Ввести таблицу на панели с полосами прокрутки . 4. Ввести панель с полосами прокрутки на панели содержимого. В приведенном ниже примере аплета демонстрируется создание и применение простой таблицы. В данном аплете сначала создается одномерный массив сим­ вольных строк colHeads для заголовков столбцов, а также двухмерный массив символьных строк da ta для данных в ячейках таблицы. Каждый элемент масси­ ва da ta является массивом из трех символьных строк. Эти массивы передаются конструктору класса JТаЫ е. Та бл ица вводится на панели с полосами прокрутки , а та - на панели содержимого. В таблице отображаются данные из массива data. Стандартная конфигурация таблицы позволяет также редактировать содержимое ячеек. Все вносимые в них изменения отражаются на содержимом базового масси­ ва da ta. На рис . 32. 12 показана таблица, отображающая данные в окне аплета из данного примера.
Гл ава 32. Исследо в ание библиотеки Swi ng 11 Продемонстрировать применение компонента J'laЬle import java . awt .*; import javax . swing .*; /* */ <applet code= "JTaЬle Demo " width=400 height=200> </applet> puЫic class JT aЬl e Demo extends JApp let ( puЫic void init () ( try ( SwingUtilities . invo keAndWait ( new RunnaЫe () ( ); puЫic void run () ma keGUI () ; catch (Except ion ехс ) ( System.out . println ("Can't create Ьecause of " + ехс) ; // Нельзя создать по указанной причине private void ma keGUI () ( // инициализировать заголовки столбцов String [] colHeads = ( "Name", "Extension", "ID#" }; 11 Имя , добавочный номер телефона, идентификационный номер 11 инициализировать данные Object[](] data = ( }; ( "Ga il", "4567", "865" }, { "Ken", "7566" , "555" ), { "Viviane ", "5634", "587" }, { "Melan ie" , "7345", "922 " } , { "Anne ", "1237", "333" } , ( "John", "5656", "314" } , { "Matt", "5672", "217" }, { "Claire", "6741", "444" }, ( "Erwin" , "9023", "519" } , ( "Ellen" , "1134", "532" } , ( "Jennifer", "5689", "112" } , ( "Ed" , "9030", "133" } , { "Helen" , "6751", "145" } 11 создать таблицу JТ аЫе tаЫе = new JTaЬ le (data, colHeads ); // ввести таблицу на панели с полосами прокрутки JS crollPane jsp = new JScroll Pane (taЫe) ; // ввести панель с полоса.ми прокрутки на панели содержимого add(jsp) ; 1173
117' Часть 111. Введение в программирование ГПИ средств ами Swing Applet Name E)o{tension ID# Gail 4567 865 Ken 7566 555 Vivi ane 5634 587 Melanie 7345 922 An ne 1237 333 John 5656 314 Matt 5672 217 Claire 6741 444 Erwin 9023 519 Ap pl�t sta rte a. Рис. 32 .12. Та бл ица, отобража ющая да нные в окне аnлета JTaЬleDemo
33 Введение в меню Swing В этой главе представлены меню - один из гл авных элементов построения ГПИ на основе библиотеки Swing. Меню являются неотъемлемой частью многих приложений, поскольку они представляют пользователю функциональные воз­ можности прикладной программы. В силу своего особого значения меню нашли широкую поддержку в библиотеке Swing. Именно в меню проя вляется истинный потенциал библиотеки Swing. Система меню в Swing поддерживает целый ряд гл авных элементов меню, вклю­ чая следующие: • Строка меню, отображающая гл авное меню прикладной программы. • Стандартное меню, которое может содержать выбираемые пункты или дру­ гие меню, иначе называемые подменю. • Всплывающее или контекстное меню, которое обычно активизируется щелчком правой кнопкой мыши. • Панель инструментов, предоставляющая быстрый доступ к функциональ­ ным возможностям прикладной программы (зачастую параллельно с пун­ ктами меню). • Действие, позволяющее одному объекту управлять разными (двумя или больше) компонентами. Действия обычно употребляются вместе с меню и панелями инструментов. Меню в Swing поддерживают также оперативные клавиши, позволяющие бы­ стро выбирать пункты, не активизируя само меню, а также мнемонику для выбора пунктов меню нажатием клавиш после отображения самого меню. Основные положе ния о меню Система меню в Swing опирается на ряд связанных с ней классов. В табл . 33. 1 перечислены классы, упоминаемые в этой гл аве и составляющие основу системы меню в Swing. На первый взгляд, меню в Swing могут привести в некоторое замеша­ тельство, но на самом деле пользоваться ними очень просто . Эти меню допускают специальную настройку, если таковая требуется, в самых широких пределах, но как правило, классы меню применяются в без изменений, поскольку они поддер-
1176 Часть 111. Введение в програм мирование ГПИ средствами Swing живают все необходимые варианты построения меню. Например, в меню совсем не трудно ввести изображения и клавиатурные сокращения . Табл ица 33.1. Основные классы меню в Swi ng Кnасс JМenuВar JМenu JМenu item JChecltВoxМenuiteш Описание Объект этого класса содержит меню верхнего уровня для п ри­ ложения Стандартное меню, состоящее из од ного или бол ьше пунктов в виде объектов типа JМenuiteas Объект этого класса представляет пун кты , наполняющие меню Отмечаемый флажком пункт мен ю JRadioButtonИenu itelD Отмечаемый кнопкой-переключателем пункт меню JSeparator JPopupМenu Визуальный разделитель пунктов меню Меню, которое обычно активизируется щелчком правой кноп­ кой мыш и Сделаем краткий обзор совместного применения классов, перечисленных в табл. 33 . 1. Чтобы построить меню верхнего уровня для приложения, сначала следует создать объект класса JMe nuBar. Проще говоря , этот класс служит кон­ те йнером для меню. В экземпляр класса JMe nuBar обычно вводятся экземпляры класса JМe nu, причем каждый объект типа JМe nu определяет отдел ьное меню. Это означает, что каждый объект типа JMe nu содержит оди н или больше выбираемых пунктов. А пун кты, отображаемые объектами типа JMe nu, в свою очередь, явля­ ются объектами класса JМ enu l tem. Следовательно, класс JMenultem определяет пункт меню, выбираемый пользователем. Помимо меню, раскрывающихся из строки меню, можно создавать авто­ номные всплывающие меню. С этой целью следует сначала создать объект типа JPopupMenu, а затем ввести в него объекты типа JМ enul tem в виде пунктов меню. Как правило, всплывающее (или контекстное) меню акти визируется щелчком правой кнопкой мыши, когда курсор находится на том компоненте, для которого определено всплывающее меню. В менюможно вводить не только стандартн ые пункты, но и пункты, отмечаемые флажками или кнопками-переключателя ми. В частности , отмечаемый флажком пункт меню создается средствами класса JCheckBoxMenu I tem, а отмечаемый кноп­ кой-переключателем пункт меню - средствами класса JRadioButtonMenu ltem. Оба эти класса расширяют класс JМ enuitem. Их можно применять в стандартных и всплывающих меню. Класс JToo lBar позволяет создать панель инструментов в виде автономного ко мпонента, связанного с меню. Та кой компонент нередко служит для быстро­ го доступа к функциональным возможностям, имеющимся в меню приложения. Например, панель инструментов может предоставлять быстрый доступ к коман­ дам форматирован ия, которые поддерживаются в текстовом редакторе. А служеб­ ный класс JSeparator позволяет создать линию, разделяющую пункты меню. В отношении меню Swing следует иметь в виду, что каждый пункт меню пред­ ставлен объектом класса, расширяющего класс Ab stractButton. Напомним, что
Гл ава 33. Введение в меню Swing 1177 класс Abs tractButton служит суперклассом для всех компонентов кнопок в би­ блиотеке Swing, в том числе и компонента типа JButton. Следовательно, все пун­ кты меню, по существу, являются кнопками. Очевидно, что пункты не похожи внешне на кнопки , когда выбираются из меню, но они действуют, гл авным обра­ зом, как кнопки . Та к, если выбрать пункт меню, то событие действия будет сгене­ рировано таким же образом, как и при нажатии экранной кнопки. Следует также иметь в виду, что класс JMe nuI tern служит суперклассом для клас­ са JМe nu. Это дает возможность создавать подменю, т. е. одни меню, вложенные в другие. Чтобы построить подменю, следует сначала создать меню в виде объекта типа JMe nu и наполнить его пунктами, а затем ввести его в другой объект типа JMe nu. Этот процесс демонстрируется в следующем разделе. Как упоминалось выше, при выборе пункта меню генерируется событие действия. Символьная строка с командой действия, связанная с этим событием действия, по умолчанию обозначает наименование выбранного пункта меню. Следовательно, проанализировав команду действия, можно выяснить, какой именно пункт меню бьm выбран. Безусловно, для обработки событий дейсгвия от каждого пункта меню мож­ но воспользоваться отдел ьными анонимными классами или лямбда-выражениями . В этом случае выбранный пункт меню уже известен, и поэтому нет нужды анализиро­ вать команду действия, чтобы выяснить, какой именно пункт меню бьm выбран. Меню могут генерировать и другие типы событий. Например, всякий раз, когда активизируется или выбирается меню или же отменяется его выбор, генерируется событие типа Me nuEvent, которое может отслеживаться приемником событий из интерфейса MenuListener. К числу других событий, связанных с меню, относят­ ся события типа MenuKeyEvent, MenuDragMouseEvent и PopupMenuEvent. Но как правило, отслеживать требуется только события де йствия , и поэтому в этой гл аве рассматривается обработка только этих событий. Краткий обзор кл а ссов JМenuВar, JМenu и JМenu item Прежде чем строить меню, нужно иметь хотя бы какое-то представление о трех ос новных классах меню: JMenuBar, JМenu и JMenultern. Эти классы составляют тот минимум средств, которые требуются для построения гл авного меню прило­ же ния. Кроме то го , классы JMe nu и JMenuitem служат для построения всплываю­ щих меню. Та ким образом, эти классы образуют основание системы меню в Swing. Класс JМenuBar Как упоминалось выше, класс JMenuBar, по существу, служит контейнером для меню. Как и все остальные классы ко мпонентов, он наследует от класса JComponent, а тот - от классов Container и Cornponent. У этого класса имеется еди нственный конструктор по умолчан ию. Следовательно , строка меню первона­ чально будет пустой, и поэтому ее придется наполнить меню, прежде чем ею вос­ пользоваться. У каждого приложения имеется одна и только одна строка меню.
1178 Часть 111. Введение в п рограммирование ГПИ средств ами Swi ng В классе JMenuBar определяется несколько методов, но на практике приме­ няется только метод add (). Этот метод вводит меню в виде объекта типа JМenu в строку меню. Ниже приведена его общая форма. JМenu add (JМenu неню) Здесь параметр меню обозначает экземпляр класса JMe nu, вводимый в строку мен ю. Меню располагаются в строке меню слева направо в том порядке , в каком они вводятся. Есл и же меню требуется ввести в конкретном месте, то с этой целью следует воспользоваться приведенным ниже вариантом метода add ( ) , наследуе­ мого из класса Container. Coшponent add (Coll lpO nent неню, int J1 1НД ехс) Здесь параметр меню обозначает конкретное меню, вводимое по указанному индексу. Индексирование начинается с нуля, причем нулевой индекс обозначает крайнее слева меню в строке меню. Иногда из строки меню требуется уд алить меню, которое больше не нужно. Для этого достаточно вызвать метод remo ve (), наследуемый из класса Container. У этого метода имеются следующие общие формы: void r8l llO V8 (Coшponent неню) void ruove (int JrlfR&J:c) Здесь параметр меню обозначает ссьwку наудаляемое меню, а параметр индекс­ указанный индекс уд аляемого меню. Индексация меню начинается с нуля . Иногда полезным оказывается и метод getMenuCount ( ) . Ниже приведена его общая форма. Этот метод возвращает количество элементов, содержащихся в строке меню. int qetмenuCount () В классе JMenuBa r определяются и другие методы , которым можно найти специальное применение. Например, вызвав метод getS ubElements (), можно получить массив ссьwок на меню, находящихся в строке меню, а вызвав метод isSelected ( ) , - определить, выбрано ли конкретное меню. Как только строка меню будет создана и наполнена отдел ьными меню, ее мож­ но ввести в контейнер типа JFrame , вызвав метод setJMenuBar () для экземпляра класса JFrame . (Строки меню швводятся на панели содержимого.) Ниже приведе­ на общая форма метода setJMe nuBar (). void setJМenUВar ( JМenUВar C!l'poxa_неню) Здесь строка_меню обозначает ссылку на строку меню. Строка меню отобража­ ется на позиции, определяемой стил ем оформления ГПИ . Как правило , оно рас­ полагается в вдоль верхн его края окна приложения. Класс JМenu Класс JМenu инкапсулирует меню, наполняемое пунктами в виде объектов типа JМenuI tem. Как упоминалось ранее, этот класс наследует от класса JМenuI tem. Это оз­ начает, что одно меню типа JМe nu можно выбирать из другого. Следовательно, одно
Гл ава ЗЗ. Введен ие в меню Swing 1179 меню может служить подменю для другого. В классе JМe nu определяется целый ряд конструкторов. Здесь и далее в этой гл аве применяется следующий конструктор: JМ&nu (Strinq •ня) Этот конструктор создает меню с заголовком, обозначаемым параметром имя. Разумеется, присваивать наименование меню совсем не обязательно. Для соз­ дания безымянного меню можно воспользоваться следующим конструктором по умолчанию: JМenu () В классе JMe nu поддерживаются и другие конструкторы. Но в любом случае соз­ даваемое с их помощью меню будет пустым до тех пор, пока в него не будут введе­ ны отдел ьные пункты. В классе JМe nu определяется немало методов. Здесь вкратце описываются лишь наиболее употребительные из них. Для ввода пункта в меню служит метод add ( ) , у которого имеется несколько общих форм. Ниже приведены две его общие формы. JМenuite1 11 add ( JМenuite1 11 пуиж�) JМenuite1 11 add (C01 11pO nent пунж� , int хндежс) Здесь параметр пункт обозначает пункт, вводимый в меню. В первой форме пункт вводится в конце меню, а во второй форме - по указанному индексу. Как и следовало ожидать, индексирован ие пунктов меню начинается с нул я. В обе­ их рассматриваемых здесь формах метод add { ) возвращает ссылку на вводимый пункт меню. Любопытно, что для ввода пунктов в меню можно также воспользо­ ваться методом insert {). В меню можно ввести разделитель (объект типа JSeparator) , вызвав метод addSeparator {),как показано ниже. Разделитель вводится в ко нце меню. void addSeparator () А для ввода разделителя в нужном месте меню можно вызвать метод insert Separator ().Ниже приведена его общая форма, где параметр индекс обозначает отсчитываемый от нуля индекс , по которому вводится разделитель. void insertSeparator (int жндежс) Вызвав метод remove { ) , можно уд алить пункт из меню. Ниже приведены две его общие формы, где параметр меню обозначает ссылку на удаляемый пункт меню, а параметр индекс - указанный индекс удаляемого пункта ме ню. void r8Jl lO ve (JМenuitem неию) void r8Jl lO ve (int хндежс) Вызвав метод getMenuComponentCount {), можно получить количество пунк­ тов в меню: int getмenUCoU1pOnentCount () А если вызвать метод ge tMenuComponent s (), то можно получить массив из пунктов меню, как показано ниже . Этот метод возвращает массив, содержащий компоненты меню. Co11 1p onent [] qetМenuComponents ()
1180 Ч асть 111. Введение в п рограммирование ГПИ средствам и Swi ng Класс JМenuI tem Класс JMe n и I t em инка псулирует пункт меню. Выбор этого элемента ГПИ может быть связан с некоторым действием прикладной программы, например, с сохране­ нием дан ных или закрытием файла, или же с отображением подменю. Как упоми­ налось ранее , класс JМеnu!tеm является производным oт клacca Ab stractButton, и поэтому каждый пункт меню можно рассматривать как особого рода кнопку. Следовательно, когда выбирается пункт меню, событие действия генерируется таки м же образом, как и при нажатии экранной кнопки типа JButton. В классе JMe nu item определено немало конструкторов. Ниже приведены применяемые здесь и далее конструкторы данного класса. JМenuitem (Strinq •нir) JМenuitem (Icon •.sображеюtе) JМenuiteш (String 1U U1 , Icon •sобра •еюtе) JМenuitel ll (String IU UI , int ннвNо�wжа) JМenui tem (Action дdc!1'.&1 1t e) Первый конструктор создает пункт меню, обозначаемый параметром имя. Второй конструктор создает пункт меню, отображающий указанное из о бражение. Тр етий конструктор создает пункт меню, обозначаемый параметром имя и ото­ бражающий указан ное изображение . Четвертый конструктор создает пункт меню, обозначаемый параметром имя и использующий указанную клавиатурную мнемонику. Эта мнемоника позволяет выбрать пункт меню нажатием указанной клавиши. И последний конструктор создает пункт меню, используя сведения, обо­ значаемые параметром действие. В дан ном классе поддерживается также кон­ структор по умолчанию. Благодаря тому что класс JMenuitem наследует от класса Ab stractButton, функциональные возможности последнего доступны для пунктов меню. В час'Пlо­ сти , для меню нередко оказывается полезным метод setEnaЫed (), позволяющий включать или отключать отдел ьные пункты меню. Ниже приведена общая форма этого метода. void setEnaЫed (Ьoolean B.lt'ЛIOVX!l'•) Если параметр включить принимает логическое значение true, то пункт меню включается. А если этот параметр принимает логическое значение false, то пункт меню отключается и не может быть выбран. С оэдание гл авного меню По традиции наиболее употребительным считается главное меню. Оно доступно из строки меню и определяет все (или почти все) функциональные возможности приложения. К счастью , библиотека Swing значительно упрощает создание глав­ ного меню и управление им. Ниже будет показано , как создать элементарное гл ав­ ное меню и ввести в него отдел ьные варианты выбора. Процесс создания гл авного меню состоит из нескольких стади й. Сначала созда­ ется объект типа JМe nuBar, который будет содержать отдел ьные меню. Затем соз-
Гл ава ЗЗ. Введен ие в меню Swing 1181 дается каждое меню, располагаемое в строке меню. В общем, построение меню на­ чинается с создания объекта типа JMenu, в который затем вводятся пункты меню в виде объектов типа JМenuitem. Как только отдел ьные меню будуг созданы, они вводятся в строку меню. А саму строку меню следует ввести во фрейм, вызвав ме­ тод setJMe nuBa r (). И наконец, каждый пункт меню должен быть дополнен при­ емником действий, обрабатывающим событие действия, наступающее при выбо­ ре отдел ьного пункта из меню. Процесс создания меню и управления ими станет понятнее , если продемон­ стрировать его на конкретном примере. Ниже приведена программа, в которой создается простая строка меню, состоящая из трех меню. Первым из них являет­ ся меню File (Файл}, состоящее из выбираемых пунктов Open (Открыть) , Close (Закрыть) , Save (Сохранить) и Exit (Выход) . Второе меню называется Options (Параметры) и состоит из двух подменю: Colors (Цвета) и Priority (Приоритет) . Тр етье меню называется Help (Справка) и состоит из единственного пункта About (О программе). Когда выбирается пун кт меню, его наименование отображается в метке на панели содержимого. Вид гл авного меню в окне программы из данного примера приведен на рис . 33 . 1. 11 Продемонстрир овать простое главное меню import java.awt.*; import java .awt . event .*; import javax . swing.*; cl ass Me nuDemo implements Act ionLi stener { JLabel jlab ; MenuDemo () { // создать новый контейнер типа JFraшe JFrame jfrm = new JFrame ("Menu Demo " ); // Демонстрация ме ню 11 указать диспетчер поточной компоновки тиnа FlowLayout jfrm. setLayout (new FlowLayout () ); // зада ть исходные размеры фрейма jfrm .setSize (220, 200) ; // завершить прикладную программу, когда пользователь // закроет ее окно jfrm . setDefaultCloseOperation ( JFrame .EXIT _ ON_CLOSE ); // создать ме тку для отображения резуль татов выбора из меню jlab = new JLabel (); 11 создать строку меню JМenuBar jmЬ s new JМenuBar (); 11 созда ть меню File JMenu jmFile = new JМe nu ("Fi le" ); // Файл JMenultem jmiOpen = new JМenuitem ("Open" ); // Открыть JМenuitem jmiClose = new JМe nuitem("Close " ); // Закрыть JМenuitem jmiSave = new JМe nuitem ("Save " ); // Сохранить JMenuitem jmiExit = new JМenuitem ( "Exit") ; / / Выход jmFile .add(jmiOpen) ; jmFi le .add(jmiClose );
1182 Часть 111. Введение в программирован ие ГПИ средствами Swi ng jmFile .add (jmiSave ) ; jmFile . addSeparator () ; jmFi le .add (jmiExit ); jmЬ.add(jmFile) ; 11 создать меню Options JМenu jmOp tions = new JМenu ("Options ") ; // Параме тры 11 создать nодменю Colors JМenu jmColors = new JМenu ("Colors") ; // Цвета JМenuitem jmiRed = new JМenuitem("Red") ; // Кр асный JМenuitem jmiGreen = new JМenu item ("Green" ); // Зеленый JМenui tem jmiBlue = new JМe nui tem ("Blue " ); // Синий jmColors .add(jmiRe d) ; jmColors .add(jmiGreen) ; jmColors .add(jmiBlue) ; jmOp tions .add(jmColors ); 11 создать подменю Priority JМenu jmPriority = new JМenu ("P riorit y") ; // Приоритет JМenui tem jmiHigh = new JМenuitem ("High" ); // высокий JМenui tem jmiLow = new JМenuitem("Low ") ; // Низкий jmPriority . add ( jmiHigh) ; jmPriority .add(jmiLow) ; jmOp tions .add(jm Priority) ; 11 создать пункт меню Reaet JМenuitem jmiReset = new JМenuitem ("Re set") ; // Сбросить jmOp tions .addSeparator () ; jmOp tions .add(jmiReset ); // И наконец, ввести все выбираемые меню в строку ме ню jmЬ.add (jmOp tions ); 11 создать меню Help JМenu jrnнelp = new JМenu ("Help") ; // Справка JМenuitem jmiAЬout = new JМenuitem ( "AЬout ") ; // О программе jtnНelp .add(jmiAЬout ); jmЬ.add (jtnНelp) ; // ввести приемники действий от пунктов меню jmiOpen . addActionListener (this) ; jmiClose . addActionLi stener (thi s) ; jmiSave . addActionListener (this) ; jmiExit . addActionListener (this ); jmiRed . addActionListener (this) ; jmiGreen . addActionLi stene r(t hi s) ; jmiBlue . addActionListener (thi s) ; jmiHigh . addActionLi stener (thi s) ; jmiLow . addActionListene r(t hi s) ; jmiReset . addActionLi stener (thi s) ; jmiAЬout . addA ctionLi stener (thi s) ; 11 ввести ме тку на панели содержимого jfrm .add(jlab ) ; // ввести строку меню во фрейм jfrm .se tJМenuBar (jmЬ) ; 11 отобразить фрейм jfrm . setVisiЬle (true) ;
Гл ава ЗЗ. Введение в меню Swing // обработать события действия от пунктов меню puЫic void action Performed (ActionEvent ае ) { // получи ть команду действия из выбранного меню String cornS tr = ae . getActi onCommand () ; 11 выйти из програыы ы , если пользователь выберет пун кт меню Zxit if ( comStr . equals ( "Ex it" )) System. exi t (O) ; 11 отобразить в противном случае результат выбора из меню jlab . setText ( comStr +"Selected" ); // Выбрано указанное puЫic static vo id ma in (String args [] ) { 11 создать фрейм в потоке диспе тчеризации событий SwingUtilities . invokeLater (new RunnaЬle () { puЫic void run () { } }); new MenuDemo ( ) ; Open Close � у е ExJt 1183 Рассмотрим подробнее, каким образом в данной программе создаются меню, начиная с конструктора класса MenuDerno . Сначала в этом конструкторе созда­ ется фрейм типа JFrarne , устанавливается диспетчер ко мпоновки , задаются размеры фрейма и стандарт­ ная операция закрытия прикладной программы. (Все эти операции подробно описаны в гл аве 31.) Затем создается метка типа JLabel, предназначенная для отображения результатов выбора из меню. Далее создается строка меню, а ссылка на нее присваивает­ ся переменной jrnЬ, как показано ниже. Рис. ЗЗ .1 . Вид гл авного меню в окне программы Menu Demo 11 созда ть строку меню JМenuBar jmЬ = new JМenuBar () ; Затем создается меню File и его пункты, а ссылка на него присваивается пере­ ме нной jrnFi le: 11 создать ме ню File JМenu jmFi le = new JМenu ("File " ); // Файл JМenuitem jmiOpen = new JМenuitem("Open" ) ; // Открыт ь JМenu item jmiClose = new JМenuitem("Close " ); // Закрыть JМenuitem jmiSave = new JМenu item("S ave " ); // Сохранить JМenuitem jmiExit = new JМenuite m("Exit" ); // Выход Имена отдельных пунктов Ореп, Close, Save и Exit меню File будут отображать­ ся на месте метки при их выборе. Далее эти пункты вводятся в меню File следую­ щим образом: jmFile .add(jmiOpen) ; jmFile . add ( jmiClose1 ; jmFile .add ( jmiSave) ; jmFile . addSeparator () ; jmFile . add ( jmiExit ) ;
Часть 111. Введение в программи рование ГПИ с р едства ми Swing И наконец, меню File вводится в строку меню, как показано ниже. jmЬ .add ( jmFile ); По завершении приведенной выше последовательности кода строка меню бу­ дет содержать оди н элемент: меню File, тогда как само меню File - пункты Open , Close , Save и Exit. (Обратите внимание на разделитель, введенный перед пунктом меню Exit, - он визуально отделяет пункт Exit от остальных пунктов меню File.) Аналогичным образом создается и меню Options, но оно состоит из двух подме­ ню, Colors и Priority, и одного пункта Reset. Сначала подменю создаются по отдель­ ности, а затем вводятся в строку меню. И последним в данное меню вводится пункт Reset. После этого меню Options вводится в строку меню. Подобным же образом создается и меню Help. Следует заметить, что класс Me nu Demo реализует интерфейс ActionListener, а события действия , генерируемые при выборе меню, обрабатываются методом actionPe r forme d {), определяемым в классе MenuDemo . Следовательно, для пун­ ктов меню в данной программе вводятся приемники действий по ссылке this. В то же время приемники действий не вводятся для пунктов меню Colors и Priority, поскольку они не выбираются , а просто активизируют подменю. И наконец, строка меню вводится во фрейм следующим образом: jfrm .setJМenuBar (jmЬ) ; Как упоминалось выше, строки меню вводятся не на панели содержимого , а не­ посредственно во фрейм типа JFrame . В методе actionPerformed {) обрабатываются события действия , генерируе­ мые в меню. Для конкретного события в этом методе вызывается метод ge tAc­ tionCommand {),чтобы получить символьную строку с командой действия , связан­ ной с выбором из меню. Ссылка на эту символьную строку сохраняется в перемен­ ной comStr. Затем эта строка проверяется на наличие команды действия " Exit ": if (comStr . equals ("Exit")) System. exit (O) ; Если символьная строка содержит команду действия "Exit " , то программа завершается вызовом метода Sys tem . exit (). Этот метод немедленно заверша­ ет программу и передает свой аргумент в качестве кода состояния вызывающему процессу, кото рым обычно является операционная система или браузер. Ус ловно нулевой код состояния означает нормальное завершение программы, а любой дру­ гой код состояния - ненормальное ее завершение. Результат выбора всех осталь­ ных пунктов меню, кроме Exit, отображается на месте заданной метки. Можете поэкспериментировать с программой Menu Demo , попробовав ввести еще одно меню или дополнительные пункты в уже имеющиеся меню. Ваша гл авная задача - уяснить основные принципы создания меню, поскольку данная програм­ ма будет постепенно усложняться по ходу изложения материала этой гл авы. В вод мнемоники и о перативных кл авиш в меню Меню, созданное в предыдущем примере, вполне работоспособно, но его мож­ но усовершенствовать. Меню реальных прикладных программ обычно поддержи-
Гл ава 33. Введен ие в меню Swing 1185 вают клавиатурные сокращения, предоставляя опытным пользователям возмож­ ность быстро выбирать пункты меню. Клавиатурные сокращения принимают две формы: мнемонику и оперативные клавиши. Применительно к меню мнемон ика определяет клавишу, нажатием которой выбирается отдел ьный пункт активного меню. Следовательно , мнемоника позволяет выбирать с клавиатуры пункты того меню, которое уже отображается, а оперативная 'Кltавиша - сделать то же самое , не активизи руя предварительно меню. Мнемонику можно указать как для объектов типа JМenu ltem (пунктов меню) , так и для объектов типа JМenu (самих меню). Мнемоника для объекта типа JMe nu ltem задается двумя способами. Во-первых , ее можно указать при создании данного объекта с помощью следующего конструктора: JМenui teш (String 'l lНR , int мнемоюrжа) В данном случае наименование пункта меню передается в качестве параметра имя, а мнемоника - в качестве параметра мнемоника . И во-вторых, мнемонику для пун­ ктов меню (объектов типа JМenu ltem) или самого меню (объекта типа JМe nu) мож­ но задать, вызвав метод setМnemonic ( ) . Этот метод наследуется обоими классам, JМe nultem и JМe nu из клacca AЬ stractButton. Ниже приведена его общая форма. void setмneJDOnic (int мнеиоюrжа ) Здесь параметр мнемоника обозначает задаваемую мнемонику. Этот параметр должен принимать значение одной из констант, определяемых в классе j а va . awt . event . KeyEvent, в том числе Ke yEvent . VK_F или Ke yEvent . VK_ Z . (Имеется и другой вариант метода setMnemonic (), принимающий аргумент типа char, но он считается устаревшим.) Мнемоника не зависит от регистра, а следовательно, мнемоники VK_А и VK_а равнозначн ы. По умолчанию подчеркивается первая буква в наименовании пункта меню, со­ впадающая с заданной мнемоникой. Если же требуется подчеркнуть другую букву, следует указать ее индекс в качестве аргумента метода setDi splayedMnemonic Index (), наследуемого классами JМenu ltem и JМenu из класса AЬ stractButton. Ниже приведена его общая форма, где параметр индекс обозначает указан ный индекс подчеркиваемой буквы. void setDisplayeclМnemoni cindex (int жндежс) Оперативная клавиша может быть связана с объектом типа JMenultem. Она за­ дается с помощью метода setAc ce lerator (),как показано ниже. void setAccelerator (КeyStroke cove�aюre_irлaaJOr) Здесь параметр со четание_ кла виш обозначает комбинацию клавиш, нажима­ емых для выбора пункта меню. Класс Ke yS troke содержит ряд фабричных мето­ дов для построения разнотипных комбинаций клавиш быстрого выбора пунктов меню. Ниже приведены три общие формы одного из таких методов. static КeyStroke getкeyStroke (char сянвол) static КeyStroke qetКeyStroke (Character сжнвол , int нодя фяжа�ор) static КeyStroke qetКeyStroke (int �ол, int ноджфяжа�ор) Здесь параметр симв ол обозначает конкретный символ оперативной клавиши. В первой форме данного метода этот символ указывается в виде значения типа
1186 Ч асть 111. Введе н ие в п рограммирование ГПИ средствами Swi ng char; во второй форме - в виде объекта типа Character; в третьей форме - в виде значения константы типа Ke yEvent, как пояснялось выше. И в качестве параме· тра моди фика тор следует указать значение одной или нескольких констант, пере· численных ниже и определяемых в классе j ava . awt . event. InputEvent. Inputжvent.ALT _DON N _NA SK InpuU:vent .CТRL_DOWN_NAS K InputEvent . SBIF'r DOНN NASK Inputжvent .ALT_GRAPB_DOНN_NASK InpuU:vent .НВTA_D OИN_NAS K Та к, если в качестве параметра символ передать методу getKeyS troke () кон· станту VK_А , а в качестве параметра моди фика тор - константу Inpu tEvent • CTRL _ DOWN _МАSК, то для выбора пункта меню будет задана комбинация операти вных клавиш <Ctr\+A>. �ve СЬ1s fXil C•l-E Рис. 33 .2. Меню File, активи­ зированное в окне прогр аммы MenuDemo В приведенном ниже фрагменте кода в меню File, которое создается в рассмотренном ранее примере программы MenuDemo , вводится мнемоника и комби­ нации оперативных клавиш. После ввода этого фраг­ мента кода в программу MenuDemo меню File можно будет оперативно выбрать, нажав комбинацию кла­ виш <Alt+F>, а затем воспользоваться мнемоникой О, С, S или Е для быстрого выбора соответствующих пунктов этого меню. С другой стороны, эти пункты меню можно выбрать непосредственно, нажимая комбинации клавиш <Ctrl+O>, <Ctrl+C>, <Ctr\+S> или <Ctrl+E> соответственно. На рис . 33.2 показано меню File, акти визированное в окне программы MenuDemo . 11 создать ме ню Fila с мнемоникой и операти внЫЬIИ кла вишами JМ enu jmFi le = new JМ enu ("File" ); jmFile . setмnemoni c(KeyEvent .VK _ F) ; JМ enuitem jrniOpen = new JМ enui tem ( "Open ", Ke yEvent .VK_O) ; jrniOpen .setAccelerator ( KeySt roke .getKeyStroke ( KeyEve nt .VK О, InputEvent . CTRL_DOWN_МASK ) ); JМenuitem jrniClose = new JMenu item ("Close", KeyEvent .VK_C) ; jmiClose .setAc celerator ( KeySt roke . get KeyS troke (KeyEve nt .VK С, InputEvent . CTRL_DOWN_МAS K) ); JМe nuitem jmiSave = new JМenuitem ("Save ", KeyEvent .VK_S) ; jmiSave .setAccelerator ( Ke yStroke .getKeyStroke ( KeyEvent .VK S, InputEvent . CTRL_DOWN_МAS K) ); JМ e nuitem jmiExit = new JМ enuitem ("Exit", KeyEvent . VK_E) ; jrniExit . se tAc celerator ( Ke yStroke .getKeyStroke ( KeyEve nt .VK Е, InputEvent . CTRL_DOWN_МAS K) );
Гл ава ЭЭ. Введение в меню Swi ng Ввод изображений и всплывающих подсказок в пункты меню 1187 Иаображения можно вводить в пункты меню или употреблять их вместо текста. Самый простой способ ввести изображение в пункт меню - указать его при созда­ нии этого пункта меню с помощью одного из следующих конструкторов: JМenui teDI (Icon •sображе1Wе) JМenuiteDl (String 11Ю1, Icon •sodpazeюre) Первый конструктор создает пункт меню, отображающий указанное изо бражение; второй конструктор - пункт меню, обозначаемый параметром имя и отображающий указанное изображение. Например, в приведенном ниже фраг­ менте кода создается пункт меню AЬout вместе с указанным иаображением. Image lcon icon = new Imageicon ( "AЬouticon .gif") ; JМenuitem jmiAЬout = new JМenuitem ("AЬout ", icon) ; Если ввести этот фрагмент кода в программу Menu­ Demo , то рядом с текстом наименования пункта меню AЬout появится значок, указанный в переменной icon (рис. 33.3). Этот значок можно ввести и после создания пункта меню, вызвав метод setlcon (), наследуемый из класса AЬstractButton. А вызвав метод setHori zon­ talTextPos i tion () , можно установить выравнивание изображения по горизонтали относительно текста наи­ менования пункта меню. Вызвав метод setDisaЫedi con (), можно также указать неактивный значок, который отображается Рис. 33.3. В ид пункта меню AЬout в окне программы вместе с недоступным пунктом меню. Как правило, MenuDemo после ввода значка такой значок отображается светло-серым цветом, когда пункт меню недоступен. Если неакти вный значок указан, то он отображается при условии, что пункт меню недоступен. Вспл:ывающая подсказка -это небольшое сообщение, кратко описывающее пункт меню. Такая подсказка всJUiывает автоматически , если навести курсор на пункт меню и оставить его на мгновение в таком положении. Чтобы ввести всJUiыва­ ющую подсказку в пункт меню, достаточно вызвать метод setToolTipText () для этого пункта меню, указав текст, который требуется вывести во всJUiывающей подсказке. Ниже приведена общая форма этого метода. void setToolTipText ( String coodJrfeюre) В данном случае параметр сообщение обозначает символьную строку, которая отображается, когда появляется всплывающая подсказка. Например, в следующей строке кода создается всJUiывающая подсказка для пункта меню AЬout: jmiAЬout .setToolTipText ("Info about the MenuDemo program. ") ; Любопытно, что метод setToolTipText ( ) наследуется классом JМenu item из класса JC omponent. Это означает, что всJUiывающую подсказку можно ввести и в другие типы компонентов, в том числе и экранные кнопки. Попробуйте сде­ лать это в качестве упражнения.
1188 Часть 111. Введение в п рограммирование ГПИ средствами Swing Клacc ы JRadioButtonМenui tem и JCheckВoxМenu item В приведенных выше примерах демонстрировались наиболее употребитель­ ные типы пунктов меню, но в Swing определяются также два друrих типа пункта меню: с флажками и кнопками-переключателями. Эти отмечаемые пункты меню упрощают построение ГПИ, делая доступными из меню такие функциональные возможности , для предоставления которых в противном случае потребовались бы дополнительные автономные компоненты. Кроме того, наличие флажков и кно­ пок-переключателей в меню иногда кажется вполне естественным для выбора кон· кретного ряда средств. Но независимо от конкретных причин для применения флажков и кнопок-переключателей в меню библиотека Swing позволяет сделать это довольно просто, как поясняется ниже . Чтобы ввестифлажоквменю, следуетсоздать объект класса JСhе сkВохМеnu itеm. В этом классе определяется несколько конструкторов. Ниже приведен применяе· мый здесь и далее конструктор данного класса. JChecltВoXМ8nuit&1 11 (Strin9 .l lfНl l ) Здесь параметр имя обозначает наименование пункта меню. Исходно флажок находится в сброшенном состоянии. Есл и же требуется явно указать исходное со­ стоя ние флажка, следует воспользоваться конструктором JChecltВoxМenuit&1 11 (Strin9 .l lfНl l , Ьoolean coc�oJU01e) Если параметр состояние принимает логическое значение true, то флажок исходно установлен, а иначе - сброшен. В классе JCheckBoxMenultem предостав· ляются также конструкторы, позволяющие снабдить значком пункт меню с флаж· ком. Ниже приведен один из таких конструкторов. JCheckВoxМenuitein (Strinq ЯХR, Icon энаvож) В данном случае параметр имя обозначает наименование пункта меню, а пара· метр зна чок - связанное с ним изображение. Исходно флажок в данном пункте меню сброшен. В классе JCheckBoxMenu item предоставляются и друrие конструк· торы. Флажки в меню действуют аналогично автономным флажкам. В частности , они генерируют события действия и события от элементов при изменении их состо­ яния. Флажки особенно полезны в тех меню, где требуется отобразить состояние выбранных ил и невыбранных пунктов. Чтобы ввести кнопку-переключатель в пункт меню, следует создать объект класса JRadioButtonMenu item. Этот класс наследует от класса JМenu item и пре­ доставляет богатый набор конструкторов. Ниже приведены конструкторы, при· меняемые здесь и далее в этой гл аве . JRadioButtonмenuit&1 11 (Strinq ЯНR) JRadioButtonмenuit&1 11 (Strin9 ЯНR, Ьoolean сос�оаюrе) Первый конструктор создает пункт меню с невыбранной кнопкой·переключа· телем и наименованием, определяемым параметром имя. А второй конструктор
Гл ава 33. Введение в меню Swing 1189 позволяет дополнительно указать исходное со с тояние кнопки-переключателя. Если параметр состояние принимает логическое значение true, то кнопка-пере­ ключатель исходно выбрана, а иначе - не выбрана. Другие конструкторы данного класса позволяют дополнительно указать изображение для пункта меню с кноп­ кой-переключателем. Ниже приведен пример одного из таких конструкторов. JRadioButtonМenuiteш (Strinq IOCJr, Icon sяаvож , Ьoolean сос!l'оянже) Этот конструктор создает пункт меню с кнопкой-переключателем, опреде­ ляемый параметром имя и отображающий указанный зна чок. Если параметр состояние принимает логическое значение true, то кнопка-переключатель ис­ ходно выбрана, а иначе -не выбрана. В классе JRadioButtonMenu ltem поддержи­ вается и ряд других конструкторов. Пункт меню ти па JRadioButtonMenu item действует аналогично автономной кнопке-переключателю, генерируя событие от элемента и событие действия. Как и автономные кнопки-переключатели, в меню кнопки-переключатели долж­ ны быть объединены в группу, чтобы они действовали в режиме взаимоисключа­ ющего выбора. Классы JCheckBoxMenu ltem и JRadioButtonMenu item наследуют от класса JRadioBu t tonMenu ltem, и поэтому функциональные возможности последнего до­ ступны каждому из них. Помимо дополн ительных возможностей флажков и кно­ пок-переключателей, отмечаемые пункты меню ничем не отличаются от обычных пунктов меню. Чтобы опробовать на практике пункты меню, отмечаемые флажками и кнопка­ ми-переключателями, уд алите сначала из примера программы MenuDemo фрагмент кода, в котором создается меню Options. Затем подставьте приведенный ниже фрагмент кода, в котором создаются подменю Colors и Priority с флажками и кноп­ ками-переключателями соответственно. После этой замены меню Options будет выглядеть так, как показано на рис. 33 .4. 11 создать меню Options JМenu jmOp tions = new JМ enu ("Options ") ; 11 создать подменю Colors JМ enu jmColors = new JМenu ( "Colors ") ; 11 использовать флажки , чтоОы пользователь мог выбрать 11 сразу нескол ько цветов JC hec kBoxMenuitem jmiRed = new JCheckBoxMenui tem ("Red") ; JC hec kBoxMe nuitem jmiGreen = new JCheckBoxMenui tem ( "Green" ); JC heckBoxMenu item jmiBlue = new JChec kBoxMenuitem("Blue") ; jmC olors . add ( jmiRed} ; jmColors .add ( jmiGreen} ; jmC olors .add(jmiBlue) ; jmOptions .add (jmColors) ; 11 создать подменю Priority JМenu jmPriority = new JМ enu ("P riority" }; 11 Использовать кнопки-переключатели для установки приоритета . 11 Благ одаря этому в ме ню не только отоОражается установленный 11 приоритет, но и гаран тируе тся установка одно го и только
1190 Часть 111. Введение в програ ммирован ие ГПИ средствами Swing 11 одного приоритета . Исходно выбирается кнопка-переключ атель 11 в пункте меню Bigh JRadioButtoпМeпuitem jmiHigh = new JRadioButtoпМenuitem ( "High", true) ; JRadioButtoпМe nu item jmiLow = new JRadioButtonMenuitem ( "Low" ); jmPriority.add(jmiHigh) ; jmPriority .add(jmiLow) ; jmOptions .add(jmPriority) ; 11 создать группу кнопо к-переключ ателей в пунктах подменю Priority But tonGroup bg = new ButtonGroup (); bg . add (jmiHigh) ; bg . add (jmiLow) ; 11 создать пункт меню Reset JМenultem jmiReset = new JМenultem("Reset" ); jmOptions . addSeparator () ; jmOptions .add(jmiReset ); 11 и наконец, ввести меню Options в строку ме ню jmЬ .add(jmOpt ions ); а б Рис. 33 ., . Резул ьтат уста новки флажка [а) и кнопки-переключателя [б) в пун ктах меню Optioпs Создание всплывающего меню Всплывающее меню служит весьма распространенной альте рнативой ил и до­ полнением строки меню. Как правило, всплывающее меню активизируется щелч­ ком правой кнопкой мыши на компоненте. Всплывающие меню поддерживаются в классе JPopupMenu из библиотеки Swing. У этого класса имеются два конструк­ тора, но здесь и далее в этой гл аве применяется только следующий конструктор по умолчан ию: JPopupМenu () Этот конструктор создает стандартное всплывающее меню. А другой конструк­ тор позволяет указать заголовок всплывающего меню. Отображение этого заго­ ловка зависит от выбранного стиля оформления ГПИ.
Гл ава ЗЗ. Введение в иеню Swi ng 1191 В общем, всШiывающие меню создаются таким же образом, как и обычные меню. Сначала создается объект типа JPopupMenu, а затем он наполняется пунктами всШiывающего меню. Обработка результатов выбора пунктов всШiывающего меню выполняется тем же самым способом: приемом событий действия. А отличаются всплывающие меню от обычных гл авным образом процессом их активизации. Процесс активизации всплывающего меню разделяется на три стадии. • Регистрация приемника событий от мыши. • Отслеживание в приемнике событий момента запуска всплывающего меню. • Отображение всплывающего меню при получении события запуска всплы­ вающего меню, для чего вызывается метод show ( ) . Рассмотрим каждую из этих стадий подробнее. Всплывающее меню обычно активизируется щелчком правой кнопкой мыши, когда курсор находится на том компоненте , для которого определено всплывающее меню. Следовательно, запуск всплъtвающего меню происходит после щелчка правой кнопкой мыши на компонен­ те , снабженном всплывающим меню. Для приема события запуска всплывающего меню сначала реализуется интерфейс MouseListener, а затем регистрируется при­ емник подобных событий. С этой целью вызывается метод addМouseListener (). Как пояснялось в гл аве 24, в интерфейсе MouseLi s tener определяются приведен­ ные ниже методы. void mouseCliclted (МouseEvent собuт•е от Нl ll llЖ ) void mouseEntered (MouseEvent собuт•е-от-Нl ll/IЖ ) void mouseExi ted (МouseEvent собuтяе от 'Ю.�.и) void mousePressed (МouseEvent собuт•ё о-; Нl ll/IЖ ) void mouse:Released (МouseEvent соб�rтиё_о-;_.Nl llD ) Из всех перечисленных выше методов для создания всплывающих меню осо­ бое значение имеют два метода: mousePres sed () и mo useRe leased ( ) . В зави­ симости от выбранного стиля оформления ГПИ любой из этих методов приема событий от мыши может запустить всплывающее меню. Именно поэтому зача­ стую проще воспользоваться классом Mo useAdapter, чтобы реализовать интер­ фейс Mo useListene r и просто переопределить его методы mo usePressed () и mou seReleased (). В классе Mo useEvent определяется целый ряд методов, но только четыре из них обычно требуются для активизации всплывающего меню. Эти методы пере­ числены ниже . int getx () int getY () Ьoolean isPopupTrigger () Coшponent getCoшponent () Те кущие координаты Х,У положения курсора относительно источника события определяются с помощью методов getX () и getY ( ) соответственно. Полученные с их помощью координаты служат для указания левого верхнего угла всплывающе­ го меню при его отображении. Метод isPopupTrigger ( ) возвращает логическое значение true, если событие от мыши обозначает запуск всплывающего меню, а иначе -логическое значение false. Чтобы получить ссылку на компонент, сге­ нерировавший событие от мыши, следует вызвать метод getComponent ().
1192 Ч асть 111. Введение в п рограммирование ГПИ средствами Swing Чтобы отобразить всплывающее меню, следует вызвать метод show ( ) , опреде­ ляемый в классе JPopupMenu следующим образом: void show (Component .-rsvwan rж• , intХ,intУ1 где параметр вызыв ающий обозначает компонент, относительно которого отобра­ жается всплывающее меню; Х и У - координаты Х,У местоположения левого верх­ него угла всплывающего меню относительно вызыв ающего компонента. Чтобы получить вызывающий компонент, проще всего вызвать метод getComponent () для объекта события, передаваемого обработчику событий от мыши. Все сказанное выше относительно всплывающих меню можно применить на практи ке, введя всплывающее меню Edit (Правка) в пример программы Me nu Demo , представленный в начале этой гл авы. Это меню будет состоять из трех пунктов: Cut (Вырезать) , Сору (Скопировать) и Paste (Вставить) . Сначала введите в программу Menu Demo приведенную ниже строку кода, где переменная jpu объ­ является для хранения ссылки на всплывающее меню. JPopupMe nu jpu; Затем введите следую щий фрагмент кода в конструктор класса Menu Demo : // создать всплыв ающе е меню Edit jpu = new JPopupMenu () ; 11 создать пункты всплыв ающе го меню JМenui tem jmiCut = new JМenuitem("Cut " ); JМ enui tem jmiCopy = new JМenuitem ("Copy" ); JМ enuitem jmiPaste = new JМ enu item("P aste" ); // ввести пункты во всплывающе е меню jpu.add ( jmiCut ); jpu.add ( jrniCop y) ; jpu.add ( jmiPaste ); 11 ввести приемник событий запуска всплывающе го меню jfrrn . addMouseListener (new Mous eAdapter () { puЫic vo id mousePressed (MouseEvent rne ) { if (rne.isPopupTrigger () ) jpu. show (rne . getComp onent () , me .getX() , rne .getY() ); ) puЫic vo id rnous eReleased (MouseEvent me ) { if (rne.isPopupTrigger ()) } }); jpu. show (rne . getCornp onent () , rne .getX() , me .getY() ); Сначала в приведенном выше фрагменте кода создается экземпляр класса JPopupMenu, сохраняемый в переменной jpu. Затем в нем создаются обычным об­ разом три пункта меню, Cut, Сору и Paste, которыми наполняется объект, храня­ щийся в переменной jpu. На этом создание всплывающего меню Edit завершается. Всплывающие меню не вводятся ни в строку меню, ни в какой-нибудь другой объект. Далее создается анонимный внутренний класс для ввода приемника событий от мыши типа MouseListener. Этот класс опирается на класс MouseAdapter, а следовательно, в приемнике событий нужно переопределить только те методы, которые имеют отношение ко всплывающему меню, а именно mousePressed ( ) и mou seReleased ().Вклассе адаптера предоставляются стандартные реализации
Гл ава 33. Введение в меню Swing 1193 других методов из интерфейса Mo useListener. Следует также заметить, что при­ емник событий от мыши вводится во фрейм, хранящийся в переменной j f rm. Это означает, что если щелкнуть правой кнопкой мыши в любом месте панели содер­ жимого , то произойдет запуск всплывающего меню. В методах mo usePressed () и mo useReleased () вызывается метод isPopup Trigger (), чтобы выяснить, связано ли наступившее событие с запуском всwtы­ вающего меню. Если это действительно так, то вызывается метод show (), чтобы отобразить всплывающее меню. С целью получить вызывающий компонент вы­ зывается метод getComponent () для события от мыши. В данном случае вызываю­ щим компонентом будет панель содержимого. А для получения координат левого верхнего угла вспл ывающего меню вызываются методы getX ( ) и ge tY ().Витоге левый верхний угол вс wtывающего меню оказывается прямо под курсором. И наконец, в данную программу нужно ввести приведенные ниже приемники действий. Они обрабатывают события действия , наступающие в тот момент, когда пользователь выбирает пункт из всплывающего меню. jmiCut . addActionLi stener (thi s) ; jmiCopy . addActionLi stener (this) ; jmiPaste . addActionLi stener (this) ; После внесения описанных выше дополнений в программу Menu Demo вспл ывающее меню можно активизировать щелчком правой кнопкой мыши в любом месте панели содержимого. Полученный результат показан на рис. 33.5 . В отношении рассматриваемого здесь примера всплывающего меню следует также заметить, что ко мпонентом , вызы вающим всwtывающее меню, в данном случае всегда остается фрейм j frm, и по­ этому его можно передать явным образом, не вы- зывая метод getComp onent () .Сэтой целью фрейм j frm нужно присвоить переменной экземпляра f.lle Optlons Hetp Cut Sele<:ted Сору Paste Рис. 33 .5 . Меню, всплывающее в окне программы MenuDemo класса Menu Demo , а не локальной переменной, чтобы сделать его доступным во внутре ннем класс е. И тогда для отображения всплывающего меню метод show () можно вызвать следующим образом: jpu . show(jfrm, me .getX() , me .getY()); И хотя такой прием вполне пригоден в данном примере, преимущество вызова метода getComponent () заключается в то м, что всплывающее меню будет авто­ матически появляться относительно вызывающего компонента. Следовательно, один и тот же код может быть использован для отображения любого всплывающе­ го меню относительно вызывающего его объекта. Соэда н ие панел и инструмен тов Панель инструментов является компонентом, служащим как альтернативой , так и дополнением меню. Такая панель состоит из ряда кнопок (или других ком-
119, Ч асть 111. Введение в прогр аммирование ГПИ средствами Swi ng понентов) , предоставляющих пользователю возможность немедленного доступа к различным параметрам и режимам работы программы. Например, панель ин­ струментов может содержать кнопки для выбора параметров шрифтового оформ­ ления текста, в том числе полужирного или наклонного начертания, выделения или подчеркивания текста. Эти параметры можно выбрать, не раскрывая меню. Как правило, кноПКJ;f на панели инструментов обозначены значками, а не тексто­ выми надпися ми, хотя допускается и то и другое. Кроме того , кнопки на панели инструментов нередко снабжаются всплывающим подсказками. Панель инстру­ ментов можно размещать вдоль любой стороны рабочего окна, перетаскивая ее в пределах окна и даже за его пределы. В последнем случае она становится свобод­ но плавающей. В библиотеке Swi ng панели инструментов являются экземплярами класса JT oolBar. Конструкторы этого класса позволяют создать панель инструментов как с заголовком, так и без него, а также указать расположение панели инструмен­ тов по горизонтал и или по вертикали . Ниже перечислены конструкторы класса JT oolBar. JТoolBar () JТoolBar (String .заголовоr) JТoolBar (int op1teн!l'aЦJ1•) JТoolBar (String .заrоловоr , int ор•ен1'аця•) Первый конструктор создает горизонтал ьную панель инструментов без заголов­ ка. Второй конструктор создает горизонтальную панель с указанным за головком, который отображается только в том случае, если панель перемещается за пределы окна. Тр етий конструктор создает панель инструментов, расположение которой определяется параметром ориента ция. Этот параметр должен принимать зна­ чение константы JToolBar . VE RT I CAL или JT oolBar . HORIZONTAL. И четвертый конструктор создает панель инструментов с указанным за головком и расположе­ нием, определяемым параметром ор иентация. Как правило, панель инструментов применяется в окне с граничной компо­ новкой по двум причинам. Во-первых, это позволяет первоначально расположить панель инструментов вдоль одной из границ окна. (Зачастую она располагается вдоль верхней границы окна.) И во-вторых, это дает возможность перетаскивать панель инструментов на любую сторону окна. Панель инструментов можно перетаскивать не только в любое место окна, но и за его пределы. В последнем случае панель инструментов оказывается отстыко­ ванной. Если при создании панели инструментов указать заголовок, то он появит­ ся, когда панель будет отстыкована. Кнопки (или другие компоненты) вводятся на панели инструментов таким же об­ разом, как и в строке меню. Для этого достаточно вызвать метод add ( ) . Компоненты отображаются на панели инструментов в том порядке, в каком они вводятся. Как только панель инструментов будет создана, ее не следует вводить в строку меню, если таковая имеется . Вместо этого ее следует ввести в контейнер окна. Как упоминалось выше, панель инструментов обычно вводится вдоль верхней грани­ цы (северного расположения в граничной компоновке) с горизонтальной ориен­ тацией. А затрагиваемый ко мпонент вводится по центру граничной компоновки . При таком расположении панель инструментов окажется там, где и предполагает-
Гл ава ЭЭ. Введение в меню Swing 1195 ся ее обнаружить при запуске прикладной программы. Но это не мешает перета­ щить па:нель инструментов в любое другое место окна или вообще за его пределы. В качестве примера ниже показано, как ввести панель в упоминавшуюся ранее программу MenuDemo . Эта панель состоит из трех кнопок выбора режимов отлад­ ки: установки точки прерывания , очистки точки прерывания и возобновления вы­ полнения программы. Все эти стади и процесса отладки следует ввести на панели инструментов. Прежде всего уд алите из программы MenuDemo приведенную ниже строку кода. Благодаря этому во фрейме типа JFrame автоматически используется граничная компоновка. jfrm. setLayout (new FlowLayout () ); Затем внесите приведенные ниже изменения в то й строке кода, где во фрейм вводится метка j lab, поскольку те перь применяется диспетчер гран ичной компо­ новки типа Borde rLayo ut. jfrm . add (jlab , BorderLayout .CENTER) ; В данной строке кода метка j lab явным образом вводится по центру гранич­ ной компоновки . (Формально указывать явным образом центральное расположе­ ние вводимого ко мпонента совсем не обязательно , поскольку в граничной ком­ поновке компоненты по умолчанию располагаются по центру. Но явное указание центрального расположения компонента дает читающему исходн ый код ясно по­ нять, что в данном случае применяется граничная компоновка, а метка j lab раз­ мещается по центру.) Далее введите следующий фрагмент кода для создания панели инструментов Debug (Отладка): 11 создать панель инструме нтов DeЬu9 JToo lBar j tb = new JToolBar ( "Debug" ) ; // загрузить изображения значков кнопок Imagelcon set = new Image icon ("setBP .gif" ); Image l con clear = new Image icon ("c learBP .gif") ; Imagelcon resume = new Image icon ("r esume.gif") ; // создать кнопки дл я панели инструментов JButtoп jbtпSet = new JButton (set) ; jЬtnSet . setActionCommand ("Set Breakpoint") ; jЬtnSet .setToolTipText ("Set Breakpoint") ; // Установить точку прерывания JButton jЬtnClear = new JButton (clear) ; jЬtnClear .setAc tionCommand ("C lear Breakpoint") ; jЬtnClear .setToolTipText ("Clear Breakpoint") ; // Очистить точку прерывания JButton jЬtnRe sume = new JB utton (resume) ; jЬtnRe sume .setAct i onComma nd ("Resume") ; jЬtnResume .setToolTipText ("Resume") ; // Возобновить выполнение программы 11 ввести кнопки на панели инструментов jtb.add(jЬtnSet ); jtb.add(jЬtnClear) ;
119 6 Часть 111. Введение в программирование ГПИ средствами Swi ng jtb.add (jbtnRe sume) ; 11 ввести панель инс трументов в северном расположении 11 на панели содержимо го jfrm . add (jtb, BorderLayout . NORTH ); Рассмотрим подробнее приведенный выше фрагмент кода. Сначала в нем соз­ дается панель инструментов в виде объекта ти па JT oolBar, и ей присваивается заголовок "Debug ". Затем создается ряд объектов типа Ima geicon для хранения изображений значков кнопок на панели инструментов. Далее создаются три кноп­ ки для панели инструменто в, причем у каждой из них имеется свой значок вме­ сто текстовой надписи. Кроме того , для каждой кнопки явным образом задается команда действия и всплывающая подсказка. Команды действия задаются потому, что кнопкам не присваиваются имена при их создании. Всплывающие подсказки особенно уд обны для компонентов, представленных только значками на панели инструментов, поскольку не всегда удается оформить такие значки , которые инту· итивно понятны всем пользователям. После этого кнопки вводятся на панели ин­ струментов, а сама панель - на северной стороне граничной компоновки фрейма. 'll l!" f'i5'1�1 �-='�-�=-- _) --- 1 flle Optlons Help � Set Breakpoint Selected Рис. 33.6. Вид панели ин­ струментов Debug в окне программы MenuDemo Действи я И наконец, введите приемники действий для пане­ ли инструментов, как показано ниже . 11 ввести приемники действий дл я панели jbtnSet . add.ActioпListener (this ); jbtnClear . add.ActionLi stener (thi s) ; jbtnResume .add.ActionLi stener (this) ; инструме нтов Всякий раз, когда пользователь щелкает на кноп· ке на панели инструментов, наступает событие дей­ ствия, которое обрабатывается таким же образом, как и другие связанные с меню события . Вид панели ин­ струментов отладки в окне программы MenuDemo по­ казан на рис. 33.6 . Нередко панель инструментов и меню содержат одинаковые элементы. Например, режимы отладки , доступные на панели инструментов Debug из предыдущего приме­ ра, моrут быть также доступны для выбора из меню. В подобных случаях выбор какого­ нибудь варианта (например, установки точки прерывания при отладке) из меню или панели инструментов вызывает одно и то же действие. Кроме того, для обозначения кнопки на панели инструментов и пункта меню используется (чаще всего) один и тот же значок. Более того, если кнопка становится недоступной на панели инструментов, то недоступным оказывается и соответствующий пункт ме ню. В подобных случаях обычно накапливается довольно большой объем дублирующегося взаимосвязанного кода, что нельзя назвать оптимальным решением. Правда, библиотека Swing предла­ гает выход из этого затрудн ительного положения в виде действий. Действие является экземпляром интерфейса Act ion, рас ширяющего интер­ фейс Ас t i onLi s tener и предоставляющего средства для сочетания сведений о со­ стоя нии с обработкой событий методом actionPerformed (). Благодаря такому
Гл ава 33. Введение в меню Swi ng 1197 сочетанию одн им действием можно управлять двумя или более ко мпонентами. Например, действие позволяет централизовать управле ние кнопкой на панели инструментов и пунктом в меню. Вместо дубл ирования кода в прикладной про­ грамме нужно лишь создать действие, управля ющее обоими компонентами. В связи с тем что интерфейс Action расширяет интерфейс ActionListener, действие должно предоставить реализацию метода actionPerforme d (). Этот ме­ тод будет обрабатывать события действия , генерируемые объектами , связанными с отдел ьным действием. Помимо наследуе мого метода actionPerforme d (),винтерфейсе Action опре­ деляется ряд собственных методов. Особый интерес среди них вызывает метод putVa lue (), зад ающий значения различных свойств, связанных с действием, как показано ниже. void putValue (Strinq lGЛID'l , Ob ject знаvенае) Этот метод присваивает указанное зна чение требуемому свойству, обозначае­ мому параметром ключ. В интерфейсе Ac tion предоставляется также метод ge t­ Va lue (), получающий указанное свойство, хотя в представленном далее примере он не применяется. Ниже приведена общая форма этого метода. Он возвращает ссылку на свойство, обозначаемое параметром ключ. OЬject qetValue (Strinq жлюv) Значения ключей, используемые в методах putVa lue () и ge tValue (), приве­ дены в табл. 33.2. Та бл ица 33.2. Значения кл ю чей, обознач ающи х свойства, связанные с де йствиями Значение кпюча atatic final Strinq ACCELERATOR ПУ atatic final Strinq ACTION_ cao oum DY atatic final Strinq DISPLAYED МNDIONIC INDZX ПУ - - static final Strinq LARGE ICON КЕУ atatic final Strinq LONG DESCRIPТION static final Strinq МNEИ>NIC ПУ atatic final Strinq NАМЕ Описание Представляет свойство оперативной клавиши. Оперативные клавиши указываются в виде объектов типа КеуStrоkе Представляет свойство команды действия. Команда действия указывается в виде символьной строки Представляет и ндекс символ а, отображаемого в мне­ мон ическом виде. Это значен ие типа Inteqer Представляет крупный значок, связанный с действи­ ем. Значок указывается в виде объекта типа Icon Представляет длинное описание действия. Это описа­ ние указывается в виде символьной строки Представляет мнемоническое свойство. Мнемоника указывается в виде константы типа КeyEvent Представляет имя действия, которое становится также именем элемента (кнопки или пун кта меню), с которым связано это действие. Это имя указывается в виде символьной строки
119 8 Часть 111. Введе н и е в программировани е ГПИ средствами Swi ng Значение ключа atatic final Strinq SELECТED DY static final String SBORT D!SCRIPТION static final Strinq SМAL L ICON Окакчание таlИ. 33. 2 Описание Представляет состояние выбора. Если оно установле­ но, то элемент выбран. Состояние представлено логи­ ческим значением Представляет текст всплывающего сообщения, свя­ занный с действием. Этот текст указывается в виде символьной строки Представляет значок, связанный с действием. Этот значок указывается в виде объекта тип Icon Например, чтобы задать мнемонику для буквы Х, достаточно сделать следую­ щий вызов метода putVa lue (): actionOb .putValue (МNEMON IC_KEY, new Integer (KeyEvent .VK _X) ); Ед инственным свойством типа Action, недоступным через методы p u t Val­ ue () и ge tVa lue (), является разрешенное или запрещенное состояние. Для до­ ступа к нему следует воспользоваться методами setEnaЫed () и isEnaЫed ( ) . Ниже приведены общие формы этих методов. void setEnaЬled (Ьoolean раsр81 1' ен.е) Ьoolean isEnaЬled () Если параметр ра зрешение метода setEnaЫed ( ) принимает логическое зна­ чение true, то действие разрешено, а иначе оно запрещено. Если же действие раз­ решено, то метод isEnaЫed ( ) возвращает логическое значение true, а иначе - логическое значение false. Полностью реализовать интерфейс Act ion можно и самостоятельно, но обыч­ но в этом нет никакой нужды. Вместо этого в библиотеке Swing предоставляются частичные его реализации, называемые Ab stractA ction и допускающие расши­ рение. Рас ширяя класс Ab stractAction, придется реализовать только один метод actionPerforme d (), а остальные методы из интерфейса Ac tion предоставляют­ ся автоматически . У класса Ab stractA ct ion имеются три конструктора. Ниже приведен конструктор, применяемый здесь и далее в этой гл аве. Этот конструктор создает абстрактное действие в виде объекта класса AЬstractA ction, получаю­ щее заданное имя и указанное из ображение значка. AЬstractAction (Strinq •нst, Icon •soбpazeюre) Как только действие будет создано , его можно ввести на панели инструментов ти па JT oolBar и воспользоваться им для создания пункта меню типа JMe nu l tem. Чтобы ввести действие на панели инструментов типа JT oolBar, следует восполь­ зоваться следую щим вариантом метода add ( ) : void add (Action o65eж!l'_дeirc!l'S1 1r •) где параметр объект_действия обозначает действие, вводимое на панели инстру­ ментов. Свойства , определяемые параметром объект_ действия, испол ьзуются для создания кнопки на панели инструментов. А для создания пункта меню из кон­ кретного действия служит приведенный ниже конструктор класса JMe nu ltem, где
Гл ава 33. Введение в меню Swi ng 1199 объект_действия обозначает действие, используемое для создания пункта меню в соответствии с его свойствами. JМenuiteш (Action о�ех�_дейс�) На заметку! Помимо ко мпонентов ти па JToo lBar и JМenuitem, де йствия поддержи вают­ ся и в других компонентах Swing, в том числе JPopupMe nu, JButton, JRa dioButton, JChec kBox, JRadioButtonMenuitem и JCheckBoxMenuite� Продемонстрируем преимущества действий на примере управления созданной ранее панелью инструментов Debug в рассматриваемом здесь примере программы Menu Demo . Для этого меню Options будет дополнено подменю Debug со следующи­ ми пунктами, аналогичными кнопкам выбора режимов отладки на панели инстру­ ментов Debug : Set Breakpoint (Установить точку прерывания) , Clear Breakpoint (Очистить точку прерывания) и Resume (Возобновить выполнение) . Одни и те же действия будуг поддерживать как пункты в подменю Debug , так и кнопки на од­ ноименной панели инструментов. Следовательно, вместо того чтобы писать ду­ блирующийся код управления подменю и панелью инструментов Debug по отдель­ ности, достаточно организовать действия, которые будут управлять обоими этими элементами ГПИ. Итак, создайте сначала внутренний класс DebugAction, расширяющий класс AЬ stractAction и приведенный ниже. 11 Кл асс действий для подме ню и панели инструментов DeЬuq class DebugAc tion extends Ab stractAct ion { puЫ ic DebugAction (String name , Icon image , int mnem, int accel, String tTip ) { super(name, image); putValue (ACCELERATOR КЕУ , Ke yStroke .getKeyStroke (accel, InputEvent . CTRL DOWN МАSК ) ); putValue (МNEMON IC КЕУ, new Integer (mnem ) ); - - putValue ( SHORT_ DESCRI PT ION, tTip) ; 11 обработать события как на панели инструментов , так // и в подменю DeЬuq puЫ ic void actionPe rformed (ActionEvent ае ) { String comStr = ae . getActionCommand () ; jlab . se tText (comStr + " Selected" ); // Выбрано указанное 11 изме ни ть разрешенное состояние вариантов выбора 11 режимо в установки и очистки точек прерывания if ( comStr . equals ( "Set Breakpoint ") ) { clearAct .se tEnaЬled (true) ; setAct .setEnaЫ ed (false) ; else if (comS t r.equals ( "Clear Breakpoint ") ) clearAct . setEnaЫ ed (false) ; setAct . setEnaЫed (true) ;
1200 Часть 111. Введение в п р ограммирован ие ГП И средства ми Swi ng Приведен ный выше класс DebugAc tion расширяет класс Ab stractA ction. В итоге получается класс действия, предназначенный для определения свойств, связанных с подменю и панелью инструментов Debug. Его конструктор принима­ ет пять параметров, позволяющих указать следую щие элементы : • Имя. • Значок. • Мнемоника. • Оперативная клавиша. • Всплывающая подсказка. Два первых параметра передаются конструктору класса AЬstractA ction по ссылке supe r, а остальные три задаются через вызов метода putValue (). Метод actionPerforrne d () из класса DebugAction обрабатывает собы­ тия , связанные с выполняемым действием. Это означает, что если для создания кн опки на панели инструментов и пункта меню используется экземпляр класса DebugAction, то события , генерируемые любым из этих компонентов , обрабаты­ ваются методом actionPerforrned ( ) из класса DebugAction. Следует заметить, что этот обработчик событий отображает результаты выбора в метке j lab. Если выбирается вариант (кнопка или пункт меню) Set Breakpoint, то вариант Clear Breakpoint становится доступным, а вариант Set Breakp oint - недоступным. Если же выбирается вариант Clea r Breakpoint, то вариант Set Brea kpoint становится доступным, а вариант Clear Breakpoint - недоступным. Это наглядно показывает, каким образом действие позволяет активизировать или дезактивизировать компо­ нент. Если действие запрещено, то зап рещены и все варианты его использования. В данном случае становятся недоступным кнопка на панели инструментов и пункт меню Set Breakpoint, если запрещено действие установки точки прерывания. Затем введите приведенные ниже переменные экземпляра класса DebugAc tion в программу Menu Derno . DebugAction setAct ; DebugAc tion clearAct ; DebugAc tion resumeAct ; Далее создайте три значка типа Irnage lcons , представляющих режимы отлад­ ки , как показано ниже. 11 загрузить изображения для обозначения действий при отладке Image lcon setlcon = new Image icon {"setBP .gif" ); Image lcon clearlcon = new Imagelcon {"clearBP .gif") ; Image lcon resumelcon = new Imagelcon {"r esume.gif") ; А теперь создайте действия , управляющие вариантами выбора режимов отлад­ ки в подменю и на панели инструментов Debug, как показано ниже. 11 создать действия setAc t = new DebugAction {"Set Breakpoint ", seticon , KeyEvent .VK S, KeyEvent .vк=:в,
Гл ава 33. Введение в меню Swi ng "Set а break point.") ; 11 Установить точку прерывания clearAct = new DebugAc tion ("Clear Breakpoint ", clear!con , KeyEvent .VK_C, KeyEvent .VK L, "Clear а br�ak point.") ; 11 Очистить точку прерывания resumeAct = new DebugAction ("R esume ", resume!con , Ke yEvent .VK R, Ke yEvent .VK-R, "Resume execution after breakpoint .") ; 11 ВозоОновить выполнение после точ ки прерывания 11 сделать первоначально недоступным вариант выОора Clear Breakpoint clearAct .setEnaЫed (false) ; 1201 Следует заметить, что для быстрого выбора варианта Set Breakpoint назначена оперативная клавиша <В>, а для быстрого выбора варианта Clear Breakpoint - опе­ ративная клавиша <L>. Выбор именно этих клавиш, а не клавиш <S> и <С> объ­ ясняется тем, что последние уже назначены для быстрого выбора пунктов Save и Close соответственно из меню File. Но в то же время их можно использовать в ка­ честве мнемоники, поскольку каждая мнемоника действует только в пределах сво­ его меню. Следует также заметить, что действие, представляющее режим очистки точки прерывания (Clea r Breakpoint) , первоначально запрещено. Оно будет раз­ решено только после установки точки прерывания. Далее воспользуйтесь действиями для создания и ввода кнопок на панели ин­ струм ентов, как показано ниже. 11 создать кнопки для панели инструментов , 11 используя соответствующие действия JButtoп jbtпSet = new JBu ttoп (setAc t ); JButtoп jbtnClear = пеw JButtoп (clearAct ); JButtoп jbtпRe sume = пеw JButtoп (re sumeAc t ); 11 создать панель инструм ентов DeЬuq JTool Bar jtb = пеw JToolBar ( "Breakpoiпts") ; 11 ввести кнопки на панели инструме нтов jtb.add(jbtпSet ); jtb.add(jbtпCl ear ); jtb.add(jbtnRe sume) ; 11 ввести панель инструментов в северном расположении 11 панели содержимого jfrm . add (jtb, Borde rLayout . NORTH ); И наконец, создайте подменю Debug, как показано ниже. 11 создать подме ню DeЬuq, входящее в ме ню Options , 11 исполь зуя действия для создания пунктов этого подменю JМenu jmDebug = new JМeпu ("D ebug " ); JМ enuitem jmiSetBP = new JМeпu item (setAct ); JМ eпuitem jmiClearBP = new JМ eпu! tem (clearAct );
1202 Часть 111. Введение в п рограммирование ГПИ средствами Swi ng JМenuitem jmiResume = new JМenu ltem (resumeAct ) ; jmDebug .add ( jmiSetBP) ; jmDebug .ad d(jmiClearBP) ; jmDebug .ad d(jmiResume) ; jmOptions .add(jmDebug) ; После внесения описанных выше изменений и дополнений в программу MenuDemo созданные действия будут одновременно управлять подменю и панелью инструментов Debug. Та ким образом , любое изменение в свойстве действия ( на­ пример, его запрет) будет оказывать воздействие на все варианты его использо­ вания. Вид, который теперь принимает подменю и панель инструментов Debug в окне данной программы, показан на рис. 33 .7 . :: 1-!; � 1-L 1t [jO+° ,R&sumeJ Рис. 33 .7. Вид подменю и па нели инструментов Debug, управляемых дейст виями, в окне программы MenuDemo Соста вл ение оконч ател ьного вар ианта про граммы MenuDemo По ходу изложения материала этой гл авы в исходный вариант прикладной про­ граммы Menu Demo было внесено немало изменений и дополнений. Прежде чем за­ вершить эту главу, целесообразно составить окончательный вариант данной про­ граммы с учетом всех изменений и дополнений. Это позволит не только избежать недоразумений относительно согласованности отдел ьных частей программы, но и получить рабочую программу для демонстрации меню и дальнейшего экспери­ ментирования с ними. В приведенный ниже окончательный вариант прикладной программы MenuDemo вошли все изменения и дополнения , описанные ранее в этой гл аве. Ради большей ясности эта программа была реорганизована таким образом, чтобы ис­ пользовать отдел ьные методы для создания разных меню и панели инструментов. Следует также иметь в виду, что некоторые переменные, связанные с меню, в том числе jmЬ, jmFile и j tb, были превращены в переменные экземШiяра, чтобы сде­ лать их доступ ными в любой части класса. 11 Окончатель ный вариант програNМЫ complete МenuDeшo import java .awt.*; import java .awt . event .*; import javax . swing .*;
Гл ава 33. Введение в меню Swi ng class MenuDemo impleme nts Ac tionListener { JLabel jlab ; JМenuBar jmЬ; JToolBar jtb; JPopupMenu jpu; DebugAction setAct; DebugAction clearAct; DebugAc tion resumeAct; MenuDemo () { 11 создать новый контейнер тиnа JFr88 8 JFrame jfrm = new JFrame ("Complete Menu Demo " ); 11 Полная демонстрация меню 11 использовать граничную комп оно вку, выбираемую по умо лчанию 11 задать исходные размеры фрейма jfrm .setSize (ЗбO, 200) ; 11 завершить прикладную программу, когда поль зователь 11 закроет ее окно jfrm . setDe faultCloseOpe ratioп ( JFrame .EXIT_ON_C LOSE) ; 11 создать ме тку для отображе ния резуль татов выбора из ме ню jlab = new JLabel (); 11 создать строку меню jmЬ = new JМenuBar (); 11 создать ме ню File ma keFileMenu (); 11 создать действия отладки makeActions () ; 11 создать панель инструментов ma keToolBar () ; 11 создать меню Options ma keOptionsMenu () ; 11 создать меню Belp ma keHelpMenu (); 11 создать меню Zdit ma keEdit PUМenu (); 11 ввести приемник событий запуска всплывающего меню jfrm . addМouseListener (new MouseAdapter () {· puЫic void mousePressed (MouseEvent те ) { if (me.isPopupT rigger () ) jpu. show (me . getComp onent () , me .getX() , me .getY() ); ) puЫ ic void mouseReleased (MouseEvent me ) { if(me.isPopupTrigger () ) 1203
} }); Часть 111. Введение в п рогра ммирование ГПИ средствами Swi ng jpu.show (me . getComp onent () , me .getX() , me .getY() ); 11 ввести ме тку в центре панели содержимо го jfrm .add(jlab, SwingConstant s.CENTER) ; 11 ввести панель инструментов в северном положении 11 панели содержимого jfrm . add (jtb, BorderLayout .NORTH) ; 11 ввести строку ме ню во фр ейм j frm . setJМenuBar (jmЬ) ; 11 отобразить фрейм jfrm . setVisiЬle (true) ; // Обработать события действия от пунктов ме ню . 11 Здесь НЕ обрабатываются события , генерируемые // при выборе режимов отладки в подменю или на панели 11 инструме нтов DeЬug puЫ ic void actionPerformed (ActionEvent ае ) { // получить команду действия из выбранного меню String comStr = ae .getAc tionCommand () ; // выйти из програММЬI, если поль зователь выберет пун кт ме ню Bxi t if (comStr . equals ("Exit") ) System.exit (O) ; 11 отобразить в противном случ ае результат выбора из меню jlab . setText (comS tr +"Selected" ); // Кл асс действий для подменю и панели инструментов D.Ьuq class DebugAc tion extends AЬstractAction { puЫ ic DebugAc tion (String name , Icon image , int mn em, int ассе!, String tTip ) { super(name, image); putValue (ACCELERATOR КЕУ , KeyStroke .getKeyStroke ( ассе!, InputEvent . CTRL DOWN МАS К) ); putValue (МNEMON IC КЕУ , new Integer (mnem) ); - putValue ( SHORT_DESCRI PTION, tTip) ; 11 обработать события как на панели инструментов , так 11 и в подменю DeЬuq puЫ ic void actionPerforme d (ActionEvent ае ) String comS tr = ae . getActionCommand () ; jlab . setText ( comStr +"Selected" ); // Выбрать указанное 11 изменить разрешенное состояние вариантов выбора 11 режимов установки и очистки точек прерывания if (comstr . equals ( "Set Breakpoint") ) { clearAct .setEnaЬled (true) ; setAct . setEnaЬl ed (false) ; else if (comStr . equal s("Clear Breakpoint ") ) clearAct .setEnaЫed (false) ; setAct . setEnaЫ ed (true) ;
Гл ава 33. Введение в меню Swi ng 11 созда ть меню File с мнемоникой и оперативными клавишами void makeFileMeпu () { JМenu jmFi le � new JМenu("File" ); jmFile .setMnemoni c(KeyEvent .VK _ F); JMe nuitem jmiOpen = new JMenuitem("Open", KeyEvent .VK _ O); jmiOpen .setAccelerator ( KeyS troke .getKeyStroke ( Ke yEvent .VK _ O, InputEvent .CTRL_DOWN_МAS K) ); JМenuitem jmiClose = new JМenuitem ("Close", KeyEvent .VK С) ; jmiClose .setAccelerator( - KeyStroke .getKe yS troke ( Ke yEve nt .VK _ C, InputEvent .CTRL_DOWN_МAS K) ); JМe nu item jmiSave = new JМenuitem ("Save", KeyEvent .VK S) ; jmiSave .setAc celerator ( - KeyStroke .getKe yStroke ( KeyEvent .VK s, InputEvent .CTRL DOWN МАSК) ); JМenui tem jmiExit = new JМenuitem("Ex it", KeyEvent:-vк Е) ; jmiEx it .setAcc elerator ( - KeyStroke .getKeyStroke ( Ke yEvent .VK Е , InputEvent . CTRL_DOWN_МASK) ); jmFile .add(jmiOpen) ; jmFile .add(jmiClose ); jmFile .add(jmiSave) ; jmFile . addSepa rator () ; jmFile .add ( jmiEx it) ; jmЬ .add (jmFile ); 11 ввести приемники действий для пунктов ме ню File jmiOpen . addActionLi stener (this ); jmiClose . addActionLi sten er (this) ; jmiSave . addActionLi stener (this) ; jmiExit . addActionLi stener (this ); 11 создать меню Optiona vo id ma keOptionsMenu () ( JМenu jmOptions � new JМenu ("Options ") ; 11 создать подменю Colora JМenu jmCol ors = new JMe nu ("Colors") ; 11 исполь зовать флажки , чтобы пользователь мо г выбрать 11 сразу несколь ко цветов JCheckBoxMenuitem jmiRed = new JCheckBoxMenuitem ("Red" ); JCh eckBoxMe nui tem jmiGreen = new JCheckBoxMenuitem ("Green" ); JCheckBoxMenuitem jmiBlue = new JCheckBoxMenuitem ("B lue " ); 11 ввести пункты в подменю Colors jmColors .add(jmiRed) ; jmColors .add(jmiGreen) ; jmColors .add(jmiBlue) ; jmOptions .add(jmColors ); 11 создать подменю Priori ty 1205
1206 Часть 111. Введе ние в программирование ГП И средствами Swi ng JМenu jmPriority = new JМenu ("Priority" J; 11 Исполь зовать кнопки-переКJIJ)Чатели для установки приоритета . 11 Благодаря этому в меню не ·толь ко отображается установленный 11 приорит ет, но и гарантируется установка одного и только 11 одного приорите та . Исходно выбирается кнопка -пере ключатель 11 в пункте ме ню Bigh JRadioButtonМenu item jmiHigh = new JRadioButtonMenuitem ("High", true) ; JRadioButtonМenuitem jmiLow = new JRadioBut tonMenuitem ("Low" J ; 11 ввести пункты в подменю Priority jmPriority .add(jmiHigh) ; jmPriority .add(jmiLow) ; jmOptionз .add(jmPriority); 11 создать группу кнопок-переключателей 11 в пунктах подменю Priority Bu ttonGroup bg = new ButtonGroup () ; bg. add(jmiHigh) ; bg.add(jmiLow); 11 создать подменю DeЬug, входящее в меню Optiona , 11 исполь зуя действия для создания пунктов этого подменю JМenu jmDebug = new JМenu ("Debu g"J ; JМenuitem jmiSetBP = new JМe nui tem (зetAct ); JМenuitem jmiClearBP = new JМenuitem ( clearAct) ; JМenu item jmiReзume = new JМenu item (resumeAct ); 11 ввести пункты в подменю DeЬug jmDebug .add ( jmiSetBP) ; jmDebug .add(jmiClearBP) ; jmDebug . add ( jmiResume J ; jmOp tionз .add (jmDebug) ; 11 создать пункт меню R8set JМenui tem jmiReset = new JМenui tem ("Reset" ); jmOptions . addSeparator (); jmOptions .add(jmiReset ); 11 И наконец , ввести все выбираемые меню в строку меню jmЬ .add (jmOp tionз ); 11 ввести приемники действий для пунктов меню Options , 11 кроме тех , что поддерживаются в подменю DeЬug jmiRed . addActionLi зtener (thi s) ; jmiGreen . addActionLi stener (this) ; jmiBlue . addActionListener (this) ; jmiHigh . addActionListener (this) ; jmiLow . addActionLi sten er (thi s ); jmiReset . addActionLi stener (this ); 11 создать меню Belp void makeHelpMenu () { JМenu jmнelp = new JМenu ("Help" ); 11 ввести значок для пункта ме ню AЬout Imageicon icon = new Image icon ( "AЬouticon .gif") ;
Гл ава 33. Введение в меню Swing JМ enuitem jmiAЬout = new JМenuitem( "AЬout ", icon) ; jmiAЬout .setToolTipText ( "Info about the MenuDemo program. ") ; jmНelp .add(jmiAЬout ); jmЬ .add (jmНelp) ; 11 ввести приемник действий дл я пункта ме ню AЬout jmiAЬout .addActionListene r(t hi s) ; 11 создать действия для управления подме ню и 11 панелью инструме нтов DeЬug void ma keActions () { 11 загрузить изображения дл я обозначения действий Image !con seticon = new Imageicon ("setBP .gif" ); Imageicon clearicon = new Image icon ("c learBP .gif") ; Imagei con resume icon = new Image icon ("r esume.gif") ; 11 создать действия setAct = new Deb ugAction ("Set Breakpoint ", set!con , KeyEvent .VK S, KeyEvent .VK - B, "Set а break point.") ; clearAct = new DebugAction ("Clear Breakpoint ", clearicon , KeyEvent . VK С, KeyEvent .VK - L, "Clear а break point.") ; resumeAct = new DebugAction ("R esume", resumeicon , KeyEvent .VК R, KeyEvent .VK - R, "Resume execution after breakpoint .") ; 11 Initially disaЫe the Clear Breakpoint opt ion . clearAct .se tEnaЫed (false) ; 11 создать панель инструментов DeЬug void ma keToolBar () { 11 создать кнопки для панели инструментов , 11 исполь зуя соответствующие действия JButton jbtnSet = new JButton (setAc t) ; JButton jbtnClear = new JButton (clearAct ); JButton jbtnRe sume = new JB utton (re sumeAct ); 11 создать панель инструме нтов DeЬuq jtb = new JToolBar ( "Breakpoints") ; 11 ввести кнопки на панели инс трументов jtb.add(jbtnSet ); jtb.add(jbtnClear) ; jtb.add(jbtnRe sume) ; 11 создать всплывающе е ме ню Edit 1207
1208 Часть 111. Введение в программирование ГПИ средствами Swi ng void makeEditPUMeпu () { jpu = пеw JPopupMeпu () ; 11 создать пункты всплывающе го меню Edi t JМ eпui tem jmiCut = пеw JМenu!tem("Cut " ); JМeпui tem jmiCopy = пеw JМenultem("Copy" ); JМenuitem jmiPaste = new JMe nuitem("P aste" ); 11 ввести пункты во всплыв ающее меню Edi t jpu. add ( jmiCut) ; jpu. add ( jmiCop y) ; jpu . add ( jmiPaste ); 11 ввести приемники действий для всплыв ающе го меню Edit jmiCut . addActionLi stener (thi s) ; jmiCopy . addActionLi stener (this ); jmiPaste . addActionLi stener (this) ; puЫic static vo id main (String args[] ) { 11 создать фрейм в потоке диспетчеризации событий SwiпgUtilities . invo keLater(new RunnaЬle () { puЬl ic void run() { } }); пеw MeпuDemo () ; Дальнейшее изучение библиотеки Swing В библиотеке Swing определяется довольно большой набор инструментальных средств для построения ГПИ . Она предоставляет немало других средств, которые вам придется освоить самостоятельно. В частности , библиотека Swing предостав­ ляет классы JOptionPane и JDialog, упрощающие процесс построения диалого­ вых окон, а также дополнительные элементы упрамения , помимо предстамен­ ных в гл аве 31. Рекомендуется изучить два компонента: JSpinne r (для создания счетчика) и JFo rmattedTextField (для форматирования текста) . Можете также поэкспериментировать с определением собственной модели для различных ком­ понентов. Откровенно говоря , самый лучший способ изучения возможностей би­ блиотеки Swing - экспериментировать с ней.
ЧАС ТЬ IV ГЛ АВА Э4 Введение в Java FX ГЛАВА Э5 Элементы управления JavaFX ГЛАВА Э6 Введение в меню Java FX Введение в прогр аммирование ГПИ средствами Java F)(
34 Введение в JavaFX Как и все уда чно разработанные языки программирования, Java продолжает раз­ виваться и совершенствоваться. И это относится к его библиотекам. Характерным примером такого развития служат библиотеки (или так называемые каркасы) для по­ строения ГПИ. Как пояснялось ранее в данной книге, первоначально для построения ГПИ была разработана библиотека АWГ. Но в силу ряда присущих ей ограничений вскоре бьта разработана библиотека Swi ng, предлагавшая более совершенный под­ ход к построению ГПИ . Эта библиотека оказалась настолько удачной, что до сих пор остается основной вJava для построения ГПИ. И скорее всего, она таковой останется еще долго, несмотря на быстро меняющую ситуацию в области программирования ! Те м не менее библиотека Swing была созадана в то время, когда в разработке про­ граммного обеспечения господствующее положение занимали корпоративные при­ ложения. А ныне особое значение приобрели потребительские приложения, особен­ но для мобильных устройств. И от таких приложений зачастую требуется визуальная привлекательность. Более того, наметилась общая тенденция к наделению прило­ жений, независимо от их типа, захватывающими визуальными эффектами. Поэтому с учетом этой тенденции потребовался новый подход к построению ГПИ, что и при­ вело к разработке библиотекиJаvаFХ. Она стала новым поколением платформы и би­ блиотеки для построения ГПИ клиентских приложений вJava. JavaFX служит эффективным, рациональным, уд обным каркасом для построе­ ния современных визуально привлекательных ГПИ, а следовательно, это доволь­ но крупная система, которую, как и Swing, просто невозможно описать полностью в рамках данной книги . Поэтому в этой и двух последующих гл авах представлены лишь основные средства и способы построения ГПИ на основе JavaFX. Овладе в этими основами , вы сможете без особого труда самостоятельно изучить остальн ые особенности JavaFX. В связи с появлением JаvаFХ возникает следующий вполне резонный вопрос : служит лиJavaFX в качестве замены Swing? Определенно служит. Но, принимая во внимание немалый объем унаследованного кода, а также огромную армию специ· алистов, умеющих разрабатывать приложения на основе библиотеки Swing, она еще нескоро выйдет из употребления. И это особенно касается корпоративных приложений. Те м не менее JavaFX бьта ясно определена как перспективная плат­ форма. Ожидается, что через несколько лет JavaFX полностью вытеснит Swing для разработки новых проектов. Поэтому ни один программирующий на Java не должен игнорировать J avaFX.
1212 Ч асть IV. Введение в про гр аммирование ГПИ с р едства ми Java FX Прежде чем продолжить дальше, следует вкратце упо мянугь, что разработка javaFX происходила в две основные стадии. Первоначально разработкаj аvаFХ ве­ лась на основе языка сценариевja vaFX Script, но этот язык был упразднен. Поэтому, начиная с версии 2.0 программирование javaFX велось уже непосредственно нajava, в результате чего появился исчерпывающий прикладной программный ин­ терфейс API. ВJavaFX поддерживается также язык FXМL, на котором можно (хотя и не обязательно) размечать пользовательский интерфейс. Начиная с обновления 4 версииJDK 7,JavaFX входит в комплектJava. А последняя версияjavaFX 8 входит в комплект JDK 8. (Номер 8 для версии выбран для того , чтобы соответствовать версииJDК, поэтому номера версии 3-7 были пропущены.) Та ким образом, на мо­ мент написания данной книги самой последней была версия JavaFX 8, которая здесь и рассматривается. Более того , под обозначением Ja vaFX здесь и далее под­ разумевается версия JаvаFХ 8. На заметку! В этой и двух последующих гл авах предпола гается, что вы владеете основами обработки событий, представлен ными в гл аве 24, а также основами Swi пg, описанными в части 111 да н­ ной книги. Основные поняти я Java FX В общем , кapкac javaFX обладает всеми полезными средствами Swi ng, включая легковес ность ко мпонентов и поддержку архитектуры МVС . Бальшая часть того, что вам уже известно о построении ГПИ средствами Swing, пригодится и вJavaFX. Те м не менее уJavaFX и Swing имеются существенные отличия. Первые отличия, с точки зрения программирования , состоят в организации са­ мого каркаса и взаимосвязи гл авных его компонентов. Проще говоря ,JаvаFХ пред­ лагает более рациональный, простой в употреблении и ус овершенствованный подход к построению ГПИ, а также значител ьно упрощает воспроизведение объ­ ектов благодаря автоматической перерисовке. Решать эту задачу вручную больше не нужно. Но это совсем не означает, что библиотека Swing разработана неудачно. Просто искусство программирования заметно продвинулось вперед, и плоды это­ го прогресса бьuш пожаты в JavaFX. В целом javaFX способствует выбору более динамичного визуально подхода к построению ГПИ. Пакеты JavaFX КомпонентыJavaFX содержатся в отдел ьных пакетах, имена которых начинают­ ся с префикса javafx. На момент написания данной книги насчитывалось более 30 пакетов, составляющих прикладной программный интерфейс JavaFX API. К их числу относятся следующие пакеты: java fx . application, java fx . stage , javafx . s cene и j avafx . scene . layout. В примерах этой гл авы употребляются лишь неко­ торые пакеты, поэтому вам придется ознакомиться с фун кциональными возможно­ стями остальных пакетовjаvаFХ самостоятельно , а они довольно обширны.
Гл ава 34. Введение в Java FX 1213 Классы подмостков и сцены Центральным понятием, внедренным вJavaFX, являются подмостки. Как и в на­ стоящей театральной постановке, подмостки содержат щсну. Проще говоря, под­ мостки определяют пространство, а сцена - то , что находится в этом пространстве. Иными словами , подмостки служат контейнером для сцен , а сцена - контейнером для элементов, которые ее составляют. Та ким образом, все JаvаFХ-приложения со­ стоят, по крайней мере, из одних подмостков и одной сцены. Эти элементы постро­ ения ГП И инкапсулированы в классах Stage и Scene, входя щих в cocтaвJavaFX API. Чтобы создать JаvаFХ-приложение, нужно ввести хотя бы один объект типа Scene в контейнер типа Stage. Рассмотрим оба эти класса более подробно. Класс Stage служит контейнером верхнего уровня . Все JаvаFХ-приложения ав­ томатически получают доступ к одному контейнеру ти па Stage, называемому г.лав­ Н'ЬIМU подмостками. Гл авные подмостки предоставляются исполняющей системой при запуске JаvаFХ-п риложения на выполнение. Для многих приложений требу­ ются лишь одн и гл авные подмостки , хотя можно создать и другие подмостки. Как упоминалось выше, класс Scene служит контейнером для всех эл ементов, составляющих сцену. Это могут быть элементы уп равления , в том числе экранные кнопки и флажки, а также текст и графика. Чтобы создать сцену, следует ввести все эти элементы в экземпляр класса Scene. Узлы и графы сцены Отдельные элементы сцены называются узлами. Например, узлом считается элемент управления экранной кнопкой. Но сами узлы могут состоять из групп дру­ гих узлов. Более того , у каждого узла может быть потомок, ил и порожденный узел, и тогда он называется родителъским узлом, или узлом ветвления. А узлы без потомков являются концевыми и называются лисmЫ1Мu. Совокупность всех узлов в сцене на­ зывается графом, образующим дерево. В графе сцены имеется специальный тип узла, называемый корневым. Это са­ мый верхн ий и единственный узел графа, не имеющий родителя. Следовательно, все узлы, кроме корневого , являются родительскими, причем все они прямо или косвенно происходят от корневого узла. Базовым для всех узлов служит класс Node . Имеется ряд других классов, кото­ рые прямо или косвенно происходят от класса Node. К их числу относятся классы Parent , Group, Region и Control . Ко м поновки В JavaFX предоставляется ряд панелей компоновки для управления процессом размещения элементов в сцене. Например, класс FlowPane предоставляет поточ­ ную компоновку, а класс GridPane - сеточную компоновку в виде рядов и столбцов таблицы. Имеются и другие разновидности компоновки , в том числе компоновка типа Borde rPane , аналогичная граничной компоновке типа BorderLayout из би­ блиотеки АWГ . Панели компоновки входят в состав пакета j ava fx . scene . layout.
Часть IV. Введе н ие в программиро ван ие ГПИ средств ами Java FX Кл а сс приложе ния и м етоды его жизненного цикл а JаvаFХ-приложение должно быть подклассом, производн ым от класса Application, входя щего в состав пакета j ava fx . application. Следовател ьно, кл асс приложения должен расширять класс Аррliсаtiоn. Вклассе Арр liсаtiоn определяются три метода жизненного цикла, которые могут быть переопреде­ лены в кл ассе приложения. Эти методы называются init (), start (), stop () и приведены ниже в том порядке, в каком они вызываются . void init () aЬs tract void start (Stage rл&ВНJlе поднос�хж) void stop () - Метод init () вызывается в тот момент, когда приложение начинает выпол­ няться. Он служит для выполнения различных инициализаций. Но, как поясня­ ется далее , с его помощью нельзя создать подмостки или построить сце ну. Если же инициализация не требуется , то и переопределять метод ini t () не нужно, по­ скольку по умолчанию предоставляется его пустой вариант. Метод start () вызывается после метода init (). Именно с него и начинается приложение, поскольку он позваляет построить и установить сцену. Как видите, в ка­ честве параметра гла вные_ подмо стки этому методу передается ссылка на объект типа Stage. Это гл авные подмостки, предоставляемые исполняющей системой. (Имеется возможность создать и другие подмостки, но для простых приложений этого не требуется .) Следует заметить, что метод start () является абстрактным, а следовательно, он должен быть переопределен в приложении. Когда приложение завершается , вызывается метод stop (). Именно в нем должны быть произведены все операции очистки или закрытия. Если же такие операции не требуются, то по умолчанию предоставляется пустой вариант данно­ го метода. Запуск JаvаFХ-приложения Для того чтобы запустить автономноеJаvаFХ-приложение на выполнение, сле­ дует вызвать метод launch (), определяемый в классе Application. У этого мето­ да имеются две общие формы . Ниже приведе на его общая форма, применяемая здесь и далее в этой гл аве. puЫ ic static void launch (String . . . apryнeн nr) Здесь параметр аргуме нты обозначает пустой, возможно, список символ ьных стро к, обозначающих, как правило, аргументы командной строки. Вызов метода launch () приводит к построению приложения и последующему вызову методов init () и start (). Возврат из метода launch () не происходит до тех пор, пока приложение не завершится. В рассматриваемой здесь форме данного метода на­ чинает выполняться подкласс, производный от класса Application, а из него вы­ зывается метод launch ( ) . Во второй форме данного метода можно указать для за­ пуска другой, а не охватывающий его класс. Прежде чем продолжить дальше, следует особо подчеркнуть, что в JаvаFХ­ приложения, упакованные с помощью утилиты javaf'xpackager (или ее эквива-
Глава 34. Введен ие в JavaFX 1215 лента в ИСР) , совсем не обязательно включать вызов метода launch ().Тем не ме­ нее его включение упрощает процесс тести рования и отладки приложения и по­ зволяет эксплуатировать программу, не прибегая к созданию архивногоJАR-ф айла. Именно поэтому метод launch ( ) включен в примеры всех JаvаFХ-приложений, представленные в части N данной книги. Скелет Jаvа FХ-приложения Все JаvаFХ-приложения создаются по одному и тому же образцу - типично­ му скелету. Поэтому, прежде чем перейти к рассмотрению каких-нибудь других средств JavaFX, стоит продемонстрировать скелет JаvаFХ-приложения. Помимо общей формы такого приложения , скелет демонстрирует порядок его запуска и вызова методов его жизненного цикла. Когда вызывается каждый метод жизнен­ ного цикла, на консоль выводится извещающее об этом сообщение. Ниже приве­ ден весь скелетJаvаFХ-приложения. 1 1 Скелет Jаvа FХ -приложе ния import java fx . appl icatioп .*; import javafx . sceпe .*; import javafx . stage .*; import java fx .sceпe . layout .*; puЫ ic class Java FXSkel exteпds Applicatioп puЫic static void ma iп (Striпg [) args ) ( System. out . priпtlп ("З aпycк Jаvа FХ - приложе ния .") ; 11 запустить JаvаFХ- приложение , вызвав ме тод launch () lauпch (args) ; 11 переопределить метод init () puЫic vo id iпit () ( System. o ut . priпtlп ("B теле ме тода iпit () ."); 11 переопределить ме тод start () puЫic void start ( Stage myStage ) System. o ut . priпtln ("B теле метода start () .") ; 11 присвоить заголовок подмосткам myStage .setTitle ("JavaFX Skeleton.") ; 11 Скелет Jаvа FХ-приложения 1 1 Создать корневой узел . В данном случае используе тся 11 панель поточной компоновки , хотя возможны и другие 11 варианты компоновки FlowPaпe rootNode = пеw FlowPaпe () ; 11 создать сцену Sсепе mySceпe = пеw Sceпe ( rootNode , 300 , 200) ;
1216 Часть IV. Введение в программировани е ГПИ средствами Java FX 11 установить сцену на подмостках my Stage .se tSceпe (myScene }; 11 показать подмостки и сцену на них myStage . show (); 11 переопредели ть ме тод stop () puЬl ic void stop () { System.out . println ("B теле ме тода stop(} ."); Несмотря на всю краткость такого скелета JаvаFХ-приложения, его можно скомпилировать и запустить на выполнение. В итоге на экране появляется окно, приведенное на рис. 34 . 1 . JavaFX Slce�tor\. Рис. 3, . 1 . Окно скел ета Jаvа FХ- nриложе ния Кроме того, при выполнении данного скелета JаvаFХ-приложения на консоль выводятся следующие сообщения: Запус к JаvаFХ-приложе ния . В теле метода init(). В теле ме тода start() . В теле метода stop(). При закрытии окна данного скелетаJаvаFХ-приложения на консоль выводится следующее сообщение: в теле ме тода stop() . Разумеется , в реальном JаvаFХ-приложении методы его жизненного цикла обычно не направляют никаких сообщений в стандартный поток вывода System. out. Но в данном скелете это делается ради то го , чтобы показать, когда именно вызывается каждый метод жизненного цикла JаvаFХ-приложения . Более того , ме­ тоды init () и s top () придется, как пояснялось ранее, переопределить только в том случае, если приложение должно выполнить специальные действия для за­ пуска и закрытия. В противном случае можно воспользоваться реализациями этих методов, предоставляемыми по умолчанию в классе Application. Рассмотрим данный скелетJаvаFХ-приложения более подробно. Сначала в нем им­ портируются четыре пакета. Первым из них является пакет javafx . application,
Гл ава 34. Введение в Java FX 1217 содержащий класс Application; вторым - пакет javafx . scene , содержащий класс Scene; третьим - пакет j avafx . stage, содержащий класс Stage; четвертым - пакет j ava fx . scene . layout, предоста11ЛJ1ющий ряд панелей компоновки. В данном случае используется панель поточной компоновки типа FlowPane . Далее создается класс приложения Java FXS kel, расширяющий класс Ap plication. Как пояснялось ранее, от класса Application происходят классы всех JаvаFХ-п риложений. Класс Java FXSkel содержит два метода. Первый из них называется ma in () и служит для запуска приложения через метод launch (). Обратите внимание на то , что параметр args передается не только методу ma in (), н о и методу launch (). И хотя это типичный подход, методу launch () можно передать и другие параметры или вообще не передавать их. И, как поясня­ лось ранее, метод launch ( ) требуется только для автономных приложений. Если же он не нуже н, то ненужн ым оказывается и метод ma in ( ) . Но по упоминавшимся ранее причинам оба метода, ma in () и launch (), включаются в примеры jаvаFХ­ приложений, представленные в части IV данной книги . Когда приложение начинает выполняться , первым из исполняющей системы JavaFX вызывается метод init ( ) . Ради большей наглядности примера этот метод просто направляет сообщение в стандартный поток вывода Sys tem. аи t, но, как пра­ вило, он служит для инициализации некоторых свойств приложения. Разумеется, если инициализация не требуется , то и переопределять метод ini t ( ) не нужно, по­ скольку по умолчанию предоставляется его пустая реализация. Следует особо под­ черкнуть, что с помощью метода init ( ) нельзя создать подмостки или сцену. Эти элементы ГПИ следует создавать и отображать с помощью метода start (). По завершении метода init () начинает выполняться метод start ().Именно в нем и создается первоначальная сцена и устанавливаются гл авные подмостки. Проанализируем этот метод построчно. Прежде всего следует заметить, что у ме­ тода start () имеется параметр типа Stage. Когда метод start ( ) вызывается , этот параметр получает ссылку на гл авные подмостки приложения , где и устанав­ ливается сцена для приложения. После вьшода на консоль сообщения о том, что метод start () начал выполнять­ ся, вызывается метод setTitle (), чтобы задать заголовок сцены, как показано ниже. myS tage .setTitle ("JavaFX Skeleton.") ; Эта операция характерна для автономных приложений, хотя и не является обя­ зател ьной. Ук азанный заголовок становится заглавием гл авного окна приложения. Далее создается корневой узел сцены. Это единственный узел графа сцены, у которого отсутствует родитель. В данном случае в качестве корневого узла слу­ жит панел ь поточной ко мпоновки типа FlowPane , как показано ниже. Хотя в кор­ невом узле могут быть использованы и другие классы. FlowPane rootNode ; new FlowPane () ; Как упоминалось ранее, класс FlowPane предоставляет диспетчер поточной ко мпоновки , где элементы располагаются построчно с автоматическим переходом на новую строку, есл и требуется. Следовательно, этот класс действует аналогично классу FlowLayout из библиотек АWГ и Swing. В данном случае применяется по­ точная компоновка по горизонтали , хотя можно указать и поточную ко мпоновку по вертикали. Имеется возможность указать и другие свойства компоновки , в том
1218 Часть IV. Введение в программирован и е ГПИ средств ами Java FX числе промежугки между элементами по горизонтали и по вертикали , а также выравнивание, хотя в данном скелетном приложении этого не требуется. Далее в этой гл аве будет пol(i i 3aнo, как устанавливаются свойства компоновки. В следующей строке кода корневой У3ел исполЬ3уется для построения сцены в виде объекта типа Scene: Scene myS cene = new Scene ( rootNode , 300 , 200) ; В классе Scene предоставляется несколько форм конструктора. В той форме конструктора, которая применяется 3Десь и далее в этой гл аве , создается сцена с указан ным корневым умо м и 3аДан ной шириной и высотой. Ниже приведена данная форма варианта конструктора класса Scene. Scene (Parent .в:opнeJ10.й_y.seir , douЫe •JIPJUla , douЫe .&IUCO!l'a) Как видите , параметр корн евой_узел относится к типу Parent. Это класс, про­ изводный от класса Node и инкапсулирующий ум ы, у которых могут быть потом­ ки. А параметры шир ина и высота относятся к типу douЫ e. Это дает возможность передавать дан ному конструктору дробные числовые значения ширины и высоты сцены. В данном скелете JаvаFХ-п риложения указан корневой узел rootNode и за­ дана ширина 300 и высота 200 сцены. В приведенной ниже строке кода устанавливается сцена rnyS cene на подмос'Г' ках myS tage . Для этого вызывается метод setScene ( ) , определяемый в классе Stage и устанавливающий сцену, указываемую в качестве его аргумента. my Stage .setScene (myScene ); В тех случаях, когда сцена больше не исполЬ3уется, обе предыдущие операции можно объединить в одну, как показано ниже. Благодаря своей компактности эта форма исполЬ3уется в большинстве представленных далее примеров. my Stage .setScene (new Scene ( rootNode , 300 , 200) ); И в последней , приведенной ниже строке кода из метода start () отобража­ ются подмостки и сцена. По существу, метод show ( ) отображает окно , созданное на подмостках и сцене. myStage .show (); Когда приложение закрывается , его окно удаляется с экрана, а из исполняющей системы JаvаFХ вызывается метод stop (). В данном случае метод stop ( ) просто выводит на консоль сообщение о том , что он начал выполняться. Но, как правило, этот метод ничего не выводит на экран . Более того, есл и в приложении не требуе'Г' ся выполнять никаких закрывающих действий, то и переопределять метод s top ( ) не нужно, поскольку по умолчанию предоставляется его пустая реализация. Компил яци я и выполнение J аvа FХ- при ложения К числу самых гл авных преимуществJavaFX относится возможность выполнять одну и ту же прикладную программу в различных исполняющих средах. В частно­ сти , ее можно выполнить как автономное настольное приложение, в окне браузе-
Гл ава 31.. Введение в JavaFX 1219 ра или же как приложение ти па Web Start. Но иногда прикладной программе мо­ гут потребоваться ра3Личные вспомогательные файлы, в том числе НТМL-файл ил и файл форматаJNLР Uava Network Launch Protocol - сетевой протокол запуска приложений нajava). В общем,JаvаFХ-приложение компилируется аналогично любой другой програм­ ме, написанной на Java. Но поскольку ему требуется дополнительная поддержха для ра3Личных исполняющих сред, то его проще всего скомпилировать в интегри­ рованной среде разработки (ИСР) , полностью поддерживающей программирова­ ние средствамиjаvаFХ, например, в NetВeans. Для этого достаточно следовать ин­ струкциям в применяемой ИСР. С другой стороны, если требуется скомпилировать и проверить JаvаFХ­ приложение , используя инструментальные средства в режиме командной строки, то и это сделать совсем не трудно. Для этого достаточно скомпилировать и выпол­ нить приложение обычным образом по командам javac и java соответственно. Следует, однако, иметь в виду, что компилятор, работающий в режиме командной строки, не создает ни архивныйJАR-файл приложения, ни файлы формата НТМL или JNLP, которые могут потребоваться, если приложение необходимо выпол­ нить не как автономное , а как-то иначе. Для создания этих типов файлов придется воспользоваться такой утилитой, как javafxpackager. Поток исполнения приложения Как упоминалось ранее, метод init ( ) нельая использовать для построения под­ мостков или сцены. Э-Ш элементы ГПИ нельая создать и в конструкторе класса прило­ жения, потому что подмостки или сцена должны строиться в nитоке испаянения nри.ло­ :ж:еиия. Но конструктор класса приложения и метод init ( ) вызываются в гл авном по­ токе исполнения, иначе называемом запускающим потокам 'UСl'UИнения. Следовател ьно, их нельзя использовать для построения подмостков или сцены. Вместо этого нужно воспользоваться методом start ( ) , чтобы построить первоначальный ГПИ, как про­ демонстрировано в рассмотренном выше скелете JаvаFХ-приложения, поскольку ме­ тод start ( ) вызывается в потоке исполнения приложения. Более того, любые изменения в ГПИ, отображаемом в данный момент, долж­ ны производиться из потока исполнения приложения. Правда, в JavaFX события посылаются прикладной программе в потоке исполнения приложения. Поэтому для взаимодействия с ГПИ могут быть использованы обработчики событий. Метод stop ( ) также вызывается в потоке исполнения приложения. Метка - простейший элемент управления в JavaFX Гл авным компонентом большинства ГПИ является элемент управления, по­ скольку он дает приложению возможность взаимодействовать с пользователем. Как и следовало ожидать, в JavaFX предоставляется богатый арсенал элементов
1220 Часть IV. Введение в п рограммирование ГПИ средства ми Java FX управления. Простейшим элементом управления является метка, поскольку она только отображает сообщение, например текстовое. Несмотря на всю свою про­ стоту, метка служит уд обным примером для демонстрации приемов, которыми нужно овладеть, чтобы приступить к построению графа сцены. В JavaFX метка представлена экземпляром класса Label, входящего в состав пакета java fx . scene . control. Класс Labe l наследует, среди прочих, от классов Labeled и Control. В классе Labeled определяется ряд средств, общих для всех отмечаемых меткой элементов, т. е . тех , что могут содержать текст, а в классе Control - средства, связанные со всеми эл ементами управления. В классе Labe l определяются три конструктора. Ниже приведен конструктор, применяемый здесь и далее в этой гл аве , где параметр строка обозначает символь­ ную строку, отображаемую на месте метки . LaЬel (Strinq crpoxa) Как только метка будет создана, она должна быть введена в содержимое сце­ ны, т. е. в граф сцены. Это же относится и к любому другому элементу управле­ ния . С этой целью сначала вызывается метод getChildren () для корневого узла в графе сцены. Этот метод возвращает список порожденных узлов в форме Ob servaЫeList<Node>. Класс Ob servaЬl eList входит в состав пакета java fx . collections и наследует от класса java . util . List, а следовательно, поддержи­ вает все средства, доступные для составления списка и определенные в каркасе коллекций Collections Framework. В возвращаемый список порожденных узлов можно ввести метку, вызвав метод add ( ) и передав ему ссылку на метку. Все сказанное выше демонстрируется на практике в следующем примере, где создается jаvаFХ-приложение, отображающее метку: 11 Продемонстрировать применение элемента управления ме ткой в Java FX import java fx . application .*; import javafx . scene .*; impo rt javafx . stage .*; import javafx .scene . layout .*; import javafx .scene . control .*; puЫ ic class Java FXLabelDemo extends Application puЫ ic static void ma in (Striпg [] args ) { 11 Start the JavaFX application Ьу cal ling launch () . launch (args) ; 11 переопределить ме тод start () puЫ ic void start ( Stage my Stage ) 11 пр исвоить заголовок подмосткам myStage .setTitle ( "Demonstrate а Java FX label ."); 11 Продемонстрировать ме тку в Java FX 11 использовать панель типа FlowPane в качестве корне вого узла FlowPane rootNode new FlowPane () ; 11 создать сцену Scene myScene = new Scene ( rootNode , 300, 200) ;
11 установить сцену на подмостках myStage .setScene (myScene ); 11 создать ме тку Глава 34. Введение в JavaFX Label myL abel = new Label ("Thi s is а Java FX label") ; 11 Это ме тка в JavaFX 1 1 ввести метку в граф сцены rootNode .getChildren () .add (myLabel ); 1 1 показать подмостки и сцену на них myStage . show (); 1221 На рис. 34 .2 показано окно данногоJаvаFХ-п риложе ния с отображаемой меткой. is is а J.,.,of)( la�I Рис. Зlt .2. Окно JаvаFХ-приложения с отобража емой меткой В данном JаvаFХ-приложении особое внимание обращает на себя следующая строка кода: rootNode .getChildren () .add (myLabel) ; В этой строке кода метка вводится в список порожденных узлов, родителем ко­ торых является корневой узел rootNode. И хотя эту строку кода можно, если по­ требуется , разделить на отдел ьные части , зачастую она записывается указанным выше способом. Прежде чем продолжить дальше, следует заметить, что в клacce Ob servaЫeLi st предоставляется метод addAl l () ,спомощью которого можно ввести в граф сце­ ны сразу два и больше порожденных узлов, как будет показано в приведенном далее примере. Чтобы уд алить элемент управления из графа сцены , достаточно вызвать метод remove () для объекта типа Ob servaЬleList. Та к, в приведенной ниже строке кода метка myLabe l удаляется со сцены. rootNode .getChildren () .remove (myLabel ); Применение кнопок и событий Несмотря на то что в предыдущем примере JаvаFХ-приложения демонстриро· валось применение простого элемента управления и построение графа сцены ,
1222 Часть IV. Введение в программирование ГП И средства м и Java FX в нем не бьvю показано, каким образом обрабатываются события. Как вам долж­ но быть уже известно, большинство элементов управления ГПИ ге нерируют со­ бытия , обрабатываемые в прикладных программах. Например, кнопки , флажки и списки - все эти элементы управления генерируют события, когда ими манипу­ лирует пользователь. Во многих отношениях обработка событий вJavaFX органи­ зуется таким же образом, как и в Swing или АWГ, но только она более рационали­ зирована. Следовательно, если вы приобрели практические навыки обработки со­ бытий в ГПИ на основе библиотек Swing и АWГ, то вам не составит особого труда овладеть системой обработки событий, предоставляемой вjavaFX. К числу наиболее употребительных элементов управления относится экранная кнопка, поэтому события от кнопок обрабатываются чаще всего. И это обстоя­ тельство делает кнопку отличным примером для демонстрации основ обработки событий вJavaFX. Именно поэтому экранная кнопка и обработка событий рассма­ триваются вместе в этом разделе. Основы обработки событи й в Java FX Базовым для событий вjavaFX служит класс Event, входящий в состав пакета j ava fx . event. Класс Event наследует от класса j ava . util . EventObj ect, а следо­ вател ьно, события в JavaFX обладают общими функциональными возможностями для всех событий вjava вообще. BjavaFX определено несколько подклассов, про­ изводных от класса Event. Здесь и далее рассматривается подкласс ActionEvent, предназначен ный для обработки событий, генерируемых экранной кнопкой. В общем, для обработки собьrгий в JavaFX применяется модель делегирования событий. Чтобы обработать событие, нужно сначала зарегистрировать его обра­ ботчик, действующий как ttриемник данного события. Когда наступает событие, вызывается его приемник, который должен отреагировать на событие и выпол­ нить возврат. В этом отношении управление событиями в JavaFX осуществляется таким же образом, как, например, в Swing. Для обработки событий следует реализовать интерфейс EventHandler, кото­ рый также входит в состав пакета javafx . event. Это обобщенный интерфейс, объя вляемый следующим образом: interface Eventвand.ler<T eztende Event> где параметр Т обозначает ти п события, обрабатываемого соответствующим об­ работчиком. В данном интерфейсе определяется единственный метод handle (), принимающий в качестве параметра объект события. Ниже приведена общая форма этого метода. void handle (Т о65еж!I'_co&r!I'-) Здесь параметр объект_ события обозначает наступившее событие. Как пра­ вило, обработчики событий реализуются в анонимных внутренних классах или лямбда-выражениях, но для этой цели подходят и автономные классы, если они более пригодны для приложения. (Например, в том случае, если один обработчик должен обрабатывать события из нескольких источников.)
Гл ава 34. Введение в Java FX 1223 Иногда полезно знать источник события , хотя в представленных далее при­ мерах этого не требуется. Это особенно важно, если один обработчик должен обрабатывать события из разных источников. Чтобы получить источник собы­ тия , достаточно вызвать метод ge tSource (), наследуемый из класса j ava . util . EventObj ect. Ниже приведена общая форма этого метода. OЬject qetSource () Остальные методы из класса Event позволяют получить тип события , вы­ ясн ить, было ли событие употреблено, употребить или сгенерировать событие, а также получить адресат события . Как только событие будет употреблено, его передача родительскому обработчику прекращается. И последнее замечание: в JavaFX обработка событий выполняется по цепочке диспет'ЧеjJиз ации событий. Когда событие генерируется, оно передается сначала корневому узлу, а затем вниз по цепочке адресату события. После обработки со­ бытия в узле его адресата оно передается обратно вверх по цепочке, предоставляя возможность родительским узлам обработать его по мере надобности . Та кой ме­ ханизм распространения событий называется всплыванием событий. Он позволяет любому узлу цепочки употребить событие, чтобы оно больше не обрабатывалось. Н а заметку! В Jаvа FХ- приложении можно та кже реал изовать фильтр событий для управления ими, хотя эдесь он не рассматри вается. Та кой фильтр вводится в узел с помощью метода addEventFilter (), определенного в классе Node. Фильтр может употребить событие, предотв рати в тем самым его дал ьней шую обработку. Элемент управления э кранной кнопко й В JavaFX эл емент управления экранной кнопкой предоставляется в классе Button, входящем в состав пакета java fx . scene . control. Класс Button наследу­ ет довольно обширный перечень базовых классов, включая ButtonBa se, Labe led, Re gion, Cont rol, Parent и Node . Как следует из документации на класс Button, большая часть его функциональных возможностей наследуется из базовых клас­ сов. Кроме того , в этом классе поддерживается большое разнообразие массивов. Но здесь рассматривается его форма, испол ьзуемая по умолчан ию. Экранные кнопки могут содержать текстовую надпись, кнопку или и то и другое. В примерах из этой гл авы применяются кнопки с текстовой надписью, а пример кнопки с гра­ фикой приводится в следующей гл аве. В классе Bu t ton определяются три конструктора. Ниже приведен конструктор, применяемый здесь и далее в этой гл аве, где параметр строка обозначает тексто­ вую надпись на кнопке. Вutton (Strin9 C!l'p()Xa) После щелчка на экранной кнопке генерируется событие типа ActionEvent. Класс Act ionEvent входит в состав пакета j ava fx . event. Вызвав метод se tOn­ Ac tion () , можно зарегистрировать приемник данного события . Ниже приведена общая форма этого метода. final void setOnAction ( EventHandler<ActionEvent> обрабо�v•ж)
1224 Ч асть IV. Введение в программирование ГПИ средствами Java FX Здесь параметр обра ботчик обозначает регистрируемый обработчик событий указанного типа. Как упоминалось ранее , для реализации обработчика событий не­ редко применяется анонимный внутренний класс или лямбда-выражение. Метод se tOnAct ion () устанавливает свойство onAction, сохраняющее ссылку на обра­ ботчик событий. Как и при обработке всех остальных событий в Java, такой обра­ ботчик событий должен как можно быстрее реагировать на событие и выполнять возврат. Если же обработч ик событий делает это слишком медленно, то тем самым он заметно тормозит работу приложения. Поэтому для длительных операций сле­ дует предусмотреть отдел ьный поток исполнения. Демонстрация обработки событий на п рим ере э кранных кнопок В приведенном ниже примере JаvаFХ-приложения обработка событий демон­ стрируется на примере экранных кнопок. В этом приложении используются две такие кнопки и одна метка. Всякий раз , когда нажимается экранная кнопка, на ме­ сте метки выводится сообщение, извещающее , какая именно кнопка была нажата. 11 Продемонстрировать приме нение экранных кнопок 11 и обработку событий в Java FX import java fx . application .*; import java fx . scene .*; import javafx . stage .*; import java fx .scene . layout .*; import javafx .scene . control .*; import javafx . event .*; import java fx . geometry .*; puЫ ic class JavaFXEventDemo extends Application 1 Label response ; puЫ ic static void main (String (] args ) 1 11 запустить JаvаFХ- приложе ние , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод start () puЫic void start ( Stage myS tage ) 11 присвоить заголовок подмосткам my Stage .setTitle ("D emonstrate JavaFX Buttons and Events .") ; 11 Продемонстрировать кнопки и события в Java FX 11 Исполь зовать панель поточной компоно вки FlovPane 11 в качестве корневого узла . Установить промежутки 11 между элементами управления по горизонтали и 11 по вертикали равными 10 FlowPane rootNode = new Fl owPane (lO, 10 ) ; 11 выровня ть элементы управления по центру сцены rootNode .se tAl i gnment (Pos . CENTER ) ;
Глава 3'. Введение в JavaFX // создать сцену Scene my Scene = new Scene ( rootNode , 300, 100) ; // установить сцену на подмостках myStage .setScene (myScene ); 11 создать ме тку response = new Label ( "Push а Button") ; // Нажа ть кнопку // создать две экранные кнопки Button ЬtnAlpha = new Button ( "Alpha" ) ; Button ЬtпВеtа = new Button ("Beta") ; 11 обработать события действия от кнопки Alpha ЬtnAl pha .setOnAction (new EventHandler<ActionEvent> () puЫic vo id handle (ActionEvent ае ) { response .setText ( "Alpha was pressed.") ; // Нажа та кнопка Alpha } }); // обработать события действия от кнопки Веtа ЬtnBeta . setOnAct ion (new EventHandler<ActionEvent> () puЫic void handle (ActionEvent ае ) { response .setText ("Beta was pressed. ") ; ) }); 11 Нажа та кнопка Веtа // ввести ме тку и кнопки в граф сцены rootNode .getChildren () .addAll ( ЬtnAlpha , ЬtnBeta, response ); 11 показать подмо стки и сцену на них rnyS tage .show() ; 1225 На рис. 34 .3 приведен примерный результат выполнения JаvаFХ-приложения , демонстрирующего обработку событий от экранных кнопок. 1 Demonstrat" J1W11FX B ut t ons а- Alpha 1 (ВеtаJ Bm was press. .d . Рис. Зlt .З. Резул ьтат нажати я одной из экранных кнопок в окне приложения Java FXE vent Demo Рассмотрим данное JаvаFХ-приложение по некоторым из его гл авн ых частей. Прежде всего обратите внимание на приведенные ниже строки кода, в которых создаются экранные кнопки . But ton ЬtnAlpha = new Button ( "Alpha" ) ; But ton ЬtnBeta = new Button ("Beta" );
1226 Ч асть IV. Введени е в программирование ГПИ средствами Java FX В этих строках кода создаются две экранные кнопки с текстовыми надписями. Первая из них содержит надпись Alpha, вторая - надпись Beta. Далее для каждой экранной кнопки задается обработчик событий действия. Ниже показано , как это делается для кнопки Alpha. 11 обработать события действия от кнопки Alpha btnAlpha .se tOnAction (new EventHandler<Ac tionEvent>() puЫ ic void handle (ActionEvent ае ) { re sponse .setText ( "Alpha wa s pressed.") ; 11 Нажата кнопка Alpha } }); Как пояснялось ранее, экранные кнопки реагируют на события типа Act ionEvent. Чтобы зарегистрировать обработчик этих событий от конкретной кнопки , вызывается метод setOnAction ( ) , в котором используется анонимный внутренний класс для реализации интерфейса EventHandler. (Напомним, что в ин­ терфейсе EventHandler определяется единственный метод handle (). ) В методе handle ( ) задается текст, выводимый на месте метки respon se, чтобы отразить факт нажатия экранной кнопки Alpha. С этой целью для данной метки вызывается метод setText (). Аналогичным образом обрабатываются события от кнопки Beta. Обратите внимание на то , что метка re sponse объявляется как поле в клас­ се приложения JavaFXEventDerno , а не как локальная переменная. Это делается для того , чтобы она была доступна для обработчиков событий от экранных кно­ пок, реализуемых в виде анонимных внутренних классов. После установки обработчиков событий метка response и экранные кнопки ЬtnAlpha и ЬtnBeta вводятся в граф сцены. С этой целью вызывается метод addAl l (): rootNode .getChildren () .addAll ( btnAlpha , btnBeta, response ); Метод addAl 1 ( ) вводит список узлов в вызывающий корневой узел. Безусловно, эти узлы можно было бы ввести и по отдел ьности , вызвав метод add ( ) , но в дан­ ном случае удо бнее вос пользоваться методом addA 11 ( ) . В дан ном примере интересно также отметить две особен ности отображения элементов управления в окне приложения. Во-первых, когда создается корневой узел, применяется следующий оператор присваивания: FlowPane rootNode = new FlowPane (lO, 10) ; В этом операторе конструктору класса Fl owPane передаются два параметра, обозначающих промежутки между элементами управления , оставляемые в сце­ не по горизонтали и по вертикали. Если эти промежутки не указаны, элементы управления (в данном случае две экранные кнопки) располагаются вплотную друг к другу. Следовательно, пользоваться ими будет неудобно, а сам пользовательский интерфейс приобретет неприглядный вид. Во избежание этого и указывается про­ межуток между элементами управления. И во-вторых, элементы управления выравниваются по центру на панели поточ­ ной ко мпоновки типа Fl owPane , как показано ниже. rootNode .setAl i gnrnent (Pos . CENTER) ;
Гл ава 34 . Введение в JavaFX 1227 Чтобы выровнять элементы управления по центру, вызывается метод set Alignment () для панели типа Fl owPane , которая в данном случае служит ко рне­ вым узлом. Значение константы Po s . CENTER обозначает центровку как по верти­ кали, так и по горизонтали, хотя можно указать и другие стили выравнивания. Класс Pos представляет собой перечисление, в котором определяются константы выравнивания. Этот класс входит в состав пакета java fx . geometry. Прежде чем продолжить дальше, следует заметить, что в рассматриваемом здесь примере для обработки событий используются анонимные внутренние классы. Но поскольку в интерфейсе EventHandler определяется единственный абстрактный метод handle ( ) , то вместо него методу setOnAction ( ) можно пере­ дать лямбда-выражение. В данном случае параметр типа передает методу setOn Action () целевой контекст лямбда-выражения. В качестве примера ниже приведен обработчик событий от кнопки Atpha, переписанный в виде лямбда-выражения. btnAlpha .setOnAction ( ( ае ) -> response .setText ( "Alpha was pressed .") ); Как видите , лямбда-выражение выглядит более компактно, чем анонимный внутренний класс. Но в последующих примерах все же применяются анонимные внутренние классы, поскольку лямбда-выражения внедрены лишь недавно в Java, тогда как анонимные внутренние классы уже давно нашли широкое применение и хорошо известны практически всем программирующим наJava. Это позволяет также компилировать примеры программ, используя версиюJDK 7, где лямбда-вы­ ражения не поддерживаются. Хотя вы можете самостоятельно поэксперименти­ ровать с лямбда-выражениями , чтобы приобрести практические навыки их при­ менения в своем коде. Рисование непосредственно на холсте Как упоминалось ранее , задачи воспроизведения решаются вJavaFX автомати­ чески , а не вручную. В этом состоит одно из гл авных усовершенствований JavaFX по сравнению с Swing. Как вам должно быть уже известно, в библиотеке Swing или АWГ для перерисовки окна приходится вызывать метод repaint (). Более то го, в прwюжении приходится хранить содержимое окна, перерисовывая его по мере надобности. Это неудобство устраняется в J avaFX благодаря слежению за тем, что требуется отображать в сцене, и повторному воспроизведению сцены по мере на­ добности в так называемом режиме удержания. При таком подходе отпадает потреб­ ность вызывать метод repaint (), поскольку повторное вос произведение выпол­ няется автоматически . Та кой подход оказывается особенно полезным при воспроизведении таких гра­ фических объекто в, как линии, окружности и прямоугол ьники. Методы , применяе­ мые вJаvаFХ для воспроизведения графики, находятся в классе GraphicsContext , входящем в состав пакета j ava . scene . canvas. Эти методы могут быть использо­ ваны для рисования непосредственно на поверхности холста, инкапсулирован­ ного в классе Canvas, входя щем в состав того же самого пакета. Когда на холсте рисуется какой-нибудь графический объект, например линия , он автоматически
1228 Часть IV. Введение в програ ммировани е ГПИ средствами JavaFX воспроизводится средствами JavaFX всякий раз, когда требуется воспроизвести его повторно. Прежде чем рисовать на холсте , следует выполнить два действия. Во-первых, создать холст в виде объекта типа Canvas. Во-вторых, получить графический контекст, ссылающийся на этот холст, в виде объекта типа GraphicsCont ext. Полученным в итоге графическим контекстом типа GraphicsContext можно за­ тем воспользоваться для рисования выводимых графических данных на холсте. Класс Canvas является производным от класса Node , а следовательно, им мож­ но пользоваться как узлом в графе сцены. В классе Canvas определяются два кон· структора. Одним из них является конструктор по умолчанию, а другой приведен ниже , где параметры ширина и высота обозначают соответствующие размеры хол­ ста. Canvaв (douЬle /IDф1Ula , douЬle aucO'l'a) Чтобы получить графический контекст типа GraphicsContext, ссьтающийся на холст, следует вызвать метод getGraphicsContext2D (). Ниже приведена об­ щая форма этого метода. Он возвращает графический контекст для холста. Graphicscontext getGraphi csContext2D () В классе GraphicsContext определяется немало методов для рисования форм, воспроизведения текста и изображений, а также для поддержки визуальных эф­ фектов и графических преобразований. Если вы связываете свое будущее с про­ граммированием сложной графики , вам определенно следует внимательно из­ учить возможности этого класса. В целях демонстрации далее будут использованы лишь некоторые методы из этого класса, хотя и они дают ясное представления об его огромном потенциале. Эти методы вкратце описываются далее . Вызвав метод strokeLine (), можно нарисовать линию. Ниже приведена об­ щая форма этого метода. void strokeLine (douЬle наvало Х, douЬle наvало У, douЬle .к:о.нец_Х, douЬle .к:о.нец_У) Этот метод рисует линию от точки с указанными координатами на чало_ Х, на чало_ У к точке с указанными координатами конец_ Х, конец_ У, используя теку­ щую обводку, которая может быть выполнена сплошным цветом или оформлена в более сложном стиле. Чтобы нарисовать прямоугольник, необходимо вызвать метод strokeRect () или fillRect ().Ниже приведены общие формы этих методов. void strokeRect (douЬle верх Х, douЬle верх У, douЬle ll l ;ч>Da , douЬle .вuсо-;а ) void fillRect (douЬle аерх Х, douЬle верх У, douЬle llf:ч>D a, douЬle auco-;a) Параметры координат верх_Х, верх_ У обозначают левый верхний угол рису­ емого прямоугольника, параметры ширина и высота - его ширину и высоту со­ ответственно. Метод strokeRect () рисует контур прямоугольника, испол ьзуя те­ кущую обводку, а метод fillRect () заполняет площадь прямоугольника текущей заливкой : сплошным цветом или более сложным рисун ком .
Гл ава 34. Введение в Java FX 1229 Чтобы нарисовать эллипс, следует вызвать метод stro keOva l () или fill Ova l (). Ниже приведены общие формы этих методов. void strokeOval (douЬle аерх: Х, douЬle верх У, douЬle �а , douЬle ll ll' co';a) void fillOval (douЬle верх Х, douЬle верх У, douЬle �а, douЬle ll ll' CO-;a) Параметры координат верх_Х, верх_ У обозначают левый верхний угол ри­ суемого прямоугольника, а параметры шир ина и высота - его ширину и высоту соответственно. Метод strokeOva l () рисует контур эллипса, используя теку­ щую обводку, а метод fillOval () заполняет площадь эллипса текущей заливкой: сплошным цветом или более сложным рисунком. Чтобы нарисовать окружность, методу stro keOva l () следует передать одинаковые значения параметров шир ина и высота . Для воспроизведения текста на холсте служат методы strokeText () и fill Text ().Здесь и далее в этой гл аве используется метод fillText ().Ниже приведе­ на его общая форма. Этот метод воспроизводит указанную строку, начиная с точки с указанными координатами верх_Х, верх_ У и заполняя текст текущей заливкой. void fillText (Strinq crirpoxa , douЬle верх_Х, douЬle .верх_У) Вызвав метод setFont (), можно задать тип и размер шрифта, которым должен вос производиться текст, а вызвав метод ge t Fon t ( ) - получить шрифт, используе­ мый на холсте. По умолчанию используется системный шрифт. Построив объект типа Font, можно также создать новый шрифт. Класс Font входит в состав пакета j avafx . scene . text. Например, используя приведенный ниже конструктор дан­ ного класса, можно создать выбираемый по умолчанию шрифт указанного разме­ ра, где параметр ра змер_шрифта обозначает требуемый размер нового шрифта. Font (douЬle раs.мер_шр• ф!l'а) Используя приведенные ниже методы из класса Canvas, можно указать заливку и обводку соответственно. void setFill (Paint ноаая sаля.вха) void setStroke (Paint но.вая_обводха) Как видите , параметр обоих этих методов относится к типу Paint. Класс Paint является абстрактным и входит в состав пакета j avafx . scene . paint. В его под­ классах определяются конкретные разновидности заливки и обводки. Здесь и да­ лее в этой гл аве применяется подкласс Color, который просто описывает сплош­ ной цвет. В классе Color определяется ряд статических полей для обозначения широкого спектра цветов, например Color . BLUE, Color . RE D, Color . GREEN и т. д . В приведенном ниже примереJаvаFХ-п риложения демонстрируется рисование на холсте описанными выше методами. Сначала в данном приложении воспроиз­ водятся три графических объекта на холсте . Затем цвет этих объектов изменяется всякий раз, когда нажимается кнопка Change Color (Изменить цвет) . Запустив это приложение на выполнение, вы обнаружите , что формы , цвет которых не меня­ ется, не оказывают никакого вл ияния на изменение цвета остальных графических объектов. Более того , если попытаться скрыть окно данного приложения , а затем раскрыть его , содержимое холста будет автоматически перерисовано без участия
1230 Часть IV. Введение в программирование ГПИ средствами Java FX самого приложения. На рис. 34 .4 приведен пример рисования графики на холсте в окне JаvаFХ-приложе ния из данного примера. Th is is drawn оп the canva s. Chongt Color Рис. Э'·'· Пример рисования графики на холсте в окне п р иложения DirectDrawDerno 11 Продемонс трировать рисование на холсте import javafx . appl ication .*; import javafx . scene .*; import javafx . stage .*; import javafx .scene . layout .*; import javafx .scene . control .*; import javafx . event .*; import javafx . geom etry .*; import java fx .scene . shape .*; import javafx .scene . canvas .*; import javafx .scene . paint .*; import javafx .scene .text.*; puЫ ic class DirectDrawDemo extends Application { GraphicsContext gc; Color [] colors = { Color .RED, Color . BLUE , Color . GREEN , Color . BLACK }; int coloridx = О; puЫ ic static vo id main ( String [] args ) { 11 запустить JаvаFХ-nриложение , вызвав ме тод launch () launch (args) ;
11 пере определить ме тод start () puЫic void start ( Stage myStage ) Гл ава 34. Введение в Java FX 11 присвоить заголовок подмосткам myStage .setTitle ("D raw Directly to а Canvas ."); 11 Рисование пр ямо на холсте 11 Исполь зовать панель поточной компоновки FlowPane 11 в качестве корневого узла FlowPane rootNode = new FlowPane (); 11 расположить узлы по центру сцены rootNode .setAlignment (Pos . CENTER) ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 450 , 450) ; 11 установит ь сцену на подмостках my Stage .se tScene (myScene ); 11 создать холст Canva s myCanva s = new Canvas (400, 400) ; 11 получи ть графический контекст для холста gc = myCanvas . getGraphi csContext2D() ; 11 создать экранную кнопку Button ЬtnChangeColor = new But ton ("Change Color") ; 11 обработать события действия от кнопки Chanqe Color ЬtnChangeCol or . setOnAction (new EventHandl er<ActionEvent>() puЫic void handle (ActionEvent ае ) { } }); 11 задать цвет обводки и заливки gc . setSt roke ( colors [coloridx] ); gc . setFill (colors [coloridx] ); 11 Перерисовать линию , текст и заполненный пр ямоугольник 11 новым цветом. При этом цвет осталь ных узлов графа сцены 11 остане тся без изменения gc .strokeLine (O, О, 200, 200) ; gc . fillText ("This is drawn on the canvas .", 60, 50) ; 11 Это рисуе тся на холсте gc . fillRect (lOO, 320, 300, 40) ; 11 изменить цвет coloridx++; if ( coloridx == colors . length) coloridx= О; 11 нарисовать на холсте графиче ские объе кты 11 первоначально выводимые на экран gc .strokeLine (O, О, 200, 200) ; gc .strokeOval (lOO, 100, 200, 200) ; gc .strokeRect (O, 200, 50, 200) ; gc . fillOval (O, О, 20, 20) ; gc . fillRect (lOO, 320 , 300, 40) ; 11 задать шрифт размером 20 и воспроизвести текст 1231
1232 Часть IV. Введение в программирование ГПИ средствами Java FX gc . setFont (new Font(20) ); gc . fillText ("This is drawn on the canvas .", 60 , 50) ; 11 ввести холст и кн опку в граф сцены rootNode .getChildren () .addAll (myCanvas , btnChangeColor ) ; 11 показать подмостки и сцену на них my Stage .show() ; Следует еще раз подчеркнуть, что в классе GraphicsContext поддерживается намного больше операций, чем те, что были продемонстрированы в приведенном выше примере JаvаFХ-приложения. В частности , к графическим объектам мож­ но применять рааличные эффекты и выполнять над ними преобразования, в том числе вращение, масштабирование и искажение. Несмотря на обилие средств, доступных в этом классе, овладеть ими не так уж и трудно. Следует также иметь в виду, что холст прозрачен. Та к, если расположить два холста один за другим, то видно будет содержимое обоих холстов. Иногда такое свойство прозрачности хол­ стов может быть полезным. На эаметкуl В состав пакета j avafx. scene . shape входит нескол ько классов, которые можно та кже применять дnя рисования различных графических форм, в том числе окружностей, дуг и линий. Эти формы предста влены узлами, а следовател ьно, они могут быть введены непо­ средственно в граф сцены. Рекомендуется изучить их самостоятел ьно.
35 Элементы управления JavaFX В предыдущей гл аве были рассмотрены основные понятия , имеющие отношение к построению ГПИ средствамиjаvаFХ. По ходу их описания были представлены два элемента управления: метка и кнопка. А в этой гл аве будет продолжено рассмотре­ ние элементов управления JavaFX. В начале гл авы поясняется, каким образом метка и кнопка снабжаются изображениями. Затем дается краткий обзор некоторых дру­ гих элементов управленияJavaFX, в том числе флажков, списков и деревьев. Следует, однако, иметь в виду, что JavaFX - насыщенный функциональными возможностями и довольно мощный каркас. Поэтому основное назначение этой гл авы - предста­ вить наиболее употребительные элементы управленияjаvаFХ и описать некоторые общие приемы их применения. Овладев основами , вы сможете без особого труда самостоятельно изучить остальные элементы управленияjаvаFХ. Ниже перечислены классы элементов управленияjаvаFХ, обсуждаемые в этой гл аве. Классы этих и других элементов управления JаvаFХ входят в состав пакета java fx .scene . cont ro l. Button LiatView Textl'ield CheckВox RadioButton ToqgleButton LaЬel ScrollPane тr-vi- В этой гл аве обсуждаются также классы Image и ImageView, поддерживающие изображения в элементах управления; класс Too ltip, предназначенный для ввода всплывающих подсказок в элементы управления; а также различные визуал ьные эффекты и преобразования. Классы Image и ImageView Изображениями можно снабдить целый ряд элементов управления JavaFX. Та к, помимо текстовой надписи, метку или кнопку можно снабдить изображени­ ем. Более того , изображения можно встраивать непосредственно в сцену как авто­ номные графические объекты. Основу поддержки изображений в JavaFX состав­ ляют два класса: Image и ImageView. В частности , класс Image инкапсулирует само изображение, а класс ImageView управляет его воспроизведением. Оба эти класса входят в состав пакета java fx . scene . image.
123' Часть IV. Введение в прогр аммир ование ГПИ с р едства ми Java FX Класс Image загружает изображение из потока ввода типа InputStream, веб­ ресурса по указанному URL или по пуги к файлу изображения . В классе Image определено несколько конструкторов. Ниже приведен конструктор, применяе­ мый в примерах из этой гл авы. Imaqe (Strinq url) Здесь параметр url обозначает URL или пугь к файлу с изображением. Если этот параметр не состоит из сформированного надлежащим образом URL, то предполагается , что он обозначает пугь к файлу изображения. В противном слу­ чае изображение загружается по указанному URL. В представленных далее приме­ рах изображения загружаются из файлов, находящихся в локальной файловой си­ стеме. Другие конструкторы класса Image позволяют указать ряд дополнительных параметров, в том числе ширину и высоту изображения. Следует также заметить, что класс Image является производным от класса Node. Следовательно, его объект может быть введен как узел в граф сцены. Итак, получив изображение в виде объекта типа Image , можно воспроизвести его средствами класса ImageView. Класс ImageView является производным от клас­ са Node , а следовательно, его объект может бьrгь введен как узел в граф сцены. В классе ImageView определяются три конструктора. Ниже приведен конструктор, применяемый в примерах из этой гл авы. Этот конструктор создает объект типа ImageView, представляющий указанное из ображение. I-qeView ( Imaqe J1Jsoбpazeюre) Все сказанное выше демонстрируется на примереJаvаFХ-приложения, загружа­ ющего изображение песочных часов и воспроизводящего его средствами класса ImageView. Изображение песочных часов хранится в файле hourglass .png, ко­ то рый, как предполагается, находится в локальном каталоге. 11 Загрузить и воспроизвести изображе ние import javafx . appl icatioп.*; import javafx .sc eпe .*; import java fx .st age .*; import javafx .scene . layout .*; import java fx . geometry .*; import java fx .scene . ima ge .*; puЫ ic class Image Demo extends Ap plication puЫic static vo id ma in (String [] args ) { 11 запустить JаvаFХ-приложение , вызвав метод launch () launch (args) ; 11 переопределить ме тод •tart () puЫ ic vo id start ( Stage myStage ) 11 присвоить заголовок подмосткам my Stage .setTitle ( "Display an Image" ); 11 Использовать панель поточной компоновки FlowPane 11 в качестве корне вого узла
Гл ава 35. Эл ементы управления Java FX FlowPaпe rootNode = пеw FlowPaпe () ; 11 выполни ть выравнивание п о центру rootNode .setAl i gnmeпt (Pos . CENTER) ; 11 создать с цену Scene myScene = new Scene ( rootNode , 300 , 200) ; 11 установить сцену на подмостках my Stage .setSceпe (mySceпe ); 11 со здать объе кт из ображения Image hourglass = пеw Image ("hourglass.png") ; 11 создать предс тавление этого изображения ImageVi ew hourglassIV = пеw ImageView (hourglass) ; 11 ввести изображение в граф сцены rootNode .getChi ldreп () .add (hourglassIV) ; 11 показать подмостки и сцену на них myStage . show (): 1235 На рис. 35. 1 показано изображение песочных часов , отображаемое в окне JаvаFХ-приложения из данного примера. Рмс. 35 .1 . Изображение песочных часов, отображаемое в окне приложения IrnageDerno Обратите в данном примере особое внимание на следующий фрагмент кода, в котором сначала загружается изображение, а затем создается его представление в виде объекта типа IrnageView: 11 создать объект изображения Image hourglass = new Image ("hourglass .pпg") ; 11 создать представление этого изображения ImageView hourglass IV = new IrnageView ( hourglaзs) ; Как пояснялось ранее, само изображение нелЬ3Я ввести в граф сцены. Для это­ го его нужно сначала встроить в объект типа IrnageView. В тех случаях, когда изображение не используется в каких-нибудь других целях, при создании его представления в виде объекта типа IrnageVi ew можно указать
1236 Часть IV. Введение в программирование ГПИ средства ми Java FX URL или имя файла. Это избавляет от необходимости создавать объект типа Image явным образом. Вместо этого экземпляр класса Irnage, содержащий изображение, получается автомати чески и встраивается в объект типа ImageView. Для этой цели служит приведенный ниже конструктор класса IrnageView, где параметр ur 1 обозначает URL или пугь к файлу, содержащему изображение. I11 1& geView (String url ) Ввод изображения в метку Как пояснялось в предыдущей гл аве , класс Label инкапсулирует метку. Он по­ зволяет отображать на месте метки текстовое сообщение, графику или и то и дру­ гое. В приведенных ранее примерах на месте метки отображался только текст, но не составляет особого труда воспроизвести там же изображение. Для этой цели служит следующий конструктор класса Labe l: LaЬel (Strinq стреха , Node •sоб�•еи.е) где параметр строка обозначает отображаемое текстовое сообщение, а параметр из ображение - воспроизводимое изображение. Это очень удо бно для выбора типа изображения , вводимого в метку, но в целях демонстрации изображение будет да­ лее представлено типом ImageView. В приведенном ниже примере JаvаFХ-приложения демонстрируется метка, содержащая графику. В этом приложении создается метка, отображающая текст "Hourglass" (Песочные часы) и воспроизводящая изображение песочных часов, загружаемое из файла hourglass .png. 11 Продемонс трирова ть изображение на ме сте ме тки import javafx . appl ication .*; import javafx .sc ene .*; import javafx .stage .*; impo rt javafx .scene . layout .*; import javafx .scene . control .*; import javafx . geometry .*; import java fx .scene . ima ge .*; puЫ ic class Label image Demo extends Application puЫ ic static void ma in (String [J args ) { 11 запус тить JаvаFХ-приложе ние , выз вав метод launch () launch (args) ; 11 переопределить ме тод start () puЫic void start ( Stage my Stage ) 11 присвоить заголовок подмосткам myS tage .setTitle ("Use an Image in а Labe l") ; 11 Исполь зовать изображе ние в ме тке 11 Использовать панель поточной компоновки FlowPane 11 в качестве корне вого узла FlowPane rootNode = new FlowPane () ; 11 вып олни ть выравнивание по центру
Глава 35. Эл емен ты управлени я JavaFX rootNode .setAl i gnment (Pos . CENTER) ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 300, 200) ; 11 установить сцену на подмостках mySt.age.setScene (myScene ) ; 11 создать представление указанного изображения ImageView hourgla ssIV = new ImageView ( "hourglass.png") ; 11 создать ме тку, содержащую изображение и текст Label hourglassLabel = new L abel ( "Hourg lass ", hourglas sIV) ; 11 ввести метку в граф сцены rootNode .getChildren () .add (hourglass Label) ; 11 показать подмостки и сцену на них myStage . show (); 1237 На рис. 35 .2 показано окно JаvаFХ-приложения из данного примера, в котором на месте метки отображается текст и воспроизводится изображение песочных часов. Рис. 35 .2 . Вид метки с изображением песочных часов и текстом в окне приложения Labelimage Demo Как видите , на месте метки отображается не только текст, но и воспроизво­ дится изображение. Обратите внимание на то , что текст воспроизводится справа от изображения . Это делается по умолчанию. Но относительное расположение изображения и текста можно изменить, вызвав метод setContentDisplay () для метки. Ниже приведе на общая форма этого метода. final void setContentDi splay (ContentDisplay DOSJl'Цl lf •) Значение, передаваемое в качестве параметра позиция, определяет порядок воспроизведения текста и изображения. Это должно быть значение одной из при­ веденных ниже констант, определяемых в перечислении типа ContentDisplay.
1238 Часть IV. Введение в программирование ГПИ средствами JavaFX &е перечисленные выше константы, кроме TEXT_ONLY и GRAPH IC_ONLY, обо­ значают местоположение изображения. Так , если ввести следующую строку кода вjаvаFХ-приложение из предыдущего примера: hourglassLabel .setContentDi splay ( ContentDisplay .TO P) ; то изображение песоч ных часов появится над тексто м, как показано на рис. 35 .3 . Hourg!as s Рис. 35.3 . В ид метки с изображением песочных часов над текстом Две другие константы позволяют отображать только текст или только изобра­ же ние. Это может быть уд обно в том случае, если изображение применяется в при­ ложении лишь иногда. (Но если требуется воспро извести только изображение, это можно сделать и без метки , как пояснялось в предыдущем разделе .) Изображение можно ввести в метку и после ее с оздания, вызвав метод set Graphic ().Н иже приведена общая форма этого метода , где параметр из ображение обозначает вводимое изображение. final void setGraphic (Hode жs оСJражею r е) П рименение и з ображения в экранно й кнопке Класс Button служит вjavaFX для создания экранных кн опок. Этот класс был представлен в предыдущей гл аве , где приводился пример применения экранной кнопки с текстовой надписью. И хотя такие кнопки широко распростран ены, этим их применение в ГПИ не ограничивается, поскольку в них можно вводить не только текст, но и изображение, а если требуется , то лишь одно изображение. Процедура ввода изображения в экранную кнопку практически ничем не отл ича­ ется от его ввода в метку. С этой целью сначала получается объект типа ImageView, представляющий изображение, а затем оно вводится в кнопку. Это можно сделать, например, с помощью следующего конструктора: ВUtton (Strinq с�жа , Hode жs�е) где параметр строка обозначает текст надписи на кнопке , а параметр из ображение - воспроизводимое в ней изображение. Вызвав метод setContent Di splay (), можно указать относительное расположение текста н адписи и изобра­ же ния способом, описанным выше для метки.
Гл ава 35. Эл ементы уп равления Java FX 1239 В приведенном ниже примере в окне JаvаFХ-приложения отображаются две экранные кнопки , содержащие изображения песочных и аналоговых часов соот­ ветственно. При нажатии кнопки сообщается о выбранной разновидности часов. Обратите внимание на то , что текстовая надпись на каждой из кнопок отобража­ ется под соответствующим изображением часов. // Приме ни ть изображе ние в кнопке import javafx . appl ication.*; import javafx . scene .*; import javafx .stage .*; import javafx .scene . layout .*; import javafx .scene . cont rol .*; import javafx . event .*; import javafx . geometry .*; import javafx .scene . image .*; puЫ ic class ButtonimageDemo extends Application ( Label re зpons e; puЫ ic зtatic vo id ma in (String () argз ) ( 11 запустить Jаvа FХ - приложе ние , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод start () puЫic void зtart ( Stage myS tage ) 11 присвоить заголовок подмосткам myStage .setTitle ("Use Imageз with Buttons ") ; 11 Исполь зовать изображе ния в кнопках // Исполь зовать панель поточной компоновки FlowPane 11 в качестве корневого узла . В данном случ ае с // промежут ками 10 по вертикали и по горизонтали Fl owPane rootNode = new FlowPane (lO, 10) ; // выровнять элементы упр авления по центру сцены cootNode .setAl ignment (Pos . CENTER) ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 250, 450) ; 11 установить сцену на подмостках myS tage .setScene (myScene ); 11 создать ме тку . response = new Label ("Puзh а Button") ; // Нажа ть кнопку 11 создать две экранные кнопки с те кстовыми надписями 11 и соответствующими изображе ниями часов Button btnHourglass = new Button ("H ourglass ", new ImageView ("h ou rglass .png ") ); Button ЬtnAnalogClock new Button ( "Analog Clock" , new ImageView ("analog .png") ); 11 расположи ть текст под изображе нием ЬtnHourglass .setContentDi splay ( Content Display.TO P) ; ЬtnAn alogClock . setContentDi splay ( Content Display .TO P) ;
1240 Ч асть IV. Введение в программирование ГПИ средствами Java FX 11 обработать события действия от экранной кнопки 11 с изображением песочных часов btnHourglass .setOnAction (new EventHandler<ActionEvent> () puЫic void handle (ActionEvent ае ) { response .setText ( "Hourglass Pressed" ); } )); // Нажата кнопка с изображением песочных часов // обработать события действия от экранной кнопки 11 с изображением аналоговых часов btnAn alogClock . setOnAction (new EventHandler<ActionEvent> () puЬl ic void handle (ActionEvent ае ) { re sponse .setText ( "Analog Clock Pressed" ); } }); // Нажата кнопка с изображением аналоговых часов // ввести ме тку и кнопки в граф сцены rootNode .getChi ldren () .addAll ( btnHourglass, btnAnalogClock, re sponse ); // показать подмостки и сцену на них my Stage .show() ; На рис. 35.4 показан вид экранных кнопок с текстовыми надписями и соответ­ ствующими изображениями песочных часов в окне JаvаFХ-приложения из данно· го примера. Если же требуется, чтобы кнопка содержала только изображение, вместо тексто­ вой надписи ее конструктору следует передать пустую строку. а затем вызвать ме­ тод setContentDisplay (), передав ему в качестве параметра константу Content­ Display .GRAPH IC_ONLY . Так, если внести подобные изменения в приложение из предыдущего примера, обе экранные кнопки будут выглядеть в его окне так, как по­ казано на рис. 35.5. Кnacc тoggleButton Полезной разновидностью экранной кнопки является так называемый переклю­ 'Чатмъ. Он похож на экранную кнопку, но действует иначе , поскольку может нахо­ диться только в одном из двух состояний: нажатом и отпущенном. Это означает, что если нажать переключатель, он останется в нажатом состоянии, а не перейдет после нажатия обратно в отпущенное состояние, как это обычно делает экранная кнопка. Если же нажать переключатель еще раз , он будет опущен. Следовательно, всякий раз , когда переключатель нажимается , он переходит в одно из двух своих состояний. BJavaFX переключатель инкапсулирован в классе ToggleButton. Как и класс Button, класс ToggleButton является производным от класса ButtonBase. Он реализует интерфейс Toggle, в котором определяются функциональные воз· можности , общие для всех типов кнопок с двумя состояниями.
Гл ава 35. Элементы управления Java FX Aлalog Clock -� �- Push • Button Рис. 35 ., . Вид экранных кнопок с текстовыми надпи­ ся ми и соответствующими изображениями песочных часов в окне приложения Button imageDemo • UseJmag� "ith But t. .. Push • Бutton Рис. 35.5. В ид экра нных кнопок тол ько с изображен иями часов в окне JаvаFХ-приложения В классе ToggleButton определяются три конструктора. Здесь и далее приме­ няется следующий конструктор: ToqqleButton (Strinq строжа) где строка обозначает текстовую надпись на переключателе. Другой конструк­ тор данного класса позволяет также ввести изображение в переключатель. Как и остальные разновидности кнопок, переключатель типа ToggleButton генери­ рует событие действия , когда он нажимается. Итак, в классе ToggleButton определяется элемент управления с двумя состо­ яниями, а следовательно , он зачастую применяется с целью предоставить пользо­ вателю возможность выбрать ко нкретный вариант. Когда же переключатель от­ пускается, то выбор данного варианта отменяется. Именно поэтому в прикладной программе обычно требуется определить состояние переключателя . С этой целью вызывается метод isSelected (), общая форма которого приведена ниже . Этот метод возвращает логическое значение true, если переключатель нажат, а ина­ че - логическое значение fa lse. final boolean isSelected () В следующем кратком примере демонстрируется применение класса ToggleBu t ton.
12,2 Ч асть IV. Введение в п рогра ммирование ГПИ средствами Java FX 11 Продемонстрировать применение переключателя import java fx . application .*; import java fx . scene .*; import java fx . stage .*; import java fx .scene . layout .*; import javafx .scene . cont rol .*; import java fx . event .*; import javafx . geometry .*; puЫic class ToggleButtonDemo extends Application { ToggleButton tЬOnOff ; Label response; puЫ ic static void main (String [] args ) { 11 запустить JаvаFХ-приложе н ие , вызвав ме тод launch () launch (args) ; 11 пере определить ме тод start () puЫ ic vo id start ( Stage myStage ) 11 присвоить заголовок подмосткам my Stage .setTitle ("Demonstrate а Toggle Button" ); 11 Продемонстрировать переключатель 11 Исполь зовать панель поточной компоновки FlowPane 11 в качестве корне вого узла . В данном случае с 11 промежут ками 10 по вертикали и по горизонтали Fl owPane rootNode = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены rootNode .setAlignment (Pos . CENTER) ; 11 создать сцену Scene my Scene = new Scene ( rootNode , 220, 120) ; 11 установить сцену на подмостках my Stage .setScene (myScene ); 11 создать ме тку response = new Label ("P ush the Button.") ; // // Нажать кнопку 11 создать переключатель tbOnO ff = new ToggleButton ("On/Off" ); // Включ ить / Выключить 11 обработать события действия от переключателя tbOnOff . setOnAction (new EventHandler<Ac tionEvent>() puЫ ic void handle (ActionEvent ае } { ) )); if ( tbOnOf f.is Selected (} } response . setText ( "Button is on . ") ; // Переключатель нажат else response . setText ( "Button is off.") ; 11 Переключатель отпущен // ввести ме тку и переключатель в граф сцены rootNode .getChildren () . addAll ( tbOnOff, re sponse );
Гл ава 35. Эл ементы управления JavaFX 11 покаэать подмостки и сцену на них myStage.show(); На рис. 35.б показан вид нажатого переключателя в окне JаvаFХ-приложения из данного примера. В данном примере обратите внимание на определение состояния нажатого или отпущенного переключателя. Это делается в следующих строках кода из обработ­ чика действий от переключателя: if ( tbOnOf f.is Selected () ) response .setText ( "Button is on .") ; else response .setText ("B utton is off.") ; Когда переключатель нажимается, метод i s Selected () возвращает логическое значение true. А когда переключатель отпускается, метод isSelected () возвращает логическое значение false. Следует также иметь в виду, что переключа­ тели можно объединять в группу. В этом случае одновременно может быть нажат только один переключатель. Процесс создания и применения группы переключателей такой же, как и для кно­ пок-переключателей. Он описывается в следую­ щем разделе. Клacc RadioBu tton Button is оп. Рис. 35.6. Вид нажато го пере­ кл ючател я в окне приложе ния Togg leButtonDemo Еще одной разновидностью кнопок в JavaFX является 'КНоn'Ка-переключателъ. Кнопки-переключатели образуют группу взаимоисключающих кнопок, где одно­ временно может быть выбрана только одна кнопка. Они поддерживаются в классе RadioButton, расширяющем классы ButtonBase и Togg leButton. Этот класс ре­ ализует также интерфейс Toggle. Следовательно, кнопка-переключатель являет­ ся особой формой переключателя. Кнопки-переключатели должны быть вам уже знакомы , поскольку они относятся к числу основных элементов управления ГПИ, с помощью которых пользователь может выбрать один и только один вариант из нескольких возможных. Для создания кнопки-переключателя здесь и далее применяется следующий конструктор класса RadioButton: RadioВutton (String C!l lp oжa) где параметр строка обозначает метку кнопки-переключателя. Как и остальные разновидности кнопок, акти визируемая кнопка- переключатель типа Radi оВ и t t on генерирует исключение. В силу своего взаи моисключающего характера кнопки-переключатели должны быть объединены в группы. При этом одновременно может быть выбрана лишь одна из них. Та к, если пользователь щелкает на кнопке-переключателе в груп пе,
1244 Часть IV. Введение в п рогра ммирование ГПИ средствами Java FX автоматически отменяется выбор любой кнопки-переключателя, нажатой ра­ нее в этой груп пе. Гр уппа кнопок-переключателей создается средствами класса ToggleGroup, входящего в состав пакета j ava fx . scene . control. В этом классе предоставляется только конструктор по умолчанию. Для ввода кнопок-переключателей в группу служит метод setToggleGroup (), определенный в классе ToggleButton. Этот метод вызывается для отдельной кнопки-переключателя следующим образом: final void setToqgleGroup ( ToqqleGroup rp,vпna) где параметр группа обозначает ту группу, в которую вводится данная кнопка­ переключатель. Как только кнопки-переключатели будут введены в одну и ту же группу, акти визируется режим их взаимоисключающего выбора. В общем, когда кнопки-переключатели объединяются в одну группу, одна из них выбирается , как только их группа появляется в ГПИ. Этого можно добить­ ся двумя способами. Во-первых, вызвать метод setSelected () для выбираемой кнопки-переключателя. Этот метод определяется в классе ToggleButton, кото­ рый служит суперклассом для класса RadioButton. Ниже приведена общая форма данного метода. final void setSelected (Ьoolean сос�о.1 1ИJ1 е) Если параметр со с тояние принимает логическое значение true, то кнопка-пе­ реключатель выбирается, в противном случае ее выбор отменяется . И хотя кноп­ ка-переключатель выбирается, никакого события действия не генерируется. И во-вторых, чтобы первоначально выбрать кнопку-переключатель, можно вы­ звать метод fire () для этой кнопки-переключателя. Ниже приведена общая фор­ ма данного метода. Метод f i re ( ) генерирует событие действия от данной кноп­ ки-переключателя, если она не была выбрана ранее. void fire () Кнопки-переключатели можно применять самыми разн ыми способами. И про­ стейшим из них, вероятно, является реагирование на событие действия , генери­ руемое при нажатии кнопки-переключателя, как показано в приведенном ниже примере. В данном примере кнопки-переключатели предоставляют пользователю возможность выбрать тип транспортного средства. 11 Простой пример , демонстрирующий применение кнопок-переключателей 11 11 Данное Jаvа FХ -приложе ние реагируе т на события действия , генерируемые 11 выбир аемыми кнопками-переключа телями . В нем демонстрируе тся также 11 активизация кнопки-переключателя под управлением пр ограммы import java fx . application .*; import java fx . scene .*; impo rt java fx . stage .*; impo rt javafx .scene . layout .*; import java fx .scene . control .*; import javafx . event .*; irnport java fx . geometry .*; puЫ ic class Radi oButtonDemo extends Application {
Гл ава 35. Элементы управления JavaFX Label responзe ; puЫ ic static vo id rna in (String [J args ) { 11 запустить Jаvа FХ- приложение , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод start () puЫ ic vo id start (Stage rnyS tage ) // присвоить заголовок подмосткам rnyS tage .setTitle ("Dernon strate Radio Button s" ) ; // Продемонстрировать кнопки -переключ атели // Исполь зовать панель поточной компоновки FlowPane // в качестве корневого узла . В данном случае с // промежут ками 10 по вертикали и по горизонтали FlowPane rootNode = new FlowPane (lO, 10) ; // выровнять элементы управления по центру сцены rootNode .setAl i gnrne nt (Pos . CENTER) ; 11 создать сцену Scene rnyS cene = new Scene ( rootNode , 220, 120) ; // установить сцену на подмостках rnyStage .setScene (rnyScene ); // создать ме тку, извещающую о сделанном выборе response = new Label ("") ; // создать кнопки-переключатели Radi oButton rbT rain = new Radi oButton ("Train" ); // Поезд Radi oButton rbCar = new Radi oButton ("Car" ); // Ав томобиль Radi oButton rb Plane = new RadioButton ( "Ai rplane") ; // Самолет // создать группу кнопо к-переключателей ToggleGroup tg = new ToggleGroup () ; // ввести каждую кнопку-переключатель в группу rbT rain .setToggleGroup (tg) ; rbCar .setToggleGroup (tg) ; rЬPlane .setToggleGroup (tg) ; // обработать события действия от кнопо к-переключателей rbTrain .setOnAc tion (new EventHandler<Ac tionEvent>() { puЫic vo id handle (ActionEvent ае ) { response .setText ( "Transport selected is train.") ; // Выбранным транспортным средством является поезд } }); rbCar .setOnAction (new EventHan dler<ActionEvent> () { puЫ ic void handle (ActionEvent ае ) { response .setText ("Transport selected is car.") ; } }); // Выбранным транспор тным средством является автомобиль rbPl ane . setOnAction (new EventHandler<ActionEvent> () puЫ ic void handle (ActionEvent ае ) {
1246 Часть IV. Введе н ие в п рограммирован и е ГПИ средствами Java FX re sponse .setText ("T ransport selected is ai rplane .") ; 11 Выбранным транспортным средством является самол ет } }); // Инициировать событие от первой выбранной кнопки-переключателя . // В итоге кнопка-переключатель выбира ется и наступает событие 11 действия от этой кнопки-переключателя rЬT rain. fire() ; 11 ввести ме тку и кнопки-переключатели в граф сцены rootNode .getChildren () .addAll ( rЬT rain, rЬCar, rЬPlane , re sponse }; 11 показать подмостки и сцену на них myS tage .show() ; li!J Oemonrtrate Rl!_:• т""" Car • Airpillne Transport <elЮecl is airplane. Рис. 35.7. Резул ьтат выбора одной из кнопок-переключател ей в окне пpилoжeния RadioButtonDemo На рис. 35.7 показан результат выбора од­ ной из кнопок-переключателей в окне JаvаFХ­ приложения из данного примера. Обратите в данном примере особое внимание на порядок создания кнопок-переключателей и их объединения в группу. Сначала создаются кнопки-переключатели, как показано ниже. Radi oButton rЬTrain new RadioBut toп ("Train" ); // Поезд Radi oButton rbCar = new Rad ioButton ("Car" ); // Автомо биль Ra dioButton rbPlane = new Ra dioButton ("Airplane") ; // Само лет Затем объект типа ToggleGroup, представля ющий группу, создается следую­ щим образом: ToggleGroup tg = new Toggl eGroup () ; И наконец, каждая кнопка-переключатель вводится в группу приведенным выше образом. Как пояснялось ранее, кнопки-переключатели должны быть объ­ еди нены в группу, чтобы активизировать режим их взаимоисключающего выбора. rbT rain .se tToggl eGroup (tg) ; rbCar .setToggleGroup (tg) ; rЬPlane .setToggleG roup (tg) ; После определения обработчиков событий от каждой из кнопок-переключателей вызывается метод fire (), чтобы выбрать кнопку-переключатель rbT rain. В итоге данная кнопка-переключатель инициализируется как выбираемая по умолчанию. Обработка событий изменения в группе кнопок-перекл ючателей Хотя в продемонстрированном выше способе управления кнопками-переклю­ чателями путем обработки событий действия нет ничего неверного, тем не ме-
Гл ава 35 . Эл ементы управления Java FX 1247 нее иногда оказывается удоб нее (и проще) принимать и обрабатывать события изменения во всей группе кнопок-переключателей в целом. Когда в такой группе происходит изменение, обработчик событий изменения может без особого труда определить, какая именно кнопка-переключатель была выбрана, и на этом основа­ нии предпринять соответствующее действие. Для этого нужно зарегистрировать приемник событий изменения типа ChangeListener в группе кнопок-переключа­ телей. Когда же наступит событие изменения, можно определить, какая именно кнопка-переключатель была выбрана. Чтобы опробовать такой способ управления кнопками-переключателями, уд алите сначала строки кода, в которых вводятся об­ работчики событий действия и вызывается метод fire (), из предыдущего при­ мераJаvаFХ-приложения, а затем подставьте вместо н их следующие строки кода: 11 исполь зовать приемник событий изменения, чтобы реагировать 11 на изменения при выборе кнопо к-переключателей из группы tg . selectedToggl ePrope rty() .addListener (new ChangeListener<Toggle> () puЫ ic vo id changed (ObservaЫeValue<? extends Toggle> changed, Toggle oldVal , Toggle newVal ) { } }); 11 привести новое значение к типу RadioВutton RadioButton rb = (RadioButton) newVa l; 11 отобразить результат выбора response .setтext ("T ransport selected is " + rb . getText () ); 11 Выбран указанный вид транспорта 11 выбрать первую кнопку-переключатель , чтобы инициировать 11 событие изменения в группе rbT rain .setSelected (tru e) ; Кроме того , в начале исходного кода из данного примера нужно ввести следую­ щий оператор irnport для поддержки интерфейса ChangeListene r: import java fx . beans .value .*; Эта версия JаvаFХ-приложен ия действует таким же образом, как и преды­ дущая . Всякий раз, когда выбирается кнопка-переключатель, обновляется мет­ ка re sponse. Но в данном случае в группу кн опок-переключателей требуется ввести лишь один обработчик событий вместо трех для каждо й кнопки-пере­ ключателя в отдельн ости . А теперь рассмотрим подробнее приведенный выше фрагмент кода. Сначала в этом фрагменте кода регистрируется приемн ик событий изменения в группе кнопок-переключателей. Чтобы прин имать события изменения , следует реализовать интерфейс ChangeListener. С этой целью вызывается метод add Listener ( ) для объекта, возвращаемого методом selectedToggleProperty ( ) . В интерфейсе ChangeListener определяется единственный метод changed ( ) . Ниже приведена его общая форма. void changed (ObservaЫeValue<? extends Т> яsненение , Т c!!'apoe_sнav8Нl lf e, Т но•ое_sнаvеюrе) В данном случае параметр изменение обозначает экземпляр интерфейса Ob servaЬ leValue<T>, инкапсулирующего объект, в котором наблюдаются изме-
Часть IV. Введение в программирование ГП И средствами Java FX нения , а параметры старое_ зна чение и нов ое_ зна чение - предыдущее и следую­ щее значения соответственно. Та ким образом, параметр новое_ зна чение содер­ жит ссылку на только что выбранную кнопку-переключатель. В данном примере для установки первоначально выбираемой кнопки-переклю­ чателя вызывается метод setSelected (),ане fire ().А поскольку такая установ­ ка вызывает изменение в группе кнопок-переключателей, то когда приложение из данного примера начинает выполняться , генерируется событие изменения. Для установки первоначально выбираемой кнопки-переключателя можно было бы вызвать и метод fire (), но вместо него был выбран метод setSelected ( ) , чтобы продемонстрировать то обстоятельство, что при любом изменении в груп­ пе кнопок-переключателей генерируется событие изменения . Другой способ управления кнопками- перекл ю чателями Несмотря на всю пользу от обработки событий, генерируемых кнопками-пере­ ключателями, иногда удо бнее просто проигнорировать события от них и получить выбранную в настоящий момент кнопку-переключатель, когда такая информация потребуется. Именно такой способ демонстрируется в приведенном ниже приме­ ре. Это переделанный вариант предыдущего примера, где дополнительно введена кнопка Confirm Transport Selection (Подтвердить выбор транспортного средства) . После щелчка на этой кнопке сначала получается выбранная в настоя щий момент кнопка-переключатель, а затем на месте метки отображается подтвержденный ре­ зультат выбора транспортного средства. Опробуя JаvаFХ-приложение из данного примера, обратите внимание на то , что изменение в выбираемой кнопке-пере­ ключателе не влечет за собой изменение подтвержденного результата выбора транспортного средства до тех пор, пока не будет нажата кнопка Confirm Tra nsport Selection . 11 В этом примере приме нения кнопо к-переключ ател ей 11 демонстрируе тся получение кноп ки -пере ключ ател я, 11 выбранной в текущий момент иэ группы , под упр авлением 11 программы, когда в этом возникает потребность , вместо 11 реагирования на события действия или изменения . 11 11 В данном примере события, связанные с кнопками-переключ ател ями , 11 не обрабатываются . Вме сто этого просто получ ается выбранна я в 11 данный моме нт кнопка- переключ ател ь, когда нажима ется экранная 11 кнопка Confirs Tranaport Selection import javafx . appl ication .*; import javafx . scene .*; import javafx .stage .*; irnport java fx .scene .la yout .*; import javafx .scene . control .*; import javafx . event .*; import javafx . geometry.*; puЫ ic class Radi oBut tonDemo2 extends Application ( Label response; ToggleGroup tg;
Гл ава 35. Элементы управления Java FX puЫ ic static void ma in (String [] args ) { 11 запустить JаvаFХ-приложение , вызвав ме тод launch () launch (args) ; 11 пере определить ме тод start () puЬlic vo id start ( Stage mys tage ) // присвоить заголовок подмосткам my Stage .setTitle ("Demonstrate Radi o Button s") ; 11 Продемонстрировать кнопки-переключатели 11 Исполь зовать панель поточной компоновки FlowPane 11 в качестве корневого узла . В данном случ ае с 11 промежутками 10 по вертикали и по горизонтали Fl owPane rootNode = new FlowPane (lO, 10) ; // выровнять элементы управления по центру сцены rootNode .setAlignment (Pos . CENTER) ; 11 создать сцену Scene myScene = new Scene ( rootNode , 200, 140) ; // ус тановить сцену на подмостках myStage .setScene (myScene ); // создать две метки Label choose = new Label (" Select а Transport Туре "); // Выбор транспортного средства re sponse = new Labe l ("No transport confirmed" ); // Выбор транспортного средства не подтвержден 11 создать экранную кнопку дл я подтверждения выбора 11 транспортного средства Button ЬtnConfirm = new Button ("Confirm Transport Selection" ); 1 1 Подтвердить выбор транспортного средства 11 создать кнопки-переключатели Radi oButton rЬT rain = new Radi oButton ("Train" ); // Поезд RadioButton rbCar = new RadioButton ("Car") ; // Автомобиль RadioButton rbPlane = new RadioButton ( "Ai rplane ") ; // Самоле т // создать группу кнопок-пере ключателей tg = new ToggleGroup (); 11 ввести каждую кнопку-переключатель в группу rЬTrain .setToggleGroup (tg) ; rЬCar .setToggleGroup (tg) ; rЬ Plane .setToggleGroup (tg) ; 11 первоначально выбрать одну из кнопок-переключателей rЬTrain .setSelected (true) ; 11 обработать события действия от кнопки подтверждения // выбора транспортного средства ЬtnCon firm. setOnAction (new EventHandler<ActionEvent> () puЫic void handle (ActionEvent ае ) { 11 получи ть выбранную в настоящий моме нт кнопку-переключатель RadioButton rb = (RadioButton) tg . getSelectedToggle () ; 11 отобразить результат выбора транспортного средства respon se . setText (rb.getText () +" is confirmed .") ; 12д9
1250 Ч асть IV. Введение в программирование ГПИ с р едствами Java FX 11 Подтверждено указанное транспортное средство } }); 11 исполь зовать разделитель , чтобы улучшить порядок 11 расположе ния элементов управления Separator separator = new Separator () ; separator .setPrefWidth (180) ; 11 ввести метку и все виды кнопок в граф сцены roo tNode .getChildren () .addA ll (choos e, rbTrain, rbCar, rbPlane , separator, btnConfirm, re sponse ); 11 пока зать подмостки и сцену на них myS tage .show () ; �Transport Sel� Car is confirm�. Рис. 35.8. Резул ьтат подтверждения выбора одн ой из кнопок-пере- На рис. 35 .8 показан результат подтвержде· ния выбора одной из кнопок-переключателей в окне JаvаFХ-приложения из дан ного примера. Бальшую часть кода из данного примера не­ трудно понять, но особый интерес в нем вызыва· ют два обстоятельства. Во-первых, обратите вни· мание на следующую строку кода из обработчика событий действия от кнопки ЬtnConfirm, где по­ лучается выбранная кнопка-переключатель: RadioButton rb = (RadioButton ) tg . ge tSelectedToggle (); ключателей в окне приложе ния RadioBut tonDemo2 где метод getSelectedToggle (), определен· ный в классе ToggleGroup, получает результат текущего выбора в группе переклю· чателей (в данном случае кнопок-переключателей). Ниже приведена общая форма этого метода. final Toqqle qetSelectedToqqle () Этот метод возвращает ссылку на выбранный переключатель типа Toggle. В дан ном случае значение, возвращаемое методом getSelectedToggle () , приво­ дится к типу RadioButton, поскольку группа состоит из кнопок-переключателей. И во-вто рых, обратите внимание на применение визуал ьного разделителя, соз· даваемого в следующем фрагменте кода: Separator separator = new Separator (); separator .setPrefWidth (18 0) ; Сначала в данном фрагменте кода средствами класса Separator создается разделяющая линия, которая может быть вертикальной или горизонтальной. По умолчанию создается горизонтальная линия. А второй конструктор этого клас· са позволяет выбрать вертикальную разделяющую линию. Та кая линия улучшает внешний вид компоновки элементов управления ГПИ. Класс Separator входит в состав пакета j ava fx . scene . control. Затем в данном фрагменте кода вызыва­ ется метод setPrefWidth (), чтобы задать ширину разделяющей линии.
Гл ава 35. Элементы управления Java FX 1251 Кл а сс CheckВox Класс CheckBox инкапсулирует фун кциональные возможности флажка. Его не­ посредственным суперклассом служит класс But tonBase. Флажки вам, без сомне­ ния, хорошо известны, поскольку они широко применя ются в ГПИ, но в JavaFX флажок является несколько более сложным элементом управления, чем может показаться на первый взгляд. Дело в том, что в классе CheckBox подцерживают­ ся три состоя ния флажка. Двумя первыми являются установленное и сброшенное состояния, как и следовало ожидать, поскольку это стандартное поведение флаж­ ка. А третьим является так называемое жоnредме н:ное состояние флажка. Оно, как правило , обозначает незаданное или не соответствующее конкретной ситуации состоя ние флажка. Если требуется неопределенное состояние флажка, его при­ дется разрешить явным образом. В классе CheckBox определяются два конструктора. Первым из них является конструктор по умолчанию. А второй конструктор позволяет указать символьную строку, обозначающую флажок. Ниже приведена форма этого конструктора. CheckВox (Strinq с!l'р()жа) Он создает флажок с текстом метки , обозначаемым параметром строка . Как и остальные разновидности кнопок, флажок типа CheckBox генерирует событие де йствия , когда он устанавливается. В приведенн ом ниже примере демонстрируется применение флажков. В о кне JаvаFХ-приложения из этого примера отображаются флажки , дающие пользовате­ лю возможность выбирать различные варианты развертывания приложения: Web (Веб), Desktop (Настольная система) и Moblle (Мобильное устройство) . Всякий раз, когда флажок изменяет свое состояние, генерируется событие, обработка ко­ торого заключается в отображении нового (установленного или сброшенного) со­ стояния флажка, а также списка всех установленных флажков. 11 Продемонстрировать приме нение флажков import java fx . application .*; import javafx . scene .*; import java fx .st age .*; import javafx .scene . layout .*; import java fx .scene . control .*; import java fx . event .*; import java fx . geometry .*; puЫ ic class CheckboxDemo extends Appl ication { Chec kBox cbWeb; CheckBox cbDe sktop; CheckBox cbMobile ; Labe l response ; Label allTargets; String targets = ""; puЫ ic static void ma in (String [J args ) { 11 запустить JаvаFХ-nриложение, вызвав ме тод launch ()
1252 Часть IV. Введение в программирование ГПИ средствами Java FX launch (args) ; / / переопределить метод start () puЫ ic void start ( Stage myStage ) // присвоить заголовок подмосткам myS tage .setTitle ("Demonstrate СhесkЬо хез " ); 11 Продемонстрировать флажки 11 Использовать панель поточной компоновки FlowPane 11 в качестве корне вого узла . В данном случае с // промежутками 10 по вертикали и по горизонтали Fl owPane rootNode = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены rootNode .setAl i gnment (Pos . CENTER) ; 11 создать сцену Scene myScene = new Scene ( rootNode , 230, 140) ; 11 установить сцену на подмостках myS tage .setScene (myScene ); Label heading = new Label ("Select Deployment Option s" ) ; 11 Выбрать вариант развертывания приложения 11 создать ме тку, извещающую о состоянии установленного флажка response = new Label ("No Deployment Selected" ); // Ни один из вариантов развертывания не выбран 11 создать ме тку, извещающую обо всех установленных флажках allTargets = new Label ( "Target List : < none>") ; 11 Список целевых флажков 11 создать флажки cbWeb = пеw CheckBox ("Web"); // Веб cbDe sktop = new CheckBox ("Desktop" ); // Настольная система cbMoЬile = new CheckBox ("MoЫ l e") ; // Мобильное устройство 11 обработать события действия от флажков cbWeb .setOnAction (new EventHandler<ActionEvent >() { puЫ ic void handle (ActionEvent ае ) { if (cbWeb .isSelected ()) } )); re sponse .setText ( "Web de ployment selected. ") ; 11 Выбрано развертывание приложения в веб else response . setтext ( "Web deployment cleared.") ; 11 Развертывание приложения в веб отменено showAl l () ; cbDe sktop .setOnAction (new Even tHandler<Ac tionEvent>(J { puЫ ic void handle (ActionEvent ае ) { if (cbDe s ktop .isSelected () ) re sponse .se tText ("Desktop dep loyment selected. ") ; // Выбрано развертывание приложе ния в настоль ной системе else re sponse . setText ("Desktop deployment cleared. ") ; 11 Развертывание приложения в настольной системе отменено showAl l () ;
Гл ава 35. Эл ементы управления JavaFX } }); cbMobile . setOnAction (new EventHandler<ActionEvent>() ( puЫic vo id handle (ActionEvent ае ) ( if (cbMobile .is Selected () ) response .setText ( "MoЬile deployment selected. ") ; 11 Выбрано развертывание приложе ния на мобильном устройстве else re sponse .setText ( "MoЬile deployment cleared. ") ; 11 Развертывание приложе ния на мобильном устройстве отменено ) }); showAl l (); 11 исполь зовать разделитель , чтобы улучшить порядок 11 расположения э лементов управления Separator separator = new Separator () ; separator .setPrefWidth (200) ; 11 ввести элементы упр авления в граф сцены rootNode .getChildren () .addAll (heading , separator, cbWeb, cbDe sktop, cbMoЬi le, re spons e, allTargets ); 11 показать подмостки и сцену на них myStage .show() ; 11 обновить и показать список целевых флажков vo id showAll () ( targets = ""; if (cbWeb .isSelected () ) targets =" Web "; if(cbDesktop .isSelected () ) targets += "Desktop "; if(cbMobile . iзSelected () ) targets += "Mobile"; if (targets . equals ("") ) targets = "<none>" ; allTargets . setText ( "Target List : "+targets ); На рис. 35 .9 показан результат установки флаж­ ков в окнеJаvаFХ-приложения из данного примера. Принцип действия JаvаFХ-приложения из дан­ ного примера довольно прост. Всякий раз, когда со­ стояние флажка изменяется, формируется команда действия. С целью определить, был ли флажок уста­ новлен, вызывается метод isSelected (). Selett Deployml!nt Opti ons МоЬ� deployment selected. Targ� 1.ist: Dмlctop MoЬile 1253 Как упоминалось ранее, по умолчанию средства­ ми класса CheckBox реализуется флажок с двумя со­ стояниями: установленным и сброшенным. Если же требуется ввести третье, неопределенное состояние фл ажка, оно должно быть разрешено явным образом. С этой целью вызывается метод setAl lowindeter mina te (). Ниже приведена его общая форма. Рис. 35.9 . Результат установки флажков в окне приложения Che ckЬoxDerno final void setAllowind&terlD.i.nate (boolean pa.spei ir EIНl!e)
125, Часть IV. Введение в п рограммирование ГПИ средствами JavaFX Если параметр ра зрешение принимает логическое значение true, то неопре­ деленное состояние флажка разрешается, а иначе оно запрещается. Когда неопре­ деленное состояние флажка разрешено, пользователь может выбирать между уста­ новленным, сброшенным и неопределенным состоя нием флажка. Вызвав метод isin de terminate (), можно определить, находится ли флажок в неопределенном состоя нии. Ниже приведена общая форма это го метода. Он возвращает логическое значение true, если флажок находится в неопределенном состоя нии, а иначе - логическое значение false. final Ьoolean isindeterai.nate () Чтобы опробовать флажок с тремя состоя ниями на практике, можете немно­ го видоизменить исходный код из предыдущего примера. С этой целью вызовите сначала метод setAl lowindetermi nate ( ) для каждого флажка , чтобы разрешить его неопределенное состояние, как по казано ниже. cbWeb .setAl lowindeterminate (true) ; cbDe sktop .setAllowi ndetermi nate (true) ; cbMobi le . setAl lowindeterminate (true) ; Затем организуйте обработку событий перехода флажков в неопределенное со­ стояние в соответствующих обработчиках событий действия. В качестве примера ниже приведен видоизмененный обработчик событий от флажка cbWeb. cbWeb .setOnAction (new EventHandler<Ac tionEvent>() { puЫic void handle (ActionEve nt ае ) { if (cbW eb .isindeterminate () ) ) }); response .setText ( "Web deployment indetermi nate .") ; 11 Развертывание приложе ния в веб не определено else if (cbWeb .isSelected () ) response .setText ( "Web deployment selected. ") ; 11 Выбрано развертывание приложе ния в веб else re sponse .se tText ( "Web deployment cleared.") ; 11 Развертывание приложе ни я в веб отменено showAl l () ; Se!«t �t Opt1ons - Wt:Ьo,fDeshop..!МоЬ;�, , WеЬ deplcrjrn4'nt ·ndelюniмR- T8f9el l.tst Des<top MoЬile Те перь проверяются все три состояния флажка. Внесите аналогичные изменения в обработчики со­ бытий от двух других флажков. После внесен ия этих изменен ий неопределенное состояние флажков мо­ жет быть выбрано. Как показано на рис . 35. 10, фла­ жок Web находится в нео пределенном состоянии. Рис. 35.10 . Вид окна приложения CheckЬoxDemo с флажком Vl le b в неопределенном состоян ии Класс ListView Еще одним элементом управления , часто применяемым при построении ГПИ, является представление списка, которое в JavaFX инкапсулировано в классе Li s tView. Представления списков - это элементы управления, отображающие
Гл ава 35. Эл ементы уп равления Java FX 1255 списки , из кото рых можно выбрать один или несколько эл ементов. Благодаря тому что представления списков эффективно используют полезную площадь экра­ на, они служат широко распространенной альтернативой другим типам элементов управления выбором. Класс ListView является обобщенным и объявляется следую щим образом: class ListView<T> где параметр Т обозначает тип элементов, хранимых в представлении списка. Зачастую это элементы типа String, хотя допускаются элементы других типов. В классе ListView определяются два конструктора. Первым из них является конструктор по умолчанию, создающий пустой объект типа ListView. А второй конструктор позволяет указать конкретн ый список эл ементов, как показано ниже. Li stView (ObservaЬleList<T> сп.сож) Здесь параметр список обозначает отображаемый список элементов. Он прини­ мает объект типа Ob s е rvaЫ eLi s t, определяющий список наблюдаемых объектов. Класс Ob serva ЬleList наследует от класса j а va . util . List. Следовательно, он поддерживает стандартные методы обработки кштекций. Класс Ob servaЫeList входит в состав пакета j avafx . collections. Едва ли не самый простой способ создать список типа ОЬ s е rvаЫеLis t для при­ менения в представлении списка типа ListView - воспользоваться фабричным методом observaЫeArrayList ( ), который определен как статический метод в классе FXCollections , также входящем в пакет javafx . collections. Ниже приведена применяемая здесь и далее общая форма этого метода, где параметр Е обозначает тип элементов , передаваемых в качестве параметра элементы. static <Е> ObaervaЬleLi st<E> oЬaervaЫeArrayList (:&: • • • аленен!DJ) По умолчанию в элементе управления типа ListView допускается одновремен­ ный выбор из списка только одного элемента. Но, изменив режим выбора, можно разрешить выбор нескольких элементов из списка. Ниже будет использована стан­ дартная модель выбора одного элемента из списка. Несмотря на то что в элементе управления типа ListView предоставляется раз­ мер списка по умолчанию, иногда требуется установить предпочтительную высоту и/или ширину в соответствии с конкретными потребностями. С этой целью мож­ но вызвать методы setPre fHe ight () и setPrefWidth () соответственно. Ниже приведены их общие формы. final void setPrefHeiqht (douЫe вuсота) final void setprefWidth (douЫe l!Dl lplr Ha) С другой стороны, оба предпочтительных размера можно установить сразу, вы­ звав метод setPrefSi ze ().Его общая форма выглядит следующим образом: void setprefSize (douЫe llfJIPJrН& , douЫe вuсота) Элементом управления типа ListView можно воспользоваться двумя основ­ ными способами. Во-первых, проигнорировать события, генерируемые списком, и просто получить результат выбора из списка, когда это потребуется в приклад­ ной программе. И во-вторых, отслеживать изменения в списке, зарегистрировав
1256 Ч асть IV. Введе н ие в программирован ие ГПИ средствами Java FX приемник событий изменения. Последний способ дает возможность оперативно реагировать на изменения, вносимые пользователем в выбор элементов из спи­ ска. Именно этот способ и применяется далее. Для приема событий изменения нужно сначала получить модель выбора, при­ меняемую в элементе управления типа ListView. С этой целью для списка вызыва­ етс я метод getSelectionMode l (),общая форма которого приведена ниже. final МUltipleSelectionмod&l<T> ge tSelectionМodel () Этот метод возвращает ссылку на модель. В классе Mu l tipleSelectionMode l определяется модель, применяемая для одновременного выбора нескольких эле­ ментов из списка. Этот класс наследует от класса SelectionModel. Но одновре­ менно выбирать несколько элементов из списка, представленного объектом типа ListView, можно только в том случае, есл и активизирована модель, поддержива­ ющая такой режим выбора. Используя модель, возвращаемую методом ge tSelectionModel (), можно по­ лучить ссылку на свойство элемента, выбранного из списка. Это свойство опреде­ ляет событие, происходящее при выборе элемента из списка. С этой целью вызы· ваетс я метод selectedi temP rope rty (),общая форма которого приведена ниже. К полученному в итоге свойству присоединяется приемник событий изменения. final ReadOnlyOЬjectproperty<T> selected.I tel llP roperty () Все сказанное выше о представлении списка демонстрируется в приведенном ниже примере. В частности , в этом примере создается представление списка, ото­ бражающее различные виды транспортных средств, которые пользователь может выбрать из списка. Результат выбора отображается на месте метки. 11 Продемонстрировать приме нение представления списка import java fx . appl ication .*; import java fx . scene .*; import java fx . stage .*; import java fx .scene . layout .*; import java fx .scene . control .*; import java fx . geometry .*; import java fx . beanз .value .*; import java fx . collectionз .*; puЬlic clas s L iзtVi ewDemo extends Application { Label response ; puЫic static voi d ma in (String [] args ) { 11 запус ти ть JаvаFХ- приложение , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод start () puЬl ic vo id start ( Stage myStage ) 11 прис воить заголовок подмосткам my Stage .setTitle ("L istView Demo" ) ; 11 Демонстрация представления списка 11 Исполь зовать панель поточной компоновки FlowPane
Гл ава 35. Элементы управления Java FX 11 в качестве корне вого узла . В данном СЛУЧ ае с 11 промежутками 10 по вертикали и по горизонтали FlowPaпe rootNode = пеw FlowPaпe (lO, 10) ; 11 выровнять элементы упр авления по центру сцены rootNode .setAl i gnmeпt (Pos . CENTER) ; 11 создать сцену Sсепе myScene = пеw Scene ( rootNode , 200, 120) ; 11 установить сцену на подмостках my Stage .setScene (myScene ); 11 создать ме тку re sponse = new Label ( "Select Traпsport Туре") ; 11 Выбрать вид транспортного средства 11 создать список типа ObservвЬleLi st из элементов 11 для представления списка Ob servaЬleList<String> transportTypes FXC ollections . ob servaЫ eAr rayList ( "Train" , "Car", "Airplane") ; 11 создать представле ние списка ListView<String> lvTransport = new ListVi ew<Striпg> (transportTypes) ; 11 задать предпочтитель ную высоту и ширину представления списка lvTransport .setPrefSize (80, 80) ; 11 получить модель выбора для представления списка Mul tipleSelectioпModel<Striпg> lvSelMode l = lvTraпsport .getSelectioпModel (); 11 ввести приемник событий изменения , чтобы реагировать на 11 выбор элеме нтов в представлении списка lvSelModel .se lecteditemP roperty () .addListener ( new ChangeListener<String> () puЬlic void changed (ObservaЫeValue<? extends String> changed, String oldVal , String newVal ) { 11 отобразить резуль тат выбора response .setText ( "Transport selected is " + newVal ); 11 Выбрано указанное транспортное средство } }); 11 ввести ме тку и представление списка в граф сцены rootNode .getChildren () . addAll ( lvTransport, response ); 11 показать подмо стки и сцену на них myStage . show (); На рис. 35 .11 показан результат выбора транс­ портного средства из списка в окне JаvаFХ-при­ ложения из данного примера. Тransport �есtЮ 1s Airplan� 1257 Обратите в данном примере особое внима­ ние на порядок создания представления списка типа ListView. Сначала создается список типа Ob servaЬl eList, как показано ниже. Рис. 35 .11 . Результат выбора транспортного средства из с писка в окне прилож ения ListViewDemo
1258 Часть IV. Введение в про гра ммиро вание ГПИ средствам и JavaFX Ob servaЫeLi st<String> transportTypes = FX Collections . ob se rvaЫ eAr rayLi st ( "Train", "Car" , "Ai rplane ") ; Для составления списка из символьных строк в данном фрагменте кода вы­ зывается метод observaЫ eArrayList (). Затем составленный список типа Ob serva ЫeLi st используется для инициализации представления списка типа ListView приведенным ниже образом. А после этого задаются предпочтительные размеры дан ного элемента управления. ListView<String> lvTransport = new ListVi ew<String> (tr ansportTyp es) ; В следую щей строке кода показано, каким образом модель выбора получается для представления списка l vT ransport: MultipleSelectionMode l<String> lvSelModel = lvTransport .getSelectionModel () ; Как пояснялось выше, в элементе управления типа ListView применяется мо­ дель типа Mu l tipleSelectionMode l, хотя и разрешается одновременный выбор из списка только одного элемента. Для полученной в итоге модели выбора далее вызывается метод selectedi temPrope rty (),адля возвращаемого свойства реги­ стрируется приемник событий изменения . Представление списка с полосами прокрутки Transport setю� is Bicycle Рис. 35 .12. Список транспортных средств с полосой про крутки в окне приложения ListViewDemo К числу очень полезных особенностей клас­ са ListView относится возможность автоматиче­ ски снабжать список полосами прокрутки, когда количество его элементов превышает отобража­ емое в пределах задан н ых размеров. Например, объявление списка transportType s можно изме­ нить таким образом, чтобы он включал элементы "Bicycle" (Велосипед) и "Walking " (Пеший ход ) , как показано ниже. И тогда список transportType s будет автома­ тически снабжен полосами прокругки , как пока­ зано на рис. 35.12. Ob se rvaЬleLi st<String> transportType s = FXC ollections .obse rvaЫ eAr rayLi st ( "Train" , "Car", "Airplane ", "Bicycle" , "Wal king" ); Акти визация режима одновременного выбора нескольких элементов из списка Если требуется разрешить одновременный выбор из списка нескольких элементов, такой режим следует запросить явным образом , указав константу SelectionMode .MULT I PLE при вызове метода setSelectionMode ( ) для моде­ ли выбора в элементе управлен ия типа ListView. Ниже приведена общая фор­ ма данного метода, где параметр режим должен принимать значение константы SelectionMode .MULT IPLE или SelectionMode . SINGLE . final void setSelectiol'IМode (SelectionМode pez!IUI)
Гл ава 35. Элементы уп равления Java FX 1259 Если режим одновременного выбора нескольких элементов из списка разре­ шен, то список выбранных элементов можно получить в двух формах: в виде самих выбранных элементов или их индексов. Здесь и далее будет использоваться список выбранных элементов, но та же самая процедура распространяется и на список индексов выбранных элементов. Следует, однако , иметь в виду, что и ндексация элементов в представлении списка типа ListView начинается с нуля. Чтобы получить список выбранных элементов, следует вызвать метод get Selectedlterns () для модели выбора. Ниже приведена общая форма данного метода. ObservaЫeList<T> qe tSelectecUteшs () Этот метод возвращает список выбранных элементов типа Ob servaЬleList. А поскольку класс Ob servaЬleList расширяет класс j ava . util . List, то доступ к элементам списка можно получить таким же образом, как и к элементам любой другой коллекции типа List. В качестве эксперимента с одновременным выбором нескольких элементов из списка попробуйте внести следующие изменения в исходный код из предыдущего примера. Сначала объявите список lvTransport как final, чтобы сделать его до­ ступным в пределах обработчика событий изменения. Затем введите следующую строку кода: lvTransport . ge tSelectionModel () .setSelectionMode (SelectionMode .MULTI PLE ); В этой строке кода акти визируется режим одновременного выбора нескольких элементов из списка типа lvTransport. И наконец, замените исходный код обра­ ботчика событий изменения на следующий: lvSelModel .se lectedi temProperty () .addLi stener ( new ChangeLi stener<String> () { puЫic void changed (ObservaЬleValue<? extends String> changed, String oldVal , String newVal) { String selitems = 11 11 ; Ob servaЫeLis t<String> selected = lvТ ran sport .getSelectionModel () . getSelecteditems (); } }); // отобразить ре зуль таты выбора for (int i=O ; i < selected. size() ; i++ ) selitems += 11 \n 11 + selected .get (i) ; response . setтext (11All transports selected : 11 + selitems) ; // Все выбранные трансп ортные средства После внесения упомянутых выше изменений в ис­ ходный код приложения ListViewDerno все выбранные виды транспортных средств будут отображаться ниже списка, как показано на рис. 35. 13. Рис. 35 .13. Перечисление всех выбранных из списка транс­ портн ых средств в окне приложения ListViewDemo АН traruporls s•lected: Car Bicycl<!
1260 Часть IV. Введение в программировани е ГПИ средства ми Java FX Кл а сс СоmЬоВох С представлением списка связан также элемент управления, называемый комби­ нированнъш спискам и реализуемый в JavaFX классом СоmЬоВох. В ко мбинирован­ ном списке отображается лишь один выбираемый элемент, а остальные элементы можно выбрать из дополнительно раскрываемого списка. Кроме того , пользовате­ лю можно предоставить возможность редактировать выбираемый элемент. Класс СоmЬоВох наследует от класса ComЬoBoxBase, предоставляющего большую часть функциональных возможностей комбинированного списка. В отличие от пред­ ставления списка ти па ListView, допускающего одновременный выбор несколь· ких элементов из списка, ко мбинированный список типа СоmЬоВох предназначен для одновременного выбора единственного элемента. Класс СоmЬоВох является обобщенным и объявляется следующим образом: clas s СошЬоВох<Т> где параметр т обозначает тип элементов. Зачастую это элементы типа String, хотя допускаются и другие их типы. В классе СоmЬоВох определяются два конструктора. Первым из них является конструктор по умолчан ию, создающий пустой комбинированный список типа СоmЬоВох. А второй конструктор позволяет указать список элементов, как пока­ зано ниже . CoшЬoBox (ObservaЫeList<T> спясож) В данном случае параметр список обозначает отображаемый список элемен­ тов. Это объект ти па Ob servaЫeList, определяющий список наблюдаемых объ­ ектов. Как пояснялось ранее, класс Ob serva ЬleList наследует от класса java . util . List, а для создания списка типа Ob servaЫeList проще всего вызвать фабричный метод observaЬ leArrayList (), который определяется как статиче­ ский метод в классе FXCollections . Комбинированный список типа СоmЬоВох генерирует событие действия, когда в нем изменяется выбираемый элемент. Он будет также генерировать событие из­ менения. С другой стороны, события, наступающие в комбинированном списке , можно проигнорировать и просто получить выбранный в настоящий момент эле­ мент по мере надобности . Вызвав метод getValue ( ) , можно получить элемент, выбранный в настоящий мо­ мент из комбинированного списка. Ниже приведена общая форма данного метода. final Т qetValue () Если значение в комбинированном списке не установлено (вручную пользова­ телем ил и программно), то метод getVa lue () возвращает пустое значение null. Чтобы установить значение в комбинированном списке программно , следует вы­ звать метод setVa lue (), как показано ниже, где параметр нов ое_ зна чение обо­ значает вновь устанавливаемое значение. final void setValue (Т но.вое_sнаvеюrе) В приведенном ниже примере демонстрируется применение комбинирован­ ного списка. Это переделанный вариант предыдущего примера, в котором демон-
Гл ава 35 . Элементы управления JavaFX 1261 стрировалось применение представлен ия списка. В данном примере обрабатыва­ ется событие действия , генерируемое комбинированным списком. // Продемон стрировать приме нение комбинированного списка import java fx . applicatioп .*; import java fx . scene .*; import javafx . stage .*; import javafx .scene . layout .*; import javafx .scene . control .*; import javafx. geometry. *; import java fx . collections .*; import javafx . event .*; puЫ ic class ComЬoBoxDemo extends Application { ComЬoBox< String> cbT ransport ; Label respons e; puЫ ic static vo id ma iп (String [] args ) { // запус тить Jаvа FХ - приложение , вызвав ме тод launch () launch (args) ; // переопределить метод start () puЫ ic vo id start ( Stage myStage ) 11 присвоить за головок подмосткам myS tage .setTitle ( "ComЬoBox Demo" ) ; 11 Демонс трация комбинированного списка 11 Исполь зовать панель поточной компоновки FlowPane 11 в качестве корневого узла . В данном случае с // промежутками 10 по вертикали и по горизонтали FlowPane rootNode = new FlowPane (lO, 10) ; 11 выполнить выравнивание по центру rootNode .setAl i gnment (Pos . CENTER) ; 11 создать сцену Scene myScene = new Scene ( rootNode , 280, 120) ; // установить сцену на подмостках myS tage .setScene (myScene ); 11 создать ме тку respons e = new Label (); 11 создать список типа ObservaЬleList из элементов , // предназначенных для комбинированного списка Ob servaЫeList<String> transportType s = FXCollections . obse rvaЫ eAr rayList ( "Train" , "Car" , "Ai rplane " ); // создать комбинированный список cbTransport = new ComЬoBox<String> (transportTyp es) ; 11 установить значение по умолч анию cbTransport .setValue ("Train" ); // Поезд
1262 Ч асть IV. Введение в п рограммирован ие ГПИ средствами Java FX 11 установить ме тку отве тной реакции для отображе ния 11 резуль тата выб ора по умолчанию re spoпse .setText ( "Selected Traпsport is " + cbT ransport.ge tValue () ); 11 Выбрано указанное транспортное средство 11 принима ть события действия от комбинированного списка cbT ransport . setOnAct ion (new EventHandler<ActionEvent>() { puЫ ic void handle (ActionEvent ае ) { re sponse .setтext ( "Selected Transport is "+cbT ransport . ge tValue () ); 11 Выбрано указанное транспортное средство ) }); 11 ввести ме тку и комбинированный список в граф сцены rootNode .getChildren () .addAll (cbTransport , re sponse ); 11 показать подмо стки и сцену на них myS tage .show () ; СоmЬоВох Demo � Selected Тransport is Car ____.., Рис. 35.1' . Резул ьтат выбора тра нс­ портного средства из ко мбинированного списка в окне приложения ComЬoBoxDemo На рис. 35 . 14 показан результат выбора транспортного средства из комбинирован­ ного списка в окне JаvаFХ-приложения из данного примера. Как упоминалос ь выше, комбиниро­ ванный список можно составить таким образом, чтобы пользователь мог редак­ тировать выбираемый в нем элемент. Та к, если комбинированный список составлен только из элементов типа String, то раз­ решить подобное редактирование совсем не трудно. Для этого достаточно вызвать метод setEditаЫе (), общая форма кото­ рого приведена ниже. final void setEdi tаЫе (boolean pa .spe1 1 8Нl l e) Если параметр ра зрешение принимает логическое значение true, то редакти­ рование разрешается, а иначе - запрещается . Чтобы посмотреть результаты ре­ дакти рования элементов комбинированного списка, введите приведенную ниже строку в исходный код из предыдущего примера. После внесения этого изменения вы сможете отредактировать выбранный элемент комбинированного списка. cbT ransport . setEditaЬle (true) ; Помимо упомянутых выше , в классе СоmЬоВох поддерживается немало дру­ гих средств и функциональных возможностей комбинированных списков. Вам , вероятно, будет инте ресно изучить их самостоятельно. Иногда в качестве аль­ тернативы ко мбинированному списку можно выбрать элемент управления типа ChoiceBox. Им нетрудно манипулировать, поскольку у него немало общего с эле­ ментами управления ListView и СоmЬоВох.
Гл ава 35. Эл ементы управления JavaFX 1263 Кла сс TextField Безусловно, рассмотренные выше эл ементы управления очень уд обны и не­ редко присутствуют в ГПИ, тем не менее все они реализуют средства для выбора предопределенного варианта или действия. Но иногда пользователю требуется предоставить возможность ввести избранную им символьную строку. Для ор­ ган изации такого типа ввода пользовательских данных в J avaFX предусмотрено несколько текстовых элементов управления. К их числу относится рассматрива­ емый здесь эл емент управления типа TextField. Та кой элемент управления по­ зволяет ввести одну текстовую строку, а следовател ьно, он уд обен для ввода имен, идентификаторов, адресов и прочих аналогичных данных. Как и все остальные классы текстовых эл ементов управления , класс Text Field наследует от класса Text inputControl, в котором определяется бальшая часть фун кциональных воз­ можностей для ввода текста. В классе TextField определяются два конструктора. Первым из них является конструктор по умолчанию, создающий пустое текстовое поле стандартных раз­ меров. А второй конструктор позволяет указать исходное содержимое текстового поля. Здесь и далее будет использоваться конструктор по умолчанию. Хотя стандартных размеров текстового поля иногда оказывается достаточно, зачастую его размеры требуется указывать явным образом. Для этого нужно вы­ звать метод setPrefColumnCount (),общая форма которого приведена ниже , где параметр стол бцы служит для определения размеров текстового поля в элементе управления типа TextField. final void setPrefColWDnCount (int c�OJ J бцu) Вызвав метод setText (), можно задать текст, отображаемый в текстовом поле, а вызвав метод getText () - получить текст из текстового поля . Помимо этих ос­ новных операций с текстовым полем, в эл ементе управления типа TextField поддерживается ряд других функциональных возможностей, которые полезно из­ учить самостоятельно, в том числе операции вырезания, вставки, присоединения , а также выделения части текста под управлением прикладной программы. Особенно полезной в элементе управления типа TextField является возмож­ ность задать наводящее сообщение (т.е. подсказку) в текстовом поле, чтобы поль­ зователь не пытался воспользоваться пустым полем. Для этого достаточно вызвать метод setPromp tText (), общая форма которого приведена ниже. final void setpromptText ( String C!Fp()xa) В данном случае параметр строка обозначает символьную строку, отображае­ мую в текстовом поле, если в нем отсутствует введенный текст. Эта строка отобра­ жается светлым (например, светло-серым) цветом. Когда пользователь нажимает клавишу <Enter> в текстовом поле эл емента управления типа TextField, генерируется событие действия. Зачастую это собы­ тие обрабатывается, но иногда в прикладной программе проще получить введен­ ный текст по мере надоб ности , чем обрабатывать события действия от текстовых полей. Оба способа демонстрируются в приведенном ниже примере, где созда­ ется текстовое поле , в котором вводится строка запроса на поиск информации.
126, Часть IV. Введение в программирование ГПИ с р едства ми JavaFX Когда фокус ввода находится в текстовом поле и пользователь нажимает клавишу <Enter> или щелкает на кнопке Get Search Stri ng (Получить строку запроса на по­ иск) , введенная символьная строка извлекается и отображается. Кроме то го, тек­ стовое поле снабжается наводящим сообщением. 11 Продемонстрировать применение текстового поля import javafx . appl ication .*; import java fx . scene .*; import javafx .stage .*; import javafx .scene . layout .*; import javafx .scene . cont rol .*; import javafx . event .*; import javafx . geometry .*; puЫ ic class TextFieldDemo extends Application { TextField tf; Label re sponse ; puЫic static void main ( String (] args ) { 11 запустить Jаvа Е'Х-приложение , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод •tart () puЫ ic void start ( Stage myStage ) 11 пр исвоить заголовок подмосткам myStage .setTitle ("Demonstrate а TextFi eld" ); 11 Продемонстрировать элемент упр авления типа TextField 11 Исполь зовать панель поточной компоновки FlowPane 11 в каче стве корне вого узла . в данном случае с 11 промежутками 10 по вертикали и по горизонтали FlowPane rootNode = new FlowPane (lO, 10) ; 11 выровнять э лементы управления по центру сцены rootNode .setAl ignment (Pos . CENTER) ; 11 создать сцену Scene myScene = new Scene ( rootNode , 230, 140) ; 11 установить сцену на подмостках myS tage .setScene (myScene ); 11 создать ме тку, извещающую о содержимом текстового поля response = new Label ("Search String : ") ; 11 Строка запроса на поиск информации 11 создать кнопку дл я получения текста Button btnGe tText = new Button ("Get Search String" ); 11 Получи ть строку запроса на поиск информации 11 создать текстовое поле tf = new TextField () ; 11 задать подсказку tf. setPromptText ("Enter Search String" ); 11 В вести строку запроса на поиск информации
Гл ава 35. Элементы управлен и я JavaFX 11 задать предпочтительное количество столОцов tf.setPre fColurn n Count (l5) ; 11 Обработать события действия от текстового поля . 11 События действия генерируются при нажатии клавиши 11 <ENTER> , когда фокус ввода находится в текстовом поле . 11 В таком случае получается и отображается текст , введенный 11 в текстовом поле tf .setOnAction (new EventHandler<Ac tionEvent> () { puЫic void haпdle (ActionEvent ае ) { response .setтext ("S earch String : "+tf . getText {) ); 11 Строка запроса на п оиск информации } }); 11 получить текст из текстового поля , если нажата 11 клавиша <ENTER> , а затем отобразить его btnGetText .зetOnAction (new EventHandler<Ac tionEvent> () puЫ ic void handle (ActionEvent ае ) { response .setText ( "Search String : "+tf.getText () ); 11 Строка запроса на поиск информации } }); 11 исполь зовать разделитель , чтобы улучшить порядок 11 расположения э лементов упр авления Separator separator = new Separator () ; separator .setPrefWidth (lBOJ ; 11 ввести все элементы управления в граф сцены rootNode .getChildren () .addAll (tf, btnGetText , separator, reзponse ); 11 показать подмостки и сцену на них my Stage .show() ; На рис. 35 . 15 показан результат ввода строки запроса на поиск информации в текстовом поле окнаJаvаFХ-приложения из данного примера. Search String: Тор Pnonty 1265 К числу других текстовых элементов управ­ ления , которые полезно изучить самостоятель­ но, относятся класс TextArea для ввода много­ строчного текста, а также класс PasswordField для ввода паролей. Полезным может также ока­ заться класс HTMLEditor, реализующий редак­ тор НТМL-разметки. Рис. 35 .1 5. Резул ьтат ввода строки запроса в текстовом поле окна приложения Text FieldDemo Кл а сс ScrollPane Содержимое элемента управления порой не вмещается в тех пределах, которые отведены данному элементу управления на экране. Характерными тому примера­ ми могут служить крупное изображение, не вписывающееся в разумные границы, или дл инный текст, который требуется отобразить в небольшом окне . Для подоб­ ных случаев узлы графа сцены вJavaFX снабжаются полосами прокрутки. С этой
1266 Часть IV. Введение в п рограммирование ГПИ средствами Java FX целью соответствующий узел заключается в оболочку класса ScrollPane, и тогда такой узел автоматически наделяется полосами для прокрутки его содержимого, не требуя больше ничего от программиста. Благодаря универсальности класса Scrol l Pane пользоваться отдельными эл ементами управления полосам прокрут­ ки придется крайне редко. В классе Scroll Pane определяются два конструктора. Первым из них является конструктор по умолчанию, а второй конструктор позволяет указать узел, кото­ рый требуется прокручивать. Этот конструктор объявляется следующим образом: ScrollPane (Node содерж1 1r иое) В данном случае параметр содержимо е обозначает прокручиваемые данные. Если же применяется конструктор по умолчанию, то для ввода прокручиваемого узла следует вызвать метод setContent ().Ниже приведена общая форма данного метода. final void setContent (Node coдepJDr.Noe) Как только будет задано прокручиваемое содержимое , следует ввести панель прокрутки в граф сцены. В итоге содержимое может быть прокручено, когда оно отображается. На эаме'IКуl Чтобы изменить содержимое, прокручиваемое на панел и прокрутки, можно также вы­ звать метод setContent ().В итоге прокручиваемое содержи мое может быть изменено во время выполнения прикладной программы. Как правило , размеры окна просмотра задаются явным образом, хотя по умол­ чанию выбираются стандартные размеры. Окно просмотра представляет собой просматриваемую область панели прокрутки , где отображается прокручиваемое содержимое. Та ким образом, в окне просмотра отображается видимая часть со­ держимого , а полосы прокрутки позволяют прокручивать его в пределах окна про­ смотра, изменяя тем самым видимую часть содержимого . Вызвав один из следующих методов, можно задать размеры окна просмотра: final void setPrefViewportHeiqht (douЫe JUZco�a) final void setprefViewportWidth (douЫe вorpJtНa ) В стандартном режиме работы элемент управления типа ScrollPane динами­ чески вводит или уд аляет полосы прокрутки по мере надобности . Та к, если ком­ понент оказывается выше, чем окно просмотра, в последнее автоматически вво­ дится вертикальная полоса прокрутки . А если компонент полностью вписывается в окно просмотра, то полосы прокрутки уд аляются из окна просмотра. Замечательной особенностью элемента управления типа ScrollPane является его способность панорамировать содержимое перетаскиванием мыши. По умол­ чанию режим панорамирования отключен. Чтобы включить его , следует вызвать метод setPannaЫe (), общая форма которого показана ниже . Если параметр разрешение принимает логическое значение true, то панорамирование разреша­ ется, а иначе оно запрещается. final void setPan n aЫe (boolean раsрешеюrе)
Гл ава 35. Эл ементы управления Java FX 1267 Вызвав методы setHvalue ( ) и setVva lue (), можно установить расположение полос прокруrки под управлением прикладной программы. Ниже приведены об­ щие формы этих методов. fina1 void aetвvalue (douЫe нпr) fina1 void aetVValue (douЫe Нl1В) Новая позиция полос прокругки по горизонтали обозначается параметром НПГ, а по вертикали - параметром НЛВ. По умолчанию позиции полос прокругки по горизонтали и по вертикали начинаются с нуля . В элементе управления типа ScrollPane поддерживаются и другие возмож­ ности. В частности, можно задать минимальные и максимальные позиции полос прокруrки , а также установить правила их появления в определенный момент. А вызвав методы getHva lue ( ) и ge tVva lue (),можно получить текущие позиции полос прокруrки по горизонтали и по вертикали соответственно. Применение элемента управления типа Scrol l Pane демонстрируется ниже на примере прокрутки многострочной метки . При этом разрешается также пано­ рамирование прокручиваемого содержимого. 11 Продемон стрировать приме нение панели прокрутки . 11 В данном Jаvа FХ-прилоzении прокручивается содержимое 11 многострочной ме тки , хотя прокручиваться моzе т любой 11 узел графа сцены import java fx . application .*; import java fx . scene .*; import java fx .stage .*; import java fx .scene . layout .*; import javafx .scene . control .*; import javafx . event .*; import java fx . geometry .*; puЫ ic class ScrollPaneDemo extends Application { ScrollPane scrlPane ; puЫ ic static void main (String [] args ) { 11 запустить JаvаFХ- приложе ние , вызвав ме тод launch () launch (args) ; 11 переопредели ть ме тод start () puЫic vo id start ( Stage myS tage ) 11 пр исвоить заголовок подмосткам myStage .setTitle ("Demonstrate а ScrollPane") ; 11 Продемонстрировать элемент управления ScrollPane 11 Использовать панель поточной компоновки FlowPane Fl owPane rootNode = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены rootNode .setAl i gnment (Pos . CENTER) ; 11 создать сцену Scene myScene = new Scene ( rootNode , 200, 200) ;
1268 Часть IV. Введение в п рогра ммир ова ние ГПИ средства ми Java FX 11 установить сцену на подмостках myStage .setScene (myScene }; 11 создать многострочную пр окр учиваемую ме тку, где 11 отмечаются преимуще ства элемента управления ScrollPane 11 над отдельными элементами управления полосами прокрут ки Label scr!Labe l = new Label ( "А Scrol!Pane streaml ines the process of\n" + "adding scroll bars to а window whose\n" + "contents exceed the window ' s dimensions .\n" + "It also enaЫes а control to fit in a\n" + "sma ller space than it otherwi se would .\n" + "Аз such, it often provides а supe rior\n" + "approach ove r using individual scroll bars."} ; 11 создать панель прокрутки , установив в качестве 11 содержимо го метку scrlLaЬel scr!Pane = new ScrollPane (sc rlLabel }; 11 задать ширину и высоту окна просмотра scrlPane .setPrefVi ewportWidth (lЗO} ; scrlPane .setPrefViewportHeight (80} ; 11 разрешить панорамирование прокручиваемого содержимо го scrlPane .se tPannaЬle (tru e} ; 11 создать кнопку сброса Button ЬtnReset = new Button ("Reset Scroll Bar Positions ") ; 11 Установить полосы прокрутки в исходное положение 11 обработать соОытия действия от кнопки сОроса btnReset .setOnAction (new EventHandler<ActionEvent> () puЬlic void handle (ActionEvent ае } { 11 установить полосы прокрут ки на нулевые позиции scrlPane .setVvalue (O} ; scrlPane .setHva lue (O} ; } }); 11 ввести ме тку и кнопку сброса в граф сцены roo tNode .getChildren (} .addAl l ( scrlPane , btnReset ); 11 показать подмостки и сцену на них myS tage .show () ; :roll Ь. . rs to а window l!J<Ce<!d�wmdow's1 оЫеs • control to frt ir рос" !Мn rt oth"1Wl.5<0 v > j Res"' Scroll Bar Pos1tJons J На рис. 35. 16 показан вид многострочной метки с по­ лосами прокрутки и кнопкой их установки в исходное положение в окне JаvаFХ-приложения из данного при­ мера. Рис. 35.16 . Вид многострочной метки с полосами прокрутки в окне npилoжeния scrollPaneDemo
Гл ава 35. Эл ементы управления JavaFX 1269 Кл а сс TreeView Одн им из самых сложных в JavaFX считается элемент упрамения TreeView. Он реализует иерархическое предстамение данных в древовидной форме. В дан­ ном контексте термин иерархи11еский означает, что одни элементы древовидной структуры подчиняются други м. Например , древовидная иерархия часто применя­ ется для отображения содержимого файловой системы. В этом случае отдельные файлы подчиняются каталогу, который их содержит. В элементе упрамения типа TreeView отдельные ветви древовидной структуры могут разворачиваться или сво­ рачиваться по требованию пользователя . Это дает возможность представить иерар­ хические данные в компактной, но развертываемой по мере надобности форме. И хотя элемент упрамения типа TreeView поддерживает разнообразную специаль­ ную настройку, зачастую вполне досгаточно и стандартного стиля и возможностей древовидного предстамения данных. Та ким образом , деревья достаточно просты в применении, хотя и поддерживают довольно сложную иерархическую структуру. В элементе управления типа TreeView реализуется принципиально простая древовидная структура, которая начинается с одного ко рневого узл а, обозначаю­ щего начало дерева. Под корневым узлом располагается оди н или несколько n(}­ рожден'Н'ЫХ узлов. Имеются два типа порожденных узлов: ко-нцев'Ьlеузлы, называемые иначе О'КО'НСЧ'Н'ЫМU и не имеющие порожденных узл ов, и узлъt ветвления, образующие корневые узлы поддерееъев. Поддерево представляет собой обычное дерево , являю­ щееся частью более крупного дерева. Последовательность узлов, простирающаяся от корневого узла к указанному узлу, называется nymi!М. Одной из удо бных особенностей элемента управления типа TreeView явля­ ется автомати ческое предоставление полос прокрутки , когда размеры дерева превышают размеры представления. Если полностью свернутое дерево может быть достаточно маленьким, то его развернутая форма - достаточно круп ной. Автоматически снабжая дерево полосами прокрутки , элемент управления типа TreeView позволяет использовать меньше полезного пространства экрана, чем обычно требуется. Класс TreeView является обобщенным и определяется следующим образом: class TreeView<T> где параметр Т обозначает тип значения , находящегося в узле дерева. Зачастую это значения типа String. В классе TreeView определяются два конструктора. Ниже приведен применяемый здесь и далее конструктор. TreeView (Treei tem<T> xopнeв ai i _ yse.1I) В данном случае параметр корневой_ узел обозначает корень дерева . Это еди нственный параметр , который требуется передать конструктору класса типа TreeView, поскольку все узлы дерева происходят от корневого узла. Узл ы, образующие дерево , являются объектами типа Tree itern. Прежде всего следует заметить, что класс Tree i tern не наследует от класса Node. Поэтому объ­ екты типа Treeitem не являются объектами общего назначения. И хотя их можн о использовать в элементе управления типа TreeView, они не являются автономны­ ми элементами управления . Класс Tree I tem является обобщенным и объявляется
1270 Часть IV. Введение в п рограммирование ГПИ средства м и Java FX приведенным ниже образом, где параметр Т обозначает тип значения, находяще­ гося в объекте типа Treel tem. class Treeitea<'l> Прежде чем воспользоваться элементом управления типа TreeView, следу­ ет построить дерево , которое он должен отображать. Для этого нужно сначала создать ко рневой узел дерева, а затем добавить к нему другие узлы. С этой целью вызывается метод add () или addAll () для списка, возвращаемого методом get Children (). Добавляемые узлы могут быть концевыми узлами или поддеревьями. Как только дерево будет построено, можно создать объект класса TreeView, пере­ дав его конструктору корневой узел в качестве параметра. События выбора, наступающие в элементе управления типа TreeView, могут быть обработаны таким же образом, как и в элементе управления типа ListView, с помощью приемника событий изменения. Для этого следует сначала получить модель выбора, вызвав метод getSelectionModel (), а затем свойство выбранно­ го элемента, вызвав метод selectedltemProperty (),инаконец ввести приемник событий изменения, вызвав метод addListener ( ). Всякий раз, когда делается выбор, приемнику событий changed ( ) в качестве нового значения передается ссЬVJка на вновь выбранный элемент. (Более подробно этот процесс описан ра­ нее при обсуждении обработки событий изменения в элементе управления типа ListView.) Значение элемента типа Treel tem можно получить, вызвав метод getValue (). Кроме того, можно последовать по пути к элементу, перемещаясь по дереву в пря­ мом или обратном направлении. В частности , вызвав метод ge tParent (),можно дойти до родительского узла, а вызвав метод getChildren () - до порожденных узлов. В приведенном ниже примере демонстрируется построение и применение дерева типа TreeView, представляющего иерархию пищевых продуктов. В узлах этого дерева находятся элементы типа символьных строк. Корневой узел дерева обозначен меткой Food (Пища) , а под ним располагаются три производных узла: Fruit (Фрукты ), Ve getaЫes (Овощи) и Nuts (Орехи). Ниже узла F ruit располагаются три порожденных узла: Apples (Яблоки), Pears (Груши) и Oranges (Апельсины), а ниже узла Apples - три концевых узла с назван иями сортов яблок Fuji (Фудзи ), Winesap (Уайнсэп) и Jonathan (Джонатан). Всякий раз, когда элемент выбирается из дерева, он отображается вместе с путем к нему от корневого узла дерева. С этой целью неоднократно вызывается метод getParent (). 11 Продемонстрировать применение элемента управления TreeView import javafx . application .*; import java fx . scene .*; import java fx .stage .*; import java fx .scene . layout .*; import java fx .scene . control .*; import java fx . event .*; import java fx . beans .value .*; import java fx . geometry .*; puЫ ic class TreeVi ewDemo extends Appl ication {
Гл ава 35. Эл е менть1 управления Java FX Label re sponse; puЫ ic static void ma in (String (J args ) { 11 запустить Jаvа FХ-приложение , выз вав ме тод launch () launch (args) ; 11 переопределить ме тод atart () puЫ ic void start ( Stage myS tage ) 11 присвоить заголовок подмосткам myS tage .setTitle ("Demon strate а TreeView" ); 11 Продемонстрировать элемент управления TreeView 11 Исполь зовать панель поточной компоновки FlowPane 11 в каче стве корне вого узла . В данном случ ае с 11 промежут ками 10 по вертикали и по горизонтали Fl owPane rootNode = new FlowPane (lO, 10) ; 11 выр овнять элементы управления по центру сцены rootNode .setAl i gnme nt (Pos . CENTER) ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 310, 460) ; 11 установить сцену на подмостках myS tage .setScene (myScene ); 11 создать ме тку, извещающую о состоянии элемента , 11 выбранно го из дерева response = new Label ("No Selection" ); 11 Ничего не выбрано 11 создать узлы дере ва, начиная с корневого узла Treeitem< String> tiRoot = new Treeitem<String> ("Food" ); 11 ввести поддеревь я, начиная с узла фруктов Treeitem< String> tiFruit = new Treeitem<String> ("Fruit") ; 11 построить узел яблок Treeitem< String> tiApples = new Treeitem< String> ( "Apples" ); 11 ввести порожденные узлы сортов яблок в узел яблок tiApp les . ge tChildren () .add (new Tree item< String> ("Fuj i" )); tiApples . getChildren () .add (new Treeitem< String> ("Winesap" ) ); tiApp les . ge tChildren () .add (new Treeitem< String> ("Jonathan" )); 11 ввести порожденные узлы видов фруктов в узел фруктов tiFruit . getChi ldren () .add (ti Apples) ; tiFruit . getChildren () .add (new Treeitem< String> ("P ears" )); tiFruit . getChi ldren () .add (new Treeitem< String> ("O ranges" ) ); 11 и наконец, вве сти узел фруктов в корневой узел tiRoot . getChildren () .add(tiFruit) ; 11 а теперь ввести анало гичным образом узел овощей Treeitem< String> tiVegetaЫes = new Treei tem<String> ( "VegetaЫes ") ; tiVege taЫes . getChildren () .add (new Treeitem< String> ("Corn ") ); tiVegetaЫes . getChildren () .add (new Treeitem<String> ("P eas") ); tiVegetaЫe s.getChi ldren () .add (new Treeitem<String> ("B roccoli") ); tiVege taЫes . getChildren () .add (new Treeitem<String> ("Beans" )); tiRoot . getChildren () .add ( tiVe getaЫes ); 1271
1272 Часть IV. Введение в программирование ГП И средства ми JavaFX 11 и наконец, ввести аналогичным образом узел орехов Tree i tem<String> tiNut s = new Treeitem< String> {"Nuts") ; tiNuts . getChildren {) .add {new Tree!tem<String> { "Walnuts") ); tiNuts . getChildren {) .add { new Treei tem<String> {"Peanuts") ); tiNu ts . getChildren {) .add { new Treeitem<String> ("Pecans" )); tiRoot . getChildren () .add(tiNuts ); 11 создать древ овидное представление , исполь зуя только 11 что построенное дерево TreeView<String> tvFood = new TreeView<String> (ti Root) ; 11 получи ть модель выбора для др евовидного представления Mu ltipl eSelectionModel<Treel tem<String>> tvSelModel = tvFood .getSelectionМode l () ; 11 исполь зовать приемник событий изменения , чтобы оперативно 11 реагировать на выбор элементов в дре вовидном пр едставлении tvSe lModel . зelecteditemProperty () .addListener ( new ChangeLi stener<Treeitem< String>> {) puЫic void changed { ) }); Ob servaЫ eValue<? extends Treeltem<String>> changed, Treeitem<String> oldVal , Tree!tem< String> newVal) ( 11 отобразить выбранный элемент и полный путь от 11 него к корневому узлу if{newVal != null) { } 11 пос троить весь путь к выбранному элементу String path = newVal .getValue {) ; Tree ltem< String> tmp = newVal .getParent {) ; while {tmp != null) { path= tmp.getValue{) + " -> " + path; tmp = tmp.ge tPa rent {) ; 11 отобразить выбранный элемент и полный путь к нему response .setText { "Selection is " + newVal .getValue {) + "\пComplete path iз " + path) ; 11 Выбран ука занный элемент 11 Полный путь к нему 11 ввести элементы управления в граф сцены rootNode .getChildren {) . addAll { tvFood , response ); 11 показать подмостки и сцену на них myStage .show{) ; На рис. 35. 17 показан вид древовидного представления пищевых продуктов в окнеJаvаFХ-приложения из данного примера. В данном примере особое внимание необходимо обратить на две особенности. Во-первых, это порядок построения дерева. Сначала создается корневой узел де­ рева, как показано ниже. Treeitem< String> tiRoot = new Treeitem<String> {"Food" ); Затем строятся узлы, располагающиеся ниже корневого узла. Эти узлы образу­ ют корни следующих поддеревьев: фруктов, овощей и орехов. Далее к этим под-
Гл ава 35. Элементы управления Java FX 1273 деревьям добавляются листья . Но одно из них (поддерево фруктов) содержит другое поддере­ во - сортов яблок. Суть здесь в том , что каждая ветвь дерева приводит к листу или корню под­ дерева. Как только все узлы дерева будут постро­ ены , корневые узлы каждого поддерева добав­ ляются к корневому узду дерева. В качестве при­ мера ниже показано , каким образом поддерево орехов добавляется к корневому узлу tiRo ot. Аналогичным образом любой порожденный узел добавляется к своему родительскому узлу. tiRoot . getChildren () .add ( tiNuts ) ; И во-вторых, это порядок построения пути от корневого узла к выбранному узлу в обрабо'Г' чике событий изменения. Ниже показано, как это делается. String path = newVal . getValue (); Tree itern< String> trnp = newVal . getParent () ; while(trnp != null) { path = trnp.getValue() + " -> " + path; trnp = trnp .getParent () ; .,... ·-· ,.,. -- .... . •V.. .. .. .. . Com "" .. . " • Nub w"" "' "..... . """" 17"·-� Selкtioa dW""'"f' �pl lth kfo o d-�Fшt-:>�-)� Рис. 35.17. Вид древовидного пред­ ставления пищевых продукто в в окне приложения TreeViewDerno Этот фрагмент кода действует следующим образом. Сначала получается зна­ чение из вновь выбранного узла дерева. В данном примере это строковое зна­ чение с именем узла. Полученная в итоге символьная строка присваивается строковой переменной ра th. Затем создается временная переменная tmp типа Treeite m< String>, которая инициализируется ссьшкой на родительский узел вновь выбранного узла дерева. Если у этого узла отсутствует родительский узел, то переменной tmp присваивается пустое значение nul l. В противном случае начи­ нает выполняться цикл, в котором к содержимому переменной path добавляется значение из каждого родительского узла (в дан ном случае имя этого узла). Этот процесс продолжается до тех пор, пока не будет достигнут корневой узел дерева, у которого отсутствует родительский узел. Выше бьш представлен лишь самый основной порядок обращения с элемен­ том управления типа TreeView. Следует, одн ако , иметь в виду, что этот элемент управления поддерживает разнообразную специальную настройку. Поэтому все потенциальные возможности элемента управления типа TreeView вам придется изучить самостоятельно. Эффекты и преобразования Гл авное преимущество кapкacajavaFX заключается в том , что он позволяет из­ менять внешний вид элементов управления (или уз.лов графа сцены) с помощью применяемых эффектов и/или выполняемых преобр(],Зований. Эффекты и преоб­ разования придают ГПИ изощренный современный внешний вид, который уже
127' Часть IV. Введение в п рограммирование ГПИ средствами JavaFX привыкли ожидать пользователи. И хотя рассмотрение каждого эффекта и пре­ образования, поддерживаемого в JavaFX, выходит за рамки данной книги, в этом разделе дается некоторое представление о тех преимуществах, которые они дают. Эффекты Эффекты поддерживаются в абстрактном классе Effect и его конкретных под­ классах, входящих в состав пакета j avafx . scene . effect. Применяя эти эффек­ ты, можно специально настроить внешний вид узлов в графе сцены. Имеется це­ лый ряд встроенных эффектов. В табл. 35 . 1 перечислены лишь некоторые из них. Табл ица 35.1. Избранные эффекты иэ класса Ef'f'ect Эффект BlOOID BoXВlur DropShadow Glow InnerShadow Lighting Reflection Описание Увел ичивает яркость самых ярких частей узла Размывает узел , делая его нерезким Огображает падающую тень позади узла Воспроизводит эффект свечения Огображает тень внутри узла Создает теневые эффекты источ ника света Воспроизводит отражен ие Эти и другие эффекты просты в применении и доступны для использования с любым объектом типа Node , включая и элементы управления. Разумеется, одни эффекты подходят отдел ьным элементам управления больше, чем другие. Чтобы задать эффект для узла, следует вызвать метод setEffect ( ),определяе­ мый в классе Node. Ниже приведена общая форма данного метода. final void setEffect (Effect эффех�) Здесь параметр эффект обозначает применяемый эффект. Если же никакого эффекта применять не требуется, то в качестве этого параметра следует пере­ дать пустое значение null. Следовательно, чтобы применить эффект к узлу, сна­ чала нужно создать экземпляр объекта типа Effect, а затем передать его методу setEffect () в качестве параметра. Как только это будет сделано, эффект станет применяться к узлу всякий раз, когда он воспроизводится , при условии, что он поддерживается исполняющей средой. Чтобы продемонстрировать потенциаль­ ные возможности эффектов, здесь и далее будут подробно рассмотрены два из них: свечение (класс Glow) и внутренняя тень (класс InnerShadow) . Но процесс применения, по существу, одинаков для всех эффектов. Класс Glow воспроизводит эффект, придающий узлу светящийся внешний вид. Степень свечения можно регулировать вручную. Чтобы применить эффект свече­ ния , нужно создать сначала экземпляр класса Glow. Ниже приведен применяемый здесь и далее конструктор этого класса, где параметр уров ень_ св е чения обозна­ чает степень свечения в пределах от О.О до 1.О. Glow (douЬle уро.81П._саеv8На'я) Как только будет создан экземпляр класса Glow, уровен ь свечения можно из­ менить, вызвав метод setLevel (). Ниже приведена общая форма данного мето-
Гл ава 35. Эл ементы уп равления JavaFX 1275 да, где параметр уровень_ свечения, как и прежде , обозначает степень свечения впределахотО.Одо1.О. f'inal void setLevel (douЫe yposeнъ_cseveюrR) Класс Inne rShadow воспроизводит эффект, имитирующий тень внутри узла. В этом классе предоставляется целый ряд конструкторов. Ниже приведен приме­ няемый здесь и далее конструктор данного класса. InnerShadov (douЬle радиус , Color ожрасжа_ �еюr) Здесь параметр радиус обозначает указанный радиус те ни внутри узла. По су­ ществу, радиус те ни определяет ее размеры. А параметр окра ска_ тени обознача­ ет цвет, в который окрашивается те нь. Этот параметр относится к типу Color, т. е. к классу javafx . scene . paint . Color. В этом классе определяется целый ряд констант, описывающих отдел ьные цвета , например Color . GREEN, Color . RE D и Color . BLUE, что упрощает задание цвета. П реоб разования Преобразования поддерживаются в абстрактном классе Trans fo rm, входящем в состав пакета java fx . scene .tr ans f orm. У него имеются четыре конкретных подкласса, Ro tate, Scale, Shear и Translate, для выполнения преобразований вращен ием, масштабированием, наклоном и перемещением соответственно. Имеется также подкласс Af fine , но, как правило, применяются перечисленные выше подкласс ы преобразований в определенном сочетании. Это означает, что над узлом можно выполнить несколько преобразований, например, вращать или масштабировать его. Ниже подробно рассматриваются те преобразования, кото­ рые поддерживаются в классе Node . Чтобы выполнить над узлом преобразование, его можно, например, ввести в список преобразований, поддерживаемых в дан ном узле. Для получения этого списка следует вызвать метод getTransforms {), определяемый в классе Node. Ниже приведена общая форма данного метода. f'inal ObservaЬleList<Transf'orш> qetтransf'orшs () Этот метод возвращает ссылку на список преобразований. Чтобы ввести пре­ образование в этот список, достаточно вызвать метод add ( ) , чтобы очистить спи­ сок - метод clear {), а для того чтобы удалить конкретный эл емент из списка - метод remove { ) . Иногда преобразование можно указать непосредственно , установив свойства класса Node. Например, установить угол поворота узла с точко й вращения в его центре можно, вызвав метод setRota te {) и передав ему требуемое значение угла; задать мас штаб - вызвав методы setScaleX {) и setScaleY ( ), а переместить узел - вызвав методы setTrans lateX {) и setTranslateY {).Но намного уд обнее пользоваться списком преобразований, как будет продемонстрировано далее . На заметку! Любые преобразования, указанные для узла непос редствен но, будут выполняться только после всех преобразований из списка.
1276 Ч асть IV. Введение в программировани е ГПИ средств ами JavaFX Для демонстрации преобразований здесь и далее будут использованы классы Rotate и Scale. Аналогичным образом выполняются и остальные преобразо­ вания . В классе Ro ta te поддерживается вращение узла вокруг указанной точки. В этом классе поддерживается несколько конструкто ров. Ниже в качестве приме­ ра приведен один из них. Rotate (douЫe угоп , douЫe х, douЫe у) Здесь параметр угол обозначает количество градусов, на которое следует по­ вернуть узел, а центр вращения, иначе называемый m(JЧкой вр ащения, определяется параметрами х и у. Все эти параметры вращения можно установить и после созда­ ния объекта типа Ro ta te с помощью конструктора по умолчанию, как демонстри­ руется в приведенном далее примере. Для этой цели достаточно вызвать методы setAngle (), setPivotX ( ) и setPivo tY ( ) , общие формы которых приведены ниже . Как и прежде, параметр угол обозначает количество градус ов, на которое следует повернуть узел , а центр вращения определяется параметрами х и у. final void setAn9le (douЬle угоп) final void setPivotx (douЬle х) final void setPivotY (douЬle у) В классе Scale поддерживается масштабирование узла по указанному масштаб­ ному коэффициенту. В этом классе определяется несколько конструкторов. Ниже приведен применяемый здесь и далее конструктор. Scale (douЬle наси!l'аб_по_11D1ржн в, douЬle насш!l'аб_по_JWCO!l'e) Здес ь параметр ма сшта б_по_шир ине обозначает коэффициент для масштаби­ рования узла по ширине, а параметр ма сшта б_ по_ высоте - коэффициент для его масштабирования по высоте . Эти масштабные коэффициенты можно изменить после создания экземпляра класса Scale, вызвав методы setX () и setY (), общие формы которых приведены ниже. Как и прежде , параметр ма сшта б_ по_ширине обозначает коэффициент для масштабирования узла по ширине, а параметр ма сшта б_ по_ высо те - коэффициент для его масштабирования по высоте . final void setx (douЬle наси!l'аб по .l lDlr.PJI Н S) final void setY (douЬle нaCDr!l'ad:пo:JWco!l'e) Де м онстра ция э ффекто в и преобразований Effкts and Tran�fom>$ Demo В приведенном ниже примере демон­ стрируется применение эффектов и выпол­ нение преобразований. С этой целью созда­ ются четыре кнопки - Rotate (Вращен ие) , Scale ( Масштабирование), Glow (Свечение) и Shadow (Тень) . Всякий раз , когда нажима- ется одна из этих кнопок, применяется со­ Рис. 35.18. Эффекты и преобразования ответствующий эффект или выполняется над кнопками указанное преобразование над выбранной кнопкой, как показано на рис. 35 . 18 .
Гл ава 35. Эл ементы управл ения Java FX 1277 Ан ализируя исходный код JаvаFХ-приложения из данного примера, вы сможе­ те сами убедиться, насколько просто осуществить специальную настройку ГПИ своего приложения . Поэкспериментируйте с JаvаFХ-приложением из данного примера, опробуя другие виды преобразований или эффектов или применяя их к другим типам узлов, кроме кнопок. 11 Продемонс трировать вращение , ма сштабирование , 11 свечение и внутреннюю тень import java fx . appl ication .*; import java fx . scene .*; import java fx .st age .*; import java fx .scene . layout .*; import java fx .scene . control .*; import java fx . event .*; import java fx . geometry .*; import java fx .scene . transform. *; import java fx . scene .ef fect.*; import java fx .scene . paint .*; puЫic class EffectsAndT ransforms Demo extends Ap plication { douЫe angle = О.О; douЬle glowVa l = О.О; boolean shadow = false ; douЫe scaleFactor = 1.0; 11 создать первоначаль ные эффекты и преобразования Glow glow = new Glow(0.0) ; InnerShadow innerShadow = new InnerShadow (lO.O, Color .RED) ; Ro tate rotate = new Ro tate () ; Scale scale = new Scale ( scaleFactor , scaleFactor); 11 создать четыре экранные кнопки Button btnRot ate = new Button ("Rotate") ; Button btnGlow = new Button ("Glow" ); But ton btnShadow = new Bu tton ("Shadow off") ; Button btnScale = new Button ("S cale") ; puЫ ic static void main (String (] args ) 11 запус тить Jаvа FХ-приложение , вызвав ме тод launch () launch (args) ; 11 пере определить ме тод start () puЫ ic void start ( Stage myS tage ) 11 прис воить заголо вок подмосткам myS tage .setTitle ( "Effects and Transforms Demo" ) ; 11 Демонстрация эффе ктов и пре образований 11 Исполь зовать панель поточной компоновки FlowPane 11 в качестве корне вого узла . В данном случае с 11 промежутками 10 по вертикали и по горизон тали FlowPane rootNode = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены rootNode .setAl i gnme nt (Pos . CENTER) ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 300, 100) ;
1278 Часть IV. Введение в программирование ГПИ средствам и Java FX // установить сцену на подмостках myStage .se tScene (myScene ); 11 задать первоначальный эффект свечения btnGlow. setEffect (glow) ; 11 ввести вращение в список преобра зований для кнопки Rotate btnRo tate . getTrans forms () .add ( rotate ); // ввести ма сштабирование в список преобразований дл я кнопки Scale btnScale . getTrans forms () .add (scale) ; // обработать события действия от кнопки Rotate btnRotate . setOnAction (new EventHandler<Ac tionEvent> () puЫ ic vo id handle (ActionEvent ае ) { ) )); 11 Всякий раз , когда кнопка нажимается , она поворачивается // на 30 градусов вокруг своего центра . angle += 30 .О; rotate .setAn gle (angle ); rotate .setPivotX (btnRotate . getWidth ()/2) ; rotate .setPivotY ( btnRotate . getHeight ()/2) ; 11 обработать события действия от кнопки Scale btnScale . setOnAct ion (new EventHandler<ActionEvent>() puЫ ic void handle (ActionEvent ае ) { ) )); 11 Всякий раз , когда кнопка нажимается , изменяется ее ма сштаб scaleFactor += 0.1; if ( scaleFactor > 1.0) scaleFactor = 0.4; scale .setX ( scaleFactor ); scale .setY ( scaleFactor ); 11 обработать события действия от кнопки Glow btnGlow .se tOnAc tion (new EventHandler<ActionEvent> () puЫ ic void handle (ActionEvent ае ) { ) )); // Всякий раз , когда кнопка нажимается, изменяется 11 степень ее свечения glowVal += 0.1; if ( glowVal > 1.0) glowVal = О.О; 11 установить новое значение свечения glow . setLevel ( gl owVal ); 11 обработать события действия от кнопки Shadow btnShadow .setOnAction (new EventHandler<Ac tionEvent> () puЫ ic void handle (ActionEvent ае ) { 11 Всякий раз , когда кнопка нажимается , изменяется 11 состояние ее затенения shadow = !shadow ; if(shadow) { btnShadow .setEffect ( inner Shadow) ; btnShadow .se tтext ("S hadow on ") ; else {
} )); Гл ава 35. Эл ементы управления Java FX btnShadow.setEffect (null ); btnShadow .setText ("S hadow off ") ; 11 ввести ме тку и кнопки в граф сцены rootNode .getChi ldren {) .addAll { btnRotate, btnScale, btnGlow, btnShadow) ; 11 показать подмостки и сцену на них myS tage .show() ; 1279 В завершение темы эффектов и преобразований следует заметить, что неко­ торые из них дают особенно привлекательные результаты применительно к узлу типа Text. Класс Text входит в состав пакета j avafx . scene . text и создает узел, состоящий из текста. Благодаря тому что это узел, текстом легко манипулировать как единым целым, применяя к нему различные эффекты и выполняя над ним раз­ нообразные преобразования. Ввод всплывающих подсказок Одним из самых распространенных элементов ГПИ является всплъюающая под­ сказха - короткое сообщение, отображаемое при наведении курсора на элемент управления. В JavaFX совсем не трудно добавить всплывающую подсказку в любой элемент управления. Откровенно говоря , особых причин не пользоваться всплы­ вающими подсказками не существует, принимая во внимание их преимущества и простоту внедрения в ГПИ приложения. Чтобы ввести всплывающую подсказку, следует вызвать метод setTool tip (), определяемый в классе Control, который является базовым для всех элементов управления. Ниже приведена общая форма дан ного метода. final void setTool tip (Tooltip noдcxasxa) В данном случае параметр подска зка обозначает экземпляр класса Tooltip, определяющего всплывающую подсказку. Как только всплывающая подсказка бу­ дет задана, она автоматически отображается при наведении курсора на элемент управления , не требуя больше ничего от программиста. Класс Tool tip инкапсулирует всплывающую подсказку. Ниже приведен рас­ сматриваемый здесь конструктор этого класса, где параметр строка обозначает сообщение, отображаемое во всплывающей подсказке . Tool tip (String C!l'poxa) Чтобы опробовать всплывающие подсказки на практике , введите приведен­ ный ниже фрагмент кода в приложение CheckboxDemo из представленного ранее примера. После этих дополнений всплывающие подсказки будут отображаться для каждого флажка. cbWeb . se tTool tip (new Tooltip ( "Deploy to Web" ) ) ; 11 Развернуть приложе ние в веб
1280 Часть IV. Введение в программирование ГПИ средствами Java FX cbDe sktop .setTooltip (new Tool tip ("Deploy to Desktop" )); 11 Развернуть приложе ние в настоль ной системе cbMobile . setTooltip (пew Tooltip ("D eploy to Mobile" )); 11 Развернуть приложе ние на мобиль ном устройстве Откл ю ч ение эл ементо в управл ения Прежде чем завершить рассмотрение элементов управления вJavaFX, следует упомянугь еще одну возможность манипулировать ими. Любой узел в графе сцены, в том числе и эл емент управления , можно отключить под управлением приклад­ ной программы. С этой целью нужно вызвать метод setDi saЫe (), определяемый в классе Node. Ниже приведена общая форма данного метода. final void setDi saЫe (boolean O!l'ЖJ JIO'l eюre) Если параметр отклю чение принимает логическое значение true, соответству­ ющий элемент управления отключается , а иначе он включается. Следовательно, используя метод setDi saЫe (), можно отключить элемент управления, а в даль­ нейшем включить его снова.
36 Введение в меню JavaFX Меню являются неотъемлемой частью ГПИ многих приложений, поскольку они предоставляют пользователю доступ к основным функциональным возможно­ стям прикладной программы. Более того , надлежащая реализация меню считается необходимой составляющей построения уд ачного ГПИ приложения. В связи с тем что меню играют гл авную роль во многих приложениях, вJavaFX обеспечивается обширная поддержка меню. И к счастью, эта поддержка не только мощная, но и рациональная. Как поясняется в этой гл аве, меню в JavaFX имеют немало общего с меню в Swing, описанными в гл аве 33. Поэтому если вы уже знаете , как создаются меню в Swing, то научиться делать это в JavaFX вам будет нетрудн о. Но у обеих разновид­ ностей меню имеется ряд отличий, поэтому очень важно не спешить с выводами относительно системы меню вJavaFX. Система меню вJavaFX поддерживает ряд основных элементов, включая сле­ дующие. • Строка меню, в которой находится гл авное меню приложения. • Стандартное меню, которое может содержать выбираемые пункты или дру­ гие меню, называемые подменю. • Контекстное меню, которое нередко активизируется щелчком правой кноп­ ко й мыши. Контекстные меню называют иначе всплывающими. В системе меню JavaFX поддерживаются также оперативнъw клавиши, позволя­ ющие выбирать пункты меню, не активизируя его, а также мнеминика, допускаю­ щая выбор пунктов с клавиатуры после раскрытия меню. Помимо обычных ме ню, в JavaFX поддерживается панелъ инструмен тов, предоставляющая быстрый доступ к функциональным возможностям прикладной программы и зачастую действую­ щая параллельно пунктам меню. О сновные пол ожения о меню Система меню вJavaFX опирается на группу взаимосвязанных классов, входящих в состав пакета j avafx . scene . cont rol. В этой гл аве рассматриваются классы , пе­ речисленные в табл. 36. 1. Они составляют ядро системы меню вJavaFX. Несмотря
1282 Часть IV. Введение в п рограммирова н ие ГПИ средствами Java FX на то что в JavaFX допускается специальная настройка меню в широких пределах, как правило, классы меню применяются в исходном виде, поскольку они обеспечи­ вают стандартный стиль оформления меню, который обычно и требуется. Табл ица 36.1. Основные классы меню в Java FX Кпасс CheclcИenuitea Contextмenu Мenu Иenui tel lL bdioМenu itea SeparatorМenuit.ea Описание Огмечаемый флажком пункт меню Всплывающее меню, которое обычно активизируется щелчком правой кнопкой мыши Стандартное меню, состоящее из одного или нескольких пун­ ктов типа Nenuitea Объект, содержащий меню верхнего уровня, т.е . гл авное меню приложения Объект, наполняющий меню Огмечаемый кнопкой-переключателем пункт меню Визуальный разделитель пунктов меню Сделаем краткий обзор совместного применения перечисленных выше клас­ сов. Чтобы создать гл авное меню приложения, нужно сначала получить экзем­ Wiяр класса Me nuBar. Проще говоря, этот класс служит контейнером для меню. В экземWiяр класса MenuBar обычно вводятся экземпляры класса Menu, причем каждый объект типа Menu определяет отдельное меню. Это означает, что каждый объект типа Menu содержит один или несколько выбираемых пунктов. А пункты, отображаемые объектом типа Menu, в свою очередь, являются объектами класса Menuitem. Следовательно, класс Menu item определяет пункт меню, выбираемый пользователем. В меню можно вводить не только стандартные пункты , но и пункты, отмеча­ емые флажками или кнопками-переключателя ми. Та кие пункты меню действуют параллельно с элементами управления флажками и кнопками-переключателя­ ми. В частности , отмечаемый флажком пункт меню создается средствами класса Chec kMe nu item, а отмечаемый кнопкой-переключателем пункт меню - средства­ ми класса RadioMenu item. Оба эти класса расширяют класс Me nuitem. Служебный класс SeparatorMe nu !tem позволяет создавать линию, разделяю­ щую пункты меню. Он наследует от класса CustornМenu I tem, упрощающего встраи­ вание других типов элементов управления в пункты меню. Класс CustomМenui tem расширяет класс Menu ite m. В отношении меню вjavaFX следует заметить, что класс Menu item не наследует от класса Node . Следовательно , экземпляры класса Menu !tem можно применять только в меню. Их нельзя иным образом внедрять в граф сцены. С другой сторо­ ны, класс Me nuBar наследует от класса Node , что позволяет вводить строку меню в граф сцены. Следует также иметь в виду, что класс Me nu !tem служит суперклассом для клас­ са Menu. Это дает возможность создавать подменю, которые , по существу, являют­ ся одн ими меню, вложенными в другие. Чтобы создать подменю, нужно сначала создать объект типа Menu и заполнить его объектами типа Menu !tem, представля-
Глава 36. Введение в меню Java FX 1283 ющими пункты меню, а затем ввести его в другой объект типа Menu. Этот процесс будет продемонстрирован в представ.ленных далее примерах. Когда выбирается пункт меню, ге нерируется событие действия. Те кст, связан­ ный с выбранным пунктом меню, обозначает его наименование. Следовател ьн о, проанализировав наименование пункта меню при обработке событий действия от всех выбираемых пунктов меню в одном обработчике, можно выяснить, какой именно пункт меню был выбран . Безусловно , для обработки событий действия от каждого пункта меню можно также воспользоваться отдел ьными ан онимными классами или лямбда-выражениями. В этом случае выбранный пункт меню уже из­ вестен, и поэтому нет нужды анализировать его наименование, чтобы выяснить, какой именно пункт меню был выбран . Помимо меню, раскрывающихся из строки меню, можно создавать автоном­ ные контекстные меню, которые всплывают, когда активизируются. С этой целью следует сначала создать объект ти па ContextMe nu, а затем ввести в него объекты типа Menu ltem в виде пунктов меню. Как правило, контекстное меню активизи­ руется щелчком правой кнопкой мыши , когда курсор находится на том элементе упрамения, для которого определено всплывающее меню. Следует, однако, иметь в виду. что класс ContextMenu является производным не от класса Menu ltern, а от класса PopupCont ro l. С меню связано еще одно средство, называемое nанмъю инструментов. В JavaFX панели инструментов поддерживаются в классе ToolBar, создающем автономный компонент, который нередко служит для быстрого доступа к функциональным возможностям, имеющимся в меню приложения. Например, панель инструментов может предоставлять быстрый доступ к командам форматирования, которые под­ держиваются в текстовом редакторе. Кратки й обзор классов МenuВar, Menu и Мenuitem Прежде чем создавать меню, нужно знать некоторые особенности трех основ­ ных классов меню: MenuBar, Menu и Menu I tern. Эти классы соста ал яют тот минимум средств, которые требуются для создания гл авного меню приложения. Кроме того, объекть1 класса Menu ltern служат для создания контекстных (т.е. всплывающих) меню. Та ким образом, эти классы образуют основание системы меню вjavaFX. Класс МenuВar Класс MenuBar, по существу, служит ко нтейнером для меню. Он реализует элемент упрамения, предоставля ющий гл авное меню приложения . Как и все остальные классы элементов упрамения JavaFX, он наследует от класса Node. Следовательно, объект этого класса можно ввести как строку меню в граф сцены. У класса Me nuBar имеется единственный конструктор по умолчанию. Та ким обра­ зо м, строка меню первоначально будет пустой , и поэтому ее придется наполнить меню, прежде чем воспользоваться ею. Как правило , у каждого приложения име­ ется одна и только одна строка меню.
128, Часть IV. Введение в программирование ГПИ с р едства ми Java FX В классе MenuBar определяется несколько методов, но зачастую применяется только метод ge tMenus (). Этот метод возвращает список меню, управление ко­ то рыми осуществляется из строки меню. Именно в этот список и следует вводить создаваемые меню. Ниже приведена общая форма метода getMenus (). final ObservaЬleLiat<Мenu> qet:Мenus () Для ввода экземпляра класса Menи в упомянуrый выше список меню вызывается метод add ( ) . А для одновременного ввода двух и более экземпляров класса Menu в этот список можно вызвать метод addAl1 ( ) . Меню, вводимые в строку меню, располагаются слева направо в том порядке , в каком они вводятся . Если же меню требуется ввести в конкретном месте , то с этой целью следует воспользоваться приведенным ниже вариантом метода add ( ) , где заданное ме ню вводится по ука­ зан ному индексу, причем индексация меню начинается с нуля , а нулевой индекс обозначает крайнее слева меню. void add (int 11НДежс , Мenu NeнJO) Иногда из строки меню требуется удалить меню, которое больше не нужно. С этой целью можно вызвать метод remo ve ( ) для списка типа Ob servaЫeList, возвращае­ мого методом ge tMenus ( ) . У этого метода имеются следующие общие формы: void reaove (Мenu H8НJD) void r&1 110 ve (int J1U1Дежс) где параметр меню обозначает ссылку на удаляемое меню, а параметр индекс -ука­ занный индекс удаляемого меню. Индексация меню начинается с нуля. Иногда оказывается полезно получить количество элементов, находящих­ ся в строке меню. С этой целью вызывается метод size ( ) для списка типа Ob serva ЫeList, возвращаемого методом getMenus (). На заметку! Напомним, что класс Ob serv aЫeList реализует интерфейс коллекций List, пре­ доста вляя тем са мым доступ ко всем метода м, определенным в этом интерфейсе. Как только строка меню будет создана и наполнена, ее можно ввести в граф сцены обычным образом. Класс меnu Класс Menu инкапсулирует меню, наполняемое пунктами в виде объектов типа Menultem. Как упо миналось ранее, этот класс наследует от класса Menu ite m. Это означает, что одно меню ти па Menu можно выбирать из другого. Следовательно, одно меню может служить подменю для другого. В классе Menu определяются три конструктора, но наиболее употребительным из них, вероятно, является следую­ щий конструктор: Мenu ( Strinq яня) Этот конструктор создает меню с заголовком, обозначаемым параметром имя. Вместе с текстом заголовка можно также указать изображение, используя следую­ щий конструктор: Мenu ( Strinq 10U1, Node •.soбpa.zeюre)
Глава36. Введение в меню JavaFX 1285 где параметр из ображение обозначает отображаемое изображение. Но в любом слу­ чае меню остается пустым до тех пор, пока в него не будуг введены отдел ьные пункты . И наконец, присваивать наименование меню совсем не обязательно. Для создания бе­ зымянного меню можно воспользоваться следующим конструктором по умолчан ию: Мenu () В этом случае наименование и/или изображение можно ввести задним чис­ лом , вызвав метод setText ( ) или setGraphic {). В каждом меню поддерживается свой список пунктов меню, которые оно со­ держит. Чтобы добавить пункт в меню, нужно составить прежде список из его пун­ ктов. С этой целью сначала вызывается метод ge t I tems ( ) , общая форма которого приведена ниже. final ObservaЬleLi st<Мenui tel l> getiteD18 () Этот метод возвращает список пунктов, связанных в данный момент с меню. Для ввода пунктов в этот список вызывается метод add () или addAl l (). Среди прочих де йствий можно уд алить пункт из меню, вызвав метод remo ve ( ) , а также получить размер списка, вызвав метод size (). И наконец, в список пунктов меню можно ввести разделитель, представленный объектом типа SeparatorMenu ltem. Разделители помогают луч ше организовать дл инные меню, группируя связанные элементы вместе, а также отделить важные пункты в меню, например, пункт Exit в меню File. Класс MenuI tem Класс Menu ltem инкапсулирует пункт меню. Этот пункт может выбирать­ ся и связываться с ко нкретным действием в прикладной программе, например Save (Сохранить) или Close (Закрыть) , или же приводить к появлению подменю. В классе Menul tem определяются следующие конструкторы: Мenuitem () мenuiteш (String •J.W) мenuitem (Strinq 8J.W, Node •sображен8е) Первый конструктор создает пустой пункт меню, второй позволяет указать наименование пункта меню, а третий конструктор - дополнить пункт меню изо­ бражением. Когда пункт меню типа Menu I tem выбирается, генерируется событие действия. Для его обработки можно зарегистрировать соответствующий обработчик, вызвав метод setOnAction ( ) таким же образом, как и при обработке событий от кнопок. Ради уд обства изложения ниже еще раз приведена общая форма этого метода. final void setOnAction ( EventHandler<ActionEvent> обрабо�vнх) Здесь параметр обра ботчик обозначает регистрируемый обработчик событий. Чтобы инициировать событиедействия в пункте меню, следует вызвать метод f ire ( ) . В классе Menu ltem определяется несколько методов. Наиболее употребительным из них является метод setDisaЫe () , с помощью которого можно включать или от­ ключать отдел ьные пункты меню. Ниже приведена общая форма данного метода. final void setDisaЬle (boolean о�хлюvенаrе)
1286 Часть IV. Введение в программирование ГП И средства ми Java FX Если параметр отI01ючение принимает логическое значение true, то соответ­ ствующий пункт меню отключается и становится недоступным для выбора. А если параметр отклю чение принимает логическое значение false, то соответствую­ щий пункт меню включается. Используя метод setDi saЫe (), можно делать до­ ступными или недоступными отдел ьные пункты меню в зависимости режима ра­ боты прикладной программы. Соэдание главного меню Как правило, наиболее употребительным считается глштое меню. Оно доступно из строки меню и определяет все (или почти все) функциональные возможности приложения. Как станет ясно в дальнейшем, JаvаFХ значительно упрощает создание гл авного меню и управление им. Ниже будет показано, как создать простое гл авное меню, а в последующих разделах - как ввести в него рааличные варианты выбора. На эаммкуl С целью продемонстрировать сходства и отличия систем меню в Swi пg и JavaFX в этой главе представлены примеры, являющиеся переделанными вариантами примеров из гл а· вы 33. Есл и вы уже знакомы с системой меню в Swing, вам будет полезно сра внить ее с си­ стемоИ меню в JavaFX. Процесс создания глав ного меню состоит из нескольких стадий. Сначала созда· ется объект типа MenuBar, который будет содержать отдел ьные меню. Затем созда· ется каждое меню, располагаемое в строке меню. В общем, создание меню начина­ ется с создания объекта типа Menu, в который затем вводятся пункты меню в виде объектов типа Menul tem. Как только отдел ьные меню будуг созданы, они вводятся в строку меню. После этого сама строка меню вводится в граф сцены. И наконец, каждый пункт меню должен быть дополнен приемником действий, реагирующим на событие действия , наступающее при выборе отдел ьного пункта из меню. Процесс создания меню и управления ими станет понятнее, если продемон­ стрировать его на конкретном примере. Ниже приведено JаvаFХ·приложение, в котором создается простая строка меню, состоящая из трех меню. Первым из них является меню File (Файл), состоящее из выбираемых пунктов Open (Открыть) , Close (Закрыть) , Save (Сохранить} и Exit (Выход} . Второе меню на­ зывается Options (Параметры} и состоит из двух подменю: Colors (Цвета} и Priority (Приоритет) . А третье меню называется Help (Справка) и состоит из единственно­ го пункта AЬo ut (О программе). Когда выбирается пункт меню, его наименование отображается на месте метки. 11 Продемонстрировать ме ню import java fx . application .*; import javafx . зcene .*; import java fx .зtage .*; import java fx .зcene . layout .*; import javafx .зcene . control .*; import java fx . event .*; import javafx . geometry .*;
Гл ава 36. Введение в меню Java FX puЫ ic class MenuDemo extends Application { Label re spons e; puЫ ic static void ma in (String [] args ) { 11 запустить JаvаFХ-приложение , вызвав ме тод launch () launch (args) ; 11 переопределить ме тод вtart () puЫ ic void start (Stage myS tage ) // присвоить заголовок подмосткам myS tage .setTitle ("Demon strate Menus") ; // демонстрация ме ню // исполь зовать панель граничной компоновки ВOrderPane 11 в каче стве корневого узла Borde rPane rootNode = new BorderPane () ; 11 создать сцену Scene myS cene = new Scene ( rootNode , 300, 300) ; // установить сцену на подмостках myS tage .setScene (myScene ); 11 создать ме тку дл я отображения ре зуль татов выбора из меню response = new Label ( "Menu Demo " ); // // Демонстрация ме ню 11 создать строку меню MenuBar mЬ = new Me nuBar (); 11 создать меню File Menu fileMenu = new Menu ("File " ); // Файл Menuitem open = new Menuitem("Open" ) ; // Открыть Menuitem close = new Menuitem("Close" ); // Закрыть Menuitem save = new Menuitem("S ave") ; // Сохранить Menuitem exit = new Menuitem("Exit") ; // Выход fileMenu .getitems () .addAll ( open , close, save , new SeparatorMenuitem () , exit) ; 11 ввести ме ню File в строку меню mЬ . getMenus () .add (fileMenu) ; 11 создать меню Options Menu optionsMenu = new Menu ( "Options") ; // Параме тры 11 создать подменю Colorв Menu colorsMenu = new Menu ("Colors" ); // Цвета Menuitem red = new Menuitem("Red") ; // Кр асный Menuitem green = new Menuitem( "Green" ); // Зеленый Menuitem Ыuе = new Menuitem("Blue") ; // синий colorsMenu .getltems () .addAll (red, green, Ыuе) ; optionsMenu .getitems () .add ( colorsMenu) ; 11 создать подменю Priori ty Menu priorityMenu = new Menu ("Priority" ); // Приоритет Menuitem high = new Menuitem("High" ); // Высокий Menuitem low = new Menuitem ("L ow" ); // Низкий priorityMenu .geti tems () .addAll (high, low) ; 1287
1288 Часть IV. Введе ние в программирование ГП И средствами Java FX op tionsMenu .getitems () .add(priorityMenu) ; 11 ввести разделитель optionsMenu .getitems () .add (new SeparatorMenuitem () ); 11 создать пункт меню Reвet Menu ltem reset = new Menuitem("Reset" ); // Сбросить opti onsмenu .getltems (J .add (reset) ; 11 ввести ме ню Options в строку меню mЬ.getMenus () .add(optionsMenu) ; 11 создать меню Belp Menu helpMenu = new Menu ("Help" ); // Справка Menu ltem about = new Menui tem ( "AЬout") ; // о програм м е helpMenu .getitems () .add(about ) ; 11 ввести меню Belp в строку меню mЬ.getMenus () .add(helpMenu) ; 11 создать один приемник действий дл я обработки всех 11 событий действия, наступ ающих в меню EventHandl er<ActionEvent> МEHandler = ); new EventHandler<ActionEvent> () puЫic v oid handle (ActionEvent ае ) { String name = ((Menul tem) ae . getTarget () ) .getText () ; // выйти из програмNЫ, если выбран пункт ме ню Exit if (name .equals ("Exit" )) Platform.exit (); respon se .setText ( name +"selected" ); // Выбран указанный пун кт меню 11 установить приемники действий от пунктов меню open .se tOnAction ( MEHandle r) ; close .setOnAct ion (MEHandler) ; save .se tOnAction (MEHandler) ; exit .se tOnAction (MEHandler) ; red .setOnAc tion (MEHandle r) ; green .setOnAction (MEHandler) ; Ыue .setOnAction (MEHandler) ; high .se tOnAction (MEHandler) ; low. setOnAc tion (MEHandler) ; reset .se tOnAction ( MEHandler) ; about .setOnAction (MEHandler) ; 11 ввести строку меню в верхней области панели // граничной компоновки , а метку response - 11 в центре этой панели rootNode .setTop (mЬ) ; rootNode .se tCenter ( re sponse ); 11 показать подмостки и сцену на них my Stage .show() ; Вид гл авного меню в окне JаvаFХ-приложения из данного примера приведен на рис. 36. 1.
Гл ава 36. Введение в меню JavaFX 1289 Exrt Рис. 36 .1. Вид гл авного меню в окне приложения MenuDemo Рассмотрим подробнее, каким образом в данном примере приложения созда­ ются меню. Прежде всего следует заметить, что в классе Menu Demo используется экземпляр класса BorderPane в качестве корневого узла. Класс Borde r Pane дей­ ствует аналогично классу BorderLayout из библиотеки АWГ, обсуждавшемуся в гл аве 26. В этом классе определяется окно со следующими пятью областями: верх­ ней , нижней, левой, правой и центральной. Ниже приведены методы для установ­ ки узла, назначаемого для каждой из этих областей. final void setTop (Node узел) final void setвottom (Node yз 8J J ) final void setLeft (Node узел) final void setRiqht (Node узел) final void setCenter (Node узел) Здесь параметр узел обозначает эл емент (например , элемент управления), ко­ то рый должен отображаться в каждой из упомянутых выше областей окна. Далее в рассматриваемом здесь примере приложения строка меню располагается в верх­ ней области окна, а метка , отображающая результат выбора из меню, - в централь­ ной области . Верхнее расположение строки меню гарантирует, что оно будет ото­ бражаться в верхней части окна приложения , автоматически изменяя свои раз­ меры по ширине окна. Именно поэтому в примерах меню из этой гл авы применя­ ется класс BorderPane. Разумеется, для компоновки меню можно воспользоваться и други ми классами, например VBox. Бальшая часть кода данного приложения служит для построения строки меню, размещаемых в ней меню, а также отдел ьных пунктов меню, и поэтому этот код требует более подробного рассмотрения. Сначала создается строка меню, а ссыл­ ка на нее присваивается переменной mЬ, как показано ниже. 11 создать строку меню MenuBar mЬ = new MenuBar () ; На данной стадии строка меню пока еще пуста. Она будет заполнена отдел ьны­ ми меню, когда они будут созданы.
1290 Ч асть IV. Введение в п рограммирован ие ГПИ средствами Java FX Затем создается меню File и его пункты, как показано в следующем фрагменте кода: 11 создать меню File Menu fileMenu = new Menu ("File") ; // Файл Menu item open = new Menui tem ("Open" ); // Открыть Menuitem close = new Menuite m("Close") ; // Закрыт ь Menu item save = new Menuitem("S ave " ); // Сохранить Menuitem exit = new Menuitem("Exit" ); // Вых од Наименования пунктов меню Open , Close , Save и Exit отображаются на местах их выбора из меню File. Чтобы ввести эти пункты в меню File, вызывается метод addAl l () для списка пунктов меню, возвращаемого методом getitems (): fileMenu .getitems () .addAll (open, close, save , new SeparatorMenur tem (), exit ); Напомним, что метод get items ( ) возвращает пункты меню, связанные с эк­ земпляром класса Me nu. Чтобы добавить пункты в меню, нужно составить прежде список из его пунктов. Следует также заметить, что для визуального отделения пункта Exit от остальных пунктов меню File служит разделитель. И наконец, меню File вводится в строку меню в следующем фрагменте кода: // ввести ме ню File в строку меню mЬ.getMenus () .add ( fileMenu) ; По завершении этого фрагмента кода строка меню будет содержать единствен­ ный элемент: меню File. В свою очередь, меню File будет содержать следующие выбираемые пункты: Open , Close. Save и Exit. Меню Options создается таким же образом , как и меню File. Но оно состоит из двух подменю Colors и Priority . а также отдел ьного пункта Reset. Как поясня­ лось ранее , класс Menu наследует от класса Menu item, и поэтому одно меню типа Menu может быть введено в другое. Именно таким образом и создаются подменю. А пункт Reset вводится последним. После этого меню Options вводится в строку меню. Ан алогичным образом создается и меню Help. Как только будут созданы все упомянутые выше меню, создается обработчик со­ бытий MEHandler типа ActionEvent, предназначенный для реагирования на со­ бытия выбора из меню и последующей их обработки. В целях демонстрации все события выбора из меню обрабатываются одним обработчиком, но в реальном приложении нередко оказывается проще указать отдельный обработчик событий для каждого выбора из меню, используя анонимные внутренние классы или лямб­ да-выражения. Ниже показано, каким образом создается обработчик событий типа Ac tionEvent. 11 создать один приемник дейст вий дл я обработки всех // событий действия , наступающих в меню EventHandler<ActionEvent> MEHandl er = new EventHandle r<ActionEvent >() puЫ ic vo id handle (ActionEve nt ае ) { String name = ((Menuitem) ae . getTarget () ) .getText () ; // выйти из программы , если выбран пункт меню Exi t if (name . equ als ( "Exit" ) ) Platform.exit () ; re sponse .se tText ( name +" selected" );
Гл ава 36. Введение в меню Java FX 1291 11 Выбран указанный пункт меню }; В теле метода handle () вызывается метод getTarget () для получения адре­ сата события. Возвращаемая в итоге ссылка приводится к типу Menu i tem, а наи­ менование пункта меню возвращается методом getText ( ) . Полученная в итоге символьная строка присваивается переменной name . Если эта переменная содер­ жит символьную строку "Exit ", то вызывается метод Platform. exit (), чтобы завершить приложение. В противном случае наименование выбранного пункта меню отображается на месте метки response. Прежде чем продолжить дальше , следует заметить, что для завершенияjаvаFХ­ приложения нужно вызвать метод Platform . exit (),ане System. exit ().Класс Platform входит в состав пакета j ava fx . application. Вызов его метода exit () приводит, в свою очередь, к вызову метода stop () жизненного цикла приложе­ ния, чего не происходит в результате вызова метода System. exit (). И наконец, обработчик ME Handler регистрируется как обработчик событий действия от каждого пункта меню в следующем фрагменте кода: 11 установить приемники действий от пун ктов меню open .setOnAction (MEHandler) ; close .setOnAction (MEHandler) ; save .setOnAct ion (MEHandler) ; exit .setOnAction (MEHandler) ; red.setOnAction (MEHandler) ; green .setOnAction (MEHandler) ; Ыue.setOnAction (MEHandler) ; high .se tOnAction (MEHandler) ; low.setOnAction (MEHandler) ; reset .setOnAction (MEHandler) ; about .se tOnAction (MEHandle r) ; Как видите , ни один из приемников действий не вводится в пункты меню Colors или Priority. Ведь они на самом деле являются подменю, а не выбирае мыми пункта­ ми меню. И наконец, в корневой узел вводится строка меню, как показано ниже. В итоге строка меню размещается в верхней области окна. rootNode .setTop(mЬ) ; Можете на данной стадии поэкспериментировать немного с приложением Me nuDemo , попробовав ввести в него еще одно меню или дополнительные пункты в уже существующее меню. Это поможет вам лучше понять основные принципы создания меню, прежде чем продолжить дал ьше, поскольку данное приложение будет постепенно усложняться по ходу изложения материала этой гл авы. Ввод мнемоники и оператив ных клави ш в меню Меню, созданное в предыдущем примере , вполне работос пособно, но его мож­ но усовершенствовать. Меню реальных приложений обычно поддерживают кла­ виатурные сокращения , предоставляя опытным пользователям возможность бы­ стро выбирать пункты меню. Клавиатурные сокращения принимают две формы:
1292 Часть IV. Введение в п рограммирование ГПИ средств ами Java FX мнемонику и оперативные клавиши. Применительно к меню мнемоника опреде­ ляет клавишу, нажатием которой выбирается отдел ьный пункт активного меню. Следовательно, миемоиика позволяет выбирать с клавиаrуры пункты того меню, которое уже отображается, а оперативная клавиша - сделать то же самое, не акти­ визи руя предварительно меню. Операти вная клавиша может быть связана с меню типа Menu или пунктом меню типа Menul tem. Для ее указания вызывается метод setAccelerator (), общая форма которого приведена ниже. final void setAccelerator (К&yCollЬination жоиб.1 1Н аЦJ1 1 R_жла.вJ1 11 1l ) Здесь параметр комбинация_ кл авиш обозначает комбинацию клавиш, нажи­ маемых для выбора пункта меню. Класс Ke yComЬination инкапсулирует комбина­ цию клавиш, например <Ctrl+S>. Он входит в состав пакета j avafx . scene . inpu t. В классе KeyComЬination определяются два защищенных (protected) кон­ структора, но зачасrую вместо них применяется фабричный метод keyC omЬina ­ tion (). Ниже приведена общая форма дан ного метода. static К&yCoшЬination keyColl lЬ ination (Strinq жла-,. . ) В данном случае параметр кла виши обозначает символьную строку, в которой ука­ зывается конкретная комбинация клавиш. Обычно она состоит из модифицирующей клавиши , в частности <Ctrl>, <Alt>, <Shift> или <Meta>, и клавиши буквы, например <S> . Имеется специальное значение shortcut, предназначенное для обозначения клавиши <Ctrl> в Windows и клавиши <Meta> в Мае OS, а в других операционных си­ стемах оно преобразуется в типичную для них комбинацию клавиш. Та к, если тре­ буется указать комбинацию клавиш <Ctrl+S> для оперативного выбора пункта меню Save , то при вызове метода keyComЬination () следует указать символьную строку "shortcut+S" в качестве его параметра. Та ким образом, данная комбинация клавиш будет действовать в Windows, Мае OS и других операционных системах. В приведенном ниже фрагменте кода оперативные клавиши вводятся в меню File, созданное в примере приложения Menu Demo из предыдущего раздела. После внесения этих изменений в исходный код данного приложения можно оператив­ но выбирать пункты меню File, нажимая комбинации клавиш <Ctrl+O>, <Ctrl+C>, <Ctrl+S> ил и <Ctrl+E>, даже не открывая это меню. 11 ввести оперативные клавиши для быс трого выбора пунктов меню File open .setAccelerator ( Ke yComЬination . keyComЬination ("shortcut+O" )); close . setAcc elerator ( KeyComЬination . keyComЬination ("shortcut+C" ) ); save . setAccelerator ( Ke yComЬi nation . keyComЬination ("shortcut+S") ); exit .se tAcce lerator ( KeyComЬination . keyComЬination ("shortcut+E" )); Мнемонику нетрудно указать как для пункта меню типа Menu ltem, так и для меню типа Menu. Для этого достаточно предварить наименование меню или пун­ кта меню знаком подчеркивания. Например, чтобы ввести мнемонику F в меню File приложения MenuDemo, это меню достаточно объявить следующим образом: Menu fileMenu = new Menu (" File") ; // теперь в наиме новании меню - 11 определяется мн емоника После внесения этого изменения в исходный код данного приложения для бы­ строго выбора меню File достаточно нажать сначала клавишу <Alt> , а затем клави-
Гл ава 36. Введе н ие в меню Java FX 1293 шу <F>. Но мнемоника активизируется лишь в том случае , если включен режим ее синтаксического анализа, что делается по умолчан ию. Включить или выключить этот режим можно, вызвав метод setMnemonicPars ing (), общая форма которого приведена ниже . final void setмneшonicParsinq (boolean ахлюv.1 11' !1'.Ъ) Если параметр включить принимает логическое значение true, то режим синтак­ сического анализа мнемоники включен. В противном случае этот режим выключен. После внесения описанных выше изменений в исходный код приложения Menu Demo меню файл File будет выглядеть так, как показано на рис. 36.2. Ввод изображе ний в пун кты меню Изображения можно вводить в пункты меню или использовать их вместо тек­ ста. Самый простой способ ввести изображение в пункт меню - указать его при создании этого пункта меню с помощью следующего конструктора: М&nuitell l (Strinq ЯНSI, Node .l ll' Soбpazeюre) Этот конструктор создает пункт меню, обозначаемый параметром имя и ото­ бражающий указанное изображение. Например , в приведенном ниже фрагменте кода создается пункт меню About вместе с указанным изображением значка. ImageView about IV = new ImageView ("about icon .gif" ); Menuitem about = new Menuitem ("AЬout ", aboutIV) ; После этого дополнения исходного кода приложения Menu Demo указанное изо­ бражение значка about IV появится рядом с текстом "Ab out " в соответствующем пункте меню Help, как показано на рис. 36 .3. Save selected AЬout selЮed Рис. 36 .2. Меню File, усовершенствованное Рис. 36.3. Пункт меню About в окне мнемоникой и оперативными кл авишами приложе ния MenuDemo после ввода значка в окне приложе ния Menu Demo
Часть IV. Введение в программирование ГП И средствами Java FX И последнее замечание: изображение можно ввести в пункт меню и после его создан ия, вызвав метод setGraphic ().Это дает возможность изменять изображе­ ние по ходу выполнения прикладной программы. Кл а ссы Radi oМenuitem и CheckМenu item В приведенных выше примерах демонстрировались наиболее употребитель­ ные ти пы пунктов меню, но в JavaFX определяются также два других типа отме­ чаемых пунктов меню: с флажками и кнопками-переключателями. Эти отмечае­ мые пункты меню упрощают построение ГПИ, делая доступными из меню такие фун кциональные возможности , для предоставления которых в противном случае потребовались бы дополнительные автономные компоненты. Кроме того , нали­ чие флажков и кнопок-переключателей в меню иногда кажется вполне естествен­ ным для выбора конкретного ряда средств. Но независимо от конкретных причин для применения флажков и кнопок-переключателей в меню каркас JavaFX позво­ ляет сделать это довольно просто , как поясняется ниже . Для ввода отмечаемого флажком пункта в меню служит класс CheckMen uitem, в котором определяются три конструктора, действующих парал л ельно с ко нструк­ торами из класса Me nuI tem. Ниже приведен применяемый здесь и далее конструк­ тор данного класса. CheckМenui tem ( Strinq .l lOUl ) Здесь параметр имя обозначает наименование пункта меню. Исходно флажок находится в сброшенном состоянии. Если же требуется явно указать исходное со­ стояние флажка, следует вызвать метод setSelected (), общая форма которого приведена ниже. final void setSelected (boolean .вuбра�ъ) Есл и параметр выбра ть принимает логическое значение true, то пункт меню отмечается. В противном случае отметка пункта меню снимается. Отмечаемые флажками пункты меню действуют аналогично автономным флаж­ кам. В частности, они генерируют события действия при изменении их состояния. Отмечаемые флажками пункты оказываются особенно полезными в тех меню, где требуется отобразить состояние выбранных или невыбранных пунктов меню. Чтобы ввести кнопку-переключатель в пункт меню, следует создать объект класса Radi oMenu item. В этом классе определяется целый ряд конструкторов. Ниже приведен конструктор, применяемый здесь и далее в этой гл аве. RadioМenuitem (Strinq XНJr) Этот конструктор создает отмечаемый кнопкой-переключателем пункт меню с наименованием, определяемым параметром имя. Первоначально этот пункт меню не отмечается. Чтобы отметить этот пункт меню, следует вызвать метод setSelected (), передав ему логическое значение true в качестве аргумента, как это делается и для отмечаемых флажками пунктов меню. Отмечаемый пункт меню типа RadioMenu item действует аналогично автоном­ ной кнопке- переключателю, ге нерируя событие от элемента и событие действия.
Гл ава 36. Введен ие в меню Java FX 1295 Как и автономные кнопки-переключатели, в меню кнопки-переключатели должны быть объединены в группу, чтобы они действовали в режиме взаимоисключающе­ го выбора. Классы CheckMenu itern и RadioMenu itern наследуют от класса Menuitern, и поэтому функциональные возможности последнего доступны каждому из них. Помимо дополнительных возможностей флажков и кнопок-переключателей, от­ мечаемые пункты меню ничем не отличаются от обычных пунктов меню. Чтобы опробовать на практике пункты меню, отмечаемые флажками и кноп­ ками-переключателями, уд алите сначала из примера приложения MenuDerno фраг­ мент кода, в котором создается меню Options. Затем подставьте приведенный ниже фрагмент кода, в котором создаются подменю Colors и Priority с флажками и кнопками-переключателями соответственно. 11 создать ме ню Options Menu op tionsMenu = new Menu ("Options") ; 11 создать подменю Colors Menu colorsMenu = new Menu ("Colors"); 11 исполь зовать отмечаемые флажками пункты ме ню , чтобы 11 поль зователь мо г выбрать сразу нескол ько цветов CheckMenuitern red = new CheckMenuitern ("Red" ); CheckМenui tern green = new CheckМenuitern("Green" ) ; CheckMenuitern Ыuе = new CheckМenui tern ("B lue" ); colorsMenu .getiterns () .addAll (red, green, Ыuе) ; op tionsMenu .getiterns () .add ( colorsMenu) ; 11 выбрать по умолч анию зеленый цвет green .setSelected (true) ; 11 создать подменю Priority Menu priorityMenu = new Menu ("P riority" ); 11 исполь зовать отмечаемые кнопками-переключателями пункты 11 ме ню дл я установки приоритета . Благодаря этому в меню не 11 только отображается установленный приоритет, но и 11 гарантируется установка одного и толь ко одного приоритета RadioMenuitern high = new Radi oMenuitern ("High" ); RadioMenuitern low = new RadioMenuitem ("L ow" ); 11 создать группу кнопок-переключателей в пунктах подменю Priority ToggleGroup tg = new ToggleGroup (); high.se tToggleGroup (tg) ; low.setToggleGroup (tg) ; 11 отметить исходно пункт меню Hiqh как исходно выбираемый high . setSelected (true) ; 11 ввести отмечаемые кнопками -переключателями пун кты 11 в подменю Priori ty, а последнее - в меню Options priorityMenu .getitems () . addAll (high, low) ; optionsMenu .geti terns () . add (priorityMenu) ; 11 ввести разделитель optionsMenu .getiterns () .add (new Sepa ratorMenuitern () ); 11 создать пун кт меню Reset Menu!tern reset = new Menuitem ("Re set" ); opt ionsMenu .getiterns () .add (reset) ;
1296 Часть IV. Введение в п рограммирован ие ГПИ средствами Java FX 11 и наконец, ввести меню Options в строку ме ню mЬ.ge tMenus () .add ( op tionsMenu) ; После этой замены подменю Co lors будет выглядеть так, как показано на рис. 36.4 , а подменю Priority - как показано на рис. 36 .5. R�d se!ected LO\Y selected Рис. 36 ,, , Вид подменю Colors с отмеченны- Рис. 36.5. Вид подменю Priority с отмечен- ми пун ктами в окне приложе ния MenuDemo ным пун ктом в окне приложения MenuDemo Соэдание ко нтекстного меню Кон текстное меню служит весьма распространенной альте рнативой или допол­ нением строки меню. Как правило , контекстное меню активизируется щелчком правой кнопкой мыши на элементе управления. ВJavaFX всплывающие контекст­ ные меню поддерживаются в классе ContextMenu. Его непосредственным супер­ классом служит класс PopupCont rol, а косвенным суперклассом - класс java fx . stage . PopupWindow, предоставляющим бальшую часть его основных функцио­ нальных возможностей. У класса ContextMenu имеются два конструктора. Здесь и далее в этой гл аве применяется следующий конструктор: Contextмenu (Мenuiteш . . . ПJ'НЖ!пf_неюо) где параметр пункты_меню обозначает те пункты меню, которые составляют кон­ текстное меню. А второй конструктор класса ContextMenu создает пустое меню, в которое должны быть введены нужные пункты . В общем, контекстные меню создаются таким же образом, как и обычные меню. Сначала создаются отдел ьные пун кты , а затем они вводятся в меню. Обработка результатов выбора пунктов контекстного меню выполняется тем же самым спо­ собом: приемом событий действия. А отличаются контекстные меню от обычных процессом их активизации. Связать контекстное меню с элементом управления не составляет особого тру­ да. С этой целью достаточно вызвать для конкретного элемента управления метод
Гл ава 36. Введение в меню JavaFX 1297 setContextMenu (), передав ему ссылку на то меню, которое должно всплывать после щелчка правой кнопкой мыши на данном элементе управления. Ниже при­ ведена общая форма метода setContextMenu (), где параметр меню обозначает контекстное меню, связанное с вызывающим элементом управления. final void setContextмenu (Contextнenu .N81Ц!) Ниже приведен фрагмент кода, который требуется ввести в приложение MenuDemo , чтобы продемонстрировать создание и применение контекстного меню. Это контекстное меню называется Edit (Правка), содержит пункты Cut (Вырезать) , Сору (Скопировать) и Paste (Вставить) и связывается с элементом управления текстовым полем. Оно всплывает, если щелкнуть правой кнопкой мыши на текстовом поле. Введите сначала в исходный код приложения MenuDemo следующий фрагмент кода, чтобы создать данное контекстное меню: 11 создать пун кты контекстного меню Menuitem cut = new Menuitem("Cut " ); // Вырезать Menuitem сору = new Menuitem ("C opy" ); // Скопировать Menuitem paste = new Menuitem("Paste" ); // Вставить 11 создать контекстное (т.е. всплыв ающе е) ме ню с пунктами 11 дл я выбора команд редактирования final ContextMenu editMenu = new ContextMenu (cut , сору , paste) ; Сначала в данном фрагменте кода создаются отдел ьные пункты (объекты типа Menu item) , составляющие меню. А затем создается ко нтекстное меню editMenu (экземпляр класса ContextMenu) , наполняемое этими пунктами. Далее введите обработчик событий действия в пункты контекстного мен ю, как показано ниже. cut .se tOnAction (MEHandler) ; copy .setOnAction (MEHandler) ; paste .setOnAction (MEHandler) ; На этом создание контекстного меню завершается, но оно еще не связано с кон­ кретным элементом управления. Поэтому введите в исходн ый код приложения MenuDemo следующий фрагмент кода, чтобы создать текстовое поле: 11 создать текстовое поле , задав ширину его столбца равной 20 TextField tf = new TextFi eld (); tf. set Pre fColumnC ount (20) ; Далее установите контекстное меню в текстовом поле, как показано ниже. Если теперь щелкнуть правой кнопкой мыши на данном текстовом поле , всплывет кон­ текстное меню. 11 ввести контекстное меню в текстовое поле tf. setCon textMenu (edi tMenu) ; Но чтобы это действительно произошло в окне приложения Menu Demo , нужно сначала создать панель поточной компоновки и ввести в нее текстовое поле, а так­ же метку отве-пюй реакции на действия пользователя и затем расположить эту панель по центру панели граничной компоновки типа Borde rPane . Этот шаг необ­ ходимо предп ринять потому, что в любой области панели граничной компоновки типа BorderPane можно расположить лишь один узел графа сцены. С этой целью уд алите из исходного кода приложения MenuDemo следующую строку:
1298 Ч асть IV. Введение в п рограммирование ГПИ средствами Java FX rootNode .setCenter ( response }; А вместо нее введите следующий фрагмент кода: 11 создать панель поточной компоновки , которая должна содержать 11 текстовое поле и ме тку ответной реакции на действия поль зователя FlowPane fpRoot = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены fpRoot .setAl ignment (Pos . CENTER} ; 11 ввести те кстовое поле и метку на панели поточной компоновки fpRoot .getChildren () .addAll ( re spons e, tf} ; 11 расположить панель поточной компоновки по центру панели 11 граничной компоновки rootNode .se tCenter ( fpRoot} ; Разумеется , строка меню по-прежнему располагается вдоль верхнего края па­ нели граничной компоновки . После внесения упомянутых выше изменений кон­ текстное меню должно появиться в окне приложения MenuDemo , если щелкнуть правой кнопкой мыши на текстовом поле (рис. 36.6) . File Optюns Н�р Сору selected Poste Рис. 36.6 . Ко нтекстн ое меню, всплывающее в окне приложения MenuDemo после щелчка правой кнопкой мыши на текстовом поле Контекстное меню можно также связать непосредственно со сценой. С этой целью можно, например , вызвать метод setOnContextMenuReque sted () для кор­ невого узла сцены. Этот метод определяется в классе Node следующим образом: final void setOnContextмenuRaque ated ( EventИandler<? auper ContextмenuEvent> обрабо!l'v•ж_собu!l'яй) где параметр обра ботчик_ событий обозначает тот обработчик событий, который должен вызываться при получении запроса на всплывание контекстного меню. В данном случае обработчик событий должен вызвать метод show ( ) , определен­ ный в классе ContextMenu, чтобы отобразить контекстное меню. Здесь и далее применяется следующая общая форма метода show ( ) : final void shov (Node ys an, douЫa аерх_Х, douЫa •ерх_ У)
Гл ава 36. Введение в м еню JavaFX 1299 где параметр узел обо3начает злемент уnравления, с которым сая3аНо контекстное меню, а параметры верх_ Х и верх_ У - координаты верхнего левого угла контекс'Г­ ного меню относительно зкрана. Как правило, в качестве зтих двух параметров методу show ( ) передаются координаты точки , в которой был прои3веден щелчок правой кнопкой мыши. Для получения координат зтой точки вы3ываются методы getScreenX () и getScreenY (), определенные в классе ContextMenuEvent. Ниже приведены их общие формы. Ре3ультаты вы30ва зтих методов передаются в каче­ стве параметров верх_ Х и верх_ У методу show ( ) . final douЫe getScreenX () final douЫe getScreenY () Все ска3анное выше можно применить на практике, введя контекстное меню в ко рневой У3ел графа сцены. Если после этого щелкнуrь правой кнопкой мыши на любом месте в сцене, появится контекстное меню. Для зтого введите сначала следующий фрагмент кода в исходный код приложения MenuDemo : 11 ввести контекстное меню непосредственно в граф сцены rootNode .setOnContextMenuRe que sted ( new EventHandler<ContextMenuEvent> () { puЫic vo id handle (ContextMenuEvent ае) { 1 1); 11 отобраэить контекстное меню в том ме сте , где был 11 произведен щелчок кнопкой мыши editMenu .show ( rootNode , ae . getScreenX () , ae . getScreenY () ); Затем объявите корневой узел rootNode как f i nal (т.е . 3авершенный), чтобы сделать его доступным в анонимном внугреннем классе. После внесения всех зтих и3менений и дополнений контекстное меню может бьпъ активизировано щелч­ ком правой кнопкой мыши на любом месте в сцене приложения MenuDemo . В каче­ стве примера на рис. 36 . 7 пока3ано контекстное меню, всплывающее после щелчка правой кнопкой мыши слева вверху в окне данного приложения . file Optюns �р �··tмtl --- - ---:J Рис. 36.7. Контекстное меню, всплывающее после щел чка правой кнопкой мыши слева вверху в окне приложения MenuDemo
1300 Часть IV. Введение в прогр аммирование ГПИ средства ми Java FX Соэдание панели инструменто в Панель инструментов является компонентом, служащим как альтернативой , так и дополнением меню. Та кая панель состоит из ряда кнопок (или других ком­ понентов ), предоставляющих пользователю возможность немедленного досrупа к различным параметрам и режимам работы программы. Например, панель ин­ струментов может содержать кнопки для выбора параметров шрифтового оформ­ ления те кста, в том числе полужирного или наклонного начертания , выделения или подчеркивания текста. Эти параметры можно выбрать, не раскрывая меню. Как правило , кнопки на панели инструментов обозначены значками, а не тексто­ выми надписями, хотя допускается и то и другое. Кроме того , кнопки на панели инструментов нередко снабжаются всплывающим подсказками. BJavaFX панели инструментов являются экземплярами класса ToolBar. В этом классе определяются два конструктора: ToolBar () ToolBar (Node . . . ,ysmr) Первый конструктор создает пустую горизонтальную панель инструментов, а второй конструктор - горизонтальную панель, содержащую указанные узлы, которые обычно представлены в некоторой форме кнопки. Если же требуется вертикальная панель инструментов, следует вызвать метод setOrientation ( ) для уже имеющейся панели инструментов. Ниже приведена общая форма данного метода, где параметр ор иента ция должен принимать значение одной из констант: Orientation . VERT I CAL или Or ientation . HORI ZONTAL. final void setOrientation (Orientation op• eн�a.rvr•) Кнопки (или другие ко мпоненты ) вводятся на панели инструментов таким же образом, как и в строке меню. Для этого достаточно вызвать метод add ( ) по ссыл­ ке , возвращаемой методом get ltems ().Но зачастую эл ементы, составляющие па­ нель инструментов, проще указать в конструкторе класса ToolBar. Именно такой подход и применяется здесь и далее в этой гл аве . Как только панель инструментов будет создана, ее следует ввести в граф сцены. Та к, если применяется граничная компоновка, то панель инструментов можно расположить в нижней области этой компоновки , хотя допускается и другое ее расположение. В частности , панель ин­ струментов можно расположить непосредственно под строкой меню или вдоль одной из боковых сторон окна. В качестве примера ниже показано, как ввести панель в приложение MenuDemo . Эта панель состоит из трех кнопок выбора режи мов отладки: Set Breakpoint (Установить точку прерывания), Clear Breakpoint (Очистить точку прерывания) и Resume Execu tion (Возобновить выполнение). Кроме того , кнопки на панели инструментов снабжаются всплывающими подсказками. Как пояснялось в преды­ дущей гл аве , всплывающая подсказка представляет собой небольшое сообщение, описывающее связанный с ней эл емент управления. Она автоматически всплы­ вает на некоторое время при наведении курсора мыши на эл емент управления. Чтобы снабдить пункт меню или элемент управления на панели инструментов всплывающей подсказкой, следует вызвать метод setTooltip ( ). Всплывающие
Гл ава 36. Введе н ие в меню JavaFX 1301 подсказки особенно удобны для элементов управления , представленных только значками на панели инструментов, поскольку не всегда удается оформить такие значки , которые интуитивно понятны всем пользователям. Прежде всего введите следующий фрагмент кода, в котором создается панель инструментов отладки : // определить панель инструментов ; // создать сначала кнопки на этой панели Buttoп ЬtnSet = new Button ("Set Breakpoint ", new ImageView ("з etBP .gif") ); Button ЬtnClear = new Button ("Clear Breakpoint", new ImageView ("clearBP .gif") ); Button ЬtnReзume = new But ton ( "Reзume Execution", new ImageView ("resume.gif") ); // затем отключить те кстовые надписи на кнопках ЬtnSet . setContentDi splay ( ContentDi splay . GRAPH IC ONL Y) ; ЬtnClear .setContentDi splay (ContentDisplay . GRAPHIC ONLY) ; ЬtnResume .setContentDi splay ( ContentDi зplay . GRAPHI C_ON L Y) ; // задать всплывающие подсказки btnSet . setTooltip (new Tooltip ("S et а breakpoint .")); // Установить точку прерывания ЬtnClear . setTooltip (new Tooltip ("Clear а breakpoint ."}); // Очистить точку прерывания ЬtnRe sume .setTooltip (new Tooltip ("Resume execution.") ); //Возобновить выполнение // создать панель инс трументов ToolBar tbDebug = new ToolBar (ЬtnSet, ЬtnClear, ЬtnResume} ; Рассмотрим подробнее приведенный выше фрагмент кода. Сначала в нем соз­ даются три кнопки для выбора режимов отладки на панели инструментов, причем у каждой из них имеется свой значок вместо текстовой надписи. Затем для каждой кнопки отключается режим отображения текстовой надписи, для чего вызывает­ ся метод setContentDisplay (). Любопытно, что отображение текстовых надпи­ сей на кнопках можно было бы оставить, но тогда панель инструментов имела бы не совсем привычный внешний вид. (Тем не менее текстовые надписи на кнопках нужн ы, поскольку именно по ним отдел ьные кнопки распознаются как источники событий действия в их обработчике.) Далее для каждой кнопки задаются всплыва­ ющие подсказки. И наконец, создается панель инструментов, содержащая указан­ ные кнопки для выбора режимов отладки . Далее введите следующий фрагмент кода, в котором определяется обработчик событий действия от каждой кнопки на панели инструментов: // создать обработчик событий действия от каждой кнопки // на панели инструментов Eve ntHandler<Ac tionEvent> ЬtnHandler = ); new EventHandler<Ac tionEvent>() puЫic void handle (ActionEvent ае) { response .setText ( ((Button) a e.getTarget () } .getText (} }; // установить обработчики событий действия для отдель ных кнопок 11 на панели инструментов
1302 Часть IV. Введение в программирование ГПИ средствами Java FX btnSet .setOnAction ( btnHandler) ; btnClear .setOnAction ( btnHandler) ; btnResume .setOnAction ( btnHandler) ; И наконец, введите следующую строку кода, чтобы расположить панель инстру­ ментов в нижней области граничной компоновки: rootNode .setBottom ( tbDebug) ; После внесения всех этих дополнений событие действия будет наступать вся­ кий раз, когда пользователь щелкнет на кнопке панели инструментов. Обработка этого события состоит в отображе нии текстовой надписи (т.е. наименования ) кнопки на месте метки response, как показано на рис. 36 .8 . Рис. 36 .8. Вид панели инструментов отладки с выбранной кнопкой в окне приложения MenuDemo Составление окончательного варианта приложения МenuDemo По ходу изложения материала этой гл авы в исходный вариант приложения Menu Demo бьuю внесено немало изменений и дополнений. Прежде чем завершить эту главу, целесообразно составить окончательный вариант данного приложения с учетом всех изменений и дополнений. Это позволит не только избежать недораз­ умений относительно согласованности отдел ьных частей программы, но и полу­ чить рабочее приложение для демонстрации меню и дальнейшего эксперименти­ рования с ними. В приведенный ниже окончательный вариант приложения MenuDemo включе­ ны все изменения и дополнения , описанные ранее в этой гл аве . Ради большей яс­ ности исходный код этого приложения был реорганизован таким образом, что­ бы создавать разные меню и панель инструментов, используя отдельные методы. Следует также иметь в виду, что некоторые переменные, связанные с меню, в том
Гл ава 36. Введение в меню Java FX 1303 числе mЬ и tbDebug, были превращены в переменные экземпляра, чтобы сделать их доступными в любой части класса. 11 Продемонстрировать меню - окончательный вариант import javafx . appl ication .*; import javafx . scene .*; import java fx.зtage.*; import javafx .scene . layout .*; import javafx .scene . control .*; import javafx . event .*; import javafx . geometry .*; import javafx .scene . input .*; import javafx .scene . image .*; import javafx .beans . value .*; puЫ ic clasз MenuDemoFinal extendз Ap plication MenuBar mЬ; EventHandle r<ActionEvent> MEHandl er; ContextMenu editMenu ; ToolBar tbDebug; Label re spons e; puЫ ic зtatic vo id ma in (String [) args ) { // запустить Jаvа FХ -приложе ние , вызвав ме тод launch () launch (args) ; // переопределить ме тод atart () puЫ ic void start ( Stage myS tage ) 11 присвоить заголовок подмосткам myStage .setTitle ("Demonstrate Menus -- Final Ve rsion" ); // Демонстрация меню - окончатель ный вариант // исполь зовать панель граничной компоновки Вord8rPane 11 в качестве корневого узла final Borde rPane rootNode = new BorderPane (); 11 создать сцену Scene myS ceпe = new Scene ( rootNode , 300, 300) ; 11 установить сцену на подмостках myStage .setSceпe (myScene ); // создать ме тку для отображения резуль татов выбора // из разных элементов управления ГПИ приложе ния response = new Label () ; // создать один приемник действий для обработки всех // событий деиствия , наступающих в меню MEHandler = new EventHandler<ActionEvent> () { puЬlic void handle (ActionEve nt ае ) { String name = ((Menui tem) ae . getTarget () ).g etText () ; if (name .equals ("Exit")) Platform. exit () ; respoп se . setтext ( паmе +"selected" ); };
Чacтi. IV. Введение в программирование ГП И средства м и Java FX 11 создать строку меню mЬ = new MenuBar(); 11 создать меню File makeFileMenu (); 11 создать меню Options ma keOptionsMenu () ; 11 создать меню Belp ma keHelpMenu (); 11 создать контекс тное меню ma keContextMenu () ; 11 создать текстовое поле , задав ширину его столбца равной 20 TextField tf = new TextField () ; tf . setPrefColumnCount (20) ; 11 ввести контекстное меню в текстовое поле tf . setContextMenu (editMenu) ; 11 создать панель инструментов ma keToolBar () ; 11 ввести контекстное меню непосредственно в граф сцены rootNode .se tOnContextMenuReque зted ( new EventHandl er<ContextMenuEvent> () { puЫic void handle (Co ntextMenuEvent ае ) { } }); // отобразить всплывающе е контекстное меню на том ме сте , // где был произведен ще лчок правой кнопкой NЫШИ edi tMe nu .зhow ( rootNode , ae . getScreenX (), ae . getScreenY () ); // ввести строку меню в верхней области панели rootNode .зetTop (mЬ) ; 11 создать панель поточной компоновки для хранения 11 текстового поля и ме тки отве тной реакции на 11 действия пользователя Fl owPane fpRoot = new FlowPane (lO, 10) ; 11 выровнять элементы управления по центру сцены fpRoot .setAli gnment (Po з.CENTER) ; 11 использовать разделитель , чтобы улучшить порядок 11 расположения элементов управления Separator зeparator = new Separator (); separator .зetPre fWidth (260) ; 11 ввести ме тку, разделитель и текстовое поле на // панели поточной компоновки fpRoot .getChildren () .addAll ( re sponзe, зeparator , tf) ; 11 ввести панель инструментов в нижней области панели 11 граничной компоновки rootNode .setBottom ( tbDebug ); 11 ввести панель поточной компоновки в центральной области 11 панели граничной компоновки rootNode .setCenter ( fpRoot );
Гл ава 36. Введение в меню Java FX // показать подмостки и сцену на них myStage .show() ; // создать меню File с мнемоникой void ma keFileMenu () { Menu fileMenu = new Menu ("_ Fi le" ); // Файл // создать отдель ные пункты меню File Menui tem open new Menuitem ( "Open" ) ; // Открыть Menui tem close new Menuitem ("Close") ;// Закрыть Menuitem save new Menuitem ("S ave") ; // Сохранить Menui tem exit new Menuitem("Exit") ; // Выход 11 ввести пун кты в меню File fileMenu .getitems () .addAll ( open, close, save , new SeparatorMenuitem () , exit) ; // ввести оперативные клавиши для быстрого выбора // пунктов из ме ню File open .setAc celerator ( KeyCornЬination . keyCornЬination ("shortcut+O" )); close . setAccelerator ( KeyCornЬination . keyComЬination ("shortcut+C" )); save . setAc celerator ( KeyCornЬination .keyCornЬination ("shortcut+S") ); exit . setAccelerator ( KeyCornЬination . ke yCornЬination ("shortcut+E") ); // установить обработчики событий действия для пунктов ме ню File open .se tOnAction (MEHandler) ; close .setOnAction (MEHandler) ; sav e.setOnAction (MEHandler) ; exit .setOnAction (MEHandler) ; 11 ввести меню File в строку меню rnЬ. getMenus () .add ( fileMenu) ; 11 создать меню Options void ma keOptionsMenu () { Menu optionsMenu = new Menu ("Options ") ; // Параметры 11 создать подменю Colors Menu colorsMenu = new Menu ("Colors" ) ; // Цвета 11 исполь зовать отмечаемые флажками пункты ме ню, чтобы 11 поль зователь мог выбрать сразу нескол ько цветов CheckMenuitem red = new CheckMenuitem ("Red" ); // Красный CheckMenuitem green = new CheckМenuitem ("Green" ); // Зеленый CheckMenuitem Ыuе = new CheckМenui tem ("B lue" ); // Синий // ввести отмечаемые флажками пункты в подменю Colors , а 11 само подменю Colors - в меню Options colorsMenu .getitems () .addAll (red, green , Ыuе) ; option sMenu .getitems () .add ( colorsMenu) ; // задать зеленый цвет в качестве исходно выбираемого green . setSelected (true) ; // созда ть подменю Priority Menu priorityMenu = new . Menu ("P riority" ); // Приоритет // исполь зовать отмечаемые кнопками-пере ключателями пункты 11 меню для установки приоритета . Благодаря этому в меню не // только отображается установленный приоритет, но и 1305
1306 Ч асть IV. Введе н ие в п рограммирование ГПИ средствами Java FX 11 гаран тируе тся установка одного и толь ко одного приоритета RadioMenuitem high = new Radi oMenu item ("High" ); RadioMenui tem low = new Ra dioMenuitem ("Low ") ; 11 создать группу кнопок-переключателей в пунктах подменю Priority Toggl eGroup tg = new ToggleGroup () ; high .setToggleGroup (tg) ; low. setToggleGroup (tg) ; 11 отметить пункт меню Bi9h как исходно выбираемый high . setSelected (tru e) ; 11 ввести отмечаемые кнопками-переключателями пункты 11 в подменю Priority, а последнее - в меню Options priorityMenu .getitems () .addAll (high , low) ; op tionsMenu .getitems () .add (priorityMenu ) ; 11 ввести разделитель op tionsMenu .getitems () .add (new SeparatorMenuitem() ); 11 создать пункт ме ню Reset и ввести его в ме ню Options Menui tem reset = new Menuitem("Reset" ) ; // Сбросить optionsMenu .getitems () .add(reset) ; 11 установит ь обработчики событий действия дл я 11 пунктов меню Options red .setOnAct ion ( MEHandler) ; green .setOnAction (MEHandler) ; Ыue . setOnAction (MEHandler) ; high.setOnAction (MEHandler ) ; low. setOnAction (MEHandler) ; reset .setOnAction (MEHandler) ; 11 исполь зовать приемник событий изменения , чтобы оперативно 11 реагировать на изменения в отме тке пунктов подме ню Priority 11 кнопками-переключателями tg .se lectedToggleProperty () .addListener (new ChangeLi stener<Toggle> () puЬlic void changed (ObservaЫ eValue<? extends Toggle> changed, Toggle oldVal , Toggle newVal ) { if ( newVal==null) return ; } }); 11 привести значе ние new'Val к типу RadioВUtton Radi oMenu item rmi = (Radi oMenu item) newVal ; 11 отобразить резуль тат выбора приоритета response . setтext ("P riority selected is " + rmi .getText () ); 11 Выбран указанный приоритет 11 ввести меню Options в строку ме ню mЬ.getMenus () . add ( optionsMenu ); // создать меню Belp vo id ma keHelpMenu () { // создать представление типа Iшa9eView для изображения ImageVi ew about IV = new ImageView("about icon .gif") ; 11 созда ть ме ню Belp Menu helpMenu = new Menu ("Help" ); // Справка
Гл ава 36. Введение в меню JavaFX // создать пункт AЬout и ввести его в ме ню Help Menuitem about = new Menultem ("AЬ out ", aboutIV) ; // О программе helpMenu .getltems () .add(about) ; // установить обработчик событий действия дл я 11 пункта AЬout ме ню Help about .se tOnAction (MEHandler) ; // ввести меню Help в строку меню mЬ.ge tMe nus () .add ( helpMenu) ; 11 создать пункты конте кстного меню vo id ma keContextMenu () { 11 создать пункты для выбора команд редактирования 11 из конте кстного меню Menuitem cut = new Menu item("Cut " ); // Выре зать Menultem сору = new Menultem("Copy" }; // Копировать Menuitem разtе = new Menultem("Paste" }; // Вставить 11 создать контекстное (т. е . всплывающе е ) меню 11 с пунктами для выбора команд редактирования editMenu = new ContextMenu (cut , сору, paste) ; // установить обработчики событий действия / / для. пунктов контекстного ме ню cut .setOnAction (MEHandler) ; copy .se tOnAction (MEHandler) ; paste .se tOnAction (MEHandler) ; 11 создать панель инструментов void makeToolBar (} { 11 создать кнопки для панели инс трументов Button btnSet = new Button ( "Set Breakpoint ", new ImageVi e w("setBP .gif" )); Button btnClear = new Button ( "Clear Breakpoint ", new ImageView ("c learBP .gif") }; Button btnRe sume = new Button ( "Resume Execution", new ImageView ("r esume.gif") ); 11 отключить текстовые надписи на кнопках btnSet .se tContentDi splay ( ContentDisplay . GRAPHIC ONLY} ; ЬtnClear . setContentDi splay ( ContentDi splay . GRAPHlC ONLY) ; btnRe sume .setContentDisplay(ContentDi splay . GRAPHI C_ONLY) ; // задать всплывающие подсказки для кнопок btnSet . setTooltip (new Tooltip ("Set а breakpoint ."}); // Установить точку прерывани я btnClear . se tTool tip (new Tooltip ("Clear а Ьreakpoint .")); // очистить точку прерывани я btnResume .setTooltip (new Tool tip ("Resume execution.") }; 11 Возобновить выполнение 11 создать панель инструментов tbDebug = new ToolBar (btnSet, btnClear, btnRe sume ); // создать обработчик событий от кнопок на панели инструментов EventHandler<Ac tionEvent> btnHandler = new EventHandler<Ac tionEvent> () { puЫic void handle (ActionEvent ае ) { response . setText (((Button) a e.getTarget () ) .getText () }; 1307
1308 ) ); Ч асть IV. Введение в п рограммирование ГПИ средства ми Java FX 11 ус тановить обработчики событий действия дл я кнопок 11 на панели инс трументов btпSet .setOпActioп ( btпHandler ) ; btnClear .setOnAction ( btnHandler) ; btnRe surne .se tOnAction ( btnHandler) ; Даль нейшее изуч ение Java FX Каркас JavaFX предоставляет самые совершенные средства для построения ГПИ приложений вjava. В нем также переопределяются средства платформыjаvа. В трех гл авах части IV бьти представлены лишь некоторые из основных средств JavaFX, а остальные средства вам придется изучить самостоятельно. К их числу относится ряд дополнительных элементов управления , включая ползунки , автономные полосы прокругки и таблицы. Можете также поэкспериментировать с доступными вJavaFX классами ко мпоновок, например VBox и НВох, а также с различным эффектами из пакета j avafx . scene . effect и преобразованиями из пакета j avafx . scene . trans forrn. Не менее интересными возможностями обладает класс WebView, по­ зволяющий без особого труда внедрить веб-содержимое в граф сцены. Откровенно говоря , весь арсенал средствJavaFX заслуживает серьезного изучения. И во многих отношениях кapкacjavaFX намечает дальнейший ход развитияjаvа.
1 ЧАСТЬ v ГЛ АВА З7 Комп оне нты Java Beans ГЛ АВА ЗВ Введение в сервлеты ПРИЛОЖЕН ИЕ Приме н е ние документирующих комме нтариев в Java Применение Java
37 Компоненты Java Bea ns В этой гл аве представлен краткий обзор компонентов Java Вeans. Особое значе­ ние этих программных компонентов состоит в том, что на их основе можно создавать сложные системы. Эти компоненты можно создавать самостоятельно, а также приоб­ ретать в готовом виде у сторонних производителей. Компоненты Java Beans опреде­ ляют архитектуру, устанамивающую порядок взаимодействия сгандартных блоков. Чтобы стало понятнее особое значение компонентов Java Beans, обратимся к аналогии. В распоряжении разработчиков оборудования вычислительных си­ стем имеются разнообразные компоненты , из которых можно построить вычис­ лительную систему. Резисторы, конденсаторы и катушки индуктивности служат примерами простых стандартных бл оков . Интегральные схемы предлагают еще больше функциональных возможностей. При этом каждый из этих компонентов можно использовать многократно. Их не нужно компоновать заново всякий раз, когда требуется создать новую систему. Кроме того , одни и те же компоненты мож­ но использовать в разнотипных схемах. И все это возможно потому, что поведе­ ние подобных ко мпонентов заранее известно и хорошо документировано. И в отрасли разработки программного обеспечения предпринимались попыт­ ки воспользоваться преимуществами многократного испол ьзования компонентов и их способностью к взаимодействию. Для этого необходимо было разработать такую архитектуру компонентов, которая позволила бы составлять программы из стандартных блоков, в том числе и предлагаемых сторонними производителя ми. Кроме того , разработчик должен был иметь возможность выбрать компонент, разобраться в его функциях и внедрить его в приложение. А после выхода новой версии ко мпонента нужно было обеспечить простоту внедрения его функциональ­ ных возможностей в уже существующий прикладной код. Именно такую архитек­ туру и предлагают ко мпоненты Java Beans. Общее предста вление о ко мпонента х Java Bea ns Java Beans - это ко мпоненты программного обеспечения, предназначенные для многократного применения в самых разных средах. На функциональные воз­ можности ко мпонентов Java Beans не накладывается никаких ограничений. Они могут выполнять простую функцию, например получать стоимость товарно-ма­ териальных запасов, а могут реализовать и более сложную фун кцию, в частности
1312 Часть V. П р именение Java прогнозировать котировки акций на фондовой бирже . Компоненты Java Beans могуг быть доступны для конечного пользователя. Одним из примеров таких ком­ понентов служит экранная кнопка в ГПИ. В то же время компоненты jаvа Beans могуг быть недоступны для пользователя. Примером таких ко мпонентов служит программное обеспечение для декодирования потока мультимедийной информа­ ции в реальном времени. И наконец, компоненты jаvа Beans могуг быть предна­ значены для работы в автономном режиме на рабочей станции пользователя или вместе с другими распределенными компонентами. Примером компонента, спо­ собного работать автономно, служит программное обеспечение для построения круговой диаграммы по точкам исходных данных. А компонент, предоставляю­ щий сведения о котировках акций на фондовой бирже ил и ценах на товарной бир­ же в реальном времени, может работать только вместе с другим распределенным программным обеспечением, получая от него необходимые данные. П реимуще ства ко мпоненто в Java Beans Ниже перечислены некоторые преимущества, которые дает применение тех­ нологии Jаvа Beans разработчику компонентов. • Компоненты jаvа Beans получают все преимущества от реализуемого вjava принципа "написано однажды, работает везде". • Свойствами, событиями и методами компонентов Java Beans , доступными в другом приложении, можно управлять. • Для настройки компонентовjаvа Beans можно применять вспомогательное программное обеспечение. Оно требуется только для установки параметров на стадии разработки компонента. А включать его в исполняющую среду не нужно . • Состояние компонентов Java Beans можно хранить в постоя нном хранили­ ще и восстанавливать по мере надо бности . • Компоненты Java Beans можно регистрировать на получение событий от других объектов. Он могуг и сами генерировать события , отправляя их другим объектам. Самоанал из В основу компонентов Java Beans положе самоапа.л:из - процесс анализа компо­ нентов Java Beans , в ходе которого определяются их характеристики. На самом деле это очень важная особенность прикладного программного интерфейса jаvа Beans API, поскольку с ее помощью другое приложение, например инструменталь­ ное средство разработки , может получить сведения о конкретном компоненте. Без самоанализа технология Jаvа Beans неработоспособна. Имеются два способа, с помощью которых разработч ик компонентов Java Beans может указать, какие именно свойства, события и методы компонента долж­ ны быть доступны. Первый способ состоит в то м, чтобы использовать простые
Гл ава 37 . Комп оненты Java Beans 1313 условные обозначения. Они позволяют механизмам самоанализа логически выво­ дить сведения о компонентахjаvа Beans . Второй способ подразумевает предостав­ ление дополнительного класса, расширяющего интерфейс Beanlnfo. Этот класс, в свою очередь, предоставляет нужные сведения о компоненте явным образом . Оба способа рассматриваются в последующих разделах. Ш аблоны проектирования дл я с вой ств компонентов Java Beans Свай,ствакомпонентовJаvа Веаns яв.ляются подмножесгвом их состояния. Значения, присваиваемые свойствам, определяют поведение и внешний вид компонентов Java Вeans. Значение свойства устанавливается методом запш:и, а получается оно методом палучения. Имеются две разновидности свойств: простые и индексированные. П ростые свой ства У простого свойства имеется единственное значение. Его можно распознать по приведенным ниже шаблонам проектирования , где N - имя свойства; Т - его ти п. puЬlic т qetN О puЬlic void setN (T арr,УН8Н!1') ; Оба указанных метода имеются у каждого свойства, допускающего чтение и запись, для досrупа к его значению. У свойства, допускающего только <пение, имеется лишь метод получения, а у свойства, допускающего только запись, - лишь метод установки . В приведенном ниже примере демонстрируются три простых свойства, допу­ скающих чтение и запись, а также их методы получения и установки. private douЫe depth, height , width ; puЫ ic douЫ e getDepth () return depth ; puЫ ic void setDepth ( douЫe d) { depth = d; puЫic douЫ e ge tHeight () return height ; puЫic void setHe ight (douЫ e h) { height = h; puЫ ic douЫe getWidth ( ) { return width ; puЫic void setWidth ( douЫ e w) { width = w; На заметку! Для доступа к свойству типа boolean может быть та кже использован метод типа is PropertyName ().
Часть V. П рименение Java И ндексированные сво й ства Индексированное свойство состоит из нескольких значений. Его можно рас­ познать по приведенным ниже шаблонам проектирования, где N - имя свойства; Т-его ти п. puЫ ic Т 9etN (int .ндежс) ; puЬ lic void setN (int •ндежс , Т .sнaveюre) ; puЬlic Т(] qetN() ; puЬlic void setN (T энаvеюr•[ ]) ; В приведенном ниже примере демонстрируется применение индексированно­ го свойства da ta, а также его методов получения и устан овки. private douЫ e data[] ; puЫ ic douЫ e getData (int index ) ( return data [ index] ; puЫ ic void setDa ta (int index, douЫ e value ) { data [index ] = value ; re puЫ ic douЬle [J getData () turn data; } puЫ ic void setData (douЬle [] value s) { da ta = new douЬl e[v alues .length] ; System.arraycopy (values, О, data, О, values . length) ; Шаблоны проектирования для событий В компонентах Java Beans применяется модель делегирования событий, рас­ сматривавшаяся ранее в этой книге. Они способны генерировать события и по­ сылать их другим объектам. События можно распознать по приведенным ниже шаблонам проектирован ия , где Т обозначает тип события . puЬ lic void addТListener (TListener прJt ен нж ж собuт••) puЫic void addТListener (TListener npJt eн нJt ж-coбuт••) throws java.util . тooмanyLis tenersException puЫ ic void rU1oveТListener (ТListener nрJt ен нж ж_соduт••) Перечисленные выше методы служат для ввода или уд аления слушателя указан­ ного события. Вариант метода AddTL istener ( ), не генерирующий исключение, можно использовать для групповий рш:сыл:ки собьrгий. Это означает, что на получе­ ние извещений о наступивших событиях может быть зарегистрировано несколько приемников. А вариант метода AddTListener (), генерирующий исключение типа TooManyListenersException, предназначен для целевий рш:сылки собьrгий. Это озна­ чает, что извещение о наступивших собьrгиях может получить только один приемник. Но в любом случае метод remo veTListener ( ) служит для удаления приемника собы­ тий. Так , если имеется интерфейс собьrгия типа Tempe ratureListener, то компо­ нентJava Веаn, следящий за темпераrурой , может предоставить следующие методы:
Гл ава 37. Компоненты Java Веапs 1315 pu Ыic vo id addTemperatureListener ( TemperatureLis tener tl) { puЬ lic vo id removeTemperatureListener ( TemperatureLi stener tl) Методы и шаблоны проектирования Шаблоны проектирования не используются для именования методов, не свя­ за нных со свойствами. Механизм самоанализа находит все открытые методы ком­ понента Jаvа Веан. А защищенные и закрытые методы осгаются недоступными. Применение интерфейса Beaninfo Какупоминалось выше, шаблоны проектирования неявно определяют сведения , доступные пол ьзователю компонентаjаvа Веап. А интерфейс Beaninfo позволяет явно управлять доступом к этим сведениям. В интерфейсе Beaninfo определяется несколько методов, включая следующие: PropertyDescriptor [] ge tProper tyDescriptors () EventSetDes criptor [] getEventSetDes criptors () НethodDes criptor [] getмethodDe scriptors() Эти методы возвращают массивы объектов, содержащие сведения о свой­ ствах , событиях и методах компонентовJаvа Веапs. Классы Prope rtyDescriptor, EventSe tDescriptor и Me thodDescriptor определяются в пакете jav a.beans и описывают ука:Jанные эл ементы. Реализуя эти методы, разработчик может точ­ но определ ить, что именно доступ но пользователю, не прибегая к самоанализу, выполняемому на основе шаблонов проектирования. Если создается класс , реализую щий инте рфейс Beaninfo, он должен называть­ ся по форме имя Bean info, где имя обозначает имя компонснтаJаvа Веап. Та к, если компонент Java Веан назы вается MyBean, то информационный класс должен на­ зываться MyBeanBeaninfo. Чтобы упростить применение интерфейса Beaninfo, в компонентах Jаvа Bea11s предоставляется класс SimpleBeaninfo. Он обеспечивает стандартные реал изации интерфейса Bean info, включая перечисленные выше три метода. Этот класс мож­ но расширить и переопределить в нем один или больше методов, чтобы явно управ­ лять доступными свойствами ко мпонентов Java Веанs. Если не переопределить та ­ кой метод, то будет выполнен самоанализ из шаблона проектирования. А сели нс переопределить метод getPropertyDescriptors (), то для определения свойств компонентовJаvа Веанs будут применяться шаблоны проектирования. Применение класса SimpleBeaninfo на практи ке будет продемонстрировано далее в этой гл аве. Привязанные и ограниченные свойства Компоненты Java Веапs , имеющие привязанное свойство, генерируют собы­ тия при изменении свойства. Наступающее событие относится к типу Prope rty ChangeEvent и посылается тем объектам, которые были предварительно зареги-
1316 Часть V. П р именение Java стрированы на получение подобных уведомлений. Класс, выполняющий обработку событий данного типа, должен реализовать интерфейс Рrоре rtуСhаngеL istеnеr. Ко мпоненты Java Beans, имеющие ограни'Чl!Нное свойство , ге нерируют события при попытке изменить значение этого свойства. Они также передают извещение о событи и, имеющее тип PropertyChangeEvent. Это извещение о событии по­ сылается другим объектам, предварительно зарегистрировавшимся на получение таких уведомлений. Но эти объекты могут наложить запрет на предлагаемое изме­ нение, сгенерировав исключение типа PropertyVetoException. Это позволяет компонентам jаvа Beans действовать по-разному в зависимости от конкретной ис­ полняющей среды. Класс, выполняющий обработку события данн ого типа, дол­ жен реализовать интерфейс Ve toaЫeChangeListener. С охраня емость ко мпоненто в Java Bea ns Сохран.яемостъ - это способность сохранять текущее состояние компонентов Java Beans, в том числе значения их свойств и переменные экземпляра, на энерго­ независимом запоминающем устройстве и извлекать его по мере необходимости. Для сохраняемости компонентовJava Beans испол ьзуются средства сериализации, предоставляемые библиотеками классовJava. Сериализировать ко мпоненты Java Beans проще всего , реализовав в них ин­ терфейс jav a.io. SerializaЫe, который является просто маркерным интер­ фейсом. Реализация интерфейса j ava . io . SerializaЫe обеспечит автомати­ ческое выполнение сериализации, больше ничего не требуя от компонентов Java Beans. Автоматическую сериализацию можно также наследовать. Иными словами, если какой-нибудь суперкласс ко мпонентов jаvа Beans реализует интерфейс java . io . SerializaЫe, он приобретет возможность для автоматической сериализации. При автоматической сериализации можно выборочно отключить сохранение отдел ьного поля с помощью ключевого слова transient. Таким образом, элемен­ ты данных компонентов jаvа Beans, определенные как trans ient, не будут сериа­ лизированы. Если же компонентыjаvа Веаns не реализуют интерфейс java . io . SerializaЬle, то возможность сериализации придется обеспечить самостоятельно (например, с по­ мощью интерфейса java . io . Extern alizaЫe). В противном случае контейнеры не смогут сохранить конфигурацию компонентовJava Вeans. Настройщики Разработчик компонентов Java Beans может предусмотреть возможность ис­ пользования настройщика, с помощью которого другой разработч ик сможет на­ строить эти компоненты jаvа Beans. Настройщик может обеспечивать пошаговое руководство всем процессом. Выполняя его указания, можно добиться примене­ ния компонента в определенном контексте , а также предоставить оперативно до­ ступную документацию. Разработчику компонентов Java Beans предоставляется достаточно возможностей, чтобы разработать такой настройщик, который спосо­ бен представить его программный продукт в выгодном свете на рынке.
Гл ава 37 . Компоненты Java Beans 1317 Прикладной программный интерфейс Java Bea ns API Функциональные возможности компонентов Java Beans обеспечиваются класса­ ми и интерфейсами из пакета j ava . beans. В этом разделе делается краткий обзор содержимого данного пакета. В табл. 37. 1 перечислены интерфейсы из пакета j а va . beans с кратким описанием их функциональных возможностей , а в табл. 37.2 - клас­ сы из этого же пакета. Та бл ица 37.1. Инте рфейсы из пакета java . beans Интерфейс Описание Ap p letinitializer Методы этого интерфейса служат для инициализации ком­ понентовjаvа Beans, которые являются также аплетами Вeaninf'o Позволяет разработчику указать сведения о свойствах, со­ бытиях и методах компонентов jаvа Beans Cu stoшizer Позволяет разработчику предоставить ГПИ для настройки компонентовjаvа Beans DesignМode Методы этого интерфейса определяют, выполняются ли комnоненты jаvа Beans в режиме проектирования ExceptionLis tener Метод этого интерфейса вызывается, когда возникает ис­ ключение PropertyChangeListener Метод этого интерфейса вызывается при изменении при­ вязанного свойства PropertyEdi tor Объекты классов, реализующих этот интерфейс, позво- ляют разработчикам изменять и отображать значения свойств компонентов jаvа Веапs VetoaЬleChangeLi stener Метод этого интерфейса вызывается при изменении огра­ ниченного свойства Vi siЬility Методы этого интерфейса позволяют компонентам jаvа Веапs выполняться в тех средах, где отсугствует ГПИ Табл ица 37.2. Классы из па кета java . beans Класс ВeanDescriptor Вeans Default.PersistenceDelegate Encoder Evantвandler EvantSetDescriptor Описание Предоставляет сведения о компонентахjаvа Вeans. Позволяет также связывать настройщики с компонента­ миjаvа Beans Служит для получения сведений о компонентахjаvа Веапs Служит подклассом, производным от класса PersistenceDeleqate Зашифровывает состояние совокупности компонентов Java Вeans. Может использоваться для записи этих данных в поток вывода Поддерживает динамическое создан ие приемника со­ бытий Экземпляры этого класса описывают событие, которое может генериfЮваться компонентами Java Beans
1318 Часть V. Применение Java Кпас:с Expression Featur&Descriptor I�t. Incl lnrecWr opert.yD e scriptor Int.rospect.ionEx08pt.iOD Int.rospec:tor МethodDescriptor Par1U1&terDescriptor PersistanoeD&1egat.e Propert.yChan qeEv8n t. PropertyChange Li st.enerProxy �rt. Propert.yDescriptor Propert:yВditoz:Иmager Propert:yВditorSup po rt. Propert.]!VetoEx08pt.ion SiJapleВeanI�o Stat81 118D t. VetoaЬleChanqe Li. stanerProxy VetoaЬleChanqeSup po rt. О�wнчание таб.t. 37.2 Описание Инкапсулирует вызов метода, возвращающего результат Служиг суперклассом для классов Propert.ydescriptor, Event.SetDescript.or и Мet.hodDescriptor Служиг подклассом, производным от класса Propert.yCЬan qeВven t., предсгавляя изменения в индек­ сированном свойстве Экземпляры этого класса описывают индексирован н ое свойство компонентовJava Вeans Составляет выражение, если при анализе компонентов Java Вeans возникает ошибка Анализирует компонентJava Веаn и со:щает объект типа ВеlшI�о. описывающий этот компонент Экземпляры этого класса описывают методы компонен­ товJava Вeans Экземпляры этого класса описывают параметр метода Обрабатывает данные состояния объекта Это событие генерируется при изменении привя­ зан н ых или ограниченных свойств. Оно посылается тем объектам, которые зарегистрированы на полу­ чение событий данного типа, а их классы реализуют один из интерфейсов PopertyChange Li staner или VetoaЬleChanqeL i stener Расширяет класс EventListenerProxy и реализует ин­ терфейс Propert.yehang eLi stener Компонентыjаvа Вeans, поддерживающие ограниченные свойства, мoryr использовать этот класс для уведомления объектов типа Propert.yChangeList.ener Экземпляры этого класса описывают свойства компонен­ товJava Вeans Обнаруживает редактор свойств типа Propert.yEdit.or для заданного типа Предоставляет функциональные возможности для напи­ сания редакторов свойств Исключение данного типа генерируется, если изменять ограниченное свойство запрещено Предоставляет функциональные возможности для напи­ сания классов, расширяющих интерфейс Вeaninfo Инкапсулирует вызов метода Расширяет класс EventListeneж:proxy и реализует ин­ терфеik Ve toaЬleChanqeL i st.ener Компонентыjаvа Beans, поддерживающие огран иченные свойства, мoryr использовать этот класс для уведомления объектов типа Ve toaЬleChangeLi stener Служиг для чтения компонентовJava Вeans из ХМL­ документа Служиг для записи компонентов Java Вeans в ХМL­ документ
Гл ава 37 . Ко мпоненты Java Beans 1319 В рамках одной гл авы невозможно описать все перечисленные выше классы. Те м не менее в последующих разделах будут вкратце рассмотрены следующие четыре класса, представляющие особый интерес: Introsp ector, PropertyDe scriptor, EventSetDescriptor и Me thodDe scriptor. Клacc intro spector В этом классе предоставляется несколько статических методов, поддержива­ ющих самоанализ. Наиболее интересным из них является метод getBeaninfo (). Он возвращает объект типа Beaninfo, который служит для получения сведений о компонентах jаvа Beans. У метода getBean info () имеется несколько общих форм, включая приведен­ ную ниже . Возвращаемый объект содержит сведения о компоненте Java Bean , обо­ значаемом параметром Ьеа п. static Beaninfo qetвeaninfo (Class<?> bean) throws IntrospectionException Кла сс Prope rtyDescriptor Класс Prope rtyDescriptor описывает свойство компонента Jаvа Bean и пре­ доставляет ряд методов для управления свойствами и их описания. Например , вызвав метод isBound (), можно выяснить, является ли свойство привязанным, а вызвав метод isConstrained () - является ли оно ограниченным. Имя свойства можно получить, вызвав метод getName ( ) . Клacc EventSetDescriptor Класс Eve ntSetDescriptor представляет событие в компоненте jаvа Bean . Он предоставляет ряд методов для доступа к методам, предназначенным для ввода или удал ения приемников событи й или управления иным способом события ми в компонентах Java Beans. Например , чтобы получить метод, предназначенный для ввода приемников событий, следует вызвать метод getAddListene rMe thod (); чтобы получить метод, предназначенный для уд аления приемников событий, - метод ge t RemoveListene rMe thod (); а для того чтобы получить тип приемника событий - метод getListenerType ().Имя события можно получить, вызвав ме­ тод getName () . Клacc MethodDescriptor Класс Me thodDe scriptor представляет метод компонента Java Bean . Чтобы получить имя метода, следует вызвать метод getName ().А сведения о методе мож­ но получить, вызвав метод ge tMethod (), общая форма которого показана ниже. Этот метод возвращает объект типа Me thod, описывающий искомый метод. Мethod qetмethod О
1320 Часть V. Применение Java П р имер ко мпонента Java Bean В завершение этой гл авы рассмотрим пример, демонстрирующий различные аспекты программирования компонентовjаvа Beans, включая самоанализ и приме· пение инте рфейса Beanlnfo, а также классов Introspector, Prope rtyDesciptor и EventSetDescriptor. Данный пример состоит из трех классов. Первый класс является компонентом jаvа Bean и называется Colors. Ниже показано, каким об­ разом определяется этот класс. /// Простой компонент Java Bean i mport java.awt .*; import java.awt . event .*; import java.io.Seriali zaЫe; puЫ ic class Colors extends Canva s implements SerializaЫe { transient priva te Color color; // не сохраняемая переменная private boolean rectangular; // сохраняемая переменна я puЫ ic Colors () { } addМouseListener (new MouseAdapter () { }); puЬlic vo id mousePressed (Mou seEvent me ) { change (); rectangular = false ; setSize (200, 100) ; change () ; puЫ ic boolean getRectangular () return rectangular; puЫ ic void setRectangular (boolean flag) { this . rectangular = flag; repaint (); puЬlic void change () { color = randomColor () ; repaint (); private Color randomCol or () { int r • (int ) (255*Math . random() ) int g = (int ) (255*Math . random()) int Ь = (int ) (255*Math . random() ) return new Color (r, g, Ь) ; puЫ ic void paint ( Graphics g) Dimension d = getSize (); int h = d.height ; int w = d .width; g.setColor (color) ; if ( rectangular) { g. fillRect (O, О, w-1, h-1) ; ) else ( g.fillOval (O, О, w-1, h-1);
Гл ава 37 . Компоненты Java Beans 1321 Компонент Colors отображает окрашенный в определенный цвет объект в пря­ моугол ьной рамке. Цвет компонента определяется закрытой переменной color типа Color, а его форма - закрытой переменной rectangular типа boolean. В кон­ структоре класса Colors определяется анонимный внутренний класс , расширяю­ щий класс Mous eAdapter, а также переопределяется мeтoд mou sePressed (). Метод change () вызывается в ответ на щелчок кнопкой мыши. Он выбирает произволь­ ный цвет и окрашивает им компонент. Методы getRectangular ( ) и setRectan gular () предоставляют доступ к единственному свойству данного компонентаJаvа Bean . Метод change () вызывает сначала метод randomColor (), чтобы выбрать произвольный цвет, а затем метод repaint ( ) , чтобы сделать изменение цвета види­ мым. Обратите внимание на то , что с помощью переменных rectangular и color в методе paint ( ) определяется, каким образом должен быть представлен компо­ нентJava Bean. Следующий класс ColorsBeaninfo является производным от класса SimpleBean info и предоставляет подробные сведения о цвете. В этом классе пе­ реопределяется метод ge tPrope rtyDescriptors (),чтобы обозначить свойства, доступные пользователю компонента Java Bean . В данном случае пользователю доступно только свойство rectangular. Этот метод создает и возвращает объект типа Prope rtyDescriptor для свойства rectangular. Ниже приведен применяе­ мый здесь конструктор класса PropertyDe scriptor. PropertyDescriptor ( Strinq свойс �во, Class<?> XJ J acc bean) throws IntrospectionException - Параметры св ойств о и кла сс_Ьеа п обозначают имя свойства и класс компо­ нента Java Bean соответственно. Ниже показано, каким образом определяется этот класс. 11 Кл асс сведений о компоненте Java Bean import java . beans .*; puЫic class ColorsBeaninfo extends SimpleBeaninfo { puЫic PropertyDescriptor [] getPropertyD escriptors () try { PropertyDescriptor rectangular = new PropertyDe scriptor ("rectangul ar" , Colors .class ); PropertyDescriptor pd [] = {rectangular } ; return pd; catch ( Exception е) { System. out . println ( "Пepexвaчeнo событие . "+е} ; return null; И последним в рассматриваемом здесь примере компонентаJаvа Bean является класс IntrospectorDemo . Он производит самоанализ для отображения свойств и событий, доступных в компоненте Colors. Ниже показано, каким образом опре­ деляется этот класс . 11 Продемонстрировать свойства и событи я import java . awt .*; import java . beans .*; puЫic class IntrospectorDemo
1322 Часть V. Применение Java puЬlic static vo id ma in (String args[] ) { try { Class<?> с=Class . forName ("Colors") ; Beaninfo beaninfo = Introspector . getBeaninfo (c) ; System. out . println ( "Cвoйcтвa :"); Prope rtyDe scriptor prope rtyDe scriptor [J = beaninfo . getPropertyDescriptors () ; for ( int i = О; i < prope rtyDe scriptor .length; i++) { System.out . println ( "\t" + prope rt yDe scriptor [iJ .getName()); System. out . println ( "Coбытия :") ; Eve nt SetDescriptor event SetDescriptor [J beaninfo . getEv entSetDescriptors () ; for ( int i = О; i < event SetDescriptor .length; i++) { System.out . println ( "\t" + eventSetDescriptor [i] .getName() ); ) catch ( Except ion е) { System. out .println ( "Пepexвaчeнo событие . "+е) ; Ниже приведен результат выполнения программы из данного примера. Свойства : rectangular События : mou seWhe el mo use mo useMotion component hierarchyBounds focus hierarchy prope rtyChange inputMethod key В этом результате следует обратить внимание на следующее. Во-первых, в клас­ се ColorsBeaninfo метод getPropertyD escriptors () переопределяется таким образом , чтобы возвращать единственное свойство rectangular, и поэтому вы­ водится только это свойство . И во-вторых, метод ge tEventSetDescriptors () не переопределяется в классе ColorsBeaninfo, и поэтому производится самоанализ по шаблону проектирования и обнаруживаются все события , в том числе и те , ко­ торые относятся к классу Со 1 or s суперкласса Canv а s. Напомним, что если не пере­ определить один из методов получения , определенных в классе SirnpleBeaninfo, то по умолчанию производится самоанализ по шаблону проектирования . Чтобы понаблюдать за изменениями, которые вносит класс Co lorsBeaninfo, следует уд алить файл этого класса и еще раз запустить программу IntrospectorDerno на выполнение. На этот раз будет выведено больше свойств.
38 Введен ие в сервлеты В этой гл аве речь пойдет о сервлетах . Сервлетами называют небольшие про­ граммы, которые выполняются на стороне сервера веб-подключения. Аплеты ди­ намически расширяют функциональные возможности веб-браузера, а сервлеты - возможности веб-сервера. Те ма сервлетов довольно обширна, и ее невозможно рассмотреть полностью в рамках одной гл авы . Поэтому в этой гл аве будут пред­ ставлены основные принципы, интерфейсы и классы, а также некоторые приме­ ры создания сервлетов. Предпосыл ки для разработки сервлетов Чтобы стали понятнее преимушества сервлетов, следует дать хотя бы общее представление о взаимодействии веб-браузеров и сервлетов для предоставления содержимого пользователю. Рассмотрим обработку запроса статической веб­ страницы. Пользователь вводит в поле адреса, обычно находящегося в верхней части окна браузера, URL (Uniform Resource Locator - унифицированный указа­ тель информационного ресурса). Браузер формирует запрос по сетевому протоко­ лу НТТР. направляя его соответствующему веб-серверу. А веб-сервер сопоставляет этот запрос с кон кретн ым файлом, возвращая его браузеру в виде ответа, посы­ лаемого по сетевому протоколу НТТР. В этом ответе НТТР-заголовок обозначает тип содержимого. Для этой цели служит стандарт MIME (Multipurpose Internet Mail Extensions - многоцелевые расширения электронной почты). Например, обычный текст в формате ASCII имеет тип MIME, обозначаемый как text/plain. Исходный код HTML (Hypertext Markup Language - язык разметки гипертекста) имеет тип МIМЕ, обозначаемый как text/html . А теперь рассмотрим динамическое содержимое. Допустим, база данных приме­ няется в интернет-магазине для хранения сведений о его коммерческой деятельно­ сти. В базе данных могут храниться продаваемые товары, прейскуранты , сведения о наличии товара, заказах и т. п. Владелец этого интернет-магазина решил сделать эту информацию доступной покупателям через веб-страницы. Содержимое этих веб-страниц должно создаваться динамически, чтобы отражать самые последние сведения, хранящиеся в базе данных. В первые годы существования веб-сервер мог динамически формировать страницу, создавая отдел ьный процесс для обработки каждого запроса клиента. Чтобы получить необходимую информацию, процесс мог устанавливать соедине-
Часть V. Применение Java ние с одной ил и несколькими базами данных. Связь с сервером осуществлялась через интерфейс CGI ( Common Gateway Interface - общий шлюзовой интерфейс). Инте рфейс CGI позволял отдельным процессам вводить данные из НТГР-запроса и выводить их в НТГР-ответ . Для написания СGl-программ применялись разные языки программирования, в том числе С, С++ и Perl. Но интерфейс CGI страдал серьезными недостатками, связанными с производи­ тельностью. Он был неэффективным с точки зрения ресурсов ЦП и оперативной памяти , потребляемых для создания отдел ьного процесса, а также установления и разрыва соединений с базой данных по каждому запросу клиента. Кроме того, СGI­ программы зависели от конкретной платформы. Потому для преодоления подобных недостатков бьши внедрены другие методики , к числу которых относятся сервлеты. По сравнению с интерфейсом CGI сервлеты обладают рядом преимуществами. Во­ первых, их производительность заметно вьпuе. Серв.леть1 выполняются в адресном пространстве веб<ервера. Чтобы выполнить обработку каждого запроса клиента, не обязательно создавать отдел ьный процесс. Во-вторых, серв.леть1 не зависят от кон­ кретной платформы, поскольку они разрабатываются нa java. В- -греть их, диспетчер безопасности Java на сервере накладывает ряд ограничений для защиты ресурсов на серверной машине. И наконец, для сервлета доступны абсолютно все функцио­ нальные возможности библиотек классов Java. Сервлет мочерез механизмы сокетов и удаленног жет связываться с аплетами, базой данных и другим программным обе­ спечением о вызова методов (RМI), рассматривавшиеся ранее в данной книге. Жизненный цикл сервлета Жизненный цикл серв.лета определяют следующие основные методы : ini t ( ) , service () и de stroy (). Они реализуются каждым сервлетом и в нужный момент вызываются сервером . Рассмотрим обычный сценарий взаимодействия с пользовате­ лем, который поможет лучше понять, когда именно происходит вызов этих методов. Итак, допусти м, что пользователь ввел URL в окне браузера. Исходя из этого URL, браузер формирует НТГР-запрос , посьшаемый соответствующему серверу. Затем этот НТГР-запрос принимает веб<ервер и сопоставляет его с конкретным сервлетом. Обнаруженный сервлет динамически загружается в адресное пространство сервера. Далее сервер вызывает метод init ( ) из серв.лета. Этот метод вызывается только в том случае, если сервлет впервые загружается в оперативную память. Серв.лету мож­ но передавать параметры инициализации, поэтому он допускает самонастройку. После этого сервер вызывает метод service ( ) из сервлета. Этот метод вы­ зывается для обработки НТГР-запроса. Как будет показано далее, сервлет может считывать данные, содержащиеся в НТГР-запросе, а также сформировать НТГР­ ответ клиенту. Сервлет остается в адресном пространстве сервера и доступен для обработки любых других НТГР-запросов, получаемых от клиентов. Метод service () вызывается по каждому НТГР-запросу. И наконец, сервер может принять решение выгрузить серв.лет из оперативной памяти. Для принятия такого решения на каждом сервере применяются разные ал­ горитмы. А для освобождения таких ресурсов, как дескрипторы файлов, выделяемых для сервлета, сервер вызывает метод de stroy (). Важные данные моrуг быть сохра-
Гл ава 38. Введение в сервл еть1 1325 иены в постоянном хранилище. Оперативная память, выделяемая для сервлета и его объекто в, может быть впоследствии угилизирована в процессе "сборки мусора". Варианты раз работки сервл ето в Для разработки сервлетов потребуется доступ к контейнеру сервлетов ил и серверу приложен ий. Наиболее популярными из них являются сервер приложе­ ний Glassfi sh и контейнер сервлетов To mcat. Сервер приложений Glassfish предо­ ставляется компанией Oracle в комплекте Jаvа ЕЕ SDK. Он поддерживается в ИСР NetBeans. А контейнер сервлетов To mcat - это программный продукт с открыты м исходн ым кодом, поддержи ваемый организацией Apache Software Fo undation. Он также поддерживается в ИСР NetBeans. Контейнером сервлетов To mcat и серве­ ром приложений Glassfish можно пользоваться и в других ИСР, например Eclipse. В примерах и пояснениях, приводимых в этой гл аве , применяется контейнер сервлетов To mcat по причинам, которые вам скоро станут понятны. Несмотря на то что такие ИСР, как NetBeans и Eclipse, способны значительно упростить разработку сервлетов, здесь и далее в этой гл аве они не применяютс я. В разных ИСР разработка и развертывание сервлетов имеет свои отличия , и по­ этому здесь просто невозможно рассмотреть все эти отличия. Кроме то го, многие читатели, скорее всего, будут пользоваться инструментальными средствами ко­ мандной строки , а не ИСР. Поэтому, если вы пользуетесь ИСР, за справкой о раз­ работке и развертыван ии сервлетов обращайтесь к документации на эту ИСР. А все приведенные далее пояснения подразумевают, что для разработки сервлетов применяются только инструментальные средства командной строки. Та ким обра­ зо м, они подойдут практических для любого читателя. Контейнер сервлетов To mcat применяется в этой гл аве потому, что выполнять примеры сервлетов, используя только инструментальные средства командной строки и текстовый редактор, по мнению автора, проще и доступнее в разных сре­ дах программирования. Кроме того , не придется загружать и устанавливать ИСР только для того , чтобы экспериментировать с сервлетами, поскольку для этого достаточно инструментальных средств командной строки . Следует, однако , иметь в виду, что рассматриваемые здесь основные принципы разработки сервлетов вполне пригодн ы и в той среде , где применяется сервер приложений Glassfish. Немного отличаться будет лишь механизм подготовки сервлета к тестированию. Помните! Наставления по разработке и разверты ванию сервлетов, представленные далее в этой гл аве, подразумевают испол ьзова ние только контейнера сервлетов To mcat и инструментов командной строки. Есл и вы пользуетесь ИСР и другим контейнером сервлето в или сервером приложений, обратитесь к документа ции на свою среду. Применение контейнера сервлетов To mcat В состав контейнера сервлетов To mcat входят библиотеки классов, документа­ ция и исполняющая среда для разработки и проверки сервлетов. На момент напи-
1326 Часть V. П р именение Java сания данной книги для использован ия было доступно несколько версий контей­ нера сервлетов To rncat, но в приведенном далее описании подразумевается при­ менение версии 7.О .47. Контейнер сервлетов To rncat можно загрузить по адресу tomcat . apache . org. Выберите ту версию, которая подходит для вашей среды. В примерах этой гл авы подразумевается применение 64-разрядной версии опе­ рационной системы Windows . Кроме того , предполагается , что 64-разрядная вер­ сия контейнера To rncat 7.0.47 была распакована из корневого каталога непосред­ ственно в заданное по умолчанию место, как показано ниже . C:\apache -tomcat- 7 .0 .47 -windows -x64\apache -tomcat- 7 .0.47\ Именно это место расположения контейнера сервлетов To rncat подразумева­ ется в рассматриваемых далее примерах. Если же загрузить контейнер сервлетов To rncat в другое место (или выбрать другую его версию) , то в эти примеры при­ дется внести соответствующие изменения, а возможно, и установить переменную среды окружения JAVA_НОМЕ , указав в ней каталог, где установлен комплект раз­ работки jаvа Developrnent Кit. На заметку! Все каталоги, представленные в этом разделе, подразумевают применение вер­ сии 7 .0 .47 контейнера сервлетов To mcat. Если уста новить другую версию этого контейнера, то придется откорректи ровать имена используемых каталогов и пути к ним, чтобы они соот­ ветствовал и установленной версии. После установки контейнера сервлетов To rncat следует запустить его на вы­ полнение, выбрав файл startup .bat в каталоге \apache -tomcat-7 .0 .47\Ьin. Чтобы остановить контейнер To rncat, следует выполнить файл shutdown . ba t, ко­ то рый также находится в каталоге Ьin. Классы и интерфейсы, требующиеся для создания сервлетов, содержатся в ар­ хивном файле servlet -api . j ar, который находится в следующем каталоге: C:\apache- tomcat -7 .0 .47 -windows -x64\apache -tomcat- 7 .0.47\liЬ Для доступа к архивному файлу servlet-ap i .jar следует обновить перемен­ ную окружения CLAS S PAT H таким образом, чтобы она включала следующую строку: C:\Proqram Files \Apache Software FoWldation\Tomcat 5.5\coll lDl on\ liЬ\ servlet-api .jar С другой стороны, этот файл можно указать во время компиляции сервлетов. Та к, пример сервлета из этой гл авы ко мпилируется по следующей команде: javac HelloServlet .java -clas spath " C:\apache -tomcat-7 .0.4-windowsx64 \ apache -tomcat- 7 .0 .4\liЬ\servle t-api .jar " По завершении компиляции сервлета нужно сделать так, чтобы контейнер сервлетов To rncat нашел его. Для этого следует разместить сервлет в подкаталоге \wеЬаррs\имя_ ка талога установки контейнера To rncat и ввести его имя в файл web . xml. Ради простоты в примерах этой гл авы применяется каталог и файл web . xm l, который контейнер сервлетов To rncat использует для своих образцов серв­ летов. Это избавляет от необходимости создавать файлы или каталоги только для экспериментов с примерами сервлетов. Ниже поясняются действия, которые потребуется выполнить для этого.
Гл ава 38. Введение в сервлеты Скопируйте файл класса сервлета в следующий каталог: C:\apache -tomcat -7 .0.47 -windows -x64\apache -toшcat- 7 .0.47\weЬapps\ examples\WEB- INF\ classes Введите имя сервлета и его сопоставление с файлом web . xml в каталог C:\apache-tomcat-7 .0.47 -windows -xб4 \apache-tomcat-7 .0.47\weЬapps \ examples\WEB-INF 1327 Та к, если первый пример сервлета называется He lloServlet, то в раздел фай­ ла web . xml , где описываются сервлеты , нужно ввести следующие строки кода раз­ метки : <servlet> <servl et-name >HelloServlet</servl et-name > < servlet-class>HelloServlet</servlet-class> </servlet> Введите приведенные ниже строки кода разметки в тот раздел файла web . xm l, где определяются сопоставления сервлета с шаблоном. Эти же действия следует выполнить и для всех остальных примеров сервлетов. <servlet-mapp ing> <servl et-nam e>HelloServ let</servlet-name > <url-pattern> /servlet/HelloServ let</url -pattern> </servlet-mapping> П росто й пример сервлета Чтобы стали понятнее основные принципы разработки серметов, обратимся к простому примеру создания и проверки сервлета. Для этого выполните следую­ щие основные действия. 1. Создайте и скомпилируйте исходный код сервлета. После этого скопируй­ те файл класса сермета в соответствующий каталог и введите имя сервлета и его со поставления в соответствующий файл web . xml. 2. Запустите на выполнение контейнер сервлетов To mcat. 3. Запустите на выполнение веб-браузер и запросите сервлет. А теперь рассмотрим подробнее каждое их этих основн ых действий. Соэда ние и ко мпиляция и сходного кода сервл ета Прежде всего создайте файл He lloServlet . j ava, который будет содержать исходный код следующей программы: import java.io.*; import javax .servlet .*; puЫ ic clas s HelloServl et extends Gene ricServlet puЫic void service ( ServletReque st requ est, ServletResponse response )
1328 Часть V. Применение Java throws Serv letException, IOException re sponse .setContentType ("text /htrnl") ; PrintWri ter pw = re spon se . getWriter () ; pw . println ("< B>Hello !"); pw . close (); Проанализируем исходный код этой программы. Прежде всего обратите вни­ мание на то , что в ней импортируется пакет javax . servlet. Этот пакет содер­ жит классы и интерфейсы, требующиеся для создания сервлетов. Мы еще вер­ немся к ним далее в этой гл аве . В данной программе определяется также класс He lloServlet, производный от класса GenericServlet. Класс Gene ricServlet обладает функциональными возможностями, упрощающими создание сервлетов. В частности , он предоставляет версии методов init ( ) и de stroy ( ) , которые можно использовать в исходном виде. Из всех методов, наследуемых из класса GenericServlet, в классе HelloServlet переопределяется только метод service () , который обрабатывает запросы откли­ ента. В качестве его первого аргумента указывается объект типа ServletReque st, чтобы сервлет вводил данные из запроса клиента, а в качестве второго аргумента - объект типа ServletResponce , чтобы сервлет формировал ответ клиенту. При вызове метода setContentType ( ) определяется тип MIME для НТГР­ ответа. В данной программе употребляется тип MIME, обозначаемый как text/ html . Он обозначает, что браузер должен интерпретировать содержимое как ис­ ходный код НТМL-разметки . Метод getWri ter ( ) получает объект типа PrintWriter. Все, что направляет­ ся в поток вывода, посылается клиенту как часть НТГР-ответа. Затем вызывается метод println ( ) для вывода простого фрагмента кода НТМL-разметки в качестве НТТР-ответа. Скомпилируйте исходный код данной программы и разместите файл He llo­ Servlet . class в соответствующем каталоге контейнера сервлетов To mcat, как пояснялось в предыдущем разделе. Кроме того , введите класс HelloServlet в файл web . xm l описанным ранее способом. Запуск ко нтейнера сервлетов To mcat на в ы пол нение Запустите контейнер сервлетов To mcat на выполнение, как пояснялось ранее. Контейнер сервлетов To mcat должен действовать еще до выполнения сервлета. Запуск веб-брауэера и запрос сервлета Запустите веб-браузер на выполнение и введите в его окне следую щий URL: http:// localhost : BOBO/examples/servlets/ servlet/HelloServlet С другой стороны, можно ввести и такой URL: http://127 .0.0 .1:8080/examples/servlets/ servlet/HelloServlet Это вполне допустимый URL, поскольку IР-адрес 127 . О.О .1 определяется как адрес локальной машины.
Гла в а 38. Введение в сервлеты 1329 В окне браузера должны появиться данные, выводимые сервлетом. Они будут состоять из символьной строки "Hello ! ", выделенной полужирным. П рикладной программный и нтерфейс ServLet API Рассматриваемые в этой гл аве классы и интерфейсы, требующиеся для разра­ ботки сервлетов, содержатся в двух пакетах, javax . servlet и j avax . servlet . http, и образуют интерфейс Servlet API. Следует, однако , иметь в виду, что эти пакеты не относятся к основным пакетам jаvа. Следовательно, они не входят в со­ став версии jаvа SE. Вместо этого они предоставляются ко нтейнером сервлетов To mcat , а также доступны в версииjаvа ЕЕ. Прикладной программный интерфейс Servlet API по-прежнему находится на стадии разработки и усовершенствования. Те кущая спецификация сервлетов относится к версии 3. 1, и именно она используется в примерах из этой гл авы. Но поскольку в Java все постоя нно меняется, то поинтересуйтесь, не появились ли какие-нибудь дополнения или видоизменения данной спецификации. В этой гл аве обсуждается ядро прикладного программного интерфейса Servlet API, которое до­ ступно большинству читателей и поддерживается во всех современных версиях спецификации сервлетов. П a кeт javax . servlet Пакет j avax . servlet содержит целый ряд интерфейсов и классов, образую­ щих каркас для функционирования сервлетов. В табл. 38 .1 перечислены основные интерфейсы, предоставляемые в данном пакете . Самым гл авным из них является интерфейс Servlet. Все сервлеты должны реализовывать этот инте рфейс или расширять реализующий его класс. Не менее важны интерфейсы Se rv l е tRe que s t и ServletResponce. Табл ица 38.1 . Основные инте рфейсы иэ пакета j avax . servlet Интерфейс Servlet ServletConfiq Servleteontext ServletRequeat ServletResponse Описание Объявляет методы жизненного цикла сервлета Позволяет получать параметры инициализации сервлетов Позволяет регистрировать события в сервлетах и обращаться к сведениям об их среде Служит для ввода данных из запроса клиента Служит для вывода данных в ответ клиенту В табл. 38 .2 перечислены основные классы из пакета javax . servlet. Интер­ фейсы и классы, перечисленные в обеих таблицах, будут рассмотрены далее более подробно.
1330 Часть V. П р именение Java Та бл ица 38.2. Основные классы из пакета javax . servlet Класс Generi cServlet ServletinputStreaa ServletOutpu tS treaш Servletl:xception OnavailaЬleBxception Интерфейс Servlet Опмсанме Реализует интерфейсы Servlet и ServletConfig Предоставляет поток для ввода запросов клиента Предоставляет поток для вывода ответов клиенту Ук азывает на то, что в сервлете произошла ош ибка Ук азывает на то, что сервлет недосrупен Все сервлеты должны реализовать интерфейс Servlet. В нем объявляются ме­ тоды init (), service () и de stroy (),которые вызываются сервером в течение жизненного цикла сервлета. Кроме них предоставляется также метод, позволяю­ щий сервлету получать любые параметры инициализации. Методы, определяе­ мые в интерфейсе Servlet, перечислены в табл. 38 .3 . Табл ица 38.3. Методы из инте рфейса Servlet Метод void d8stroy () ServletConfiq qetservletCon­ fig () String getservletinfo () void init ( ServletConfig •с) throws Servletвxoвption void service ( ServletRequest sапрос , ServletResponse (IO'JNIO ' ) throws Servletвxoeption , IOException Описание Вызывается при выгрузке сервлета Возвращает объект типа ServletConfiq, содержаший любые параметры инициализации Возвращает символьную строку, описывающую сервлет Вызывается во время инициализации сералета. Параметры инициализации для сералета могут бьпъ получены из параметра гс. Если сервлет нельзя ини­ циализировать, то генерируется исключение типа Servlet&xoвption Вызывается для обработки запроса клиента. Запрос клиента можно ввести из объекта, обозначаемого па­ раметром sanpoc, а ответ клиенту - вывести в объект, обозначаемый параметром О!П18!1'. Если при выполне­ нии сервлета или вводе-выводе возникаКУr ошибки, то генерирхется исключение Методы init (), service () и de stroy () определяют жизненный цикл сервле­ та. Они вызываются сервером. Метод ge tServletCon fig () вызывается сервле­ том для получения параметров инициализации. Разработчик сервлета переопре­ деляет метод getServletinfo (), чтобы предоставить строку с полезной инфор­ мацией (например , Ф.И.О . автора, номер версии, дата выпуска, авторские права) . Этот метод также вызывается сервером. И нтepфe й cservletConfig Интерфейс ServletConfig позволяет получать в сервлете данные конфигура­ ции сервлета во время его загрузки. В табл. 38.4 перечислены методы, объявляе­ мые в этом интерфейсе.
Гл ава 38. Введение в сервлеты 1331 Табл ица 38.,. Методы из инте рфейса ServletConfig Метод Описание ServletContext getServletContext () String ge tinitParlUl l8 ter (String .пар.sне!l'р) Возвращает содержимое данного сервлета Возвращает значение .пар.sне!l'р& инициа­ лизации EnW1 18 ration getinitparlUl l8 terN&1 11& s() Возвращает перечень имен параметров инициализации String getServletNaшe () Возвращает имя вызывающего сервлета И нтepфeй cservletContext Интерфейс ServletContext позволяет получать в сервлете сведения о среде исполнения сервлетов. Некоторые его методы перечислены в табл . 38.5. Табл ица 38.5. Методы из инте рфейса Servletcontext Метод OЬject getAttri.Ьute (String 4!1'р•б.)'!1') Strinq get:МiDleТype (String фdл) String getRealPath (String �,Уа.пъв вi -.П.)'!1'Ъ) String getServerinfo () void loq (String s) void log (Strinq в, ThrowaЬle е) void setAttri.Ьute (Strinq а!l'р•б.У!1' , OЬject sвачевJrе) Описани е Возвращает значение указанного а!l'р•б.У!1'а сервера Возвращает тип MIME указанного fdлa Возвращает реальный путь, соответствующий указан­ ному �,YIUIЫJONy_.П.)'!1'• Возвращает сведения о сервере Записывает указанную строку s в журнал сервлета Записывает указанную строку в и трассировку стека для заданного исключения ев журнал сервлета Присваивает заданному •!1'р•б.У!1'.У указанное sвачеаяе И нтерфейс ServletRequest Интерфейс ServletReque st позволяет получать в сервлете сведения о запро­ се клиента. Некоторые его методы перечислены в табл . 38.6 . Табл ица 38.6. Методы из инте рфейса ServletR.equest Метод OЬject qetAttri.Ьute (String ·�б,У!l') Strinq getCharacterEncodinq () Int getContentLenqth () Strinq qetcontentТype О Описание Возвращает значение указанного а!l'р•б.У!1'• Возвращает кодировку символов в запросе Возвращает длину запроса. Если длину запроса нель- 3Я определить, возвращается значение -1 Возвращает тип запроса. Если тип запроса нелЬ3Я определить, возвращается пустое значение null
1332 Часть V. П р именение Java Метод ServletinputStreaш getinputStreaa () throws IOException Strinq qetPar11 11 1& ter (Strinq :юиr аараие�) EnUJ1 18 ration<String> qetPar&D8terN11 11 1& s() String [] getPar81 118 terValues ( Strinq .-) String qetProtocol () 8\ПferedReader C}8t.Reader О throws IOException Strinq getR81Dot.Ad dr () Strinq qetR81Dotel l ost () Strinq qetSch81 118 () Strinq getServerNaiae () int 9etServerPort () Октtчание 11ш6Л. 38 . б Описание Возвращает поток ввода типа ServletinputStreщ который можно использовать для чтения двоичных данных из запроса. Если метод get.Reader () уже вы­ зывался для этого запроса, то генерируется иcклю­ чeниe типa IllegalStateException Возвращает значение указанного параметра .-_ аараие� Возвращает перечисление имен параметров по дан­ ному запросу Возвращает массив, состоящий из значений, связан­ нь�х с параметром - Возвращает описание протокола Возвращает буферизированный поток чтения, кото­ рый можно использовать для ввода текста из запро­ са. Если метод getinputStreaш () уже вызывался по данному запросу, то генерируется исключение типa illeqalStateException Возвращает строковый эквивалент IР-адреса клиента Возвращает строковый эквивалент имени хоста клиента Возвращает схему передачи URL, которая использу­ ется для запроса (например, "http", "ftp" ит.д. ) Возвращает имя сервера Возвращает номер порта И нтерфе й с ServletResponce Интерфейс ServletResponce позволяет сформировать в сервлете ответ кли­ енту. Некоторые его методы из этого интерфейса перечислены в табл. 38 .7 . Та блица 38.7. Методы из инте рфейса ServletResponce Метод Strinq qetCharacterEncodinq () ServletOutputStreaш getOutpu tStreaш () throws IOException Print1friter qet1friter () throws IOException Описание Возвращает кодировку символов в ответе Возвращает поток вывода типа ServletOutputStream, который можно ис­ пользовать для записи двоичных дан ных из сервлета в ответ. Если метод qet1friter () уже вызывался по дан ному запросу, то генерирует­ ся исключение типа IlleqalS tateException Возвращает поток записи типа Print1friter , кото рый можно использовать для вывода сим­ вол ьных данных в ответ. Если метод qerOutpu tStream () уже вызывался по данно­ му запросу, то генерируется исключение типа IlleqalS tateException
Гл ава ЭВ. Введение в сервлеты 1333 Окстчание таМ. 38. 7 Метод Описание void setContentLength (int дJDraa) Задает указанную RJU UJ Y содержимого ответа void setContentТype (Strinq !l'NoJ ) Задает тип содержимого ответа Класс GenericServlet Класс GenericServlet предоставляет реализации основных методов жизнен­ ного цикла сервлета. Этот класс реализует интерфейсы Servlet и ServletConfig, а также предоставляет метод для за писи символьной строки в журнал сервера. Ниже приведены общие формы этого метода, где параметр s обозначает записы­ ваемую в журнал символьную строку, а параметр е - исключение, генерируемое при возникновении ошибки. void log (String s) void log (Strinq s, ТhrowaЫe е) Клacc servletinputS tream Класс ServletinputStream расширяет класс InputStream. Он реализуется контейнером сервлетов и предоставляет поток ввода, который разработчик серв­ лета может использовать для чтения данных из запроса клиента. В этом классе определяется конструктор по умолчан ию, а также метод для чтения байтов из по­ тока ввода. Ниже приведена общая форма этого метода. int readLine (Ьyte [] буфер , int сне1rеюrе , int разнер) throws IOException Здесь параметр буфер обозначает массив, в котором хранится определенное количество (ра змер) байтов, начиная с указанного смещения. Этот метод возвра­ щает фактическое количество прочитанных байтов ил и значение -1, если будет достигнуто усл овие окончания потока ввода. Клacc s ervletOu tputStream Класс ServletOutputStream расширяет класс Ou tputStream. Он реализует­ ся контейнером сервлетов и предоставляет поток вывода, который разработчик сервлета может применять для записи данных в ответ клиенту. В этом классе опре­ деляется конструктор по умолчанию, а также методы print () и println (),пред­ назначенные для вывода данных в поток. Клacc ServletException В пакете javax . servlet определяются два класса исключений. Первый класс представляет исключениетипа SеrvlеtЕхсерtiоn, которое извещает об ошибке при выполнении сервлета, а второй класс - исключение типа Unava ilaЫeException, класс которого расширяет класс Se rvletException. Это исключение извещает о то м, что сервлет недоступен.
Часть V. Применение Java Ввод пара метров сервлета В интерфейс ServletReque st входят методы , позволяющие вводить имена и значения параметров, включаемых в запрос клиента. В приведенном далее при­ мере разрабатывается сервлет, демонстрирующий их применение. Этот пример состоит из двух файлов. Веб-страница определяется в файле Po stParameters . html , а сам сервлет - в файле PostParametersServlet . java. Ниже приведен исходный код из НТМL-файла PostParameters . html . В этом коде определяется таблица, состоящая из двух меток и двух текстовых полей. Одна из меток называется Employee ( Сотрудник) , другая - Phone (Телефон) . Имеется также кнопка подтверждения. Обратите внимание на то , что параметр action де­ скриптора <form> определяет URL. Этот URL обозначает сервлет, который будет выполнять обработку НТТР-запроса типа POST. <html > <body> <cente r> <form name="Forml " me thod= "post" action="http ://localhost :BOBO/examp les/servlets/ servlet /PostParametersServlet"> <tаЫе> <tr> <td><B>Employee</td> <td><iпput type=textbox name="e " size="25" value= ""> </td> </tr> <tr> <td><B>Phone </td> <td><input type=textbox name="p" size="25" value= ""></td> </tr> </tаЫе> <input type=submi t value="SuЬmit"> </body> </html > Ниже приведен исходный код из файла PostParametersServlet . java. Метод se rvice () переопределяется для обработки запросов клиента, а метод get Parame terN ame s () возвращает перечисление имен параметров. Их обработка осуществляется в цикле. Как видите, для клиента выводится имя параметра и его значение. Значение параметра получается методом getParameter (). import java.io.*; impo rt java.util .*; import javax .servlet .*; puЫic class PostParametersServl et extends Gene ricServlet { puЫ ic void service (ServletReque st request, ServletResponse response ) throws Servl etException , IOExceptioп { // получить поток записи типа Print'lfriter PrintWriter pw = re sponse.getWriter () ; // получить перечисление имен параме тров
Гл ава ЭВ. Введение в сервлеты Enume ration е = request . getParameterNames () ; 11 в ы вести име на параме тров и их значения while (e. hasMoreElements ()) { String pname = (String) e.nextEleme nt () ; pw.print(pname + "=" ); St ring pva lue = reques t.getParame te r(p name) ; pw . println (pvalue ); pw . close (); 1335 Скомпилируйте сервлет. Затем скопируйте его в соответствующий каталог и обновите файл web . xm l, как упоминалось ранее. После этого выполните следу· ющие действия для проверки сервлета из данного примера. 1. Запустите на выполнение контейнер сервлетов To rncat , если это еще не сделано. 2. Воспроизведите веб-страницу в окне браузера. 3. Введите в текстовых полях фамилию служащего и номер его телефона. 4. Передайте веб-страницу клиенту. Та ким образом, в окне браузера будет воспроизведен ответ, динамически сфор­ мированный сервлетом. П a кeт javax .servlet.http Для демонстрации основных фун кциональных возможностей сервлетов в при­ веденных ранее примерах применялись такие классы и интерфейсы из пакета javax . serv let, как ServletReque st, ServletResponse и GenericServlet. Но для работы с сетевым протоколом НТТР обычно применяются интерфейсы и классы из пакета j avax . servlet . http. Как станет ясно в дальнейшем, его функ­ циональные возможности упрощают создание сервлетов, обрабатывающих запро­ сы и ответы по сетевому протоколу НТТР. В табл . 38.8 перечислены основные ин­ терфейсы, предоставляемые в данном пакете. В табл. 38.9 описаны основные классы, предоставляемые в данном пакете. Наиболее важным из них является класс HttpServlet. Разработчики сервлетов обычно расширяют этот класс для обработки НТТР-запросов. Табл ица 38.8. Основньае инте рфейсы иэ пакета j avax . servlet . http И нтерфейс Описание BttpServletRequest П озволяет вводить данные из НТГР-запроса в сервлет BttpServletResponse Позволяет выводить данные из сервлета в НТГР-ответ HttpSession Позволяет вводить и выводить данные сеанса с вязи
1336 Ч асть V. Применение Java Табл ица 38.9. Основные классы из пакета javax . servlet .http Класс Описание Cookie BttpServlet Позволяет хранить данные состояния на машине клиента П редоставляет методы для обработки запросов и ответов по сетевому пrотоколу НТТР И нтepфeй cвttpServletRequest Интерфейс HttpServletRequest реализуется контейнером сервлетов. Он по­ зволяет получать из сервлета сведения о запросе клиента. В табл. 38. 1 О перечисле­ ны некоторые методы из этого класса. Табл ица 38.10. Методь1 из инте рфейса BttpServletRequest Метод String getAuthТype () Cookie [ ] getCookies () lonq qetDat.Н.ader ( Strinq по.пе) String qetвeader (Stri.ng по.пе) EnW1 18 ration<Strinq> 9etвeac:lerN&J1 1& s() int getintвeadar (Strinq по.пе) Strinq qetм.thod () Strinq qetpath�o () String getpath'l'ranalated () String qetQueryStri.ng () Strinq ge�teUser О Strinq qetRequested Sesaionid () String qetRequestURI () Strin9Buffer qetRequestURL () Strinq qetservletpath () BttpSession getseasion () BttpSession qetSession (Вoolean яо81 11i ) Описание Возвращает схему аугентификации Возвращает массив, содержащий сооkiе-файл в данном запросе Возвращает значение из указанного ПОЛ16 заголовка даты Возвращает значение из указанного полж заголовка Возвращает перечисление имен заголовков Возвращает целочисленный ( int) зквивалент поля за­ головка Возвращает метод ДI IЯ НТТР-запроса Возвращает любые сведения о пуги. который следует после пуги к сервлету и перед строкой запроса в URL Возвращает любые сведения о пуги, который следует после пуги к сервлету и перед строкой запроса в URL после ее преобразования в настоящий пугь Возвращает любую строку запроса в URL Возвращает имя пользователя, который составил дан­ ный запрос Возвращает идентификатор сеанса связи Возвращает URL Возвращает URL Возвращает ту часть URL, которая обозначает сервлет Возвращает сеанс связи по данному запросу. Если сеанс связи не существует, он создается, а затем возвращается Если параметр вo8Di принимает логическое значение true и сеанс связи не существует, то сеанс связи созда­ ется и возвращается по данному запросу. В противном случае возвращается существующий сеанс связи по дан­ ному запросу
Метод Ьoolean isВ&questedSession­ IclFrOIDCookie О Ьoolean isRequestedSession­ IclFrOJDURL () Ьoolean isRequestedSession­ IdVa1id () Гл ава 38. Введе н и е в сервлеты 1337 Окон'Чанш таШ. 38. 1 О Описание Возвращает логическое значение true, если сооkiе­ файл содержит идентификатор сеанса связи, а иначе - логическое значение false Возвращает логическое значение true, если URL со­ держит идентификатор сеанса связи, а иначе - логиче­ ское значение false Возвращает логическое значение true, если запрошен­ ный идентификатор сеанса является действительным в текущем содержимом сеанса связи И нтepфe й cHttpServletRe sponse Интерфейс HttpServletResponse позволяет сформиро вать в сервлете НТТР­ ответ для клиента. В нем определяется ряд констант, соответствующих кодам раз­ личных состояний, которые можно присвоить НТТР-отвеrу. Например, значение константы SC ОК обозначает, что НТТР-ответ достиг цели, а значение константы SC_NOT _FOUN D - запрошенный ресурс недоступен . В табл . 38. 11 перечислены не­ которые методы из этого интерфейса. Та бл ица 38.11. Методы иэ инте рфейса Bttp ServletRespon se М етод void addCookie (Cookie cooki•) boolean containsBeader (String попе) String encodeURL (String url ) String encodeRedirectURL (String url ) void sendError (int с) throws IOException void sendError (int с, String в) throws IOException void sendRedirect(String url ) throws IOException Описание Вводит указанный сооkiе-файл в НТТР-ответ Возвращает логическое значение true, если в заго­ ловке НТТР-ответа содержится заданное попе Определяет, должен ли идентификатор сеанса свя­ зи быть закоди рованным в указанном URL. Если должен, то возвращается измененная версия ука­ занного URL, а иначе - сам указанный URL. Этим методом должны обрабатываться все URL, сформи­ рован ные сервлетом Определяет, должен ли идентификатор сеанса свя­ зи быть закоди рованным в указанном URL. Если долже н, то возвращается измененная версия ука­ занного URL, а иначе - сам указанный URL. Этим методом должны обрабатываться все URL, переда­ ва емые методу sendRedi rect () Посылает клиенту код ошибки, обозначаемый пара­ метром с Посылает клиенту код ошибки, обозначаемый пара­ метром с, а также строку сообщения, определяемую параметром /1 Переадресовывает клиента по указанному URL
1338 Часть V. П р именение Java Метод void setDateBeader (String по.пе , long -.1UП1сежувд) void setвeader (String по.пе , Strinq энаvенJ1е) void setintвeader (String попе , int ЭВ4V8НJ1е ) void setStatu• (int ход) Око�rчание табл. 38 . 11 Описание Вводит указанное по.пе в заголовок со значением даты, вычисляемой в -JU Ul cexyвдax. Отсчет времени ведется в миллисекундах, начиная с полу­ ночи 1 января 1970 года (GMT - среднее время по Гр инвичу) Вводит указанное по.пе в заголовок со значением, обозначаемым параметром эв•vевже Вводит указанное попе в заголовок со значением, обозначаемым параметром эв•vевже Ус танавливает указанный ход состояния данного ответа И нтерфейс BttpSession Интерфейс HttpSession позволяет читать и записывать в сервлете данные со­ стояния, связанные с сеансом связи по сетевому протоколу НТГР. Некоторые из ме­ тодов этого интерфейса перечислены в табл. 38. 12. Каждый из них генерирует ис­ ключение типа IllegalStateExcept ion, если сеанс связи уже недействительный. Табл ица 38.12. Методы из инте рфейса RttpSession Метод OЬject qetAttriЬute (String 4'1'plrб,Y'1') EnU1 118 ration<String> qetAttriЬuteN&l ll8 s() lonq getCreationTiшe () Strinq qetid () lonq getLast.Acce•sedTiшe () void invalidate () Ьoolean isNew () void removeAttriЬute (String 4'1'plrб,Y'1') void setAttriЬute (Strinq а'1'р1rб.У'1', OЬject энaverare) Описание Возвращает значение, связанное с именем, передавае­ мым в качестве параметра a'1'J)lrб,Y'1'. Возвращает пустое значение null, если указанный а�•б.У'1' не найден Возвращает перечисление имен атрибутов, связанных с сеансом связи Возвращает время, прошедшее с момента создания сеан­ са связи. Отсчет времени ведется в мил л исекундах, начи­ ная с полуночи 1 января 1970 года (GMT - среднее время по Гр инвичу) Возвращает идентификатор сеанса связи Возвращает время, прошедшее с того момента, когда клиент сделал последний запрос в вызывающем сеансе связи. Отсчет времени ведется в миллисекундах, начиная с полуночи 1 января 1970 года (GMT - среднее время по Гр инвичу) Отменяет данный сеанс связи и W13Ляет его из контекста Возвращает логическое значение true, если сервер соз­ дал сеанс связи, еще не доступный клиенту Уд аляет заданный •'1'J)lrб.Y'1' из сеанса связи Связывает указанное .sв.sv.,. . e с именем заданного a'1'J)lrб,Y'1'a
Гл ава 38. Введение в сер вл еты 1339 Класс Cookie Класс Cookie инкапсулирует сооkrе-файл, который хранится на стороне клиента и содержит данные состояния. Пользоваться сооkiе-файлами уд обно для отслежи­ вания активности пользователей. Допустим, пользователь посещает интернет-ма­ газин. В сооkiе-файле можно сохран ить имя пользователя , адрес и прочие сведе­ ния о нем. В этом случае пользователю не нужно вводить эти данные всякий раз , когда он посещает этот интернет-магазин. Сервлет может сохранить сооkiе-файл на машине пользователя с помощью ме­ тода addCookie () из интерфейса HttpServletResponce. Данные из этого файла затем включаются в заголовок НТТР-ответа, который отправляется браузеру. Имена и значения из сооkiе-файлов хранятся на машине пользователя. Часть сведений, сохраняемых из каждого сооkiе-файла, включает следующее: • имя сооkiе-файла; • значение из сооkiе-файла; • срок действия сооkiе-файла; • домен и путь к сооkiе-файлу. Срок действия определяет, когда данный сооkiе-файл будет уд ален из машины пользователя. Если дата окончания срока действия сооkiе-файла не назначена явным образом, сооkiе-файл удаляется по завершении текущего сеанса связи с браузером. Домен и путь к сооkiе-файлу определяют, когда он будет включен в заголовок НТТР-запроса. Если пользователь вводит URL, где домен и путь совпадают с эти ми значениями, то сооkiе-файл предоставляется веб-серверу, в противном случае - не предоставляется . У класса Cookie имеется еди нственный конструктор. Ниже приведена его об­ щая форма. Cookie (String ., ,. , String sнav8НJ1e) Здесь параметры имя и зна чение обозначают соответственно имя и значение, которые передаются конструктору из сооkiе-файла. Методы из класса Cookie пе­ речислены в табл. 38. 13. Та бл . 38. 13. Методы иэ класса Cookie Метод OЬject clone () String getColl ll ll8 ntО String 9etD011 1a inО int getмaxAge () String get.Naшe () String getpath () Ьo o lean getSecure () String getValue () Описание Возвращает копию данного объекта Возвращает комментарий Возвращает домен Возвращает максимальный срок действия сооkiе-файла (в секундах) Возвращает имя Возвращает пугь Возвращает логическое значение true, если сооkiе-файл безопасный, а иначе - логическое значение fal•• Возвращает значение
Часть V. П р именение Java Октtчанш та6А. 38. 13 Метод Описание Возвращает версию int qetVersion () Ьoolean isBttpOnly () Возвращает логическое значение true, если сооkiе-файл содержит атрибуг BttpOnly void setC011 11 11e nt (Strinq с) Присваивает заданный комментарий с void aetDomain (Strinq d) void setC011 11 11e nt (Strinq void setвttpOnly (Ьoolean !l'OJЦ,XO_Http) void setмaxAqe (int се.а:.УНд) void setPath (Strinq р) void setSecure (Boolean беsопасво) void вetValue (Strinq v) void setVer sion (int v) П рисваивает заданный домен d Если параметр !1'олъ.а:о_Нt tр принимает логическое зна­ чение true, то атрибуг BttpOnly вводится в сооkiе-файл. А если параметр !l'ОЛЪ.а:о_Ht tp принимает логическое значение falвe, то атрибуг BttpOnly удаляется Уст анавли вает максимальный срок действия сооkiе­ файла в секундах. Этот срок определяется количеством секунд, по истечении которых сооkiе-файл удаляется Присваи вает заданный пугь р Ус танавливает признак безопасности, определяемый па­ раметром бе.эопасяо Уст анавливает заданное значение v Присваивает заданную версию v Кл асс BttpServlet Класс HttpServlet расширяет класс Gene ricServlet и обычно применяе'Г" ся при разработке сервлетов, получающих и обрабатывающих НТГР-запросы. Методы из класса HttpServlet перечислены в табл. 38. 14 . Таблица 38. 1,, Методы иэ класса BttpServlet Метод Описание void doDelete (BttpServlet.Requeвt Обрабатывает НТГР-запрос типа DELE'l'E saz zpoc , BttpServletResponвe �) throws IOException , ServletException void doGet (BttpServletR.equest saz:zpoc , Обрабатывает НТГР-запрос типа GZT BttpServlet.Responвe 0!1'"!1') throws IOException , Servletzxception void doOptions (BttpServletRequest Обрабатывает НТГР-запрос типа OPТI�S saz zpoc , BttpServletResponse О!П18!1') throws IOExoeption , ServletException void doPost (BttpServletRequest saz:zpoc , Обрабатывает Н'1 1Р - запрос типа IOST BttpServlet.Reвponse 0!1'"т) throws IOException , Servletzxoeption void doPut (BttpServletRequest saz:zpoc , Обрабатывает НТГР-запрос типа РОТ BttpServletResponse от•ет) throws IOException , ServletException void doTrace (BttpServletRequest saz zpoc , Обрабатывает НТГР-запрос типа 'l'RACE BttpServletReвponse 0!1'"!1') throws IOException , Servletzxception
Метод Lonq getLast:мodi�ied (BttpServle�est sапрос) void service (BttpServlet:Request .sапрос , BttpServlet:Response 0!1'•8!1') throws IOException , ServletException Гл ава ЭВ. Введение в сервлеть1 Окон-чание таМ. 38 . 14 Оnм санме Возвращает момент времени, измеряе­ мого в миллисекундах после полуночи 1 января 1970 года (GMT - среднее время по Гр инвичу) , когда в последний раз был изменен запрошенный ресурс Вызывается сервером, когда для дан- ного сервлета поступает НТГР-запрос. Параметры sапрос и 0!1'88!1' предосrавля­ ют доступ к НГГР-эапросу и ответу соот­ ветсrвенно Обработка НТТР-эапросов и ответов НТТР В классе HttpServlet предоставляются специальные методы для обработки разнотипных НТТР-запросов. Разработчики сервлетов обычно переопределяют один из этих методов. К их числу относятся методы do D elete (), doGe t (), do­ Head (), doOptions (), do Post (), doPut () и doTrace (). Привести здесь полное описание различных типов НТТР-запросов не представляется возможн ым. Но чаще всего при обращении с заполняемыми формами применяются НТТР-запросы типа GET и POST, и поэтому далее приводятся примеры из обработки в сервлетах. Обработка НТТР-эапросов типа GET В этом разделе представлен пример разработки сервлета, обрабаты вающего за­ прос НТТР-запрос типа GET. Этот сервлет вызывается , когда передается форма, заполненная на веб-странице. Данный пример состоит из двух файлов. В частно­ сти , разметка веб-страницы определяется в НТМL-файле ColorG et . html, а серв­ лет - в файле ColorGetServlet . java . Ниже приведен исходный код разметки из НТМL-файла ColorGet . html . В этом коде определяется форма, содержащая эле­ мент выбора и кнопку для передач и формы. Обратите внимание на то , что пара­ метр action дескриптора <form> определяет URL. Этот URL обозн ачает сервлет для обработки НТТР-запроса типа GET. <html > <body> <center> <form name= "Forml " action="http ://localhost :8080/exampl es/servl ets /servlet /ColorGe tServ let"> <B>Color :</B> <select name="color" size=" l"> <option va lue="Red">Red< /option> <option value="Green">Green< /option> <option value="Blue ">Blue< /option> </select> <br><br> <input type=submi t va lue="Submit"> </form> </body> </html>
13,2 Часть V. П р и менение Java Ниже приведен исходный код сервлета из файла ColorGetServlet.java. Метод doGet () переопределяется для обработки любых НТТР-запросов типа GET , посылаемых данному сервлету. Чтобы получить результаты выбора, сделанного пользователем, в сермете вызывается метод getPararneter () из интерфейса Ht tpServletReque st. После этого формируется ответ по данному запросу. import java.io. *; impo rt javax . servlet .*; import javax . servlet .http. *; puЫ ic class ColorG etServlet extends HttpServlet puЬlic vo id doGet (HttpServletRequest request, HttpServletResponse response ) throws Servl etExcept ion, IOException { St ring color = requ est .getParame t er ( "color " ); re sponse .se tContentType ("text/html") ; PrintWri ter pw = re sponse . getWriter () ; pw . println ("< B>The selected color is : ") ; pw . println (color ); pw. close (); Сначала скомпилируйте сервлет, а затем скопируйте его в соответствующий каталог и обновите файл web . xml, как пояснялось ранее. Далее выполните следу­ ющие действия для проверки сервлета из данного примера. 1. Запустите на выполнение контейнер сервлетов To mcat, если он еще не дей- ствует. 2. Выведите ве&-страницу в окне браузера. 3. Выберите нужный цвет. 4. Передайте форму, заполненную на веб-странице. После этого браузер выведет результат, динамически сформированный серв­ летом. Следует также иметь в виду, что параметры для НТТР-запроса типа GET включены как составная часть в URL, посылаемый неб-серверу. Допустим, поль­ зователь выбрал красный цвет и передал заполненную форму. URL, посылаемый серверу из браузера. будет выглядеть так , как показано ниже. Символы, стоящие справа от вопросительного знака, составляют стрику запроса. http:// localhost : 8080/exaш.ples/servle ts/ servlet/ColorGetServlet?color=Red Обработка НТТР-эапросов типа POST А теперь перейдем к примеру разработки сервлета, обрабатывающего НТТР­ запрос типа POST. Этот сервлет вызывается при передаче формы, заполненной на неб-странице. Данный пример состоит из двух файлов. В частности , разметка неб-страницы определяется в НТМL-файле Со 1 о r Ро s t . h trnl , а сервлет - в файле ColorPostServlet . java. Ниже приведен исходный код разметки веб-страницы из НТМL-файла ColorPost.html . Он похож на код из НТМL-файла ColorGet . htrnl , за исключе­ нием того , что параметр rne thod дескриптора <forrn> явным образом определяет,
Гл ава 38. Введение в сервл еты что следует использовать метод POST, а в параметре action тоrо же дескриптора указан другой сервлет. <html > <body> <center> <form name= "Forml " me thod= "post" action="http ://localhost :8080/examp l es/servlets /servlet/ColorPostServlet"> <B>Color :</B> <select name= "color " size="l"> <option value="Red" >Red< /option> <option va lue="Green">Green</option> <option value="Blue " >Blue</option> </select> <br><br> <input type=submi t value="Submit"> </form> </body> </html > Ниже приведен исходный код сервлета из файла ColorPostServlet . j ava. Метод doPo st () заменяется для обработки любых НТТР-запросов типа POST, 01'­ правляемых данному сервлету. Для получения результатов выбора, сделанного пользователем, в данном сервлете вызывается метод ge t Parameter () из интер­ фейса HttpServletReque st. После этоrо формируется ответ на дан ный запрос . import java.io. *; import javax . servlet .*; import javax .servlet .http.*; puЫ ic clas s ColorPostServl et extends HttpServ let { puЬlic void do Po st ( HttpSe rvl etRequest requ est, HttpSe rvletResponse re sponse ) throws Servl etExcept ion , IOExcept ion { String color = requ est . getParame ter ("color" ); re sponse .se tContentType ("t ext /html") ; PrintWriter pw = re spons e.getW riter () ; pw . println ("< B>The selected color is : ") ; pw . println (color) ; pw .close (); Скомпилируйте данный сервлет. Чтобы проверить ero , выполните те же дей­ ствия , что и в предыдущем разделе. На заметку! Параметры НПР-эапроса типа POST не включа ются в соста в URL, отп равляемый веб-серверу. В да нном примере брауэер посылает серверу следующий URL: http : / / localhost : 8080/ex11D1ples/ servlets / servlet/ColorPostServlet. Имена и зна­ чения пара метров отп равляются в теле НПР-эапроса. П р именение сооkiе - фай л ов А теперь рассмотрим пример разработки сервлета, чтобы продемонстриро­ вать применение сооkiе-файлов. Этот сервлет вызывается при передаче формы ,
Часть V. Применение Java заполненной на веб-странице. Данный пример состоит из трех файлов, перечис­ ленных в табл. 38. 15. Табл ица 38.15. Файлы, соста вл яющие пример применения сооkiе-файлов ФаАл Описание Ad d Cookie . ht.1 11 1 Дает пользователю возможность определить значение для сооkiе-файла, называемого МyCookie Ad d CookieServlet . java Обрабатывает передачу формы из файла Ad d Cookie . ht.1 11 1 GetCookiesServlet . java Отображает значения из сооkiе-файла Ниже приведен исходный код разметки веб-страницы из НТМL-файла AddCookie . html . Эга страница содержит текстовое поле для ввода значения, а также кнопку для передачи заполненной формы. Если щелкнугь на этой кнопке, значение в тексто­ вом поле будет передано сервлеrу AddC ookie Se rv let по НТГР-запросу типа POST. <html > <body> <center> <form name="Forml " me thod= "post" action= "http ://localhost :BOBO/exampl es/servl ets /servl et /AddCookieServ let"> <B>Enter а value for MyCookie :</B> <input type=textbox name="data" si ze=25 va lue= ""> <input type=submi t value=" Submit"> </fo rm> </body> </html > Ниже приведен исходный код сервлета из файла AddC ookieServlet . java . Он получает сначала значение параметра по имени da ta, а затем создает объект MyCookie типа Cookie, который содержит значение параметра da ta. После этого в заголовок НТТР-ответа вводится сооkiе-файл методом addCo okie ().Инаконец, браузер получает ответное сообщение. import jav a.io. *; import javax . servlet .*; import javax . servlet .http.*; puЫ ic class AddCookieServlet extends HttpServlet { puЬlic void doPost (HttpServletReque st reque st, HttpServl etResponse re spons e) throws Se rvletException, IOException { 11 получить параме тр из НТТР-запроса String data = requ est .getParame t er ( "data" ); // создать сооkiе-файл Cookie cookie = new Cookie ( "MyCookie ", data) ; // ввести соо kiе-файл в НТТР-ответ re spon se . addCookie (cookie ); 11 выв ести результат в окне браузера re sponse .setContentType ("text /html") ; PrintWriter pw = response . getWriter () ; pw . println ("< B>MyC ookie has been set to") ; pw . println (data) ;
Гл ава 38 . Введение в сервл еты 13,5 pw . close (); Ниже приведен исходный кодеще одного сервлетаизфайла GеtСооkiеSеrvlеt . java. Он вызывает метод ge tCoo kie () для чтения любых сооkiе-файлов, вклю­ ченных в НТГР-запрос типа GET. Имена и значения этих сооki�айлов включа­ ются в НТТР-ответ. Обратите внимание на то , что для получения этих данных вы­ зываются методы getNarne () и ge tVa lue (). import java.io.*; import javax .servlet .*; import javax . servlet .http.*; puЬlic class GetCookiesServlet extends HttpServlet { puЫ ic void doGet ( HttpServletReque st request, HttpServletResponse respons e) throws ServletException, IOException { 11 получить сооkiе-файлы из заголовка НТТ Р-запроса Cookie [J cookies = request . getCookies (); // вывести все сооkiе-файлы response .setContentType (11text/html 11 ); PrintWriter pw = re sponse . getWriter () ; pw . println ("<B>") ; for (int i = О; i < cookies. length; i++) String name = cookies [i] .getName () ; String value = cookies [i] . getValue (); pw.println(11name = 11 + name + } pw . close (); 11; value = 11 + value); Скомпилируйте эти сервлеты. Затем скопируйте их в соответствующий каталог и обновите файл web . xml , как пояснялось ранее. После этого выполните следую­ щие действия , чтобы проверить дан ный пример. 1. Запустите контейнер сервлетов To mcat на выполнение, есл и он еще не дей- ствует. 2. Откройте НТМL-файл AddCookie . html веб-страницы в окне браузера. 3. Введите значение для сооki�айла MyCooki e. 4. Передайте форму, заполненную на веб-странице. Выполнив эти действия, вы увидите, что в окне браузера отображается ответ­ ное сообщение. Введите в поле адреса, находящемся в верхней части окна браузе­ ра, следующий URL: http://localho st: 8080/exaшples /servlets/servlet/GetCookies Servlet В окне браузера должны быть отображены имя и значение из сооkiе-файла. В да нном примере не применяется метод setMaxAge () из класса Coo kie для яв­ ного назначения срока действия сооkiе-файлов. Поэтому срок действия сооkiе­ файлов истекает по завершении сеанса связи с браузером. Если же воспользовать­ ся методом setMaxAge (), то можно обнаружить, что сооkiе-файл будет сохранен на диске клиентской машины.
13'6 Часть V. Применение Java Отсл ежи вание сеансов свя зи Сетевой протокол НТГР действует без сохранения состояния. Каждый последу­ ющий запрос не зависит от предыдущего. Но в некоторых приложениях иногда тре­ буется сохранять данные состояния, чтобы накапливать их в результате нескольких взаимодействий браузера и сервера. Та кой механизм обеспечивают сеансы связи. Сеанс связи можно создать с помощью метода getSession () из интерфейса HttpServletRequest. Он возвращает объект ти па HttpSession. Этот объект способен сохранять ряд привязок имен к объектам. Этими привязками управля­ ют методы setAttribute (), ge tAt tribute (), getAt tributeName s () и remove At tribute () из интерфейса HttpSession. Состояние сеанса связи совместно ис­ пользуется всеми сервлетами, связанными с определенным клиентом. В приведенном ниже примере сервлета демонстрируется отслеживание состо­ яния сеанса связи . Метод getSession () получает текущий сеанс связи. Если се­ анс связи не существует, то создается новый сеанс связи. Метод ge tAt tribute () вызывается для получения объекта, привязанного к имени date. Это объект типа Date, инкапсулирующий дату и время последнего доступа к данной веб-странице. (Разумеется , такая привязка отсутствует при первом доступе к странице.) Затем создается объект типа Da te, инкапсулирующий текущие дату и время . Метод set At tribute ( ) вызывается для привязки имени date к этому объекту. import jav a.io. *; import java .util .*; import javax .servl et .*; import javax .servl et .http.*; puЫ ic class DateServl et extends HttpServlet { puЫic void doGet ( HttpServl etReque st requ est, HttpServletResponse response ) throws ServletException, IOException { // получи ть объе кт типа HttpSession HttpSes sion hs = requ est . getSession (true) ; // получи ть поток записи типа PrintWriter re sponse .setContentType ("text /html") ; PrintWriter pw = response . getWriter (); pw . print ("<B> ") ; 11 вывести дату и время последнего доступа к странице Date date = (Date) h s.getAttribute ("date") ; if(date != null) { pw . print ("Last access : "+date + "<br>" ); // вывести текущие дату и вр емя date = new Date(); hs . setAt tribute ("date" , date) ; pw . println ( "Current da te : "+date) ; При первом запросе этого сервлета в окне браузера выводится одна строка с информацией о текущих дате и времени. А при последующем вызове выводятся две строки . В первой строке указываются дата и время последнего доступа к серв­ лету, а во второй строке - текущие дата и время .
ПРИЛОЖЕНИЕ А Применение документирующих комментариев в Java Как пояснялось в части 1 данной книги , в языке Java поддерживаются три вида комментариев. Двумя первыми являются комментарии / / и / * * /. А третий вид называется дакумен тирующuм хоммснvшрием. Та кой комментарий начинается с по­ следовательности символов / * * и завершается последовательностью символов * /. Документирующие комментарии позволяют ввести в программу сведения о ней самой, а затем извлечь их с помощью утилиты javadoc, входящей в составJDK, и разместить в НТМLфайле. Документирующие комментарии упрощают процесс написания документации к разрабатываемым программам . Вам , должно быть, уже встречалась документация, составленная с помощью угилиты javadoc , поскольку именно она использована для документирования прикладного программного ин­ терфейсаjаvа API. Дес кр ипторы утил иты jаvаdос Ут илита j а vadoc распознает дес крипторы, перечисленные в табл . А . 1. Таблица д. 1. Дескрипторы утил иты javadoc Дескриmор @author {@code } @deprecated {@docRoot} @exception {@inheri tDoc } {@link} {@linkplain } {@literal } Назначение Идентифицирует автора Оrображает информацию в исходном виде, т.е. без обработки стилей НТМL-разметки, исполыуя шрифт кода Обозначает, что класс или ero член не рекомендуется Дl l Я применения Обозначает путь к корневому каталогу текущей докумеwгации Обозначает исключение, генерируемое методом или конструктором Наследует комментарий от непосредственного суперкласса Встааляет встроенную ссылку на другую тему Вста ал яет встроенную ссылку на другую тему, причем ссылка отображается обычным шрифтом Огображает информацию в исходном виде, т.е. без обработки стилей НТМL-разметки
13,8 Часть V. П р именение Java Дескриптор @param @return @see @serial @serialData @serialField @sinc:e @throws {@value } @version Наэначение Документирует параметр, передаваемый методу Документирует значение, возвращаемое методом Обозначает ссылку на другую тему Окон'Чание maliti. А.1 Документирует поле, сериализируемое по умолчанию Документирует данные, записываемые методами wri teObject () и wri teExternal () Документирует компонент Obj ectStreamField Обозначает выпуск, в котором было введено определенное изменение То же, что и дескриптор @exception Огображает значение константы, которая должна быть статическим (static) полем Определяет версию класса Дескрипторы утилиты javadoc , начинающиеся со знака @, называются авmQ­ номнъ�ми (или дескри пторами бл иков) и должны использоваться в отдел ьной строке . А дескрипторы, начинающиеся с фигурной скобки , например { @code }, называ­ ются встроен нъ�ми и моrуг применяться в более крупном описании. В документи­ рующих комментариях можно использовать и другие стандартные дескрипторы НТМL-разметки. Но некоторые дескрипторы (например, заголовков) нельзя ис­ пользовать, потому что они нарушают внешний вид НТМL-файла, сформирован­ ный утилитой javadoc. Документирующие комментарии можно применять для документирования классов, интерфейсов , полей, конструкто ров и методов. Но в любом случае до­ ку ментирующий комментарий должен стоять пе ред документируемым элемен­ то м. Одни дескрипторы, например @see, @since и @deprecated, применяются для документирования любого элемента, а другие - только для соответствую­ щих элементов. Каждый из этих дескрипторов рассматривается далее в отдель­ ности . На заметку! Ко мментарии могут быть та кже использова ны для документирования пакета и подго­ товки кратко го обзора, но эти про цедуры отличаются от используемых для докум ентирова­ ния исходн ого кода . Подробнее об этом можно уз нать из документа ции на утил иtу j avadoc. Дескри птор $author Документирует сведения об авторе класса и имеет следующий синтаксис: @author omrcaнare
Приложение А. Применение документирующих комментар иев в Java где параметр описа ние обычно обозначает Ф.И.О . того , кто разработал класс. Выполняя утилиту javadoc, следует указать параметр -author, чтобы включить в НТМL-документацию поле дескриптора @author. Дескриптор {@code } Позволяет встраивать в ко мментарий текст (например, фрагмент кода) . Этот текст будет отображаться шрифтом кода без последующей обработки (например, без воспроизведения в формате HTML) . Это дескриптор имеет следующий син­ таксис: {@code фраrнен !l'_rода } Дес крипто р @deprecated Определяет устаревший и не рекомендован ный к употреблению элемент про­ граммы. Чтобы уведомить программиста об имеющихся альте рнативных вариан­ тах, рекомендуется включать в исходн ый код программы дескрипторы @see или { @ 1 in k). Синтакс ис этого дескриптора выглядит следующим образом: @deprecated an•c&I01re где параметр описа ние обозначает сообщение, описывающее исключение. Дескриптор @deprecated можно испол ьзовать для документирования полей, ме­ тодов, конструкторов и классов. Дес крипто р {@docRoo t} Дес криптор { @docRoot} определяет путь к корневому каталогу текущей доку­ ментации. Дес крипто р @exception Дес криптор @exception описывает исключение в данном методе . Он имеет следующий синтаксис : @exception 10Cl l _ •crmove1Ut• пояснен.в где параметр имя_ исключения обозначает полное имя исключения, а параметр пояснение - символьную строку, описывающую причины, по которым может воз­ никнуть данное исключение. Дескриптор @exception можно использовать толь­ ко для документирования методов или конструкто ров. Дecкp иптop {@inheritD oc} Наследует комментарий от непосредстве нного суперкласса.
1350 Часть V. Применение Java Дескриптор {@link } Предоставляет встроенную ссылку на дополнительную информацию. Он имеет следующий синтаксис: (@link паж� . 1U1асс#vлен �еже�} где параметр па кет . кла сс#член обозначает имя класса или метода, на который вводится ссьшка, а параметр текст - отображаемую символьную строку. Де с кри пто р { @linkplain } Вставляет встроенную ссьшку на другую тему. Ссьшка отображается обычным шрифтом. А в остальном этот дескриптор аналогичен дескриптору { @link}. Дес кри пто р {@literal } Позволяет встраивать текст в комментарий. Этот текст отображается в ис· ходном виде , т. е. без последующей обработки (например, без воспроизведения в формате НТМL) . Ниже приведен синтаксис этого дескриптора, где параметр описа ние обозначает встраиваемый текст. (@literal аа.са. .. е} Дескриптор @param Документирует параметр и имеет следующий синтаксис: @paru __пap&IНt'l' pa поs�снеюrе где параметр имя_ параме тра обозначает имя документируемого параметра. Назначение этого параметра раскрывает предоставляемое пояснение. Дескриптор @param можно использовать только для документирования метода, конструктора, обобщенного класса или интерфейса. Дес крипто р @return Описывает значение, возвращаемое методом, и имеет следующий синтаксис: @return ПОJrСН8Н1 1 8 где параметр пояснение описывает тип и смысл значения , возвращаемого мето­ дом. Дескриптор @return можно использовать только для документирования ме­ тодов. Дескри птор @see Предоставляет ссьшку на дополнительную информацию. Ниже приведены наи· более употребительные формы этого дескриптора.
П риложение А. П рименение документирующих комментариев в Java 1351 @вее пр1 11 ..s:ка @вее паже� . жласс# vпен �еже� В первой форме параметр пр ив язка обозначает ссылку на абсолютный или относ ительный URL. Во второй форме параметр па к ет . кла сс#член обозна­ чает имя элемента , а параметр текст - текст, отображае мый для данного эле· мента. Параметр текст является необязательным, и если он не указан, то ото­ бражается элемент, обозначаемый параметром па к ет . кла сс#член. Имя члена также является необязательным. Та ким образом, можно определить ссылку на пакет, класс или инте рфейс, помимо ссылки на конкретный метод или поле. Имя может быть определено полностью или частично. Но точку, стоя щую пе· ред именем члена ( если тако вой существует) , необходимо заменить на знак t. Дес криптор @serial Определяет ко мментарий к полю, которое сериализируется по умолчанию. Ниже приведен его синтаксис, где параметр описа ние обозначает комментарий к данному полю: @вerial оп•сан.е Дес крипто р @serialData Документирует дан ные, записываемые с помощью методов writeObject () и wri teExternal (). Ниже приведен его синтаксис, где параметр описа ние обо­ значает комментарий к этим данным. @вerialData оп•сан.е Дecкp иптop @ serialField Для класса, реализующего интерфейс Seriali zaЬle, дескриптор @serial Field предоставляет комментарии к компоненту Ob j ectStreamField. Ниже при· веден его синтаксис, где параметр имя обозначает имя поля; параметр тип - кон· кретный тип поля ; параметр описа ние - комментарий к данному полю. @вerialField .,. . тип опяса.rаrе Дескриптор @since Указ ывает на то , что класс или эл емент бьш впервые внедрен в конкретном вы­ пус ке . Ниже приведен его синтаксис , где параметр выпуск обозначает символь­ ную строку, в которой указывается выпуск или версия, в которых данное средство стало доступным. @вince ВШiус:к
1352 Ч асть V. Применение Java Дес криптор @throws Имеет то же назначение, что и дескриптор @exception. Дескриптор {@value } Имеет две формы. В первой форме отображается значение константы, которая предшествует этому дескриптору и должна быть статическим (static) полем. Эта форма приведена ниже. {@value } Во второй форме отображается значение конкретного статического поля. Эта форма приведена ниже , где параметр па кет . кла сс#поле обозначает имя статиче­ ского поля . {@value паже� . ж.nасс#поле } Дес криптор @version Определяет версию класса или интерфейса. Он имеет следующий синтаксис: @version ж.нфорнацяя где параметр информация обозначает символьную строку, содержащую сведения о версии комментируемого элемента программы (как правило, номер версии, на­ пример 2.2). Запуская утилиту javadoc на выполнение, следует указать параметр -version, чтобы включить поле @version в НТМL-документацию. Общая форма документирующих комментариев После начальной последовательности символов /** первая строка (или не­ сколько строк) становится гл авным описанием комментируемого класса, ин­ те рфейса, поля , конструктора или метода . После нее можно ввести один ил и несколько различных дес крипторов @. Каждый дескриптор @ должен распола­ гаться в начале новой строки или следо вать за одним или несколькими знаками звездочки ( *), с которых начинается строка. Несколько дескрипторов одного и того же типа нужно сгруппировать вместе. Та к, если имеются три дескрип­ тора @see, их следует расположить один за другим. Встроенные дескрипторы, начинающиеся с фигурной скобки , можно разместить в пределах любого опи­ сания. Ниже приведен пример документирующего комментария к классу. /** * Этот класс строит столбико вую диаграмму * @author Герберт Шилдт * @version 3.2 */
П риложение А. П р именение до кум енти рующих комментариев в Java 1353 Результаты, вывод имые утил итой javado c В качестве выводимых данных уrилита javadoc получает файл с исходным ко­ дом прикладной программы нajava и выводит несколько НТМL-файлов, содержа­ щих документацию на эту программу. Сведения о каждом классе будуr содержаться в его собственном НТМL-файле. Ут илита javadoc выводит также индексное и ие­ рархическое деревья . Могуr быть сформированы и другие НТМL-файл ы. П ример применения до кументирующих ко ммента р иев Ниже в качестве примера приведена простая программа, в которой приме­ няются документирующие комментарии. Обратите внимание на то , что каждый комментарий располагается непосредственно перед тем элементом, который он описывает. После обработки угилитой j avadoc документирующих комментариев к классу SquareNum их можно найти в НТМL-файле Squar eNum . html . import java.io. *; /** * В этом кл ассе демонстрируе тся приме нение * докуме нтирущих комментарие в * @author Герберт Шилдт * @version 1.2 */ puЫ ic class SquareNum { /** * Этот ме тод воз вращает квадрат числа . * Это многострочное описание . В нем можн о ввести * столько строк , сколько потребуется . * @param num Значение , которое требуе тся возвести в квадрат * @return num Значение , возведенное в квадрат */ puЫic douЫ e square ( douЫ e num) { return num * num; /** * Этот ме тод * @ return вводит число , заданное поль зователем Введенное значение типа douЫ e * @exception Если при вводе возникает ошибка , то генерируется исключение типа IOException * * @see */ IOException puЫic douЫ e ge tNumЬer () throws IOException { /** // создать буфе ризированный поток чтения // типа BufferedReade r, исполь зуя стандартный // поток ввода System. i п InputStreamRe ade r isr = new InputStreamRe ader ( System.in) ; BufferedRe ade r inData = new Bu ffe redReader (isr) ; String str; str = inData . readLine () ; return (new DouЬle (str) ) .douЫ eValue () ;
Часть V. Применение Java * Э тот метод демонстрирует применение ме тода square {) * @param args Не исполь зуе т ся * @exception Если nри вводе возникает ошибка , то * гене рируе тся исключ ение типа IOException * @зее IOException */ puЫ ic static void ma in {St ring args[] ) throws IOException ( SquareNum оЬ = new SquareNum() ; douЫe val; System.out . println ( "Введите значение для возведения в квадрат : " ) ; val = ob . getNumЬer(); val = ob . square (val); System.out .println ("K вaдpaт значения равен " + val) ;
Предметн ы й указатель с Collections Framework алгоритмы, 565 изменения в версии JDK 5, 566 назначение, 564 состав ,565 F Fork/Join Framework дескрипторы, назначение, 1058 задачи асинхронное выполнение, 1055 влияние уровня парал л елизма на выполнение, 1049 возвращающие резулыгаты, выполнение, 1053 организация очереди, 1045 отмена, 1056 перезапуск, 1056 перехват работы, пршщи п выполнения, 1045 состояние завершения, определение , 1056 назначение, 49; 1040 основные классы взаимосвязь, 1041 разновидности, 1041 подцержка парал л ельного программирования, 50; 1007 рекомендации по применениk! � 1059 стратегия "разделяи и властвуи назначение, 1045 оптимальное пороговое значение, выбор, 1046 преимущества, 1046 А Автоматическое управление ресурсами, преимущества, 371 Автораспаковка в выражениях, 330 в методах, 329 данных типа Ьoolean и char, 332 назначение, 328 предупреждение против злоуnотребления ,334 Автоуnаковка в выражениях, 330 в методах, 329 данных типа Ьoolean и char, 332 предотвращение ошибок, 333 Аннотации все, получение ,339 встроенные, назначение, 345; 347 маркеры, назначение, 343 механизм создания, 334 назначение ,334 объявление, 334 одночленные, применение, 343 повторяющиеся, применение, 352 получение с помощью рефлексии, 336 правила уде ржания, 335 применение, 335 типовые применение, 347 целевые константы, 348 Аплеты архитектура, 837 безопасность и переносимость, 41 воспроизведение методы, 841 повторное,843 графический контекст, 375 заrJl}'IП ки, назначение,837 запуск, способы, 375 консольный вывод, организация, 854 назначение, 40; 374 на основе библиотеки АWГ, создание, 833 Swing, coздaниe, 1135 обязательное подписание, 376; 835 основные методы переопределение, 838 порядок вызова, 839 передача параметров, 848 принцип деиствия, 837 развертывание, способы, 834 разработка особенности ,377 стади и, 376 строка состояния, применение, 846
1356 Предметнь1 й указатель типы, 833 Аргументы командной cтpolGf назначение, 202 порядок передачи ,203 передача по значению, 184 по ссылке, 185 переменной длины и неоднозначность, 207 применение, 203 Архитектура МVС , составляющие, 1124 компоненгов Swi ng, особенности, 1124 Б Библиотеки АWГ актуальность, 885 диалоговые окна, создание и применение, 968 изображен ия, обработка, 975 классы, разновидности, 886 кнопки-переключатели, создание и применение, 933 кнопки, создание и применение, 926 компоненты, применение, 924 меню, создание и применение, 962 метки , создание и применение, 925 назначение, 374 ограниченность, 1122 окна выбора файлов, диалоговые, создание и применение, 972 переопределение метода paint (), 973 поддержка графики, 900 полосы прокругки, создание и применение, 939 применение, 886; 898 раскрывающиеся списки , создание и применение, 934 списки, создание и применение, 936 строки и пункты меню, создание и применение, 962 текстовые области, создание и применение, 945 текстовые поля, создание и применение, 943 тяжеловесные компоненты, 1122 флажки, создание и применение, 930 цветовая система, 904 JavaFX ввод изображений в экранные кнопки, 1238 внедрение, 51; 1211 воспроизведение изображений на месте меток, 1236 графы сцены, 1213 деревья, создание и применение, 1269 дополнительные средства, 1308 запуск приложении, 1214 кнопки-переключатели, создание и применение, 1243 компиляция и выполнение приложений, 1219 метки , создание и применение, 1220 неопределенное состояние флажков, разрешение, 1251 обработка событий, 1222 одновременный выбор из списка, активизация, 1258 отключение элементов управления, 1280 панели компоновки, разновидности, 1213 переключатели, создание и применение, 1240 подмостки и сцены, 1213 подсказки всплывающие, применение, 1279 представления списков, создание и применение, 1254 преобразования, выполнение, 1275 прокручиваемые панел и, создание и применение, 1265 разработка, стади и, 1212 рисование в режиме удержания, 1227 списки прокручиваемые, создание и применение, 1258 структура приложений, 1215 текстовые поля, создание и применение, 1263 узлы, разновидности, 1213 фильтры событий, реализация, 1223 флажки, создание и применение, 1251 цепочка диспетчеризации событий, 1223 экранные кнопки, создание и применение, 1223 элементы управления, классы, 1233 эффекты, применение, 1274 Swing гл авные особенности, 1122 действия, применение, 1196 другие компоненты, назначение, 1208 классы компонентов, 1143 кнопки, классы, 1147 компоненты и контейнеры. назначение, 1125 лекговесные компоненты, 1123 меню, система, 1175 обработка событий, механизм, 1132 подключаемые стили оформления, 1123 построение на основе AW't , 1122 представитель пользовательского интерфейса, 1124 приложения и аплеты, 1127 происхождение, 1121 структура приложений, 1127 динамически подключаемые, создание, 382 классов, встроенные, 73 Блоки кода назначение, 69 обозначение ,69
в Ввод-вывод адресаты вывода, определение, 717 ввод, данных с консоли, организация, 359 возврат в поток ввода, механизм, 740 вывод, данных на консоль, организация, 362 источники ввода, определение, 717 консольный применение, 64 поддержка вJava организация вJava новая система NIO, 769 пагоковая система, 717 пагоковый в системе NIO, организация, 793 традиционный, организация, 717 через сеть, 813 система NIО буферы , назначение и свойства, 770 каналы, назначение и получение, 772 копирование файлов, 792 наборы символов, кодеры и декодеры, 774 новый канальный ввод-вывод, 783 операции в файловой системе, 796 потоковый ввод-вывод, 793 прежний канальный ввод-вывод, 804 применение, 770; 782 селекторы, назначение, 774 усовершенствования вJDK 7, 774 Веб сооkiе-файлы назначение, 826; 1339 применение, 1343 содержимое, 1339 URI, назначение, 826 URL назначение, 820 формат и составляющие, 820 назначение, 819 Векторы емкость, определение , 636 инкремент, задание, 636 применение, 636 соэдание, 636 Взаимная блокировка возникновение, 309 трудности отл адки, 309 Всплывающие меню вJavaFX активизация, 1297 построение, 1296 вSwing запуск, 1191 построение, 1190 контекстные, назначение, 1296 назначение, 1190 г Гр афика графический контекст, способы получения, 900 Предметный указатель изменение размеров, 902 установка цвета, 905 д Дейтаграммы обработка, пример, 830 реализация вJava, 828 Дескрипторы APPLET атрибуты, описание, 847 полная форма, 847 OBJECT, назначение, 834 угилитыjаvаdос автономные, 1348 встроенные, 1348 описание, 1348; 1352 разновидности, 1347 1357 Динамический полиморфизм, механизм, 227 Диспетчеры компоновки вставки, применение, 952 граничнои, реалиэация, 950 каргочной, реализация ,954 назначение, 923 пагочной,реализация, 948 принцип действия, 948 сеточной, реализация, 953 сеточно-контейнерной, реализация, 957 Древовидные множества извлечение элементов, 588 применение, 587 соэдание ,587 и Идентификаторы, назначение, 71 Изображения воспроизведение, 977; 981 встраивание в гипергекст, 977 динамическое подключение фильтров, 993 загруэка, 977 наблюдатели, назначение, 977 обработка, основные операции, 976 соэдание объектов, 977 iильтрация , 99 1 орматы файлов, 975 ормирование, 975 Интерфейсы Actюn методы и свойства действий, 1197 назначение и реализация, 1196 ActionListener, назначение и методы , 871; 1155 AdjustmentListener, назначение и методы, 871 AnnotatedElement, назначение и методы, 341; 353 Annotation, расширение и методы, 334 AppendaЫe, реализация и методы, 559 AppletContext, назначение и методы, 852 AppletStub, назначение, 854 AudioCiip, назначение и методы, 854 AutoCloseaЫe, назначение и реализация, 37 1; 560; 725
1358 П редметны й указатель BaseStream, назначение и методы, 1062 BasicFileAttributes, назначение и методы, 780 Вeanlnfo назначение и методы , 1315 реализация и применение, 1315 ButtoпModel, назначение, 1148 CallaЫe, назначение и методы , 1031 CGI назначение, 1324 недостатки, 1324 ChangeListener, назначение, реализация и методы, 1247 Channel, назначение и реализация, 773 CharSequence, реализация и методы, 558 CloneaЫe, назначение и реализация, 533 CloseaЬ!e, назначение и реализация, 371; 725 Collection методы, 568; 1065 назначение, 568 сrатические переменные, 628 Collector, назначение и методы, 1080 СоmЬоВохМоdеl, назначение и реализация, 1166 ComparaЫe, назначение и реализация, 415;558 Comparator методы, 614 назначение и реализация, 459; 614 ComponentListener, назначение иметоды,872 ContainerListener, назначение и методы, 872 Datalnput методы, 747 назначение и реализация, 746 DataOutput методы , 746 назначение и реализация, 746 Dequeue методы, 577 назначение, 577 DosFileAttributes, назначение и методы, 781 Enumeration методы , 635 назначение, 635 EventHandler, назначение, реали3ация и методы , 1222 Executoi5ervice, назначение и методы , 1028 Executor, назначение и методы , 1028 ExtemalizaЬ!e, назначение и методы, 762 FileFilter, назначение и реализация, 724 FilenameFilter, назначение и реализация, 723 FileVisitor, реализация и методы, 802 FlushaЫe, назначение и реализация, 725 FocusListener, назначение и методы, 872 Future, назначение и методы, 1032 HttpServletRequest, назначение и методы, 1336 HttpServletResponse, назначение, констангы и методы , 1337 HttpSession, назначение и методы , 1338 Icon , назначение и реализация, 1144 ImageConsumer, назначение и реализация, 988 ImageObserver назначение и методы, 979 реализация, 977 ImageProducer, назначение и реализация, 986 ItemListener, назначение и методы , 872 IteraЫe, реали3ация и методы, 559 Iterator методы, 592; 1083 назначение и реализация, 591 применение, 593 JNГ. назначение, 381 KeyListener, назначение и методы , 872 List методы, 571 назначение, 571 Listlterator методы, 592 назначение и реализация, 592 п рименение, 593 ListModel, назначение, 1162 ListSelectionListener, назначение, реализация и методы , 1163 ListSelectionModel, назначение и константы, 1163 Lock методы , 1036 назначение и реализация, 1036 Мар методы, 602 назначение, 60 1 Map.Entry методы, 608 назначение, 608 MouseListener, назначение и методы, 873; 1191 MouseMotionListener, назначение и методы, 873 MouseWheelListener, назначение и методы , 873 MutaЫeComЬoВoxМodel, назначение и реализация, 1166 MutaЫeTreeNode, назначение, реализация и методы, 1169 NavigaЫeMap методы ,606 назначение, 606 NavigaЫeSet методы , 574 назначение, 574 Objectlnput, назначение и методы , 764 ObjectOutput, назначение и методы , 763 OЬserver методы, 673 назначение и реализация, 672 применение, 674 OpenOption, назначение и реализация, 779 Path методы , 775
назначение, 774 PosixFileAttributes, назначение и методы, 781 Queue методы, 576 назначение ,576 RandomAccess, назначение и реализация, 601 ReadaЫe, назначение и методы, 560 ReadWriteLock, назначение и реализация, 1038 Remote, назначение, 1105 RunnaЫe метод run(), определение, 545 назначение, 289 применение,289 реализация, 292; 545 Sch eduledExecutorService, назначение, 1029 Serializ.aЫe, назначение и реализация, 762 SetvletConfig, назначение и методы , 1330 SetvletContext, назначение и методы, 1331 SetvletRequ est, назначение и методы , 1331; 1334 SetvletResponce, назначение и методы, 1332 Servlet, реализация и методы, 1330 Sеt , назначение ,573 SortedMap методы, 605 назначение, 605 SortedSet методы ,573 назначение, 573 Spliterator методы, 596; 1085 назначение и особенности, 596 подчиненные интерфейсы, назначение, 599 применение,597 Stream, назначение и методы, 1063; 1087 SwingConstants, назначение и констангы, 1144 Ta Ь!eColumnModel, назначение, 1172 TaЫ eModel, назначение, 1172 Te xtListener, назначение и методы, 873 Thread . UncaughtExceptionHandler, реализация и методы, 560 To ggle, назначение и реализация, 1240; 1243 Tree Node, назначение и методы , 1169 TreeSelectionListener, назначение, реализация и методы, 1169 UnicastRemoteObject, назначение, 1105 WindowConstants , назначение и реализация, 1130 WindowFocusListener, назначение и методы, 873 WindowListener, назначение и методы, 873 вложенные , применение, 249 исполнителей, назначение и реализация, 1007 коллекций, разновидности и назначение, 567 назначение, 245 Предметн 1:11 й указател ь 1359 обобщенные, п рименение, 417 объявление, 245 отображений ,разновидности, 601 приемников собьrrий, разновидности, 870 применение, 250 расширение, 255 реализация, 246 Исключения более точное повторное генерирование, 283 в операциях ввода-в ывода, обработка, 367 встроенные, 276 вывод описания, 268 многократный перехват, 282 необрабатываемые , 265 непроверяемые, 276 обработка вручную в блоках операторов try/catch, 266 преимущества, 266 определение, 263 порядок генерирования ,263 обработки ,263 при вводе-выводе, обработка, 725 применение, 284 проверяемые, 277 разнотипные, перехват, 268 составление в цепочки и обработка, 280 типы, 264 Исходные файлы, именование и расширение, 61 Итераторы в потоках данных ти пы, 1083 назначение, 59 1 получение,593 разделители в потоках данных, применение, 1085 назначение, 596 применение, 596 характеристики, определение ,599 к Каталоги дерево, перечисление, 80 1 назначение, 722 получение содержимого, 798 просмотр содержимого, 722 создание, 724 фильтрация соде ржимого, способы, 799 Классы AЬstractAction , назначение и конструкторы, 1198 AbstractButton, назначение и методы , 1147 ActionEvent конструкторы и методы, 859 назначение и константы , 859 AdjustmentEvent конструкторы и методы , 860 назначение и константы, 860 Applet методы, 835; 977
1360 Предметный указатель назначение, 374; 833 Application, назначение и методы жизненного цикла, 1214 AiтayDeque консгрукторы, 590 назначение, 589 применение, 590 AiтayList консгрукторы, 581 назначение, 580 применение, 581 Aiтays методы, 629; 633 назначение, 629 AWГ Eve nt , назначение и методы, 858 BitSet конструкторы и методы, 653 назначение,653 применение, 655 Вoolean консrанты, поля и конструкторы, 519 методы , 519 назначение, 519 ВorderLayout консrрукторы и констанrы, 950 назначение, 950 ВorderPane, назначение и методы, 1289 Buffer назначение и методы, 770 производные классы, 771 BufferedlnputStrearn , назначение и ко нсrрукторы, 738 BufferedOutputstrearn, назначение и консrрукторы, 739 BufferedReader, назначение и консrрукторы, 359; 755 BufferedWriter, назначение и консrрукторы, 756 Button методы, 927 назначение и консrрукторы, 926 ButtonGroup, назначение и консrрукторы, 1155 By t eAпaylnputStrearn, назначение и консте.укторы, 734 ByteAпayuutputStream, назначение и конструкторы , 736 Byte, Short, Integer, Long консrанты и методы, 504 назначение и консrрукторы, 503 Calendar консrанты ,663 методы , 661 назначение, 661 переменные экземпляра, 661 применение, 664 Canvas консrрукторы, 1228 методы , 1229 назначение, 890 CardLayout методы ,955 назначение и консrрукторы, 954 Character консrангы и методы, 514 методы, 518 назначение и консrрукторы, 514 CharAiтayReader, назначение и консrрукторы, 752 CharAiтaj'Writer, назначение и консrрукторы, 754 CheckЬox методы, 931 назначение и консrрукторы, 930 CheckЬoxGroup, назначение и методы, 933 CheckЬoxМenultem методы, 964 назначение и консrрукторы, 963 CheckMenultem, назначение, консrрукторы и методы, 1294 Choice, назначение и методы, 934 Class методы, 336; 535 назначение, 535 ClassLoader, назначение, 538 Collections, алгоритмы и методы , 622 Collectors, назначение и методы , 1080 Color консrангы, 84 1 методы, 905 назначение и консrрукторы, 904 ComboBox конструкторы, 1260 назначение, 1260 Compiler, назначение, 545 Component методы, 84 1; 976 назначение, 889 ComponentEvent консrрукторы и методы, 861 назначение и консrанты, 86 1 Console методы , 760 назначение, 759 применение, 76 1 Container методы, 924; 952 назначение, 889 ContainerEvent конструкторы и методы, 862 назначение и консrанты, 861 ContextMenu, назначение и консrрукторы, 1296 Cookie консrрукторы и методы, 1339 назначение, 1339 CountDownLatch консrрукторы и методы, 1014 назначение, 1014 применение, 1015 CroplmageFilter, применение, 991 Currency
методы, 679 назначение и применение, 678 CyclicBarrier конструкторы и методы , 1016 назначение, 1016 применение, 1017 DatagramPacket методы ,829 назначение и конструкторы, 829 DatagramSocket методы, 828 назначение и консrрукторы, 828 DatalnputStream, назначение и консrрукторы, 746 DataOutputStream , назначение и консrрукторы, 746 Date консrрукторы и методы, 660 назначение, 659 применение, 660 DateFonnat константы и методы , 1109 назначение, 1109 DatelimeFonnatter назначение и методы, 1115 применение, 1115 шаблоны форматирования, 1116 DefaultМutableTreeNode, назначение, консrрукторы и методы , 1169 Dialog, назначение и консrрукторы, 968 Dictionary методы,641 назначение, 64 1 DouЫe методы и константы, 499 назначение и кон Fры, 498 Ef f ect, всrроенные кты, разновидносrи, 12 4 Enum методы , 323; 557 назначение, 323; 556 EnumМap консrрукторы, 613 назначение, 613 EnumSet назначение, 590 фабричные методы, 59 1 Error, назначение, 264 Event методы, 1223 назначение и подклассы, 1222 EventObject методы ,858 назначение и консrрукторы, 858 EventSetDescriptor, назначение и методы, 1319 Exception консrрукторы, 278 назначение, 264 Exchanger назначение, 1018 Предметный указател ь объявление и методы, 1019 применение, 1019 File консrрукторы, 719 методы, 719 назначение, 718 FileChannel, назначение и методы, 773 FileDialog методы, 972 назначение и консrрукторы, 972 FilelnputStream консrрукторы и методы, 364 назначение, 364 FilelnputStream, назначение и консrрукторы, 730 FileOutputStream конструкторы и методы, 364 назначение, 364 FileOutputStream, назначение 1361 и консrрукторы, 732 FileReader, назначение и консrрукторы, 751 Files методы, 777; 782 назначение, 776 FileWriter, назначение и консrрукторы, 751 FilterOutputStream, назначение и конструкторы, 737 Float методы и константы, 499 назначение и конструкторы, 498 FlowLayout консrрукторы и константы, 948 назначение, 948 FocusEvent конструкторы и методы, 862 назначение и консrанты , 862 Font консrрукторы, 91 1 методы, 909 назначение ,909 переменные, 910 FontMet rics методы, 915 назначение, 914 ForkjoinPool конструкторы, 1044 методы, 1044; 1058 назначение, 1044 ForkfoinTask, назначение и методы, 1042; 1057 Fonn atter закрьr rи е объектов, способы, 694 конструкторы, 680 методы, 681 назначение, 680 Frame конструкторы , 890 методы ,891 назначение,890 FXC oUections, назначение и методы, 1255
1362 П редметн ы й указател ь GenericServlet, назначение и методы, 1328; 1333 Glow конструкторы и методы, 1274 назначение, 1274 GraphicsContext, назначение и методы , 1227 GraphicsEnvironment, назначение и методы , 910 Graphics, назначение и методы, 375; 90 0 ; 977 GregorianCalendar методы , 665 назначение, 664 поля и конструкторы, 664 применение, 665 GridBagConstraints константы, 959 назначение, 958 поля оrраничений, 958 статические поля, 959 GridВagLayout конструкторы и методы, 958 назначение, 957 GridLayout, назначение и конструкторы, 953 HashMap конструкторы, 609 назначение ,609 применение, 610 HashSet конструкторы , 586 назначение, 585 применение, 586 HashtaЫe конструкторы , 642 методы, 643 назначение, 642 применение, 64 4 HtфServlet, назначение и методы, 1340; 1341 HttpURLConnection методы, 824 назначение, 824 применение, 825 IdentityHashMap, назначение, 613 Image конструкторы , 1234 назначение, 1233 ImageFilter, назначение и подклассы, 991 Imagelcon, назначение и конструкторы, 1144 ImageView конструкторы, 1234 назначение, 1233 Image, назначение, 976 Inet4Addres s и Inet6Address, назначение, 815 InetAddress методы экземпляра, 815 назначение, 813 фабричные методы, 814 InnentaЫeThreadLocal, назначение, 553 InnerShadow, назначение и конструкторы, 1275 InputEvent методы, 863 назначение и константы, 863; 1186 InputStreamReader, назначение, 359 InputStream, назначение и методы, 728 Insets, назначение и конструкторы, 952 Introspector, назначение и методы , 1319 ItemKve nt конструкторы и методы, 864 назначение и константы, 864 JAp plet, назначение, 1136 jaVafx.collections.OЬservaЫeList, назначение, 1255 javafx .scene.control.Button конструкторы, 1223 назначение, 1223 javafx.scene.control.CheckВox, назначение, 1251 javafx.scene.control .LaЬel конструкторы и методы, 1220 назначение, 1220 javafx.scene.control.MenuBar, назначение, конструкторы и методы, 1283 javafx.scene.control.Menultem, назначение , конструкторы и методы, 1285 javafx.scene.control.Menu, назначение и конструкторы, 1284 javafx.scene.paint.Color, назначение и поля, 1229; 1275 javafx.scene.text.Font, назначение и конструкторы, 1229 JButton конструктор ы, 1134; 1148 методы, 1134 назначение, 1134; 1148 JCheckВoxМenultem, назначение и конструкторы, 1188 JCheckВox, назначение и конструкторы, 1153 JСоmЬоВох методы, 1166 назначение и конструкторы, 1166 JComponent методы, 1140 назначение, 1125 JFrame, назначение и методы , 1129 JLaЬel, назначение и конструкторы, 1143 JList методы, 1163 назначение и конструкторы, 1162 JMenu конструкторы , 1179 методы, 1179 назначение, 1178 JMenuBar, назначение и методы, 1177 JMenultem конструкторы, 1180 назначение, 1180 JPopupMenu, назначение и конструкторы, 1190 JRadioButtonMenultem, назначение и конструкторы, 1188
JRadioButton, назначение и конструкторы, 1155 JScroUPane, назначение и конструкторы, 1160 JТа ЬЬеdРаnе ,назначение и конструкторы, 1157 JТаЫе, назначение и консrрукгоры, 1172 JТe xtField, назначение и конструкторы, 1145 JТog g leButton, назначение и конструкторы, 1150 JТoo ffiar, назначение и консrрукгоры, 1194 JTree, назначение и конструкторы, 1168 KeyCombination, назначение и методы, 1292 KeyEvent консrрукторы и методы, 865 назначение и константы , 864; 1185 LaЬel методы, 925 назначение и консrрукгоры, 925 LinkedНashMap конструкторы , 612 методы , 613 назначение, 612 LinkedHashSet назначение,587 nрименение, 587 LinkedList консrрукторы, 584 назначение, 584 применение, 584 List методы , 937 назначение и консrрукторы, 936 ListSelectionEvent, назначение и методы, 1163 ListView консrрукторы, 1255 назначение, 1254 LocalDate назначение и методы, 1113 Loca1Date1ime назначение и методы, 1113 применение, 1114 Locale константы и конструкгоры, 669 методы , 669 назначение, 668 Local1ime назначение и методы, 1113 Matcher назначение и методы, 1092 применение, 1096 Math методы, 539; 542 назначение и константы , 539 MediaTracker, применение и методы, 983 MemorylmageSource, назначение и консrрукторы, 986 MenuВar, назначение и методы , 964 Menultem методы, 963 Предметный указатель 1363 назначение и консrрукторы, 963 Menu, назначение и конструкторы, 962 MethodDescriptor, назначение и методы, 1319 Modifier, назначение и методы , 1103 MouseEvent конструкторы и методы , 865; 1191 назначение и константы, 865 MouseWheelEvent конструкторы и методы, 867 назначение и консrаJП'ЬI, 867 MultipleSelectionModel, назначение, 1256 Naming, назначение и методы, 1106 Node методы ,1223; 1275 назначение, 1213 NumЬer, назначение, методы и подклассы, 498 OЬject методы,233;474; 532 назначение, 233; 532 ObjectlnputStream конструкторы и методы, 765 назначение, 765 OЬjectOutputStream консrрукторы и методы, 763 назначение ,763 OЬservaЫe методы,672 назначение ,672 применение, 674 OЬservaЬleLi st методы, 1221 назначение, 1220 Optional методы,657 назначение,657 применение, 658 OptionalDouЬJe, Optionallnt, Optionall.ong, назначение,659 Oug>UtStream , назначение и методы , 729 Pactage, назначение и методы, 553 Paint, назначение и пОДК.1 1ас сы , 1229 Panel, назначение, 889 Paths, назначение и методы, 779 Pattern назначение и методы, 1092 Phaser конструкторы, 1021 методы, 1021 назначение, 1021 применение, 1021 PixelGraЬЬer конструкторы и методы, 988 назначение, 98 8 Platform , назначение и методы , 1291 PopupMenu , назначение, 968 PrintStream конструкторы, 743 методы, 744 назначение, 743
136, П редметный указател ь PrintStream , назначение и методы, 362 PrintWriter методы, 363; 759 назначение и консrрукторы , 363; 758 PriorityQueue консrрукторы, 589 назначение, 58 8 применение, 589 ProcessBuilder методы, 526 назначение и консrрукторы, 525 ProcessBuilder.Redirect, назначение и консrанты, 527 Process, назначение и методы, 520 Properties конструкторы, 646 методы, 646; 649 назначение, 645 применение, 647 PropertyDescriptor консrрукторы , 1321 назначение и методы, 1319 PushbacklпputStream консrрукторы и методы, 740 назначение, 740 применение, 740 PusЪbackReader консrрукторы и методы , 757 назначение, 757 RadioButton консrрукторы, 1243 назначение, 1243 RadioMenultem, назначение, консrрукторы и методы, 1294 Random консrрукторы, 670 методы, 670 назначение, 670 RandomAccessFile консrрукторы, 748 методы, 748 назначение, 748 Reader, назначение и методы, 749 RecursiveAction назначение и методы , 1043 применение, 1047 RecursiveTask назначение и методы, 1043 применение, 1053 ReentrantLock, назначение, 1037 ReentrantReadWriteLock, назначение, 1039 ResourceBundle методы, 707 назначение, 706 подклассы, назначение, 709 RGBimageFilter, применение, 992 Rotate, назначение, конструкторы и методы, 1276 Runtime выполнение процессов, 524 назначение и методы, 52 1 управление памятью, 523 RuntimeException , назначение, 264 RuntimePermission, назначение, 555 Scale, назначение, консrрукторы и методы, 1276 Scanner консrрукторы, 695 методы, 69 7; 705 назначение, 695 применение, 697; 700 Scene консrрукторы, 1218 назначение, 1213 Scrollbar методы ,940 назначение и консrрукторы, 940 ScrollPane консrрукторы и методы, 1266 назначение, 1266 SecurityManager, назначение, 555 Semaphore конструкторы и методы, 1009 назначение, 1008 SequencelnputStream, назначение и консrрукторы. 741 ServerSocket методы ,827 назначение и консrрукторы, 827 ServletlnputStream , назначение , консrрукто ры и методы, 1333 ServletOutputStream, назначение, конструкторы и методы, 1333 SimpleВeanlnfo, назначение, 1315 SimpledateFormat применение, 1112 SimpleDateFormat конструкторы, 1111 назначение, 1111 шаблоны форматирования, 1111 Simple1imeZone, назначение и консrрукторы , 667 SingleSelectionModel, назначение и методы, 1157 Socket методы, 816 назначение и консrрукторы, 816 применение, 817 SocketAddress, назначение и реализация, 828 Stack метод ы, 639 назначение, 639 применение, 640 StackTraceElement методы ,556 назначение и консrрукторы , 555 Stage гл авные подмосrки, 1213 назначение, 1213 StrictMath, назначение и методы , 54 4 String консrрукторы, 470
методы, 201; 474; 487 назначение, 200; 469 StringBuffer консгрукторы, 471; 489 методы, 490; 495 назначение, 489 StringBuilder консгрукторы, 471 назначение,496 StringTokenizer консгрукторы, 65 1 методы, 652 назначение и применение, 651 Swi ngUtilities , назначение и методы, 1131 System методы, 382; 528 назначение, 359 переменные потоков ввода-вывода, 359; 528 применение, примеры, 530 Te xtArea методы ,946 назначение и консгрукторы , 945 Te xtEvent, назначение, консганты и консгрукторы, 868 Te xtField консгрукторы и методы, 1263 методы ,943 назначение, 1263 назначение и консгрукторы, 943 Te xt , назначение, 1279 Thread константы, 545 консгрукторы, 545 методы, 289; 291 ; 297; 313; 546 назначение, 289; 545 применение, 289 расширение, 294 ТfireadGroup методы, 548 назначение и консгрукторы , 548 ThreadLocal, назначение, 553 ThrowaЫe консгрукторы, 281 методы, 278; 281 назначение , 264; 555 применение, 272 1imer консгрукторы и методы, 677 назначение, 676 применение, 678 1imerTask консгрукторы и методы , 676 назначение ,676 применение, 678 1imeZone, назначение и методы, 666 To ggleButton консгрукторы, 1241 методы, 1244 назначение, 1240 To ggleGroup, назначение, 1244 П редметн ы й указатель 1365 Too !Bar, назначение, консгрукторы и методы, 1300 Too ltip, назначение и консгрукторы, 1279 Transfonn , назначение и подклассы, 1275 Treeltem, назначение, 1269 Tre eMap консгрукторы, 611 назначение, 61 1 применение, 611 TreePath, назначение и методы, 1169 TreeSet консгрукторы, 587 назначение, 587 применение, 588 TreeView консгрукторы, 1269 назначение, 1269 URI, назначение, 826 URL консгрукторы, 820 назначение, 820 URLConnection методы, 822 назначение, 82 1 Ve ctor консгрукторы, 635 назначение, 635 поля и методы, 636 применение, 638 Vo id , назначение, 520 WindowEvent консгрукторы и методы, 869 назначение и константы, 868 Window, назначение, 890 Writer, назначение и методы, 750 абстрактные объявление, 229 примечание, 229 адаптеров назначение, 880 применение, 880 разновидносги, 880 вложенные назначение, 197 разновидносги, 197 внугренние анонимные, назначение, 883 назначение, 197; 882 применение, 198; 883 выходные файлы, порядок именования, 62 идентификаторы, обозначение, 63 иерархии ,57 исключений собственные, сщцание, 278 стандартные, 264 исполнителей назначение, 1029 разновидносги , 1007 коллекций ,разновидносги, 579 назначение, 55; 155 наследуемые, объявление, 210
1366 Предметный указатель обобщенные иерархии, применение,422 объявление, общая форма, 404 определение,396 переопределение методов, 428 приведение типов, 428 с двумя параметрами типа, 403 создание, 396 оболочектипов, назначение, 326 общая форма, 155 определение, 55 отображений, разновидности, 609 поrоков ввода-вывода байтов, 357; 728 символов, 358; 749 синхронизаторов, разновидности, 1007 собьr rnй , назначение, 858 сганда(УПIЫе, назначение, 73 члены, разновидности, 55; 156 экземпляры как объекты, 55 Ключевые слова as se rt назначение, 385 формы, 385 сlаs s ,назначение, 62; 156 defauh, назначение, 257 extends, назначение, 209 final назначение, 194 применение, способы, 231 interface, назначение, 235 nаrivе, назначение, 381 private, назначение, 63 public, назначение, 63 static, назначение, �; 193 super. применение, 215; 218; 260 t.his, назначение, 171 Кол л екци и вJava автоматическая упаковка и распаковка, 567 алгоритмы применение, 628 разновидности ,628 генеририруемые исКJ1 1О чения, 568 естественное упорядочение, 614 изменяемые и неизменяемые, 568 итераторы , назначение, 565 каркас Collections Framework, назначение, 564 обобщенные, 56 6 парал л елЬНЬ1е классы, 1007; 1035 назначение, 1035 перебор вциклеforвСТИ1 1 е for each, 595 итераторами, 593 предсгавление, наэначение, 565 произвольный доступ к элемеwгам, 601 синхронизированные, 627 сохранение объеJСТОв разных классов, 599 цикличесхий перебор в СТИ1 1 е for each, 567 Комментарии документирующие назначение, 1347 обозначение ,1347 общая форма, 1352 определение, 72 применение, 1353 мноrострочные, 62 назначение, 62 одн острочные, 63 Компактные профили назначение, �93 преимущесrва , 393 Компараторы назначение, 614 применение, 617 с есrесrвенным упорядочением, 615 с обратным упорядочением, 615 Комплекты ресурсов назначение, 706 обозначение ,706 по умwrчанию, применение, 706 применение, 710 Компоненты Java Вeans архитеК'J)'ра. 131 1 индексированные свойсrва, назначение, 1314 методы и шаблоны проектирования, 1315 назначение, 131 1 настройщики, применение, 1316 обработка событий, 1314 ограниченные свойсrва, назначение, 1316 преииущесrва, 1312 привязанные свойсrва, назначение, 1315 применение, пример, 1320 простые свойсrва, назначение, 1313 самоанализ, механизм, 1312 свойсrва, усrановка и пw�учение, 1313 сохраняемосrь, 1316 Swing дейсrвия, применение, 1199 деревья типаJГrее, соэдание и применение, 1168 значки типа Irnagelcon, соэдание и применение, 1144 клас с ы, 1143 кнопки-переключатели типa JRadioButton , соэдание и применение, 1155 комбинированные списки типа JС оmЬоВ. . ох, соэдание и применение, 1f66 метки типаJLаЬеl, соэдание и применение, 1143 панели с вкладками типаJГаЬЬеdРаnе, соэдание и применение; 1157 переключатели типа JГog g leButton, соэдание и применение, 1150 посrроение на основе АWГ, 1125 прокручиваемые панели тиnaJScrollPane, соэдание и применение, 1160
списки типaJList, создание и применение, 1162 таблицы типаJГаЫе, создание и применение, 1172 текстовые поля типaJГext Field, создание и применение, 1140 флажки типаJСhесkВох, создание и применение, 1153 :экранные кнопки типaJButton, создание и применение, 1148 визуальные, составляющие, 1124 легковесные, 974; 1123 равноправные, 1122 тяжеловесные, 974; 1122 Константы перечислимого типа, 318 самотипизированные, 318 Конструкторы вызов по ссылке this(), 39 1 инициализация объектов, 168 назначение, 160 обобщенные, применение, 417 параметризированные, применение, 170 перегрузка, 180 порядок вызова, 222 по умолчанию, применение, 160 супееклассов, вызов, 215 Контеинеры Swing панели, разновидности, 1126 разновидности, 1126 сервлетов To mcat применение, 1326 состав, 1325 установка, 1326 Круглые скобки, применение , 123 Литералы двоичные, 83 классов,338 логические, 85 назначение, 71 символьные, 85 л с плавающей точкой, формы записи, 84 строковые, 86; 472 целочисленные, 82 Лямбда-выражения блочные, 44 4 внедрение, 50 захват переменных, 452 исключения, генерирование, 450 контекст, разновидности,440 лямбда-оператор, назначение, 438 назначение, 50 одиночные, 444 определение, 438 основные принципы действия, примеры, 440 передача в качестве аргументов, 447 преимущества внедрения, 437 тела, разновидности, 444 П редметн ый указатель м Массивы индексирование,95 инициализация, 96 методы обработки, 629 многомерные итерация, 145 особенности ,97 назначение, 94 нерегулярные, 99 объявление алыгернативная форма, 101 общая форма, 94 одномерные, 94 определение, 94 парал л ельная сортировка, 632 размер, 195 создание, 95 списочные емкость, определение, 581 назначение, 58 1 получение обычного массива, 582 создание, 58 1 Меню вjavaFX 1367 гл авное меню, построение изображения, ввод в пункты меню, 1293 классы ядра системы, 1281 контекстные, создание и активизация, 1296 мнемоника, назначение, 1292 обработка собьr rи й, 1283 оперативные клавиши, назначение, 1292 основные :элементы, 1281 отмечаемые пункты, применение, 1294 порядок построения, I282 пункты меню, создание и выбор, 1285 строки меню, построение, 1283 флажки , применение в пунктах меню, 1294 в �wing всплывающие, создание и активизация , 1190 гл авное меню, построение действия ,применение, 1198 изображения, ввод в пункты меню, 1187 классы ядра системы, 1175 кнопки-переключатели, применение в пунктах меню, 1189 мнемоника, назначение в меню, 1185 обработка собьr r ий, 1177 оперативные клавиши, назначение вменю, 1185 основные :элементы, 1175 отмечаемые пункты, применение, 1188 подсказки всплывающие, ввод в пункты меню, 1187 порядок построения, 1176 пункты меню, создание и выбор, 1180 строки меню, построение, 1178 фл ажки , применение в пунктах меню, 1188
1368 П редметный указ ател ь Методы clone(), применение, 533 compareTo(), применение, 480 equals() и операция =, сравнение, 479 finalize(), определение, Г7 3 main() вызо в, 63 определение, 63 параметр args, назначение, 64 printf( ), применение, 695; 744 println(), назначение, 64 print( ), назначение, 66 super(), назначение, 218 toString( ) , реализация и переопределение, 474 valueOf(), применение, 485 абстрактные назначение, 229 объявление, 229 аргументы, назначение, 167 возвращаемые значения, особенности, 165 вызов по значению, 184 по ссылке, 184 удаленный, механизм, 1105 динамическая диспетчеризация, механизм, 225 мастовые, применение обобщенные, применение, 414 объявление, общая форма, 162 определение, 55 параметры, назначение, 64; 167 перегрузка, 177 переопределение механизм, 223 назначение, 226 применение, 227 платформенно-ориентированные применение, 381 создание, 38 1 трудности применения, 384 порядок возврата значений, 162 вызова, 164 по умолчанию внедрение, 50; 256 назначение, 245 объявление, 257 применение, 258 рекурсивные, применение, 187 с аргументами переменной длины перегрузка, 206 применение, 203 синхронизированные, применение, 301 статические в классах, оrран ичения, 193 тело, обозначение, 64 фабричные, назначение, 814 Мнемоника, назначение, 1185; 1292 Многозадачность вытесняющая, 288 на основе потоков, 285 процессов, 285 Многопогочность особенности применения, 315 поддержка вJava, 286; 1005 преимущества, 286 принцип действия, 286 Модели делегирования собьrгий преимущества, 856 применение, 874 принцип действия, 856 объектно-ориентированные, реализация, 54 ориентированные на процессы, назначение, 53 погоков исполнения вJava, 287 циклов ожидания событий с опросом, 286 Модификаторы досrупа strictfp, применение, 380 synchгonized, назначение, 301 tгansient, применение, 377 volati le, применение, 378 назначение, 63 разновидности , 190 Наследование интерфейсов, 255 многоуровневые иерархии, построение, 219 множественное, поддержка, 259 назначение, 209 применение, 209 суперклассы и подклассы, 209 Области действия вложенные, 88 назначение, 87 разновидности ,88 о Обобщения аргументы типа, назначение, 398 внедрение ,преимущества, 395 выведение типов, 430 гл авное преимущество, 403 метасимвольные аргументы ограниченные, 41 1 применение, 408 обеспечение типовой безопасности, 396; 40 1 определение, 396 ошибки неоднозначности, 433 параметры типа, назначение, 398 присущие ограничения, 434 реализация вJava, особенности, 431 стирание типов, механизм, 431 Оболочки типов классы, 326 назначение, 326 примитивных, классы, 498 числовых, классы, 327 Объекты возврат, 186 как экземпляры класса, 155
IUIОНИрование, 533 объявление, 159 передача в качестве параметров, 182 по ссылке сериализация и десериализация, 762 синхронизации, применение, 1008 Окна в виде фреймов, 890 верхнего уровня, 890 иерархия классов, 888 обрамляющие задание размеров, 89 1 закрьп-ие, 891 обработка собьr r ий, 894 порядок создания, 891 сокрьr r ие и отображение, 891 установка заголовка, 89 1 просмотра задание размеров, 1266 назначение, 1266 определение, 1160 Оперативные клавипm, назначение 1185· 1292 ' ' Операторы catch, применение, 268 finally, назначение, 275 import static основные формы, 390 применение, 38 8 import, назначение, 242 instanceof, назначение, 378 new, применение, 160 расkаgе, назначение,236 synchronized, применение, 303 throws, назначение, 273 throw, применение, 272 try вложенные, применение, 270 с ресурсами ! применение, 371; 727 ветвления, switch вложенные, 132 назначение, 128 особенности, 133 применение, 129 перехода break применение, 148 с меткой, применение, 150 continue, применение, 152 retum, применение, 153 пустые, назначение, 134 ромбовидные, назначение, 430 управляющие категории , 125 назначение, 125 условные, if вложенные, 127 конструкция if-else-if, применение, 127 назначение, 125 применение, 67 Предметн ы й ука зател ь простейшая форма, 67 цикла do-while, применение, 135 for вложенные, 147 простейшая форма, 68 разновидносm, 140 традиционная форма, 138 форма в сmле (o r each,142 while, применение, 133 Операции арифметические деления по модул ю, 105 инкремента и декремента, 69; 106 основные, 104 применение, 103 составные с присваиванием, 105 лоmческие применение, 119 разновидности, 118 укороченные, применение, 120 отношения применение, 117 разновидности, 117 поразрядные лоmческие, разновидности, 109 применение, 108 составные с присваиванием, 116 1369 потоковые без сохранения состояния, 1065 изменяемого сведения , 1083 накопления ,оrраничения , 1071 оконечные и промежуточные отличие 1064 ' ' особого сведения, 1070 с сохранением состоя ния , 1065 присваивания обозначение, 120 объединение в цепочки, 121 сдвига беэзнакового вправо, 115 влево, 111 вправо, 113 сравнения, 67 'стрелки', назначение, 438 сцепления символьных строк, 473 тернарные, применение, 121 точки, применение, 157 Отображения древовидные применение, 611 соэдание, 61 1 JUiючи и значения, назначение, 601 манипулирование подотображениями, 605 назначение, 601 основные операции, 604 отсортированные, 605 представления назначение, 605 Очереди двусторонние
1370 Предметны й указател ь ввод элементов, 590 создание, 590 по приоритетам компараторы , применение, 589 создание, 589 сортировка элементов ,589 п Пакеты java.awt.event, классы и интерфейсы событий, 858, 859; 880 java.awt.image, классы и интерфейсы, 975 java.awt, классы, 886 java.Ьeans, интерфейсы и классы, 131&-1317 javafx.application, состав , 1291 javafx.collections, состав , 1255 javafx.event, интерфейсы и классы событий, 1222 javafx.scene .control, состав , 1220-1223; 1233; 1244; 1281 javafx.scene.effect, состав, 1274 javafx.scene.image, состав, 1233 javafx.scene .input, состав, 1292 javafx.scene.layout, панели компоновки, 1213 javafx.scene.paint, состав, 1229 javafx.scene.shape, состав, 1 232 javafx.scene .text, состав, 1229; 1279 javafx.scene.transfo пn, состав , 1275 java.io классы и интерфейсы, 718 назначение, 3М'; 717 java.lang интерфейсы, 497 исключения, классы, 276 классы ,497 основные языковые средства, 243 подпакеты, назначение, 561 символьные строки, классы, 469 java.lang.annotation, аннагации, 334 java.lang.reflect интерфейсы и классы, 1101 ре<рлексия, 336; 1101 java.net классы и интерфейсы, 813; 826 назначение, 8 1 i java.nio.channels каналы, 772 селекторы, 774 java.nio.charset, наборы символов, кодеры и декодеры , 774 java.nio.file.attribute, интерфейсы атрибугов файлов, 780 java.nio.file, классы, 782 java.nio, буферы, 770 java.пni , интерфейсы и классы, 1106 java.scene.canvas, состав, 1227 �ava.t�xt, классы, 1109 Java.t1me друrие классы даты и времени, 1118 основные классы даты и времени , 1113 java.time.foпnat , состав, 1115 java.util каркас коллекций Collections Frame- work, 563 классы и интерфейсы, 563; 65 1; 710 подпакеты, назначение, 71 1 унаследованные классы и интерфейсы, 634 java.util.concurrent каркас Fork/Join Framework, 1040 уrилиты парал л елиэма, 1006 java.util.concurrent.atomic атомарные операции, средства, 1039 состав,1008 java.util.concurrent.locks, состав , 1008 java.util.function, Фvнкциональные интерфейсы, 461); 712; 1069 java.util .iege x, обработка регулярных выражений, 1092 java.util.stream, потоковые интерфейсы и классы, 1062; 1080 javax.imageio, назначение, 1004 Javax.servlet интерфейсы и классы, 1328; 1329 классы исключений, 1333 javax.sei:vlet.http, интерфейсы и классы, 1335 Javax.swшg классы компонентов, 1125; 1143 javax.swin�.event, классы и интерфейсы событии, 1132; 1163; 1168 javax.swing.taЫe, интерфейсы и классы, 1172 javax.swing.tree, интерфейсы и классы, 1168 базового АР!, разновидности, 1089 библиотеки Swing, разновидности, 1127 доступность членов классов, категории, 238 импорт, 242 каркасаjаvаF}{, состав, 1212 многоуровневые, создание, 236 назначение, 235 нового АРi даты и времени, разновидности, 1113 применение, 235 системы ввода-вывода NIO, 769 Панели инструментов вjavaF}{ всплывающие подсказки, применение, 1300 построение, 1300 вSwing действия, применение, 1198 построение, 1194 применение, 1194 назначение, 1194 отстыкованные, перетаскивание, 1194 применение, 1300 Переменные в интерфейсах, применение, 253 действительно конечные, определение, 452 назначение, 86 область действия, 89 объявление, 65; 86 управления циклом назначение, 138
объявление, 138 экземпляра length, применение, 195 назначение, 156 определение, 55 присваивание значений, 158 сокрьrrие, 172 Переход через нуль, 109 Перечисления Co 11tentDisplay, назначение и консrанты, 1237 ElementType, консrанты, 345 FileVISitResult, консrанты, 802 FoпnatStyle, назначение и консrанты , 1115 Pos, назначение и консrанты, 1227 StandardOpenOption, константы, 779 limeUnit методы синхронизации, 1035 назначение и константы, 1034 всrроенные методы, применение, 319 накладываемые ограничения, 323 определение, 317 порядковые значения консrант, 323 реализация в виде классов, 317 создание, 317 типа классов, возможности, 32 1 Подсказки всплывающие, применение, 1187; 1279; 1301 Полное завершение, механизм, 173 Потоки ввода-вывода байтов, назначение, 356; 728 буферизованные, назначение, 737 закрытие, способы, 726 определение, 356 предопределенные, 359 преимущества , 768 реализация, 356 символов, назначение, 356; 749 фильтруемые, назначение, 737 данных накопление, 1079 особенности, 1061 отл оженное поведение, механизм, 1064 отображение, 1075 парал л ельные, применение, 1073 получение, способы, 1065 применение, 1066 упорядоченность, 1075 диспетчеризации собьrrий взаимодействия с компонентами Swing, 1136 вызов обработчиков событий, 1 135 назначение, 1131 построение ГПИ, 1131 исполнения взаимодействие ,304 выбор способа создания , 295 глав ные, назначение, 290 группы, назначение, 550 завершение, способы определения, 297 П редметны й указатель запускающие, назначение, 1219 ложная активизация, 305 обмен сообщениями, 289 общий пул , назначение, 1044 определение, 285 1371 переключение контексrа, правила, 288 получение текущего состояния, 313 приложений, назначение, 1219 приоритеты, 287; 299 приосrановка, возобновление и осrановка, 31 1 синхронизация,288;300 создание, 289; 291 состояния ,287 явно задаваемый пул, назначение, 1029 Преобразование типов автоматическое ,90 расширяющее, 90 сужающее, 91 усечение, 91 Приведение типов назначение, 90 общая форма, 91 Пробелы и отступы, назначение, 71 Программирование для Интернета, нajava, 38 многопоточное потоки исполнения, 285 процессы, 285 неструктурное, на ВASIC, COBOL, FOR­ TRAN и ассемблере, 34 объектно-<>риентированное абстракция, применение, 54 инкапсуляция, принцип, 55 на С++, 36 назначение, 53 наследование, принцип, 56 определение, 36 полиморфизм, принцип, 59 принципы, совместное применение, 59 парал л ельное задачи, решаемые в Fork/Join Frame- woгk, 50 назначение, 287 определение, 49; 1040 поддержка в Fork/Join Framework, 1040 структурное, на С, 35 этапы развития ,36 Продвижение типов автоматическое,93 правила, 93 Работа в сети адреса Интернета назначение, 812 поиск, 815 р дейтаграммы, назначение, 828 доменные имена, назначение службой DNS, 812 порты
1372 Предметны й указатель определение, 81 1 разновидности ,812 сокеты, назначение, 811 Разделители лексем, применение, 704 наборы символов, 65 1 назначение, 651 символьное обозначение, 72 Распаковка, назначение,328 Расширение знака, 114 Реrулярные выражения входная последовательность, 1092 кванторы обозначение , 1094 применение, 1096 классы символов задание, 1094 применение, 1098 метасимволы обозначение, 1094 применение, 1097 обработка, 1092 определение, 1092 синтаксис, 1094 совпадение с шаблоном, строгое и нecrporoe, 1097 сопоставление с шаблоном, 1094 шаблон ы, составление, 1092 Рекурсия определение, 187 организация, 188 Рефлексия назначение, 336 определение, 1101 применение, способы, 336 свойсrва, применение, 1101 Рисование дуг. 901 линий, 900; 1228 многоугольников, 901 прямоугольников, 90 0 ; 1228 режим, усrановка, 907 средсrвами АWГ, особенности, 1138 JаvаFХ, особенности, 1227 Swing, особенности, 1139 установка цвета, 906 эл л ипсов и окружностей, 901; 1229 с Сборка "мусора" , процесс, 172 Связные списки ввод и удаление элементов, 584 создание ,584 Связывание позднее, 232 раннее, 232 Сеансы связи назначение, 1346 отслеживание состояния, 1346 создание, 1346 Сервлеты ввод параметров, 1334 жизненный цикл , 1324 назначение, 43 обработка НТ Т Р-запросов типа GET , 1341 РОSТ, 1342 определение, 1323 отслеживание сеансов связи, 1346 переносимость, 43 порядок проверки, 1335; 1342; 1345 создания, 1327 преимушества, 1324 разработка, варианты, 1325 Сериализация и десериализация объектов, применение, 767 определение, 761 при удаленном вызове методов, применение, 762 Сетевые протоколы НТТР, назначение, 812 1Pv4 и IPvб, назначение, 812 IP, назначение, 81 1 JNLP, назначение, 834 ТСР, назначение, 812 UDP, назначение, 812 Символы дополнительные определение, 517 представление в виде сурроганой пары, 517 зеркально отображаемые, определение, 516 кодовая точка, определение направленность, определение, 515 основная многоязыковая плоскость, 517 Символьные строки длина, получение, 472 замена, 484 извлечение подстрок, способы, 483 символов, способы, 475 изменение реmстра симвалов, 486 как объекты класса String, 1О1 неизменяемость, 200; 469 обрезка, 484 поиск подстрок, 481 преобразование в числа, взаимное, 513 данных, 485 реализация вJava, 469 соединение, 487 создание, способы, 200 сравнение, 477; 480 средсrва обработки вJava, 469 сцепление, 473; 484 Синтаксический анализ назначение, 651 разбиение на лексемы, 65 1 строк даты и времени, 1117
Синхронизация взаимоисключающая блокировка потоков исполнения, 300 механизм мониторов, 288; 300 потоков исполнения, определение, 30 0 степень разрешения, обозначение, 1034 Словари ввод и у.цаление ключей и значений, 64 1 создание, 64 1 События ввода данных в компонентах, разновидности, 863 в текстовых полях, обработка, 944 в компонентах деревьях типаJТrее, обработка, 1168 кнопках-переключателях типа JRadioBut­ ton , oбpaбoткa, 1155 комбинированных списках типаJСоmЬоВ­ ох, обработка, 1166 переключателях типaJТoggleButton, обработка, 1151 разновидности, 861 списках типaJList, обработка, 1163 таблицах типаJТаЫе, �аботка, 1172 текстовых полях типaJlextField, обработка, 1146 флажках типаJСhесkВох, обработка, 1153 экранных кнопках типaJВutton, обработка, 1148 в контейнере, разновидности, 861 в меню, обработка, 964; 1177; 1283 в раскрывающихся списках, обработка, 935 всплывание, механизм, 1223 групповая рассылка, 857 действия временная метка, 860 разновидности,859 из библиагеки АWГ, 858 JavaFX, 1222 Swing, 1132 изменения в группе кнопок-переключателей, обработка, f2 47 источники определение, 857 реmстрация приемников, 857 настройки, разновидности, 860 обработка, способы, 856 оконные, разновидности, 868 определ ение, 856 от выбираемых элементов, разновидности, 864 от клавиаrуры обработка, 877 разновидности, 864 от КНОПОК, обрабагка, 927 от колесика мыши, 867 от мыши обработка ,874 разновидности, 865 от полос прокруrки, обработка, 94 1 П редметный указател ь от списков, обрабагка, 938 от флажков, обработка, 931 приемники 1373 определение, 857 реmстрация и снятие с регистрации, 857 строка с командой действия назначение, 927 получение, 1148 текстовые, разновидности, 868 типы, 855 фокуса ввода, разновидности, 862 целевая рассылка, 857 Сокеты определение, 81 1 по прагоколу ТСР/IP клиентские, 816 серверные, 827 связь по сетевым протоколам, 811 Состояние гонок возникновение, 302 исключение, 303 Спецификаторы минимальной ширины поля, применение, 687 преобразован ия , обозначение , 682 точности, применение,689 формата буквы шаблона, 1116 определение, 682 прописные формы, применение, 692 разновидности, 682 Ссылки на конструкторы применение, 460 формы создания, 460; 464 на методы назначение, 453 обобщенные, применение, 458 применение в кол л екциях, 459 статические ,применение, 453 экземпляра, применение, 454 на объекты особенности, 160 присваивание, 161 Статический импорт назначение, 388 применение, 389 Стек ввод и у.цаление элементов, 640 принцип действия, 639 Стековый фрейм, назначение, 555 Стирание типов, принцип действия, 431 т Те кст выводимый в окне форматирование центровка, 917 многострочный выравнивание, 918 отображение, 916
П редметн ы й указател ь Ти пы данных базовые, назначение, 420 обобщенные, различение по аргументам, 40 0 огран иченные, назначение, 405 примитивные лоrические значения, 81 применение, 76 разновидности, 75 символы, 80 целые числа, 76 числа с плавающей точкой, 78 строковые, назначение, 101 Тр адиционное упрааление ресурсами , особенности, 374 Трассировка стека, назначение, 266 у Удал енный ВЫ3ов методов заглушки назначение, 1107 соэдание, 1107 запуск клиента, 1108 реестра, 1108 сервера, 1108 механизм, 1105 Ун иверсальное скоординированное время, определение, 666 Уп аковка, назначен ие, 328 Уп рааление доступом, механизм Уп рааляющие последовательности символов, 85 Ут верждения включение и отключение режима проверки,388 назначение, 385 применение,385 Утил иты парал л елизма атомарные операции, разновидности, 1039 барьеры, ме1W1иэм, 1016 блокировки механизм, 1036 реентерабельные , реализация, 1037 исполнители мехнизм, 1028 назначение, 1007 применение, 1029 и традиционный подход к мноrо оада чности , 1060 назначение, 1005 обмен данными, механизм, 1018 применение, 1006 самоблокировка с обратным отсчетом, механизм, 1014 семафоры механизм, 1008 применение, 1009 синхронизаторы назначение, 1007 фаз, механизм, 1021 ф Файлы атрибуrы доступ, способы, 781 назначение, 780 доступ,назначение, 721 закрьr r ие автоматическое, 371 традиционным способом, 365 запись данных через канал новые способы, 789 старые способы, 807 копирование средствами NIO, 792 назначение, 719 открьrrие для ввода, 365 переименование, 721 сведения, получение, 796 соэдание, 719 удаление, 721 фильтрация, 723 чтение данных через канал новые способы, 783 старые способы, 804 Форматирование выводимого текста, 915 выравнивание выводимых данных, 690 данных, механизм, 682 даты и времени, 685; 1109-1 115 индексы аргументов, применение, 693 относительные индексы, применение, 693 призна1G1 прочие, применение, 691; 692 формата, обозначение, 689 строк и символов, 684 чисел, 684 Функциональные интерфейсы BiCoпsumer, назначение, 1082 BinaryOperator, назначение, 1070 Consumer, назначение, 597; 1069 Function, назначение, 1076 Predicate , назначение, 1069 внедрение, 50 обо6щенные, назначение, 446 объяаление, 439 определение, 438 предопределенные, разновидности, 465 с единственным абстрактным методом, 438 Хеширование механизм ,586 преимущество, 586 Хеш-множества х емкость загрузки , определение, 586 порядок сохранения элементов, 586 связные списки, составление, 587 соэдание, 586 Хеш-отображения емкость, определение ,609 расположение элементов, 610 связные
создание, 613 создан ие, 609 Хеш-таблицы применение, 585 создание, 643 хранение ключей и значений, 642 ч Чтение данных средствами класса Scanner, механизм, 696 символов из потока ввода, 360 символьных строк с клавиатуры, 361 ш Шрифгы досrуnные, определение, 910 логическое имя, 909 наименование гарнитуры, 909 семейства, 909 описание, терминология, 914 сведения, получение, 913 создание и выбор, 91 1 Элементы управления ввод и удаление, 924 э из библиотеки лwr, разновидности, 924 из кapкacaJavaFX, разновидности, 1233 обработка событии, 925 определение, 923 я Языкjаvа архитектурная нейтральность, 45 П редметн ый указател ь байт-код интерпретация ,42 назанчение безопасность, 41 1375 виртуальная машинаjаvа, назначение, 41 внешнее сходство с языком С++, 39 высокая производительность, 46 динамический компилятор, назначение, 42 динамичность, 46 единица компиляции, определение, 61 интерпретируемость, 46 история создания, 37 культура нововведений, 51 многопоточность, 45 надежность, 44 назначение, 28 нововведения в версии i2SE 5, 47 avaSE7,49 avaSE8,50 новые языковые средства, 28 переносимость, 4 r порядок компиляции программ, 61 предпосылки для соэдания, 37 причины разработки, 33 успеха, 28 происхождение, 33 простота, 44 распределенность,46 связь с языком С#, 39 строго типизированный, 75 терминология, 43 эволюция, 46
ORAC Le � Исчерпывающее руко водство по программированию на Java j;)� =- java В это м справочном пособии, полностью обновленном с уч ето м посл едней версии Java SE 8, поясняется , как разрабаты вать , ко мпилировать , отлаживать и выпол нять программы на языке программирования Java . Это пособие составлено Ге рбертом Шилдтом , авто ром популярных во всем мире кн иг по языкам програм мирования, та ким образом, чтобы охватить все языковые средства Java , включая синтаксис, кл ючевые слова, основные принципы объектно-ориентированного программирова­ ния, значител ьную часть прикладного программного инте рфейса Java API , библиотеки кл ассов, аплеты и сервлеты , ком поненты JavaBeans, библиотеки АWТ и Swi ng, а та кже продемонст рировать их применение на простых и наглядных примерах. Не обойдены вниманием и новые средства, появившиеся в верси и Java SE 8, в том числе лямбда ­ выражения, ста ндартные интерфейсные методы , библиотека потоков ввода-вывода , а также технология Java FX. В это й книге рассматриваются следующие вопросы : • Ти пы да нных, переменные, массивы и операци и • Уп равляющие и усло вные операто ры • Кл асс ы, объекты и методы • Перегрузка и переопределение методов • Наследование • Интерфейсы и пакеты • Обработка исключений • Многопоточ ное программирование • Переч исле ния, авто упаковка и автораспаковка • Пото ки ввода-вывода • Обобщения • Лямбда -выражения Категория : Программирование Предмет рассмотрения: Язык Java Уровень: Начальный/промежуточ ный Издател ьс кий до м "Вильяме" http://ww w . williamspuЫishing.com - McGraw Hill - MHPROFESS IONAL.COM • Обработка символ ьных строк • К аркас коллекций Collection Framework • Работа в сети • Обработка событий • Библ иотекиАWТ и Swing • Прикладной программный инте рфейс Concurrent API • Прикладной программный инте рфейс Stream API • Регулярные выражения • Тех нология Java FX • Компоненты JavaBeans • Аплеты и сервлеты •, ISBN 978-5 -8459 -1918 -2 14043 9 785845 919182 1