Книга
Книга
^а5ли»ихека tvpe^ccawwwiA^^^^
ТАЛ Мб JZrVZl
Том I. Основы
Профессиональное руководство по программированию на языке Java, полностью переработанное с учетом инструментальных средств Java™ 1.4 SDK
•
Новые темы: регулярные выражения, новые средства ввода- вывода, диагностические утверждения, регистрация и многое другое
•
Приемы, о которых необходимо знать профессиональным программистам
СЕРИЯ JAVA
от Sun Microsystems Press
Кей С. Хорстманн • Гари Корнелл
JAVA™ 2
ТомIОсновы
Библиотека профессионала
КЕЙ С. ХОРСТМАНН • ГАРИ КОРНЕЛЛ
Москва • Санкт-Петербург • Киев 2003
ББК 32.973.26-018.2.75
Х82
УДК 681.3.07
Издательский дом “Вильямс”Зав. редакцией А. В. Слепцов
Перевод с английского и редакция канд.физ.-мат. наукД.А. Клюшина
По общим вопросам обращайтесь в Издательский дом “Вильямс” по адресу:info@williamspublishing.com, http://www.williamsput)lishing.com
Хорстманн К. С, Корнелл Г.
Х82Библиотека профессионала. Java 2. Том 1. Основы.: Пер. с англ. — М.: Из
дательский дом "Вильяме", 2003. — 848 с: ил. — Парал. тит. англ.
ISBN 5-8459-0385-8 (рус.)
Книга адресована, прежде всего, программистам-профессионалам и представляет
собой исчерпывающий справочник и методическое пособие по основам программирования на языке Java. Однако это не просто учебник по синтаксису языка. Назначение книги — обучить методам объектно-ориентированного программирования и решения основных проблем в этой области. Работа с книгой не требует опыта программирования на языке C++ и применения методов ООП. Любой программист, работавший с языками Visual Basic, С, Cobol или Pascal, не будет испытывать затруднений при работе с ней. Книга содержит многочисленные примеры и советы по программированию, а также разделы, в которых рассматриваются методы тестирования и отладки программ, абстрактные типы данных, базовое объектно-ориентированное программирование, включающее событийно-управляемое программирование. Книгу можно использовать не только как учебник, но и как справочник.
ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механичесю-е, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Prentice Hall, Inc.
Authorized translation from the English language edition published by Prentice Hall, Ptr., Copyright © 2003
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronicormechanical, includingphotocopying,recordingorbyanyinformationstorageretrievalsystem,
without permission from the Publisher.
Russian language edition published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2003
ISBN 5-8459-0385-8 (рус.)© Издательский дом "Вильяме", 2003
ISBN 0-1304-7177-1 (англ.)© Sun Microsystems. Inc.. 2003
Оглавление
Глава 1. Введение в язык Java15
Глава 2. Среда программирования на языке Java33
Глава 3. Основные структуры языка Java55
Глава 4. Объекты и классы117
Глава 5. Наследование181
Глава 6. Интерфейсы и внутренние классы245
Глава 7. Программирование графики285
Глава 8. Обработка событий335
Глава 9. Компоненты пользовательского интерфейса
из пакета Swing401
Глава 10. Аплеты569
Глава 11. Исключительные ситуации и отладка647
Глава 12. Потоки и файлы721
Приложение. Ключевые слова819
Предметный указатель821
Содержание
Предисловие9
Глава 1. Введение в языкJava15
Язык Java как средство программирования16
Преимущества языка Java17
Характерные особенности языка Java18
ЯзыкJava и Интернет25
Краткий курс истории языка Java27
Распространенные заблуждения относительно языка Java29
Глава 2. Среда программирования на языке Java33
Инсталляция набора инструментальных средств
Java Software Development Kit34
Среда разработки программ38
Использование инструментов в режиме командной строки39
Использование интегрированной среды разработки программ41
Компилирование и запуск программ изтекстового редактора44
Графические приложения48
Аплеты50
Глава 3. Основные структуры языкаJava55
Простая программа на языке Java56
Комментарии59
Типы данных60
Переменные64
Присваивания и инициализации65
Операторы66
Строки73
Поток управления86
Большие числа102
Массивы104
Глава 4. Объекты и классы117
Введение в объектно-ориентированноепрограммирование118
Использование существующих классов125
Создание собственных классов135
Статические ПОЛЯ и методы146
Параметры метода151
Создание объектов157
Пакеты165
Комментарии174
Советы по разработке классов178
Глава 5. Наследование181
Расширение классов182
Object: глобальный суперкласс201
Класс Class221
Отражение225
Как применять наследование242
Глава 6. Интерфейсы и внутренниеклассы245
Интерфейсы246
Клонирование объектов256
Внутренние классы261
Заместители278
Глава 7. Программирование графики285
Введение в пакет Swing286
Создание фреймов290
Позиционирование фреймов293
Отображение информации на панели299
Двухмерные фигуры304
Цвета313
Текст и шрифты318
Рисунки328
Глава 8. Обработка событий335
Основы обработки событий336
Иерархия событий библиотеки AWT355
Семантические и низкоуровневые события в библиотеке AWT357
Типы низкоуровневых событий361
Действия379
Многоадресная передача событий388
Очередь событий391
Глава 9. Компоненты пользовательского интерфейса из пакета Swing401
Создание интерфейса с помощью схемы
"модель-представление-контроллер"402
Введение в управление компоновкой408
Ввод текста418
Выбор вариантов449
Меню478
Изощренное управление компоновкой497
Диалоговые окна527
Глава 10. Аплеты
Основные понятия
Дескрипторы HTML и атрибуты аплетов
Мультимедиа
Контекст аплета
Файлы JAR
Упаковка приложений
Технология Java Web Start
Хранение параметров настройки приложения
569
570
582
593
596
607
610
618
632
Глава 11. Исключительные ситуации и отладка
Обработка ошибок
Перехват исключительных ситуаций
Как использовать исключительные ситуации
Регистрация ошибок
Диагностические утверждения
Приемы отладки
Применение отладчика
647
648
655
669
672
690
694
713
Глава 12. Потоки и файлы
Потоки
Полное семейство потоков
Потоки ZIP-файлов
Использование потоков
Объектные потоки
Работа с файлами
Новые средства ввода-вывода
Регулярные выражения
721
721
725
746
754
767
792
798
809
Приложение. Ключевые слова
819
Предметный указатель
821
Предисловие
Обращение к читателю
Язык Java ворвался в Интернет в конце 1995 года и немедленно завоевал всеобщую популярность. Он обещал стать универсальным средством, обеспечивающим связь пользователей с любыми источниками информации, независимо от того, где она расположена— на Web-сервере, в базе данных, хранилище данных и т.п. У языка Java есть все предпосылки для этого. Этот исключительно хорошо разработанный язык поддержали все производители программного обеспечения, за исключением компании Microsoft. Присущие языку Java безопасность и надежность вселяют спокойствие и уверенность как в программистов, так и в пользователей. Он имеет встроенные средства, позволяющие решать задачи повышенной сложности, например, обеспечивать сетевое программирование, управление базами данных и многопоточность.
Начиная с 1995 года компания Sun Microsystems выпустила в свет пять основных версий набора инструментальных средств Java Software Development Kit. На протяжении последних семи лет объем интерфейса прикладных программ (API) возрос с 200 до 3000 классов. Теперь он охватывает самые разные области программирования — создание пользовательского интерфейса, управление базами данных, интернационализацию, обеспечение безопасности и обработку данных с помощью языка XML.
Книга, которую вы держите в руках, представляет собой первый том шестого издания "Основ языка Java". Каждое издание этой книги выходило вслед за появлением очередной версии набора инструментальных средств языка Java, при этом авторы стремились как можно лучше отразить достоинства новой версии.
Данное издание, как и предыдущие, рассчитано на серьезных программистов, стремящихся использовать язык Java для воплощения реальных проектов. Мы, как и прежде, обещаем не нервировать читателя и не утомлять его вычурным шрифтом. Предполагается, что читатель этой книги — опытный программист, имеющий солидную подготовку вобластиязыковпрограммирования. Однако знать язык C++ иобъектно-ориентированное программирование совсем не обязательно. Опираясь на отклики, полученные после опубликования предыдущих изданий, мы по-прежнему убеждены, что программисты, изучавшие языки Visual Basic, С или COBOL, без проблем во всем разберутся. (Читатель может даже совершенно не иметь опыта разработки графического пользовательского интерфейса в средах Windows, Unix или Macintosh.)
От читателя требуется лишь:
желание создавать реальные программы для решения реальных задач;
отвращение к книгам с игрушечными примерами (вроде кухонного оборудования и фруктовых деревьев).
Книга содержит большое количество программ, демонстрирующих почти все возможности языка и библиотек, обсуждаемые в тексте. Программы преднамеренно упрощены, чтобы читатель сосредоточил внимание на основных моментах. Однако в
большинстве случаев они являются вполне реальными и не подменяют настоящее решение задачи его имитацией. Их очень удобно использовать в качестве отправной точки при создании собственных программ.
Предполагается, что читатель стремится овладеть всеми возможностями языка Java. Например, в книге описываются следующие темы:
объектно-ориентированное программирование (ООП);
отражение и заместители (proxies);
интерфейсы и внутренние классы;
блок прослушивания (event listener);
разработка графического пользовательского интерфейса с помощью набора инструментальных средств Swing IT;
обработка исключительных ситуаций;
потоки ввода-вывода и сериализация объектов.
В книге по-прежнему не рассматриваются забавные программы на языке Java, единственная цель которых — оживить вашу Web-страницу. Такие программы можно найти в других источниках.
В заключение отметим, что из-за стремительного увеличения библиотеки классов языка Java стало невозможным описать в одном томе все его возможности, необходимые серьезным программистам. Поэтому было решено разделить книгу на два тома. Первый том, который вы держите в руках, посвящен основным концепциям языка Java, а также основам программирования пользовательского интерфейса. Второй том содержит описание промышленных особенностей языка Java и средств программирования пользовательского интерфейса повышенной сложности. В этом томе подробно обсуждаются следующие темы:
многопоточность;
сетевое программирование;
распределенные объекты;
классы коллекций;
базы данных;
графика повышенной сложности;
компоненты для создания графического пользовательского интерфейса повышенной сложности;
интернационализация;
машинозависимые методы;
функциональные компоненты JavaBeans;
обработка данных с помощью языка XML.
В книге нет существенных ошибок. Мы были бы очень признательны, если бы читатели, обнаружившие какие-либо неточности, сообщили нам о них. Естественно, нам бы хотелось, чтобы таких сообщений было поменьше. Список наиболее часто за-
даваемых вопросов, а также исправлений и уточнений можно найти на Web-странице http://www.horstmann.com/corejava.html. Кроме списка самых популярных вопросов, на этой Web-странице есть анкета, в которой можно указать опечатки и записать свои пожелания. Место для этой анкеты выбрано специально, чтобы читатель сначала увидел список наиболее часто задаваемых вопросов. Пожалуйста, не огорчайтесь, если мы не ответим на каждый вопрос или ответим не сразу. Мы читаем все сообщения, посылаемые нам по электронной почте, и благодарим вас за стремление сделать будущие издания книги еще понятнее и точнее.
Мы надеемся, что чтение этой книги доставит вам удовольствие и поможет при программировании на языке Java.
О книге
В главе 1 сделан обзор особенностей языка Java, отличающих его от других языков программирования. Здесь описываются цели, которых стремились достичь разработчики языка, а также результаты, которых они действительно достигли. Далее излагается краткая история создания и развития языка Java.
В главе 2 описан процесс загрузки и инсталляции пакета Java SDK с Web-страницы http://java.sun.com/j2se. Затем показано, как загрузить примеры программ, рассмотренных в книге, с Web-страницы phptr. com/corejava. Кроме того, в главе продемострирован процесс компиляции и выполнения типичных программ, написанных на языке Java: консольного приложения, графического приложения и аплета.
Обсуждение языка Java начинается в главе 3. Здесь описаны основы языка: переменные, циклы и простые функции. Если вы умеете программировать на языках С или C++, то легко поймете, о чем идет речь, поскольку синтаксис языков C++ и Java похож на синтаксис языка С. Если же вы не умеете программировать на языке С, а изучали, например, языки Visual Basic или COBOL, вам следует внимательно прочитать эту главу.
В настоящее время объектно-ориентированное программирование является основным направлением развития программирования вообще, а язык Java представляет собой полностью объектный язык. В главе 4 рассматривается инкапсуляция (первое из двух основных понятий объектно-ориентированного программирования) и механизм ее реализации в языке Java, т.е. классы и методы. Кроме правил языка Java, приводятся советы, касающиеся объектно-ориентированной разработки программ. В заключение описывается замечательная утилита javadoc, позволяющая форматировать комментарии в программе в виде совокупности Web-страниц с гипертекстовыми ссылками. Если вы знакомы с языком C++, то можете просто пролистать эту главу. Программисты, не имеющие опыта объектно-ориентированного программирования, должны посвятить некоторое время изучению его основ, прежде чем продолжить изучение языка Java.
Классы и инкапсуляция представляют собой лишь часть ООП. В главе 5 вводится новое понятие — наследование, позволяющее программистам использовать существующие классы и модифицировать их для своих нужд. Это — основной способ программирования на языке Java. Механизмы наследования в языках Java и C++ совершенно одинаковы, поэтому программисты, знающие язык C++, могут сосредоточиться на различиях между этими языками.
В главе 6 показано, как использовать такое понятие языка Java, как интерфейс. Интерфейсы позволяют выйти за рамки простого наследования описанного в главе 5. Освоив интерфейсы, читатель получит возможность полностью использовать всю мощь объектно-ориентированного программирования на языке Java. Здесь также описаны полезные свойства языка Java, называемые внутренними классами. Они позволяют делать программы более ясными и четкими.
В главе 7 изучение программирования начинается всерьез. В этой главе показано, как создать окно, раскрасить его, нарисовать геометрическую фигуру, отформатировать текст с помощью нескольких шрифтов и отобразить рисунки на экране.
Глава 8 посвящена обсуждению модели событий и набора средств для организации абстрактного оконного интерфейса (Abstract Windowing Toolkit — AWT). (Здесь обсуждается модель событий, добавленная в версию Java 1.1, а не устаревшая и упрощенная модель событий из версии Java 1.0.) Читатель узнает, как написать программу, реагирующую на щелчок мыши или нажатие клавиши. Кроме того, в главе показано, как работать с основными элементами графического пользовательского интерфейса, например, с кнопками и панелями.
В главе 9 очень подробно обсуждается набор инструментальных средств Swing, позволяющий создавать кросс-платформный графический пользовательский интерфейс. Здесь вы узнаете все о разнообразных видах кнопок, текстовых компонентах, рамках, ползунковых регуляторах, списках, меню и диалоговых окнах. Однако некоторые, более сложные, компоненты обсуждаются во втором томе.
Завершив изучение главы 9, читатель получит знания обо всех механизмах, позволяющих создавать аплеты, т.е. минипрограммы, самостоятельно существующие в рамках Web-страницы. Они являются предметом обсуждения в главе 10, в которой показано большое количество полезных и забавных аплетов. Однако намного важнее то, что аплеты рассматриваются нами к качестве метода развертывания программ (program deployment). В главе описано, как запаковать приложение в файлы формата JAR и доставить их через Интернет, используя механизм Java Web Start. В заключение объясняется способ, с помощью которого программы, написанные на языке Java, сохраняют и извлекают информацию после своего развертывания.
В главе 11 обсуждается обработка исключительных ситуаций — надежный механизм, позволяющий выходить из затруднительных положений, в которые могут попадать даже хорошие программы. Например, при загрузке файла из сети могут произойти обрыв связи, переполнение диска и другие неприятные события. Обработка исключительных ситуаций представляет собой эффективный способ, позволяющий отделить нормальное выполнение программы от обработки ошибок. Разумеется, даже если в программе предусмотрены все исключительные ситуации, остается возможность ее сбоя. Во второй части этой главы приводится большое количество полезных советов, касающихся отладки программ. В заключение рассматриваются примеры отладки программ с помощью разных отладчиков: утилиты JDB, отладчика интегрированной среды программирования, профайлера, средства проверки кода и робота AWT.
Книга завершается описанием ввода и вывода. В языке Java любой ввод и вывод выполняется через так называемые потоки, позволяющие осуществлять связь с любыми источниками данных, например, файлами, сетевыми узлами или участками памяти. В главе 12 подробно описываются классы ввода и вывода, с помощью которых легко работать с форматом Unicode, а также раскрывается механизм сериализации объектов, позволяющий просто и удобно сохранять и загружать объекты. В заключе-
Принятые соглашения13
ние описывается несколько библиотек, включенных в версию пакета SDK 1.4: "новые классы ввода-вывода", поддерживающие более сложные и эфективные операции над файлами, а также библиотеки регулярных выражений.
В приложении перечислены ключевые слова языка Java.
Принятые соглашения
Как и в большинстве книг по программированию, тексты программ представляются с помощью шрифта courier.
В книге много замечаний, касающихся языка C++. Они описывают различия между языками Java и C++. Если у вас нет опыта программирования на языке C++ или Lвы вспоминаете о нем, как о кошмаре, который хочется поскорее забыть, можете
их пропустить.
Обычные замечания содержатся в такой врезке.
В такой врезке содержатся советы
Эта врезка предупреждает о грозящей опасности.
Язык Java сопровождается большой библиотекой программ, а также интерфейсом прикладных программ (Application Programming Interface — API). Каждый вызов интерфейса API сопровождается кратким описанием, которое отмечается пиктограммой и размещается в конце соответствующего раздела. Эти описания являются довольно неформальными, однако они более информативны, чем официальная интерактивная документация по интерфейсу API. Теперь каждое замечание об API сопровождается номером версии, в которой впервые было реализовано то или иное свойство. Это поможет пользователям легче ориентироваться в версиях этой библиотеки.
Программы, записанные на Web-странице, приводятся в тексте как листинги, например, так.
Исходные тексты программ
Web-сайт http://www.phptr.com/corejava содержит все исходные тексты программ, рассмотренных в книге. Файлы распространяются либо с хорошо известным архиватором, либо вместе с утилитой jar, являющейся частью пакета Java Software Development Kit. Более подробная информация об инсталляции этого пакета и соответствующие примеры программы приведены во втором томе.
Благодарности
Писать книгу всегда трудно. Создавать ее переработанный вариант ненамного легче, особенно если учесть постоянное изменение технологии программирования на языке Java. В издание книги всегда вовлечено много людей, и мне доставляет большое удовольствие выразить благодарность всем, кто принял участие в работе над "Основами языка Java".
Большое количество людей, работающих в издательстве Prentice-Hall PTR, Sun Microsystems Press и Navta Inc., оказали авторам неоценимую помощь, пожелав при этом остаться неизвестными. Я хотел бы выразить им всем большую благодарность. Как всегда, я горячо благодарю своего редактора Грега Донча (Greg Doench), работающего в издательстве Prenbtice-Hall PTR, за работу, выполненную им в процессе создания И публикации рукописи, а также за то, что он оставил меня в блаженном неведении о всех людях, пожелавших остаться в тени. Приношу благодарность своему соавтору в предыдущих изданиях книги Гари Корнеллу (Gary Cornell), который в настоящее время занят другими проектами.
Благодарю многочисленных читателей предыдущих изданий этой книги, сообщивших мне о многочисленных досадных ошибках и сделавших большое количество предложений, направленных на ее улучшение. Я особенно благодарен рецензентам, детально изучившим рукопись и предотвратившим появление еще большего количества ошибок.
Рецензентами этого и предыдущих изданий были: Чак Эллисон (Chuck Allison), редактор журнала C/C++ Users Journal, Алек Битон (Alec Beaton) из компании PointBase, Inc., Дэвид Браун (Dawid Brown), др. Николас Де Лилло (Dr. Nicholas J. De Lillo) из колледжа Манхэттена, Ракеш Дхупар (Rakesh Dhoopar) из компании Oracle, Дэвид Гири (David Geary), Анджела Гордон (Angela Gordon) и Дэн Гордон (Dan Gordon) из компании Sun Microsystems, Роб Гордон (Rob Gordon), Камерон Грегори (Cameron Gregory), Марти Холл (Marty Hall) из лаборатории прикладной физики университета Джонса Хопкинса (The Johns Hopkins University Applied Physics Lab), Винсент Харди (Vincent Hardy) из компании Sun Microsystems, Владимир Иванович (Vladimir Ivanovic) из компании PointBase, Inc., Джерри Джексон (Jerry Jackson) из компании ChannelPoint Software, Тим Киммет (Tim Kimmet) из компании Preview Systems, Крис Лаффра (Chris Laffra), Чарли Лай (Charlie Lai) из компании Sun Microsystems, Дуг Лэнгстон (Doug Langston), Дуг Ли (Doug Lea) из компании SUNY Oswego, Грэгори Лонгшо (Gregory Longshore), Боб Линч (Bob Lynch), Марк Моррисси (Mark Morrissey), Махеш Нилаканта (Mahesh Neelakanta) из Атлантического университета Флориды, Пол Фильон (Paul Philion), Блэйк Рагсделл (Blake Ragsdell), Стюарт Реджес (Stuart Reges) из университета Аризоны, Питер Сандер (Peter Sander) из университета ESSI (Ницца, Франция), Пол Севинк (Paul Sevinc' из компании TcamUp AG, Деванг Шах (Devang Shah) из компании Sun Microsystems, Брэдли Э. Смит (Bradley A. Smith), Люк Тейлор (Luke Taylor) из компании Valtech, Джордж Тирувату- кал (George Thiruvathukal), Ким Топли (Kim Topley), Питер ваь дер Линден (Peter van der Linden) из компании Sun Microsystems и Берт Уолш (Burt Walsh).
Кей Хорстманн
Введение в язык JavaВведение в язык JavaVWMAr 1
Введение в язык Java
Введение в язык Java
В этой главе...
V Язык Java как средство программирования
Т Преимущества языка Java
Т Характерные особенности языка Java
Язык Java и Интернет
Краткий курс истории языка Java
▼ Распространенные заблуждения относительно языка Java
у/ Долгое время трудно было представить себе компьютерный журнал без статьи, посвященной языку Java. О нем писали даже такие популярные газеты и журналы, как The New York Times, The Washington Post и Business Week. Невозможно припомнить, чтобы национальное общественное радио (National Public Radio) когда-либо посвящало языку программирования десятиминутную передачу. Хорошо это или плохо, зависит от точки зрения. А инвестиции объемом 100 миллионов долларов, вложенные в производство программного обеспечения, создаваемого с помощью конкретного языка программирования?! Телекомпании CNN, CNBC и другие средства массовой информации только и говорили, да и сейчас говорят, о том, как язык Java и то сможет, и это сделает.
Однако эта книга предназначена для серьезных программистов, а поскольку язык Java — это серьезный язык программирования, нам есть о чем рассказать. Итак, мы не станем погружаться в анализ рекламных обещаний и пытаться выяснить, что в них правда, а что вымысел. Вместо этого мы достаточно подробно опишем язык Java именно как язык программирования (включая, разумеется, особенности, позволяю-
щие использовать его для работы в Интернет, которые, собственно, и вызвали столько рекламной шумихи). После этого мы попытаемся отделить реальность от фантазий, объяснив, что язык Java действительно может, а что — нет.
На первых порах между рекламными обещаниями и реальными возможностями языка Java лежала пропасть. По мере его созревания технология становилась все более стабильной и надежной, а ожидания снизились до разумного уровня. Сейчас язык Java все шире используется для создания "промежуточного программного обеспечения" (middleware), поддерживающего связь между клиентами и ресурсами серверов (например, базами данных). Несмотря на то что эти важные приложения не поражают воображение, именно в этой области язык Java оказался наиболее полезным благодаря своей машинной независимости, многопоточности и возможностям сетевого программирования. Кроме того, язык Java захватил лидерство в области встроенных систем (embedded systems), став фактическим стандартом портативных устройств, виртуальных киосков, бортовых автомобильных компьютеров и т.п. Однако первые попытки переписать на языке Java широко распространенные программы для персональных компьютеров не увенчались успехом — полученные приложения оказались маломощными и медленными. С появлением новой версии некоторые из этих проблем удалось решить, и все же нужно признать, что пользователям, в общем-то, совершенно безразлично, на каком языке написаны купленные ими программы. Мы полагаем, что основные преимущества языка Java проявятся при создании новых видов устройств и приложений, а не при переписывании уже существующих программ.
Язык Java каксредство программирования
Как язык программирования Java перевыполнил свои рекламные обещания. Несомненно, это один из лучших языков, доступных серьезным программистам. Потенциально Java имеет все предпосылки, чтобы стать великим языком программирования, однако, вероятно, сейчас уже слишком поздно. Когда появляется новый язык программирования, немедленно возникает неприятная проблема его совместимости с программным обеспечением, созданным ранее. Более того, даже если изменения в эти программы можно внести без вмешательства в их текст, создателям языка, который так горячо приветствовался публикой, как, например, язык Java, сложно прямо сказать: "Да, возможно мы ошиблись при разработке версии X, но версия Y будет лучше". В итоге, ожидая появления дальнейших улучшений, мы должны констатировать, что структура языка Java в ближайшем будущем существенно не изменится.
Возникает очевидный вопрос: "Как удалось улучшить язык Java?". Оказывается, это сделано не за счет усовершенствования собственно языка программирования, а путем коренного изменения библиотек программ, написанных на языке Java. Компания Sun Microsystems изменила все: начиная с имен отдельных библиотечных функций (сделав их более осмысленными) и методов работы графических модулей (изменив способ обработки событий и частично переписав рабочие программы), и заканчивая созданием новых свойств языка, например, средств вывода информации на печать, которых не было в версии Java 1.0. В результате получилась гораздо более полезная программная платформа, чем все предыдущие версии языка Java.
Компания Microsoft выпустила в свет свой собственный продукт под названием J++, имеющий отношение к языку Java. Язык J++ интерпретируется виртуальной машиной, совместимой с виртуальной машиной языка Java (Java Virtual Machine) при выполнении байт-кода, но интерфейсы с внешними кодами у этих языков значительно различаются. Языки J++ и Java имеют практически одинаковый синтаксис. Однако компания Microsoft создала дополнительные языковые конструкции. Все они имеют довольно сомнительную ценность, за исключением интерфейса Windows API. Помимо того, что у этих языков одинаковый синтаксис, их основные библиотеки (строки, утилиты, средства сетевого программирования, средства поддержки многопоточности, математические библиотеки и т.п.), по существу, также совпадают. Однако графические библиотеки, пользовательский интерфейс и доступ к удаленным объектам у этих языков совершенно разные. В настоящее время компания Microsoft больше не поддерживает язык J++, разработав новый язык С#, имеющий много общего с Java, но использующий другую виртуальную машину. В этой книге ни язык J++, ни язык С# не описываются.
Преимущества языка Java
Одно из основных преимуществ языкаJava- независимость от платформы, на которой выполняются программы: один и тот же код можно запускать под управлением операционных систем Windows, Solaris, Linux, Machintosh и др. Это действительно необходимо, когда программы загружаются через Интернет для последующего выполнения под управлением разных операционных систем.
Другое преимущество заключается в том, что синтаксис языка Java похож на синтаксис языка C++, и программистам, знающим языки С и C++, его изучение не составляет труда. Правда, для программистов, владеющих языком Visual Basic, этот синтаксис, возможно, будет непривычен.
Если вы никогда не программировали на языке C++, некоторые термины, использованные в этом разделе, будут вам непонятны. В этом случае можете пропустить его. Пока вы доберетесь до конца главы 6, эти термины станут для вас привычными.
Кроме того, Java— полностью объектно-ориентированный язык, даже в большей степени, чем C++. Все сущности в языке Java являются объектами, за исключением немногих основных типов (primitive types), например чисел. (Поскольку с помощью объектно-ориентированного программирования легко разрабатывать сложные проекты, оно заменило собой более древнее структурное программирование. Если вы не знакомы с объектно-ориентированным программированием, главы 3-6 предоставят вам все необходимые сведения о нем.)
Однако разработать еще один, слегка улучшенный, диалект языка C++ недостаточно. Принципиальноважно,что разрабатывать программы, не содержащие ошибок, на язы- кеJavaлегче, чем на языке C++.
Почему? Разработчики языка Java долго размышляли о том, отчего программы, написанные на языке C++, так подвержены ошибкам. Они снабдили язык Java средствами, позволяющими исключить саму возможность создавать программы, в которых были бы скрыты наиболее распространенные ошибки. Для этого в языке Java сделано следующее.
Исключена возможность явного выделения и освобождения памяти.
Память в языке Java освобождается автоматически с помощью механизма сборки мусора. Программист гарантирован от ошибок, связанных с неправильным использованием памяти.
Введены истинные массивы и запрещена арифметика указателей.
Теперь программисты в принципе не могут стереть данные из памяти вследствие неправильного использования указателей.
Исключена возможность перепутать оператор присваивания с оператором
сравнения на равенство.
Теперь нельзя даже скомпилировать выражение if(ntries=3) . . . (программисты на языке Visual Basic могут вообще не заметить здесь никакой проблемы, поскольку эта ошибка — источник большинства недоразумений в языках С и C++).
Исключено множественное наследование. Оно заменено новым понятием — интерфейсом, позаимствованным из языка Objective С.
Интерфейс дает программисту почти все, что тот может получить от множественного наследования, избегая при этом сложностей, возникающих при управлении иерархиями классов. (Если понятие множественного наследования вам незнакомо, обратитесь к главе 5.)
Спецификации языка Java открыты. Их можно найти на Web-странице http://java.sun.com.docs/books/jls/html/index.html.
Характерные особенности языка Java
Авторы языка Java написали руководство, в котором объяснялись цели его разработки и достоинства языка. В этом документе приведено одиннадцать характерных особенностей языкаJava.
Простой
Машинокезависимый
Объектно-ориентированный
Интерпретируемый
Распределенный
Высокопроизводительный
Надежный
Многопоточный
Безопасный
Динамичный
Не зависящий от архитектуры компьютера
В последнем разделе мы уже коснулись некоторых из этих пунктов. В этом разделе мы:
приведем цитаты из руководства по языку Java, раскрывающие особенности языка;
поделимся с читателями соображениями по поводу отдельных свойств языка, основываясь на собственном опыте работы с его последней версией.
Руководство по языку Java можно найти по адресу:
http://java.sun.com/doc/language_environment.
Простой
Мы, хотели создать систему, которая легко программируется, не требует дополнительного обучения и учитывает сложившуюся практику и стандарты программирования. Поэтому, несмотря на то, что мы. считали язык C++ неподходящим для этих целей, язык Java был разработан максимально похожим на него, чтобы сделать систему более доступной.
В языке Java нет многих редко используемых, малопонятных и невразумительных средств языка C++, которые, по нашему мнению, приносят больше вреда, чем пользы.
Синтаксис языка Java, по существу, представляет собой очищенный вариант синтаксиса языка C++. В этом языке нет заголовочных файлов, арифметики указателей (и самих указателей), структур, объединений, перегрузки операторов, виртуальных базовых классов и т.п. (Различия между языками Java и C++ описываются в замечаниях о языке C++, разбросанных по всей книге.) Однако разработчики не стремились исправить все недостатки языка C++. Например, синтаксис оператора switch в языке Java остался неизменным. Зная язык C++, перейти к синтаксису языка Java будет легко.
Если обычно вы используете визуальную среду программирования (например Visual Basic), язык Java покажется вам сложным. Его синтаксис часто выглядит довольно странным (хотя понять смысл выражения не составляет труда). Важнее то, что при работе на языке Java приходится намного больше программировать. Прелесть языка Visual Basic заключается в том, что его визуальная среда программирования позволяет почти автоматически создавать инфраструктуру приложения. Чтобы достичь того же результата с помощью языка Java, необходимо программировать вручную, но при этом получаются намного более короткие программы. Существует, однако, и третья разновидность сред программирования, позволяющих создавать программы с помощью технологии "перетащить-и-опустить" ("drag-and-drop").
Другой аспект простоты - краткость. Одна из целей языка Java - обеспечить разработку программ, которые можно было бы совершенно самостоятельно выполнять на небольших машинах. Размер основного интерпретатора и средств поддержки классов составляет около 40 Кбайт; стандартные библиотеки и средства поддержки потоков (особенно авто номное микроядро (self-contained microkernel)) занимают еще 175Кбайт.
Это огромный успех. Заметим, однако, что библиотеки средств поддержки графического пользовательского интерфейса значительно крупнее.
Объектно-ориентированный
Попросту говоря, объектно-ориентированное программирование - это метод программирования, в центре внимания которого находятся данные (т.е. объекты) и средства доступа к ним. Проводя аналогию со столярным делом, можно сказать, что “объек тно-ориентирова тый мастер в основном сосредоточен на стуле, который он изготавливает, и лишь во вторую очередь его интересуют инструменты, необходимые для этого; в то же время "не объектно-ориентированный столяр думает лишь о своих инструментах. Объектно-ориентированные свойства языке Java и C++, по существу, совпадают.
Объектная ориентация за прошедшие 30 лет уже доказала свою ценность, и без нее невозможно представить себе современный язык программирования. Действительно, объектно-ориентированные особенности языка Java сравнимы с языком C++. Основное различие между ними заключается в механизме множественного наследования, для которого в языке Java найдено лучшее решение, а также в модели метаклассов языка Java. Механизмы отражения (глава 5) и сериализации объектов (глава 12) позволяют реализовать устойчивые объекты и средства для создания графических пользовательских интерфейсов на основе готовых компонентов.
DDЕсли вы никогда не программировали на объектно-ориентированных языках, внимательно изучите главы 4-6. В этих главах излагаются основы объектноориентированного программирования и показываются его преимущества при разработке сложных проектов над такими традиционными, процедурно-ориентированными языками, как язык С или Basic.
D
D
Распределенный
ЯзыкJava обладает большой библиотекой программ для передачи данных на основе таких протоколов TCP/IP (Transmission Control Protocol/Intemet Protocol - протокол управления передачей/Интернет-протокол), как HTTP (Hypertext Transfer Protocol протокол передачи гипертекстов) или FTP (File Transfer Protocol - протокол передачи файлов). Приложения, написанные на языкеJava, могут открывать объекты и получать к ним доступ через сеть с помощью URL-адресов (Uniform Resource Location - универсальный адрес ресурса) так желегко, как ILв локальной сети.
Язык Java предоставляет мощные и удобные средства для работы в сети. Каждый, кто когда-либо пытался писать программы для работы в Интернет на других языках, будет приятно удивлен тем, как легко решаются на языке Java самые трудные задачи, например, открытие сетевых соединений (sockets connection). Элегантный механизм, состоящий из так называемых сервлетов (servlets), делает работу на сервере чрезвычайно эффективной. Сервлеты поддерживаются многими популярными Web- серверами. (Работа в сети будет описана во втором томе.) Связь между распределенными объектами в языке Java обеспечивается механизмом вызова удаленных методов (эта тема также раскрывается во втором томе).
Надежный
Язы Java предназначен для создания программ, которые должны надежно работать в любых ситуациях. Основное внимание в языке Java уделяется раннему обнаружению возможных ошибок, динамической проверке (во время выполнения программы), а также исключению ситуаций, подверженных ошибкам... Единственное значительное отличие языка Java от языка C++ заключается в модели указателей, принятой в языке Java, которая исключает возможность перезаписи участка памяти и повреждения данных.
Это свойство также очень полезно. Компилятор языка Java выявляет такие ошибки, которые в других языках обнаруживаются только на этапе выполнения программы. Кроме того, программисты, потратившие многие часы на поиски ошибки, вызвавшей повреждение памяти из-за неверного указателя, будут очень рады тому, что в языке Java такие проблемы возникнуть в принципе не могут.
Если раньше вы программировали на языках Visual Basic или COBOL, в которых указатели явно не используются, возможно, вам непонятно, почему это настолько важно. Программистам на языке С повезло намного меньше. Им нужны указатели для доступа к строкам, массивам, объектам и даже файлам. При программировании на языке Visual Basic ничего этого не требуется, и программист может не беспокоиться о распределении памяти для этих сущностей. С другой стороны, многие структуры данных в языке, не имеющем указателей, реализовать очень трудно. Для обычных структур, вроде строк и массивов, указатели не нужны. Вся мощь указателей проявляется лишь там, где без них нельзя обойтись, например, при создании связанных списков. Программист на языке Java навсегда избавлен от неверных указателей, неправильного распределения и утечки памяти.
Безопасный
Язык Java предназначен для использования в сетевой гаи распределенной среде. По этой причине большое внимание было уделено безопасности. Язык Java позволяет создавать системы, защищенные от вирусов и постороннего вмешательства.
В первом издании мы написали: "Никогда не говори никогда",— и оказались правы. Группа экспертов по вопросам безопасности из Принстонского университета обнаружила первые ошибки в системе защиты версии Java 1.0 вскоре после появления в продаже первой версии набора инструментальных средств JDK. Более того, и они, и другие специалисты продолжали и впоследствии находить все новые и новые ошибки в механизмах безопасности всех последующих версий языка Java. Мнения независимых экспертов о современных механизмах безопасности, предусмотренных в языке Java, можно найти по URL- адресу Принстонской группы (http://www.cs.princeton.edu/sip/), а также на Web- странице сотр. risks. Положительной стороной этой ситуации является то, что группа разработчиков языка Java заявила о своей полной нетерпимости к любым ошибкам в системе защиты и немедленно приступила к исправлению всех проблем, обнаруженных в механизме безопасности аплетов. В частности, опубликовав внутренние спецификации интерпретатора языка Java, компания Sun намного облегчила поиск скрытых ошибок в системе безопасности и привлекла к их поиску независимых специалистов. Это повысило
вероятность того, что все ошибки в системе защиты будут вскоре обнаружены. В любом случае обмануть систему защиты языка Java чрезвычайно трудно. Обнаруженные до сих пор ошибки были почти неуловимыми, ктому же их количество (относительно) невелико.
Web-страница компании Sun, посвященная вопросам безопасности, имеет следующий URL-адрес: http://java.sun. com/sfaq/.
Перечислим некоторые ситуации, возникновение которых предотвращает система безопасности языка Java.
Переполнение стека выполняемой программы, к которюму приводил печально известный "червь", распространявшийся в Интернет.
Повреждение участков памяти, находящихся за пределами пространства, выделенного процессу.
Считывание и запись локальных файлов при использовании безопасного загрузчика классов, например Web-броузера, который запрещает такой доступ к файлам.
Все эти меры безопасности вполне уместны и обычно работают безупречно, однако осмотрительность никогда не повредит. Хотя обнаруженные к данному моменту ошибки были далеко не тривиальными, и все детали их поиска часто хранятся в секрете, следует признать, что доказать безопасность языкаJava, вероятно, псе же невозможно.
Со временем в язык были добавлены новые средства защиты. Начиная с версии 1.1, в языке Java появилось понятие классов с цифровой подписью (см. том 2). Пользуясь классом с цифровой подписью, вы можете быть уверенным в его авторе. Если вы ему доверяете, то можете предоставить этому классу все привилегии, доступные на вашей машине.
Альтернативный механизм доставки кода, предложенный компанией Microsoft, опирается натехнологиюActiveXидля безопасности использует только цифровые подписи. Очевидно, чтоэтого недостаточно —любойпользовательпрограммного обеспечения фирмы Microsoft может подтвердить, что программы широко известных производителей часто завершаются аварийно, создавая тем самым опасность повреждения данных. Система безопасности в языке Java намного надежнее, чем технология ActiveX, поскольку она контролирует приложение с момента его запуска и не позволяет ему причинятьущерб.
Не зависящий от архитектуры
Компилятор генерирует объектный файл, формат которого не зависит от архитекторы компьютера, — скомпилированная программа может выполняться на любых процессорах под управлением системы выполнения программ языка Java. Для этого компилятор языка Java генерирует команды, байт-кода, не зависящие от конкретной архитектуры компьютера. Байткод разработан таким образом, чтобы на любой машине его можно было легко интерпретировать либо на лету перевести в машиноза висимый код.
Это не новая идея. Более 20 лет назад и в системе реализации языка Pascal, разработанной Никлаусом Виртом (Niclaus Wirth), и в системе UCSD Pascal применялась та же самая технология. Использование байт-кодов дает большой выигрыш при выполнении программы (правда, синхронная компиляция во многих случаях его компенсирует). Разработчики языка Java прекрасно справились с разработкой набора команд байт-кода, которые отлично работают на большинстве современных компьютеров, легко транслируясь в реальные машинные команды.
Машинонезависимый
В отличие от языков С и C++, в спецификации Java нет аспектов, зависящих от системы реализации. И размер основных типов данных, и арифметические операции над ними точно определены.
Например, тип int в языке Java всегда означает 32-разрядное целое число. В языках С и C++ тип int может означать как 16-разрядное, так и 32-разрядное целое число, а также целое число произвольного размера, по выбору разработчика конкретного компилятора. Единственное ограничение заключается в том, 'что размер типа int не может быть меньше размера типа short int и больше размера типа long int. Фиксированный размер числовых типов позволяет избежать многих неприятностей, связанных с выполнением программ на разных компьютерах. Бинарные данные хранятся и передаются в фиксированном формате, что также позволяет избежать недоразумений, связанных с разным порядком записи байтов на разных платформах (конфликт "big endian/little endian"). Строки сохраняются в стандартном формате Unicode.
Библиотеки, представляющие собой часть системы, определяют машинонезависимый интерфейс. Например, в языке предусмотрен абстрактный класс Window и его реализации для операционных систем Unix, Windows и Macintosh.
Каждый, кто когда-либо пытался написать программу, которая одинаково хорошо работала бы под управлением операционных систем Windows, Macintosh и десяти разновидностей системы Unix, знает, что это очень трудная задача. Версия Java 1.0 предприняла героическую попытку решить эту проблему, предоставив простой инструментальный набор, адаптирующий обычные элементы пользовательского интерфейса к большому количеству программных платформ. К сожалению, библиотека, на которую было затрачено немало труда, не позволила достичь приемлемых результатов на разных платформах. (При этом на разных платформах в графических программах проявлялись разные ошибки.) Однако это было лишь началом. Во многих приложениях машинная независимость намного важнее изысканности графического пользовательского интерфейса. Именно эти приложения выиграли от появления версии Java 1.0. Однако теперь инструментальный набор для создания графического пользовательского интерфейса полностью переработан и больше не зависит от интерфейса пользователя на главном компьютере. Новая версия более осмысленна и, по нашему мнению, более привлекательна для пользователя, чем предыдущие.
Интерпретируемый
Интерпретатор языка Java может пересылаться на любую машину и выполнять байт-код непосредственно на ней. Поскольку редактирование связей - более легкий процесс, разработка программ может стать намного быстрее и эффективнее.
Возможно, это дает преимущество при разработке приложений, однако приведенная цитата — явное преувеличение. В любом случае компилятор языка Java, входящий в набор инструментальных средств JSDK (Java Software Development Kit), работает довольно медленно. (Некоторые компиляторы, принадлежащие к третьей разновидности, например, компиляторы компании IBM, работают намного быстрее.) Скорость перекомпиляции — это всего лишь один из факторов', характеризующих эффективность среды программирования. Сравнив скорость работы сред программирования на языке Java и языке Visual Basic, вы, возможно, будете разочарованы.
Высокопроизводительный
Хотя обычно интерпретируемые байт-коды имеют более чем достаточную производительность, бывают ситуации, в которых требуется еще более высокая эффективность. Байт-коды можно "на лету" (во время выполнения) транслировать в машинные коды для конкретного процессора, на котором выполняется данное приложение.
Если для выполнения байт-кодов применяется интерпретатор, не следует употреблять словосочетание "высокая производительность". Однако на многих платформах возможен другой вид компиляции, обеспечиваемый синхронными компиляторами (just-in-time compilers—JIT). Они транслируют байт-код в машинозависимый код, сохраняют результат в памяти, а затем вызывают его при необходимости. Поскольку при этом интерпретация выполняется только один раз, этот подход во много раз увеличивает скорость работы. Несмотря на то что синхронные компиляторы все равно медлительнее, чем машинозависимые компиляторы, они во всяком случае работают намного быстрее интерпретаторов, обеспечивая для некоторых программ 10- и даже 20-кратное ускорение. Эта технология постоянно совершенствуется и в конце концов может достичь той скорости, которую никогда не превзойдут традиционные компиляторы. Например, синхронный компилятор может определить, какой фрагмент кода выполняется чаще, и оптимизировать его по скорости выполнения.
МНОГОПОТОЧНЫЙ
Многопоточность обеспечивает лучшую интерактивность и выполнение программы.
Если вы когда-либо пытались организовать многопоточные вычисления на каком- нибудь еще языке программирования, вы будете приятно удивлены тем, как это легко сделать на языке Java. Потоки в языке Java могут использовать преимущества многопроцессорных систем, если операционная система позволяет это сделать. К сожалению, реализации потоков на большинстве платформ сильно отличаются друг от друга, а разработчики языка Java не предпринимают никаких попыток достичь единооб-
разия. Только код для вызова потоков остается одинаковым для всех машин; язык Java перекладывает реализацию МНОГОПОТОЧНОСТИ на базовую операционную систему или библиотеку потоков. (Потоки описываются во втором томе.) Несмотря на это, именно легкость организации многопоточных вычислений делает язык Java таким привлекательным для разработки программного обеспечения серверов.
Динамичный
Во многих отношениях язык Java является более динамичным, чем языки С или C++. Он был разработан так, чтобы легко адаптироваться к постоянно изменяющейся среде. В библиотеки можно свободно добавлять новые методы и объекты, не причиняя никакого вреда. Язык Java позволяет легко получать информацию о ходе выполнения программы.
Это очень важно в тех случаях, когда требуется добавить код в уже выполняемую программу. Ярким примером этого является код, который загружается из Интернет для выполнения броузером. В версии Java 1.0 получить информацию о ходе выполняемой программы было совсем не легко, однако нынешняя версия языкаJava раскрывает перед программистом как структуру, так и поведение объектов выполняемой программы. Это весьма ценно для систем, которые должны анализировать объекты в ходе выполнения программы. К таким системам относятся средства создания графического пользовательского интерфейса, интеллектуальные отладчики, сменные компоненты и объектные базы данных.
Язык Java и Интернет
Идея проста— пользователи загружают байт-коды языка Java из Интернет и выполняют их на своих машинах. Программы Java, работающие под управлением Web-броузеров, называются атигтачи. Для использования аплета нужен Web-броузер, поддерживающий язык Java и способный интерпретировать байт-коды. Лицензия на исходные коды языка Java принадлежит компании Sun, настаивающей на неизменности как самого языка, так и структуры его основных библиотек. К сожалению, в реальности все не так. Разные версии броузеров Netscape и Internet Explorer поддерживают разные версии языка Java, причем некоторые из этих версий значительно устарели. Эта достойная сожаления ситуация создает все больше препятствий при разработке аплетов, позволяющих использовать преимущества последней версии языка Java. Чтобы решить эту проблему, компания Sun разработала программу Java Plug-in, позволяющую создавать наиболее современную среду для запуска программ на языке Java на основе броузеров Netscape и Internet Explorer (глава 10).
Загрузка аплета напоминает внедрение изображения в Web-страницу. Лплет становится частью страницы, а текст обтекает занятое им пространство. Однако отличие заключается в том, что изображение теперь является живым (alive). Оно реагирует на команды пользователя, изменяет свой внешний вид и обеспечивает пересылку данных между компьютером, на котором просматривается аплет, и компьютером, управляющим этим аплетом.
Загрузка аплета напоминает вставку рисунка в Web-страницу. Аплет становится частью страницы, а текст обтекает занимаемое им место. Дело в том, что изображение является “живым". Оно реагирует на команды пользователя, изменяет свой внешний вид и выполняет пересылку данных между компьютером, на котором выполняется аплет, и компьютером, управляющим этим аплетом.
На рис. 1.1 показан хороший пример динамической Web-страницы, выполняющей сложные вычисления и применяющей аплет для изображения молекул. Чтобы лучше понять структуру молекулы, можно вращать ее либо изменять масштаб изображения, используя мышь. Такие манипуляции нельзя реализовать на статических Web- страницах, однако аплеты делают это возможным. (Этот аплет можно найти по адресу http :
Аплет Jmol
С помощью аплетов на Web-страницу можно добавлять новые кнопки и текстовые поля. Однако такие аплеты медленно загружаются по телефонной линии. Почти то же самое можно сделать с помощью языка Dynamic HTML, форм языка HTML (Hypertext Markup Language — язык разметки гипертекстов) или языка сценариев, например, языка JavaScript. Разумеется, первые аплеты предназначались для анимации: вращающиеся глобусы, танцующие персонажи мультфильмов, вычурные тексты и т.п. Однако большинство из перечисленного могут делать и анимированные GIF-файлы, а язык Dynamic HTML в сочетании с разработкой сценариев делает намного больше, чем аплеты.
В результате несовместимости броузеров и несогласованности процесса загрузки через медленные сетевые соединения аплеты, предназначенные для Web-страниц, не стали огромным достижением. В локальных сетях (intranets) ситуация совершенно иная. В них обычно не возникают проблемы, связанные с пропускной способностью канала, поэтому время загрузки аплетов несущественно. В локальной сети можно выбирать нужный броузер или применять программу Java Plug-In. Сотрудники не могут переместить программу, доставленную через сеть, в неправильное место или неверно ее установить, а системному администратору не нужно обходить все клиентские машины и обновлять на них программы. Большое количество программ, предназначенных для учета товаров, планирования отпуска, возмещения транспортных расходов и тому подобного, были разработаны многими корпорациями в виде аплетов, использующих броузеры.
Пока мы писали книгу, маятник вновь качнулся от клиентских программ в сторону программирования серверных приложений (server-sideprogramming). Вчас гности, прикладные серверы (application servers) могут использовать мониторинговые возможности вир туальной машины языкаJava для автоматического выравнивания нагрузки, объединения связей с базой данных, синхронизации объектов, безопасного завершения работы и повторной загрузки, а также для выполнения других процессов, необходи лых для масштабируемых серверных приложений, которые почти невозможно корректно реализовать. Таким образом, программисты, создающие приложения, получили возможность купить эти сложные механизмы, вместо того, чтобы разрабатывать их самосто ятельно. Это повысило производительность труда программистов — они сосредоточились на логике своих программ, не отвлекаясь на детали, связанные с работой серверов.
Краткий курс истории языка Java
В этом разделе кратко описывается история языка Java. В основу этого раздела положены различные опубликованные материалы (в основном, интервью с создателями языкаJava в июльском выпуске электронного журнала Sun World за 1995 год).
История Java восходит к 1991 году, когда группа инженеров из компании Sun под руководством Патрика Нотона (Patrick Naughton) и члена Совета директоров (и разностороннего компьютерного волшебника) Джеймса Гослинга (James Gosling) занялась разработкой небольшого языка, который можно было бы использовать для программирования бытовых устройств, например, контроллеров для переключения каналов кабельного телевидения (cable TV switchboxes). Поскольку такие устройства не потребляют много энергии и не имеют больших микросхем памяти, я ЗЫК должен был быть маленьким и генерировать очень компактные программы. Кроме того, поскольку разные производители могут выбирать разные центральные процессоры (Central Processor Unit— CPU), было важно не завязнуть в какой-то одной архитектуре компьютеров. Проект получил кодовое название "Green".
Стремясь изобрести небольшой, компактный и машийонезависи лый код, разработчики возродили модель, использованную при реализации первых версий языка Pascal на заре эры персональных компьютеров. Никлаус Вирт, создатс ль языка Pascal, в свое время разработал машинонезависимый язык, генерирующий промежуточный код для некоей гипотетической машины. Этот язык стал коммерческим продуктом под названием UCSD Pascal. (Такие гипотетические машины часто называются виртуальными— например, виртуальная машина языкаJava, или JVM.) Этот промежуточный код можно выполнять на любой машине, имеющей соответствующий интерпретатор. Инженеры, работавшие над проектом "Green", также использовали виртуальную машину, что решило их основную проблему.
Однако большинство сотрудников компании Sun имели опыт работы с операционной системой UNIX, поэтому в основу разрабатываемого ими языка бь л положен язык C++, а не Pascal. В частности, они сделали язык объектно-, а не процедурноориентированным. Как сказал Гослинг в своем интервью: “Язык — это всегда средство, а не цель". Сначала Гослинг решил назвать его "Oak" ("Дуб"). (Возможно потому, что он любил смотреть на дуб, растущий прямо под окнами его офиса в компании Sun.) Потом сотрудники компании Sun узнали, что слово Oak уже используется в качестве имени ранее созданного языка программирования, и изменили название наJava.
В 1992 году в рамках проекта Green была выпущена первая продукция, названная "*7". Это было средство для чрезвычайно интеллектуального дистанционного управ-
ления. (Оно имело мощность рабочей станции SPARK, помещаясь в коробочке размером 6x4x4 дюйма.) К сожалению, ни одна из компаний— производителей электронной техники не заинтересовалась этим изобретением. Затем группа стала заниматься разработкой устройства для кабельного телевидения, которое могло бы осуществлять новые виды услуг, например, включать видеосистему по требованию. И снова они не получили ни одного контракта. (Забавно, что одной из компаний, отказавшихся подписать с ними контракт, руководил Джим Кларк (Jim Clark) — основатель компании Netscape, впоследствии сделавшей очень много для успеха языка Java.)
Весь 1993 год и половину 1994 года продолжались безрезультатные поиски покупателей продукции, разработанной в рамках проекта "Green" (под новым названием "First Person, Inc."). (Патрик Нотой, один из основателей группы, впоследствии в основном занимавшийся маркетингом, налетал в общей сложности более 300 тысяч миль, пытаясь продать разработанную технологию.) Проект "First Person, Inc." был прекращен в 1994 году.
Тем временем в рамках Интернет разрасталась сеть World Wide Web. Ключом к этой сети является броузер, превращающий гипертекст в изображек ие на экране. В 1994 году большинство людей пользовалось броузером Mosaic, некоммерческим Web-броузером, разработанным в суперкомпьютерном центре Университета штата Иллинойс (University of Illinois) в 1993 году. (Частично этот броузер был написан Марком Андреес- сеном (Mark Andreessen) за 6,85 доллара в час. В то время Марк заканчивал университет и броузер был его дипломной работой. Затем он стал одним из основателей и главным программистом компании Netscape, и к нему пришли слава и богатство.)
В своем интервью журналу Sun World Гослинг сказал, что в середине 1994 года разработчики языка поняли: "Нам нужно создать действительно крутой броузер. Такой броузер должен представлять собой одно из немногих приложений модной клиент- серверной технологии, в которой жизненно важным было бы именно то, что мы сделали: архитектурная независимость, выполнение в реальном времени, надежность, безопасность — вопросы, не являвшиеся чрезвычайно важными для рабочих станций. И мы создали такой броузер".
На самом деле броузер был разработан Патриком Нотоном и Джонатаном Пэйном (Johnatan Payne). Позднее он превратился в современный броузер Hotjava. Этот броузер был написан на языке Java, чтобы продемонстрировать всю его мощь. Однако разработчики не забывали о мощных средствах, которые теперь называются аплетами, наделив свой броузер способностью выполнять код внутри Web-страниц. "Демонстрация технологии" была представлена на выставке SunWorld '95 23 мая 1995 года и вызвала всеобщее помешательство на почве языкаJava, продол жающеес я и поныне.
Компания Sun выпустила первую версию языка Java в начале 1996 года. Через несколько месяцев после нее появилась версия Java 1.02. Люди быстро поняли, что версия Java 1.02 не подходит для разработки серьезных приложений. Конечно, эту версию можно применять для разработки Web-страниц с пляшущими человечками, однако в версии Java 1.02 ничего нельзя даже напечатать. Честно говоря, версия Java 1.02 была еще сырой. Ее преемница, версия Java 1.1, заполнила большинство зияющих провалов, намного улучшив возможность отражения и добавив новую модель событий для программирования графического пользовательского интерфейса. Несмотря на это, она все еще была довольно ограниченной.
Выпуск версии Java 1.2 стал основной новостью конференцииJavaOne в 1998 году. В новой версии слабые средства для создания графического пользовательского интерфейса и графических приложений были заменены сложным и масштабным инст-
рументарием. Это был шаг вперед, к реализации лозунга "Write Once, Run Anywhere" ("Один раз напиши — и везде выполняй"), выдвинутого при разработке предыдущих версий. В декабре 1998 года через три дня (!) после выхода в свет название новой версии было изменено на громоздкое словосочетание Java 2 Standart Edition Software Development Kit Version 1.2 (Стандартное издание пакета инструментальных средств для разработки программного обеспечения на языкеJava 2, версия 1.2).
Кроме стандартного издания пакета ("Standart Edition") были предложены еще два варианта: "микроиздание" ("Micro Edition") для портативных устройств, например, для мобильных телефонов, и. "промышленное издание" ("Enterprise Edition") ДЛЯ создания сер верных приложений. В нашей книге в центре внимания находится стандартное издание.
Версии 1.3 и 1.4 стандартного издания пакета инструментальных средств намного совершеннее первоначального выпуска языка Java 2. Они обладают новыми возможностями и, разумеется, содержат намного меньше ошибок. В табл. 1 1 показан стремительный рост объема библиотеки API по мере появления новых версий стандартного издания пакета SDK.
Версия
Количество классов и интерфейсов
Количество методов и полей
1.0
212
2125
1.1
504
5478
1.2
1781
20935
1.3
2130
23901
1.4
3020
32138
Распространенные заблуждения относительноязыкаJava
В этом разделе перечислены и прокомментированы некоторые из распространенных заблуждений, касающихся языкаJava.
Язык Java - это расширение языка HTML.
Java— это язык программирования, а язык HTML— это способ описания структуры Web-страниц. Между ними нет ничего общего, за исключением расширений языка HTML, позволяющих размещать на Web-страницах аплеты, написанные на языке Java.
Яработаю на языке XML, поэтомумне не нуженязык Java.
Язык Java— это язык программирования, а язык XML— просто способ описания данных. Данные, описанные с помощью языка XML, можно обрабатывать с помощью программ, написанных на любом языке программирования, но лишь библиотека API языка Java содержит превосходные средства поддержки для обработки таких данных. Кроме того, в языке Java реализованы многие возможности языка XML. Более подробно они описаны во втором томе.
ЯзыкJava легко выучить.
Нет ни одного языка программирования, столь же мощного, как язык Java, который можно было бы легко выучить. Игрушечные программы писать довольно просто,
однако выполнять серьезную работу всегда трудно. Кроме того, обратите внимание на то, что в этой книге обсуждению собственно языка Java посвящено только четыре главы. В остальных главах объясняется, как работать с его библиотеками, содержащими тысячи классов и интерфейсов, а также многие тысячи функций. К счастью, знать каждую из них не обязательно, однако для выполнения реального проекта от пользователя требуется на удивление много знаний.
На языке Java легко программировать.
Набор инструментальных средств Java SDK использовать нелегко всем, за исключением программистов, привыкших к режиму командной строки. Существуют интегрированные среды программирования, включающие текстовые редакторы, компиляторы, средства для создания форм с помощью технологии "drag-and-drop", однако для новичка они выглядят слишком сложными и устрашающими. Кроме того, они часто генерируют программы, состоящие из сотен строк. Нам кажется, что начинать изучение языка Java по длинным программам, сгенерированным автоматически и заполненным комментариями типа "РУКАМИ НЕ ТРОГАТЬ!", — не слишком удачная идея. Мы полагаем, что лучше всего изучать язык Java, пользуясь привычным текстовым редактором. Именно так мы и поступим.
Язык Java со временем станет ^HueepcanwNMязыком программирования для всех платформ.
Теоретически это возможно. Именно об этом мечтают все производители программного обеспечения, кроме компании Microsoft. Однако есть много приложений, прекрасно работающих на персональных компьютерах, которые не будут так же хорошо работать на других устройствах или под управлением броузера. Кроме того, эти приложения написаны так, чтобы максимально использовать возможности процессоров и машинозависимых библиотек. В любом случае они уже перенесены на все важные платформы. К таким приложениям относятся текстовые и графические редакторы, а также Web-броузеры. Обычно эти приложения пишутся на языках С или C++, и пользователь ничего не выиграет, если переписать их на языке Java. Помимо всего прочего, после переписывания на языке Java эти программы станут более медленными и менее эффективными, по крайней мере, в ближайшем будущем.
Язы Java — это просто еще один язык программирования.
Java— прекрасный язык программирования. Многие программисты отдают предпочтение именно ему, а не языкам С или C++. Однако в мире существуют сотни прекрасных языков программирования, так никогда и не получивших широкого распространения, в то время как языки с очевидными недостатками, такие как C++ и Visual Basic, достигли ошеломительных успехов.
Почему? Успех любого языка программирования в основном определяется практичностью его системы поддержки (support system), а не элегантностью его синтаксиса. Существуют ли полезные, удобные и стандартные библиотеки, позволяющие сделать именно то, что необходимо программисту? Разработана ли удобная среда для создания и отладки программ? Интегрирован ли язык и его инструментарий в компьютерную инфраструктуру? Язык Java достиг успехов в области серверных приложений, поскольку его библиотеки классов позволяют легко сделать ТО что раньше было трудным, например, поддерживать работу в сети и многопоточность. Тот факт, что язык Java уменьшил количество ошибок, связанных с указателями, также говорит в его пользу. Благодаря этому производительность труда программистов повысилась. Однако не в этом кроется причина его успеха.
Программы на языке Java интерпретируются, значит, серьезные приложения будут слишком медленно выполняться на конкретной платформе.
Многие программы затрачивают большую часть времени на взаимодействие с пользовательским интерфейсом или ожидание данных из сети. Все программы, независимо от того, на каком языке они написаны, должны реагировать на щелчок мыши за определенное время. Разумеется, не следует выполнять задачи, требующие высокой производительности центрального процессора, с помощью интерпретатора языка Java. Однако на платформах, допускающих синхронную компиляцию, нужно лишь запустить байт-коды на выполнение, и большинство вопросов просто отпадут. В конце концов язык Java отлично подходит для разработки сетевых программ. Опыт показывает, что он легко поддерживает высокую скорость передачи данные в сети даже во время таких интенсивных вычислений, как шифрование. Поскольку скорость работы на языке Java выше скорости передачи данных в сети, становится совершенно неважным, что программы на языке C++ могут работать еще быстрее. На языке Java легче программировать, а программы, написанные на нем, машинонезависи мы.
Все программы на языке Java выполняются под управлением УУеЬЛроузеров.
Все аплеты, написанные на языке Java, действительно ВЫПОЛНЯЮТСЯ под управлением Web-броузеров. Собственно, это и есть определение аплета— программа, выполняемая Web-броузером. Однако вполне возможно и уместно создавать с. смостоятел ьн ые программы на языке Java, которые выполняются независимо от Web-бр эузера. Эти программы (обычно называемые приложениями) являются полностью маш инонезависимыми. Просто берите программу и выполняйте ее на другой машине! Поскольку язык Java более удобен и менее подвержен ошибкам, чем язык C++, он представляет собой хороший выбор. В сочетании со средствами доступа к базам данных, например, пакетом Java Database Connectivity (см. том 2), язык Java становится просто неотразимым. Особенно удобно его изучать в качестве первого языка программирования.
Большинство программ в этой книге являются абсолютно самостоятельными. Конечно, аплеты очень забавны. Однако приложения на языке Java на практике более важны и полезны.
Аплеты на языке Java предстпвляют собой большую опасность для систем bi защиты.
Было опубликовано несколько отчетов об ошибках в системе безопасности языка Java. Большинство из них касалось реализации языка Java с помощью конкретного броузера. Исследователи восприняли это как вызов и принялись искать лазейки в системе защиты языка Java, чтобы преодолеть силу и сложность моде/ и безопасности аплетов. Обнаруженные технические ошибки вскоре были исправлены, и, насколько мы знаем, ни одна реальная система никогда не была скомпрометирована. Чтобы оценить важность этого факта, вспомните о миллионах вирусных атак на выполняемые файлы операционной системы Windows и макросы текстового процессора Word, действительно вызвавшие немало бед, и удивительно беззубую критику слабостей атакованной платформы. Кроме того, механизм ActiveX в броузере Internet Explorer мог бы вызвать много нареканий, однако способы его взлома настолько очевидны, что лишь немногие специалисты потрудились опубликовать свои изыскания.
Некоторые системные администраторы даже стали выключать системы защиты языка Java в своих броузерах, чтобы пользователи могли, как и прежде, загружать исполняемые файлы, элементы управления ActiveX и документы, созданные с помощью текстового процессора Word. Забавно, что в настоящее время вероятность преодоления системы защиты аплетов на языке Java сравнима с вероятностью погибнуть
в авиационной катастрофе, в то время как риск заразить компьютер, открыв документ, созданный текстовым процессором Word, сравним с риском погибнуть под колесами автомобиля, перебегая дорогу в час пик.
Язык JavaScript — упрощенная версия языка Java.
Язык JavaScript — это язык разработки сценариев, который можно использовать на Web-страницах. Он был разработан компанией Netscape и сначала назывался LiveScript. Синтаксис языка JavaScript напоминает синтаксис языка Java, однако на этом их сходство заканчивается (за исключением имени, конечно). Подмножество языка JavaScript было стандартизовано под именем ЕСМА-262, однако его расширения, необходимые для реальной работы, стандартизованы не были. В результате программа на языке JavaScript, которую можно было бы выполнять как под управлением броузеров компании Netscape, так и с помощью броузера Internet Explorer, осталась несбыточной мечтой.
Для разработки сценариев графического пользовательского интерфейса нужно использовать язык Java, а не Perl.
Это правда лишь наполовину. Для разработки серверных приложений больше не следует применять не только язык Perl, но и сами сценарии графических пользовательских интерфейсов (CGI-сценарии). Сервлеты языка Java превосходно справляются с решением этой задачи. Они работают намного эффективнее, чем CGI-сценарии, и для их реализации можно использовать реальный язык программирования — язык Java.
Язы Java совершит революцию в области клиент-серверных приложений.
Это возможно. Именно в этой области язык Java особенно полезен. Некоторые серверные приложения, например BEA Weblogic, полностью встроены в ЯЗЫКJava,
ЯзыкJava отменит компонентную модель вычислений.
Вряд ли найдутся хотя бы два человека, у которых совпали бы мнения о компонентах. В качестве визуальных компонентов управления в версии Java 1.1 были предложены функциональные компоненты JavaBeans (см. том 2), аналогичные компонентам ActiveX, использующимся для создания графического пользовательского интерфейса. Функциональные компоненты JavaBeans позволяют делать то же самое, что и компоненты ActiveX, за исключением того, что они автоматически являются кросс- платформенными. С точки зрения серверных приложений промышленные компоненты, предназначенные для повторного использования, легко применять при разработке широкого круга серверных приложений. Возможно, в будущем возникнет рынок этих компонентов, аналогичный рынку компонентов ActiveX в мире Windows.
Пользуясь языком Java, можно заменить компьютер “Интернет-устройством’' стоимостью 500 долларов.
Некоторые специалисты уверены, что это непременно случится. Нам кажется абсурдным думать, что домашние пользователи могут заменить мощные и удобные персональные компьютеры ограниченными машинами без жестких дисков. Однако сетевой компьютер, оснащенный средствами языка Java, предоставляет вполне реальную возможность реализовать "нулевую администраторскую инициативу", направленную на уменьшение стоимости компьютеров, занятых в бизнесе.
Мы рассматриваем Интернет-устройство как дополнение к персональному компьютеру. Если цена позволяет, почему бы не приобрести устройство, поддерживающее работу в Интернет, для просмотра на экране электронной почты и новостей? Ядро языка Java настолько мало, что он является очевидным выбором для такого рода телефонов и других "Интернет-устройств".
Среда программирования
на языке Java
В этой главе...
Инсталляция набора инструментальных средств Java Software Development Kit
Среда разработки программ
Т Использование инструментов в режиме командной строки
Использование интегрированной среды
разработки программ
Компилирование и запуск программ из текстового редактора
Т Графические приложения
Аплеты
этой главе мы научимся инсталлировать набор инструменталы ых средств Java Software Development Kit (SDK), а также компилировать и запускать на выполнение программы различных видов: консольные программы, графические приложения и аплеты. Мы будем использовать средства пакета SDK, набирая команды в окне оболочки. Однако многие программисты предпочитают удобство интегрированной среды разработки программ. Мы покажем читателю, как использовать широко распространенную интегрированную среду разработки программ для компиляции и выполнения программ, написанных на языке Java. Изучать интегрированные среды разработки программ и пользоваться ими довольно легко, однако они долго загружаются и предъявляют высокие требования к ресурсам компьютера. В качестве компромисса можно использовать текстовый процессор, из которого вызывается компиля-
тор и интерпретатор языка Java. В главе описано несколько таких текстовых редакторов. Овладев приемами, рассмотренными в этой главе, и выбрав инструмент для разработки программ, читатель может переходить к главе 3, где и начинается изучение языка программирования Java.
Инсталляция набора инструментальных средств Java Software Development Kit
Наиболее полная и современная версия пакета Java 2 Standart Edition (J2SE) предназначена для операционных систем SOLARIS, Linux и Windows. Существуют также разные версии языка Java для операционных систем Linux, OS/2, Macintosh и многих других платформ.
Читатель, работающий под управлением операционных систем Solaris, Linux или Windows, может загрузить пакет Java Software Development Kit с Web-страницы http://java.sun.com/j2se. Способы инсталляции на разных платформах отличаются друг от друга. На момент написания этой книги инсталляционные пакеты были доступны на следующих Web-страницах:
http://java.sun.com/j2se/l.4/install-solaris.html
http://java.sun.eom/j2se/l.4/install-linux.html
□□http://java.sun.com/j2se/l.4/install-windows.html
□
□
От системы зависят только команды инсталляции и компиляции. Установив набор инструментальных средств, можно делать с ним все, что описано в этой книге. Независимость от операционной системы — основное преимущество языка Java.
При инсталляции пользователю по умолчанию предлагается использовать каталог j2sdkl.4, название которого повторяет название версии пакета JSDK. При желании можно изменить это имя на jdk. Однако если вы приверженец языка Java и коллекционируете разные версии пакета Java SDK, принимайте предложение по умолчанию. В этой книге мы предполагаем, что каталог называется jdk. Например, ссылаясь на каталог jdk/bin, мы имеем в виду каталог bin внутри каталога jdk. Заметим также, что для имен каталогов мы используем стиль операционной системы UNIX. При работе на платформе Windows нужно использовать обратную косую черту, например: с: \jdk\bin.
Задание путей к выполняемым файлам
После инсталляции пакета Java SDK вам нужно сделать еще один шаг: добавить имя каталога jdk/bin в список путей, по которым операционная система может найти выполняемые файлы. Этот шаг в разных системах выполняется по-разному.
На платформе UNIX (включая операционные системы Solaris или Linux) процедура редактирования путей к выполняемым файлам зависит от используемой
оболочки. Если используется оболочка С (принимаемая на платформе Solaris по умолчанию), добавьте строку, приведенную ниже, в конец файл а ~/ . cshrc: set path=(/usr/local/jdk/bin $path)
Если вы используете оболочку Bourne Again (принимаемую по умолчанию на платформе Linux), добавьте строку, приведенную ниже, в конец файла -/.bashrc или ~/.bash_profile:
export PATH=/usr/local/jdk/bin:$PATH
Для других оболочек операционной системы UNIX нужно найти способ выполнить аналогичную процедуру.
На платформе Windows 95/98 добавьте строку, приведенную ниже, в конец файлаAUTOEXEC.BAT:
SET PATH-c:\jdk\bin;%PATH%
Заметим, что вокруг знака нет никаких пробелов. Чтобы эта строка подействовала, нужно перезагрузить компьютер.
На платформе Windows NT/2000 откройте панель управления, выберите пиктограмму System, а затем — вкладку Environment. Прокрутите на экране окно User Variables, пока не найдете переменную PATH. Добавьте имя каталога jdk/bin в начало списка, используя точку с запятой для того, чтобы отделить новый элемент списка от уже существующих, например:
c:\jdk\bin; остальное
Сохраните свои установки. Любое новое консольное окно, которое вы создадите, будет использовать правильный путь к выполняемым файлам.
Вот как можно проверить, правильно ли вы все сделали.
Откройте окно оболочки. Как вы это сделаете, зависит от операционной системы. Наберите следующую строку: java -version
и нажмите клавишу <ENTER>. На экране должно появиться следующее сообщение.
java version "1.4.0"
Java (tm: 2 Runtime Environment, Standard Edition
Java HotSpot (TM) Client VM
Если вместо этого сообщения появляется строка наподобие “javaxommand not found", "Bad command or file name" или "The name specified is nor recognized as an internal or external command, operable program or batch file", нужно вернуться и еще раз проверить, правильно ли выполнена инсталляция.
Инсталляция библиотек и документации
Библиотечные файлы поставляются в пакете Java SDK в виде архива src .jar. Вам следует распаковать этот файл, чтобы получить доступ к исходным текстам программ. Мы настоятельно рекомендуем вам сделать это. Выполните следующие инструкции.
Убедитесь, что пакет Java SDK установлен, а имя каталога j dk/bin находится в списке путей к выполняемым файлам.
Откройте командную оболочку.
Перейдите в каталог jdk (т.е. в каталог /usr/local/jdk или С : /jdk).
Выполните команду:
jar xvf src.jar
Файл src.jarсодержит исходные тексты всех открытых библиотек. Чтобы получить еще больше исходных текстов (для компилятора, виртуальной машины, машинозависимых методов и закрытых вспомогательных классов), зайдите на Web- страницу http://www.sun.com/software/communitysource/java2.
Документация содержится в отдельном архиве, который может иметь различные расширения (.zip, . gz и . Z). Ее можно загрузить с Web-страницы http: // java.sun.com/docs. Выбирайте тот формат, который вам подходит. Если сомневаетесь, выбирайте формат . zip, поскольку такой файл можно распаковать с помощью программы jar, входящей в пакет Java SDK. Если вы решили использовать программу jar, выполните следующие действия.
Убедитесь, что пакет Java SDK установлен правильно, :i имя каталога jdk/bin находится в списке путей к выполняемым файлам.
Загрузите файл с расширением . zip, содержащий документацию, в каталог, содержащий подкаталог jdk (например, каталог /user/local или С: \) . Этот файл называется j2sdkversion-doc . zip, где слово version может означать что-нибудь, вроде 1_4_0.
Откройте командную оболочку.
Зайдите в каталог, содержащий подкаталог j dk и архив с документацией.
Выполните следующую команду:
jar xvf j2sdkversion-doc.zip
Здесь слово version означает соответствующий номер версии.
Установка примеров программ
Вам понадобятся примеры программ, приведенные в книге. Вы можете найти их на Web-странице http://www.phptr.com. corejava. Программы хранятся в архиве corejava.zip. Его нужно распаковать в отдельный каталог— мы рекомендуем назвать его CoreJavaBook. Для распаковки можно применять любую утилиту для работы с zip- файлами, например, программу WinZip (которую также можно найти по адресу http : //www. winzip. com), либо использовать утилитуjar. входящую в пакетJava SDK. Если вы решили использовать программу j ar, выполните следующие действия.
Убедитесь, что пакет Java SDK установлен правильно, а имя каталога jdk/bin находится в списке путей к выполняемым файлам.
Создайте каталог CoreJavaBook.
Скопируйте файл corej ava . zip в этот каталог.
Откройте командную оболочку.
Зайдите в каталог CoreJavaBook.
Выполните следующую команду: jar xvf corejava.zip
Навигация по каталогам
Изучая язык Java, вероятно, вы захотите заглянуть в его исходные тексты программ. Кроме того, вам, без сомнения, придется интенсивно работать с библиотечной документацией. В табл. 2.1 показано дерево каталогов, относящихся к языку Java. Схема этих каталогов может измениться, если вы работаете в интегрированной среде разработки программ, а имя корневого каталога зависит от номера инсталлированной версии.
jdk
(имя может быть другим, например jdkl .4.0)
docs
bin
demo
Библиотечная документация в формате HTML (после распаковки архива j2sdkversion-doc.zip)
Компилятор и другие инструментальные средства Демонстрационные программы
include
Файлы с машинозависимыми методами (см. том 2)
lib
Библиотечные файлы
src
j re
Разные подкаталоги с исходными текстами библиотечных программ (после распаковки файла src . j a :)
Файлы среды выполнения программ на язы ke Java
Наиболее важными среди подкаталогов являются docs и src. Каталог docs содержит документацию о библиотеках языка Java в формате HTML. Их можно просматривать с помощью Web-броузера, например, броузера Netscape.
DDУстановите в вашем броузере закладку на локальную версию файла docs/api/index.html. При изучении языка Java вам придется не раз ссылаться на эту страницу.
D
D
Каталог src содержит исходные тексты программ, находящихся в открытой части библиотек языка Java. Освоившись с языком, вы можете оказаться в ситуации, когда ни информация из Интернет, ни эта книга не помогут вам решить вашу проблему. В этом случае воспользуйтесь исходными кодами языка Java. По крайней мере, они гарантируют, что вы всегда сможете по исходным текстам понять, что именно делает та или иная библиотечная функция. Например, если вы интересуетесь, как работает класс System, загляните в файл src/java/lang/System.java.
Среда разработки программ
Если раньше вы программировали на языках Visual Basic или Visual C++, значит, вы уже знакомы со средой разработки, содержащей встроенный текстовый редактор, меню для компиляции и запуска программ, а также отладчик; Пакет Java SDK не имеет никаких средств, даже отдаленно напоминающих интегрированную среду разработки программ. Все команды выполняются путем их набора в окне оболочки. Мы рассказываем, как инсталлировать, а затем использовать пакет Java SDK, поскольку считаем, что полномасштабная среда разработки программ не обязательно облегчает процесс изучения языка Java — она может оказаться слишком сложной, скрывая некоторые важные детали, представляющие интерес для программиста.
Интегрированные среды разработки программ становятся все более неудобными для создания простых программ, поскольку они работают медленнее, требуют более мощных компьютеров и выполнения скучной процедуры установки переменных окружения для каждой вновь создаваемой программы. Интегрированные среды полезны, если вы разрабатываете большой проект, состоящий из многих файлов. Кроме того, в состав этих сред входит отладчик (debugger), крайне необходимый при разработке серьезных программ (отладчик, работающий в режиме командной строки, поставляемый в пакете Java SDK, крайне неудобно использовать). Мы покажем, как работать с программой Sun ONE Studio Community Edition, представляющей собой свободно распространяемую среду разработки, которая сама написана на языке Java. (Пока в дело не вмешались энтузиасты из отдела маркетинга компании Sun, эта программа называлась Forte.) Разумеется, если у вас уже есть среда разработки программ, например, JBuilder, Kawa, CodeWarrior или СаГё, поддерживающая новую версию языка Java, вы можете смело использовать ее вместе с этой книгой.
Для простых программ удобно использовать нечто среднее между работой в режиме командной строки и интегрированной средой разработки, а именно: текстовый редактор, интегрированный с пакетом Java SDK. На платформе Linux предпочтительнее использовать текстовый редактор Emacs. Под управлением операционной системы Windows удобна программа TextPad — прекрасный, свободно распространяемый редактор программ для среды Windows, хорошо интегрированный с языком Java. Подобными свойствами обладают и многие другие текстовые редакторы. Используя текстовый редактор, интегрированный с пакетом Java SDK, можно легко и быстро создавать программы на языке Java. Большинство программ, приведенных в этой книге, разработаны именно так. Поскольку компилировать и выполнять программы можно, не выходя из текстового редактора, фактически он образовывает интегрированную среду, используемую на протяжении всей книги.
Итак, у вас есть три возможных варианта выбора.
Использовать набор инструментальных средств Java SDK вместе со своим любимым текстовым редактором. Компиляция и запуск программы на выполнение в этом случае производится в режиме командной строки.
Использовать набор Java SDK и текстовый редактор, интегрированный вместе с этим пакетом. Эту и многие другие возможности предоставляют программы TextPad и Emacs. В этом случае компиляция и запуск на выполнение программ производятся внутри редактора.
Использовать интегрированную среду разработки программ, например, свободно распространяемую программу Sun ONE Studio Community Edition или одну из многих других как бесплатных, так и коммерческих i ч ( %
Использование инструментов в режиме командной строки
Есть два способа компиляции и запуска на выполнениенаписанных на
языке Java: из командной строки или из другой программы, например, интегрированной среды разработки или текстового редактора.
Откройте окно оболочки. Перейдите в каталог
Затем введите несколько команд:
javac
Welcome
На экране должно появиться сообщение, показанное на рис. 2.1.
Рис. Компиляция и выполнение программы
Примите наши поздравления! Вы только что скомпилировали исвою
первую программу на языке Java.
Что же произошло? Программа— это компилятор языкаОна скомпи
лировала файл Welcome . j ava в файл Welcome . class. Программа j ava — это интерпретатор языкаJava. Он интерпретирует байт-коды, которыепоместил в
файле с расширением
Программа Welcome чрезвычайноОна просто выводитна эк
ран. Можете посмотреть на текст этой программы, представленный в листинге 2.1.
1. public class
2. {
3.public static voidargs)
4.{
String!] greeting = new String[3];
greeting[О = "Welcome to Core Java";
greeting[l] = "by Cay Hortsmann”;
greeting[2] = "and Gary Cornell";
for (int i = 0; i < greeting.length; i++)
System.out.println(greeting[i]);
}
}
Возможные ошибки
В эпоху визуальных сред разработки программ многие программисты не знают методов работы в режиме командной строки. По этой причине они могут сделать много ошибок и получить неверные результаты.
Обратите внимание на следующие моменты.
Если вы набираете программу вручную, внимательно следите за употреблением прописных и строчных букв. В частности, имя класса— Welcome, а не welcome или WELCOME.
Компилятор требует, чтобы файл назывался Welcome .java. Интерпретатору нужно, чтобы класс называлсяWei come, без расширения . javaили . class.
Если вы получили сообщение "Bad command or file name" или "javacxommand
not found", вернитесь назад и вновь проверьте, правильно ли выполнена инсталляция, в частности, верно ли указаны пути к выполняемым файлам.
Если компиляторjavac выдал сообщение "cannot read: Welcome.java”, нужно проверить, находится ли файл в соответствующем каталоге.
При работе под управлением операционной системы UNIX проверьте, правильно ли использованы прописные буквы в имени файла Welcome . j ava.
При работе в среде Windows используйте команду dir, а не графические средства. Некоторые текстовые редакторы (в частности, Notepad) сохраняют текст в файлах с расширением . txt. Если вы используете эту программу для редактирования файла Welcome.java, она сохранит его под именем Welcome. j ava.txt. По умолчанию в среде Windows программа Explorer скрывает расширение . txt, поскольку такой тип является "неопределенным". В этом случае нужно переименовать файл, пользуясь командой ren.
Если компилятор java выдал сообщение об ошибке java.lang. NoClass- Def FoundError, внимательно проверьте имя соответствующего файла.
Если компилятор недоволен именем welcome (в котором буква w является строчной), повторно выполните команду java Welcome, теперь с прописной буквой W. В языке Java регистр буквы имеет очень большое значение.
Если интерпретатор выдает сообщение об ошибке Welcome/java, значит, вы случайно ввели команду j ava Welcome. j ava. Повторите команду j ava Welcome.
Если интерпретатор недоволен именем Welcome, значит, у вас неверно установлены пути к классам. Вам следует либо удалить текущие настройки перемен-
ных окружения, либо добавить текущий каталог (обозначенный точкой) в множество путей к классам. Детали описываются в главе 4.
Если в вашей программе слишком много ошибок, то все сообщения о них мелькают на экране очень быстро. Интерпретатор j ava выводит сообщения об ошибках в стандартный поток, который позволяет просматривать список ошибок, занимающий больше одного экрана.
При работе на платформах UNIX или Windows NT/2000/XP это не представляет большой сложности. Чтобы перенаправить сообщения об ошибках в файл, достаточно применить оператор 2>:
javac MyProg.java 2> errors.txt
При работе под управлением операционной системы Windows 95 невозможно перенаправить сообщения об ошибках в стандартный поток ошибок, находясь в режиме командной строки. Вы можете загрузить программу errout с Web-страницы http://www.horstmann. com/core j ava/faq.html и выполнить команду
errout javac Myprog.java > errors.txt
Вы можете найти превосходное учебное пособие, которое гораздо подробнее описывает ловушки, в которые могут попасть новички, на Web-странице http://java.sun.com.docs/books/tutoriai/getStarted/cupojava.
Использование интегрированной среды разработки программ
В этом разделе мы покажем, как скомпилировать программу в интегрированной среде Sun ONE Studio Community Edition, которая свободно распространяется компанией Sun Microsystems. Вы можете загрузить ее с Web-страницы http: //www. sun. com/software/sundev/jde. Программа Sun ONE Studio написана на языке Java и должна работать на любой платформе, в которой есть среды выполнения программ, написанных на языке Java 2. Существуют версии этой среды разработки, специально предназначенные для операционных систем Soilaris, Linux и Windows. Открытый код этой программы можно также загрузить с Web-страницы http : / /netbeans . org.
После запуска программы Sun ONE Studio на экране появляются разнообразные панели инструментов и окна (рис. 2.2).
Выберите в меню опцию File -> Open File, а затем загрузите файл CoreJavaBook/ vlch2/Welcome/Welcome.java. Вас спросят, должен ли этот файл принадлежать "стандартному пакету" ("default package"). Щелкните на кнопке "Accept". (В главе 4 содержится больше информации о пакетах. Пока будем считать, что все наши программы находятся в стандартном пакете.) Теперь посмотрите на окно с текстом программы (рис. 2.3).
2.2. Запуск среды Sun ONE Studio
2.З. Окно текстового редактора среды SunStudio
Выберите в меню опцию Build -> Compile. Теперь ваша программа скомпилирована. Если компиляция прошла успешно, выберите в меню опцию Build -> Execute. Окно редактора исчезнет, а вместо него в нижней половине экрана появится окно вывода, в котором отображаются результаты работы программы (рис. 2.4).
2.4. Окно вывода в среде Sun ONE Studio
Чтобы вернуться в окно редактора после того, как выполнение программы завершится, щелкните на закладке "Editing", расположенной в верхней части экрана.
Расположение сообщений об ошибках компиляции
Предполагается, что эта программа не содержит никаких опечаток или ошибок. (В конце концов, она состоит всего лишь из нескольких строк.) И все же, допустим, что вы случайно внесли в нее опечатку (или даже ошибку). Попробуйте сделать это— повредите наш файл, например, заменив идентификатор String, как показано ниже: string greetingnew String[3];
Теперь попробуйте скомпилировать эту программу снова. Вы получите в ответ сообщения об ошибках (см. рис. 2.5). Первое из этих сообщений ссылается на неизвестный тип string. Просто щелкните мышью на сообщении об ошибке. Курсор переместится на соответствующую строку в окне редактора, и вы сможете исправить свою ошибку.
Чтобы выполнить новую программу, выберите в пункте Template опцию File -> New. В открывшемся диалоговом окне, щелкнув мышью на пиктограмме, откройте "дверной замок" под именем Classes. Затем выберите пункт Empty и щелкните на кнопке Next (рис. 2.6).
Выполнение новой программы в среде Sun ONE
Вас спросят, хотите ли вы добавить данный файл в текущий проект. Пока вы не перейдете к серьезным проектам, можете смело выбирать либо Yes, либо No. Теперь вы готовы к редактированию, компиляции и выполнению нового файла.
Отладчик среды Sun ONE Studio обсуждается в главе
Компилирование и запуск программ из текстового редактора
Интегрированная среда разработки программ предоставляет программистам много удобств, однако у нее есть и недостатки. В частности, для простых программ, исходный текст которых не разбит на несколько файлов, среда разработки с долгой загрузкой, а также всякими звонками и свистками, кажется чрезмерно избыточной. Кроме того, многие программисты привыкли к своим любимым текстовым редакторам и могут с неохотой воспринять необходимость переходить к использованию, как
правило, неинтересных текстовых редакторов, являющихся частью интегрированной среды разработки программ. К счастью, многие текстовые редакторы позволяют запускать компилятор и интерпретатор языка Java, а также просматривать сообщения об ошибках и результаты работы программ. В этом разделе в качестве типичного примера мы рассмотрим два текстовых редактора, Emacs и TextPad.
Текстовый редактор Emacs корпорации GNU можно загрузить с Web-страницы http://www.gnu.org/software/eraacs/. Разновидность этого редактора для операционной системы Windows можно найти по адресу http://www.gnu.org/ software/emacs/windows/ntemacs.html. Используя текстовый редактор Emacs, убедитесь, что на вашем компьютере инсталлирован пакет JDEE. Его можно загрузить с Web-страницы http://j dee. sunsite.dk.
Например, на рис. 2.7 показан процесс компиляции программы, написанной на языке Java, в текстовом редакторе Emacs. (Чтобы запустить компилятор, выберите в меню опцию JDE -> Compile.)
Сообщения об ошибках показаны в верхней части экрана. Если переместить курсор на сообщение об ошибке и нажать клавишу <ENTER>, то курсор переместится в соответствующую строку исходного текста программы.
После исправления всех ошибок программу можно запустить, выбрав в меню опцию JDE -> Run App. Результаты работы программы показаны в окне текстового редактора на рис. 2.8.
Рис.2.7. Компиляция программы в текстовом редакторе EmacsРис.2.7. Компиляция программы в текстовом редакторе Emacs
Рис.2.7. Компиляция программы в текстовом редакторе Emacs
Рис.2.7. Компиляция программы в текстовом редакторе Emacs
Рис. 2.8. Выполнение программы в текстовом редакторе Emacs
Emacs — это превосходный и свободно распространяемый текстовый редактор для операционных систем UNIX и Windows. Однако многие программисты в среде Windows считают, что изучать его довольно сложно. Для таких программистов мы можем порекомендовать текстовый редактор TextPad. В отличие от редактора Emacs, TextPad соответствует стандартам системы Windows. Этот редактор можно найти на Web-странице http: //www. textpad. com. Заметим, что редактор TextPad относится к условно свободно распространяемым программам. По истечении испытательного срока вам придется заплатить за него. (Будучи вполне удовлетворенными пользователями этой программы, мы не имеем никакого отношения к ее поставщикам.)
Чтобы скомпилировать программу в текстовом редакторе TextPad, выберите в меню опцию Tools -> Compile Java или нажмите комбинацию клавиш <CTRL+1>.
• Если в меню нет такой опции, выберите опцию Configure -> Preferences, а затем опцию Tools из дерева в левой части экрана. В правой части экрана щелкните на кнопке под названием Add, пока не откроется окно с настройками команд пакета JDK (синоним названия пакета Java SDK. — Прим. ред.). Выберите эти установки, затем щелкните на кнопке ОК. Теперь команды пакета JDK добавлены в меню Tools.
Ошибки компиляции отображаются в отдельном окне (рис. 2.9).
Переместите курсор на первую строку сообщения об ошибке и нажмите клавишу <ENTER>, чтобы найти соответствующую строку в исходном тексте программы. Для перемещения по остальным сообщениям об ошибках используйте команду Search -> Jump Next (или клавишу <F4>).
Рис. 2.9. Размещение ошибок компиляции в окне редактора TextPad
Рис. 2.10.Запуск программы из текстового редактора TextPad
Чтобы выполнить программу, выберите в меню опцию Tools -> Run Java Application или используйте комбинацию клавиш <CTRL+2>. Программа выполняется в отдельном окне. На рис. 2.10 показано выполнение программы, запущенной из редактора TextPad.
После завершения работы программы нужно нажать на любую клавишу и закрыть окно оболочки.
Графические приложения
Программа Welcome не поражает воображение. Перейдем теперь к графическому приложению. Это приложение представляет собой очень простую программу для просмотра файлов в формате GIF, которая загружает и отображает на экране GIF-файл. Как и прежде, сначала скомпилируем и выполним это приложение в режиме командной строки.
Откройте окно оболочки.
Перейдите в каталог CoreJavaBock/vlch2/ImageViewer.
Введите следующие команды:
javac Imageviewer.java
java ImageViewer
На экране появится новое окно, открытое нашим новым приложением ImageViewer (рис. 2.11).
Выберите опцию File -> Open и найдите файл с расширением GIF, который вы хотите открыть. (Мы поместили в тот же каталог несколько демонстрационных файлов.)
Рис. 2.11.Выполнение приложения ImageViewer
Чтобы завершить выполнение программы, щелкните на кнопке Close в строке заголовка или откройте системное меню и закройте программ)1. (Чтобы скомпилировать и выполнить эту программу в текстовом редакторе или интегрированной среде, нужно сделать то же, что и прежде. Например, при работе с текстовым редактором Ernacs выберите опцию JDE -> Compile, а затем — опциюJDE -> Run App.)
Мы надеемся, что эта программа будет для вас интересной и полезной. Просмотрите ее исходный текст. Эта программа существенно длиннее, чем первая, однако она не кажется слишком сложной, если представить себе, сколько строк кода на языке С или C++ нужно было бы написать, чтобы создать такое же приложение. Конечно, на языке Visual Basic такую программу написать легко, пользуясь технологией "перетащить-и- опустить", — нужно лишь добавить несколько строк, чтобы код стал функциональным. В состав пакетаJDK не входит визуальное средство для создания интерфейса, поэтому для всех элементов нужно писать соответствующий код, как показано в листинге 2.2. Изучив главы 7-9, вы сможете писать аналогичные графические программы.
Если вы попробуете выполнить эту программу, пользуясь версией пакета Java SDK, предшествующей версии 1.4, при компиляции следующей строки возникнет ошибка:
frame.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);
Закомментируйте эту строку и скомпилируйте программу снова. В этом случае программа не прекратит работу при закрытии фрейма. Вместо этого пользователь должен выбрать в меню опцию File -> Exit. Подробнее эта тема освещается в главе 7.
import java.awt.*
import java.awt.event.*
import java.io.*
import javax.swing.*
/**
Программа для просмотра изображений.
/*
public class ImageViewer
{
public static void main(String[] args)
{
JFrame frame = new ImageViewerFrame();
.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.showO ;
}
}
/**
Фрейм с меткой для вывода изображения.
/*
class ImageViewerFrame extends JFrame
{
public ImageViewerFrame((
{
setTitle("Image Viewer");
setSize(DEFAULTWIDTH, DEFAULTHEIGHT);
// Для отображения изображений использовать метку.
label = new JLabel();
Container contentPane = getContentPane ();
32.32.33.33.34.35.34.35.45.46.47.53.58.59.60.61.62.67.68.69.72.73.45.46.47.53.58.59.60.61.62.67.68.69.72.73.contentPane.add(label);
32.
32.
33.
33.
34.
35.
34.
35.
45.
46.
47.
53.
58.
59.
60.
61.
62.
67.
68.
69.
72.
73.
45.
46.
47.
53.
58.
59.
60.
61.
62.
67.
68.
69.
72.
73.
// Установка диалога для выбора файлов.
chooser = new JFileChooser О;
chooser.serCurrentDirectory(new File (".");
// Установить строку меню.
JMenuBar menubar = new JMenuBar();
setmenuBar(menuBar);
Jmenu menu = new Jmenu("File");
MenuBar.add(menu);
JMenuItem openitem = new JMenuItemfOpen");
menu.add(openitem);
openitem.addActionListener(new
ActionListener(1
{
public void actionPerformed (ActionEvent event)
{
// Вывести на экран диалог выбора файла. int r=
// Если файл выбран, сделать его пиктограммой. if (r ==
{
String name =
label.seticon(new Imageicon(name));
}
}
});
JMenuItem exititem = new JMenuItem("Exit") ;
menu.add(exititem);
exititem.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event)
{
System.exit(0);
}
});
}
private JLabel label;
private JFileChooser chooser;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 400;
Аплеты
Первые два примера, представленные в этой книге, — это приложения, написанные на языке Java, т.е самостоятельные программы, аналогичные обычным машинозависимым программам. С другой стороны, как упоминалось в предыдущей главе, ажиотаж вокруг языка Java был вызван, в основном, его возможностями выполнять аплеты под управлением Web-броузеров. Мы хотим показать вам, как создать и выполнить
аплет в режиме командной строки. Затем мы загрузим этот аплет в программу для просмотра аплетов (appletviewer), входящую в состав пакета JDK. В заключение мы отобразим его в Web-броузере.
Перейдите н каталог CoreJavaBook/vlch2/WelcomeApplet, а затем введите следующие команды.
javac WelcomeApplet.java
appletviewer WelcomeApplet.html
На рис. 2.12 показано то, что вы увидите в окне программы для просмотра аплетов.
Рис.2.12. Вид аплета WelcomeApplet в окне программы просмотра аплетов
Первая команда нам уже знакома — она вызывает компилятор языка Java. Он компилирует исходный текст аплета, содержащийся в файле WelcomeApplet.java, создавая файл байт-кодаWelcomeApplet. class.
Однако на этот раз мы не запускаем интерпретатор языка Java. Вместо этого мы вызываем программу appletviewer. Эта программа специально предназначена для быстрого тестирования аплетов и входит в пакет Java SDK. Она работает не с файлом класса, а с файлом в формате HTML. Содержание файла WelcomeApplet.html показано в листинге 2.3.
<html>
<title>WelcomeApplet</title>
<body>
<hr>
<p>
This applet is from the book
<a HREF="http://www.horstmann.com/corejava.html">
Core Java</a> by <em>Cay Horstmann</em> and
<em>Gary Cornell</em>, published by Sun Microsystems Press.
</p>
ll.<applet code=WelcomeApplet.class width=4 00 height=200>
.<param name=greeting value="Welcome to Core Java!">
.</applet>
.<hr>
.<p><a href="WelcomeApplet. java">The source. </ax/p>
16.</body>
17.</html>
Если вы знаете язык HTML, то заметите некоторые стандартные команды языка HTML и дескриптор applet, приказывающие программе просмотра аплетов загрузить аплет, код которого содержится в файле WelcomeApplet. class. Программа appletviewer игнорирует все дескрипторы языка HTML, за исключением дескриптора applet.
Другие дескрипторы языка HTML распознаются, когда файл в формате HTML просматривается броузером, поддерживающим ЯЗЫКJava 2. Однако тут есть одна проблема.
Версии броузера Netscape 6, броузеры Mozilla и Opera поддерживают язык Java 2 как в среде Windows, так й на платформе Linux, хотя эта возможность является факультативной. Для того чтобы поэкспериментировать с аплетами, просто загрузите самую последнюю версию и убедитесь, что она поддерживает язык Java.
Некоторые версии броузера Internet Explorer вообще не поддерживают язык Java. Другие версии поддерживают лишь очень старую виртуальную машину Java компании Microsoft (Java Virtual Machine). Если вы работаете с броузером Internet Explorer под управлением операционной системы Windows, зайдите на сайт http: //java. sun. com/getJava и загрузите программуJava Plug-In. Она добавит в броузер Internet Explorer возможности языка Java 2.
На платформе Macintosh под управлением операционной системы OSX возможности языка Java уже интегрированы в броузер Internet Explorer. В частности, на момент написания книги этот броузер поддерживал работу с пакетом J2SE версии 1.3. Система OS 9 поддерживает лишь устаревшую версию 1.1.
Если вы работаете с броузером Netscape 4, можете воспользоваться поддержкой языка Java 2, обеспечиваемой программой Java Plug-In. Однако при работе с аплетами, загружаемыми с помощью дескриптора applet, броузер будет по- прежнему использовать устаревшую виртуальную машину языка Java. Следовательно, придется переписать HTML-файл и вместо дескриптора applet использовать довольно неудобный дескриптор embed. Этот подход нельзя назвать удовлетворительным.
Если ваш броузер поддерживает платформу Java 2, попробуйте загрузить в него аплет. Для этого выполните следующие операции.
Запустите ваш броузер.
Выберите в меню опцию File -> Open File (или эквивалентную опцию).
Перейдите в каталог CoreJavaBook/vlch2/WelcomeApplet.
В диалоговом окне вы должны увидеть файлы WelcomeApplet.html и WelcomeAppletPlugin.html. Загрузите файл, соответствующий вашей настройке. Броузер загрузит аплет вместе с окружающим его текстом. Он будет выглядеть примерно так, как показано на рис. 2.13.
Легко видеть, что это приложение действительно интерактивно и взаимодействует с Интернет. Нажмите на кнопку "Cay Horstmann”. Аплет заставит броузер открыть Web- страницу Кея Хорстманна. Щелкните на кнопке "Gary Cornell". Аплет откроет окно для электронной почты, в котором уже указан адрес электронной почты Гари Корнелла.
Рис. 2.13. Запуск аплетаWelcomeApplet с помощью броузера
Заметьте, что ни одна из этих кнопок не работает с программой просмотра аплетов. Эта программа не может посылать почту или отображать на экране Web- страницу, поэтому она просто проигнорирует ваши запросы. Программа просмотра аплетов хороша для тестирования изолированных аплетов, однако для реального взаимодействия аплета с Интернет и другими программами следует загрузить свой аплет в какой-либо броузер.
уАплет можно запустить из вашего текстового редактора или интегрированной
среды разработки. При работе с редактором Emacs выберите в меню опцию JDE -> Run Applet. При работе с редактором TextPad выберите опцию Tools -> Run Java Applet или используйте комбинацию клавиш <CTRL+3>. Перед вами откроется диалоговое окно, в котором будут представлены все HTML-файлы, хранящиеся в текущем каталоге. Если вы нажмете клавишу <ESC>, редактор TextPad автоматически создаст минимальный HTML-файл. В среде Sun ONE Studio нужно просто загрузить HTML-файл с дескрипторами аплета. Эта среда содержит небольшой броузер, выполняющий аплет на соответствующей Web-странице. В качестве альтернативы можно щелкнуть правой кнопкой мыши на файле с исходным текстом аплета и установить значение свойства "Executor" на закладке “Execution" равным "Applet Execution".
Код аплета Welcome показан в листинге 2.4. В данный момент его нужно лишь бегло просмотреть. К созданию аплетов мы еще вернемся в главе 10.
Вэтой главе мы изучилимеханизмыкомпиляции и запуска программ, написанных на языке Java. Теперь мы готовы перейти к главе 3, в которой приступим к изучению языка Java.
import
import java.awt.*;
import
import java.net.*;
public classextends JApplet
{
public void
{
Container contentPane =
label = new
JPanel panel = new
JButton cayButton = new JButton("Cay Horstmann");
cayButton.addActionListener(makeURLActionListener
("http://www.horstmann.com"));
Jbutton garyButton = newCornell");
2
("mailto:gary@thecornells.com"));
}
.
public ActionListenerString u)
{
return new
{
public voidevent)
{
try
{
getAppleContext().showDocument(u);
}
e)(}
{
}
}
} ;
}
}
Основные структуры языка Java
В этой главе...
Простая программа на языке Java
Т Комментарии
Типы данных
Т Переменные
Присваивания и инициализации
Операторы
Т Строки
Т Поток управления
Большие числа
Т Массивы
А>удем считать, что вы успешно инсталлировали средства программирования на языке Java и можете выполнять простые программы, показанные в главе 2. Настало время создавать собственные программы. В этой главе вы узнаете, как в языке Java реализуются основные концепции программирования, например, типы данных, ветви и циклы.
К сожалению, на языке Java нелегко написать программу, использующую графический пользовательский интерфейс, — для этого нужно изучить массу деталей, связанных с окнами, полями ввода, кнопками и т.п. Поскольку описание таких технических
подробностей увело бы нас далеко в сторону от нашей основной цели — анализа основных структур языка, в этой главе мы рассмотрим лишь "игрушечные" программы, иллюстрирующие то или иное понятие. Все эти программы просто выводят на консоль результаты своей работы. (В среде Windows консолью является окно MS DOS.) При вводе данных информация считывается из всплывающего окна. В частности, мы будем создавать приложения, а не аплеты.
В заключение заметим, что опытные программисты на языке C/C++ могут просто бегло просмотреть эту главу, обратив особое внимание на замечания о языке C/C++, разбросанные по тексту. Программистам на других языках, например Visual Basic, многие понятия окажутся знакомыми, хотя их синтаксис совершенно другой. Таким читателям мы советуем тщательно изучить эту главу.
Простая программа на языке Java
Рассмотрим поближе простейшую программу, какую только можно себе представить, — она просто выводит сообщение в консольное окно.
public class FirstSample
{
public static void main(String(" args!
{
System.out.printlnCWewill not use 'Hello, World!'");
Этому примеру стоит посвятить столько времени, сколько потребуется, чтобы привыкнуть к особенностям языка; эти моменты будут встречаться во всех приложениях. Первый и главный момент — в языке Java строго различаются прописные и строчные буквы. Если вы перепутаете их (например, набрав слово Main вместо main), программа выполняться не будет.
Теперь просмотрим исходный код построчно. Ключевое слово public называется модификатором доступа (access modifier); такие модификаторы управляют доступом к данному коду из других частей программы. Более подробно мы рассмотрим этот вопрос в главе 5. Ключевое слово class напоминает нам, что все сущности в языке Java находятся внутри классов. Классы будут детально рассматриваться в следующей главе, а пока мы будем считать их некими контейнерами, в которых заключена логика программы, определяющая работу приложения. Как указывалось в главе 1, классы— это строительные блоки, из которых состоят все приложения и аплеты, написанные на языке Java. Все сущности в программах на языке Java должны находиться внутри классов.
За ключевым словом class следует имя класса. Правила образования имен классов не слишком строги. Имя должно начинаться с буквы, а остальная его часть может быть любой комбинацией букв и цифр. Длина имени практически не ограничена. В качестве имени класса нельзя использовать зарезервированные слова языка Java (например, public или if). (Список зарезервированных слов приведен в приложении.)
Как видно по имени FirstSample, классы принято называть именами существительными, начинающимися с прописной буквы.
Файл, содержащий исходный текст, должен называться так же, как и открытый класс, и иметь расширение . java. Таким образом, класс мы должны записать в файл FirstSample . j ava. (Как и прежде, прописные и строчные буквы различаются — не
используйте имя firstsample. java.) Если вы этого не сделаете, то при попытке выполнить компиляцию получите замечательное по своей ясности собщение об ошибке ("Public class FirstSamplc must be defined in a file called 'FirstSample.java'” — "Открытый класс FirstSample должен быть определен в файле 'FirstSample.java'").
Если вы правильно назвали файл и не сделали опечатки в исходном тексте программы, то, скомпилировав его, получите файл, содержащий байт-коды, соответствующие данному классу. Компилятор языка Java автоматически назовет этот файл FirstSample . class и сохранит его в каталоге, где содержится исходный файл. Осталось выполнить байт-коды с помощью интерпретатора языка Java, набрав команду java FirstSample
(He забудьте отбросить расширение . class.) Выполняясь, программа просто выведет на экран строку "We will not use 'Hello, World!'".
Аплеты имеют другую структуру — см. главу 10.
Когда для запуска скомпилированной программы используется команда
java ИмяКласса,
интерпретатор языка Java всегда начинает свою работу с выполнения метода main указанного класса. Следовательно, чтобы программа вообще могла выполняться, в классе должен содержаться метод main. Разумеется, в класс можно добавить свои собственные методы и вызывать их с помощью метода main. (Мы покажем, как создавать такие методы в следующей главе.)
В соответствии со спецификацией языка Java метод main должен быть объявлен открытым (public). (Спецификация языка Java (Java Language Specification) является официальным документом. Его можно просмотреть или загрузить с Web-страницы http://java.sun.com/docs/books/jls.)Однако некоторые версии интерпретатора языка Java пытались запускать программы, даже когда метод main не имел модификатора public.Эта ошибка была помещена в список замеченных неполадок, представленный на сайте http://developer.java.sun.com/developer/bugParade, под номером 4252539. Однако она сопровождалась примечанием: "закрыта, исправлению не подлежит". Разработчики компании Sun выступили с разъяснениями, что спецификация виртуальной машины языка Java (Java Virtual Machine Specification) не требует, чтобы метод main был открытым (см. Web-страницу http:/ / j ava. sun. com/ docs/books/vmspec), а попытка исправить эту ошибку "может вызвать проблемы". К счастью, здравый смысл в итоге восторжествовал. Интерпретатор языка Java в пакете SDK 1.4требует, чтобы метод main был открытым.
Эта история имеет несколько интересных аспектов. С одной стороны, становится как-то неуютно от того, что разработчики, призванные гарантировать высокое качество программ, часто слишком много работают сверхурочно и не всегда проверяют слабые места языка Java, предпочитая отмахиваться от замеченных ошибок. С другой стороны, стоит отметить тот факт, что компания Sun разместила список ошибок и способы их исправления на Web-сайте, открыв его для исследования. Этот "парад ошибок" представляет собой весьма полезный ресурс для программистов. Вы даже можете "проголосовать" за вашу любимую ошибку. Ошибки, набравшие наибольшее число голосов, будут исправлены в следующих выпусках пакета SDK.
Обратите внимание на фигурные скобки в исходном тексте программы. В языке Java так же, как и в языке C/C++, фигурные скобки используются для выделения частей программы (обычно называемых блоками). В языке Java код любого метода должен начинаться с открывающей фигурной скобки { и завершаться закрывающей фигурной скобкой }.
Использование фигурных скобок вызвало массу недоразумений и противоречий. Обычно мы стараемся располагать скобки одну под другой, выравнивая их с помощью пробелов. В то же время компилятор языка Java игнорирует пробелы, поэтому, в принципе, фигурные скобки можно ставить где угодно. Изучая различные операторы цикла, мы поговорим о скобках более подробно.
Пока мы не будем обращать внимание на ключевые слова static void, считая их просто необходимой частью программы на языке Java. В конце главы 4 мы полностью раскроем смысл этих слов. Сейчас важно помнить, что каждое приложение на языке Java должно иметь метод main, заголовок которого приведен ниже.
public class ИмяКласса
{
public static void main(String!; args) {
; команды программы
«Вы уже знаете, что такое класс. Классы в языке Java похожи на классы в языке C/C++,
однако между ними есть несколько различий, которые могут завлечь вас в ловушку. Например, в языке Java все функции являются методами какого-либо класса. (В стандартной терминологии они называются методами, а не функциями-членами.) Следовательно, в языке Java должен существовать класс, которому принадлежит метод main. Вероятно, вы знакомы с концепцией статических функций-членов в языке C++. Это функции-члены, определенные внутри класса и не принадлежащие ни одному объекту. Метод main в языке Java всегда является статическим. В заключение, как и в языке C/C++, ключевое слово void означает, что метод не возвращает никакого значения. В отличие от языка C/C++, метод main не передает "код выхода" операционной системе. Если метод main завершает свою работу нормально, код выхода программы на языке Java равен 0, что свидетельствует о ее успешном выполнении. Чтобы прекратить выполнение программы с другим кодом выхода, используйте weTOflSystem.exit.
Теперь обратите внимание на следующий фрагмент кода:
{
System.out.println("Wewill not use 'Hello,1 World!'") ;
}
Фигурные скобки отмечают начало и конец тела метода, состоящего лишь из одного оператора. Как и в большинстве языков программирования, операторы языка Java можно считать предложениями. В языке Java каждый оператор должен заканчиваться точкой с запятой. В частности, символ конца строки не означает конец оператора, поэтому оператор может занимать столько строк, сколько потребуется.
Тело метода main состоит из оператора, который выводит на консоль одну строку текста.
В данном примере мы используем объект System. out и вызываем его метод println. Заметьте, что для вызова метода используется точка. В общем случае вызов метода имеет следующий вид: объект.метод(параметры)
В нашем примере мы вызываем метод println, передавая ему текстовую строку в качестве параметра. Метод выводит этот текст на консоль, выполняя переход на следующую строку. В языке Java, как и в языке C/C++, для разделения строк используются двойные кавычки. (Далее в этой главе мы рассмотрим работу со строками подробнее.)
Методы в языке Java, как и функции в любом другом языке программирования, могут иметь один или несколько параметров, либо не иметь их вовсе (в некоторых языках они называются аргументами). Даже если метод не имеет параметров, нужно оставлять пустые скобки. Например, есть вариант метода println без параметров, выводящий на экран пустую строку. Его вызов выглядит так:
System.out.println();
В объекте System.out есть метод print, который не добавляет к строке символ перехода на новую строку. Например, оператор Systern.out.print("Hello") выводит текст "Hello" и оставляет курсор в конце строки. Следующий вывод появится на экране сразу за буквой "о".
Комментарии
Комментарии в языке Java, как и в большинстве языков программирования, игнорируются при выполнении программы. Таким образом, в программу можно добавлять столько комментариев, сколько потребуется, не опасаясь увеличить ее объем. В языке Java есть три способа выделения комментариев в тексте. Чаще всего используются две косые черты //, при этом комментарий начинается сразу за символами //и продолжается до конца строки.
System.out.printIn("We will not use 'Hello, World!'");
// Остроумно, не правда ли?
Если нужны более длинные комментарии, можно каждую строку начинать символами //. Кроме того, для создания больших блоков комментария можно использовать разделители /* и * /, как показано в листинге 3.1.
. */
. Это первый пример программы в главе 3
Copyright (С) 1996.. .200C Cay Horstmann and Gary Cornell
*/
<10.11.12.}<10.11.12.}public class FirstSample
<
10.
11.
12.}
<
10.
11.
12.}
public static void main(String[ ; args) {
System, out.printin("Мыне будем говорить 'Hello, World!'"); }
В заключение отметим, что в языке Java есть и третья разновидность комментариев, которую можно использовать для автоматической генерации документации. Эти комментарии начинаются символами /** и заканчиваются символами */. Более подробную информацию об этом виде комментариев и автоматической генерации документации можно найти в главе 4.
Комментарии, выделяемые символами /* и */, в языке Java не могут быть вложенными. Это значит, что фрагмент кода нельзя отключить, просто окружив его символами /* и * /, поскольку отключаемый код сам может содержать разделители */ и */.
Типы данных
Язык Java является строго типизированным. Это значит, что тип каждой переменной должен быть объявлен. В языке Java есть восемь основных типов (primitive types) данных. Четыре из них относятся к целым числам, два — к действительным числам с плавающей точкой, один представляет собой символьный тип char, используемый для представления символов в формате Unicode (см. раздел, посвященный типу char), и последний — булевский тип boolean — применяется для логических величин.
В языке Java предусмотрен пакет для выполнения арифметических действий с произвольной точностью. Однако так называемые "большие числа" в языке Java являются объектами, а не новым типом. Позднее в этой главе мы покажем, как с ними работать.
Целые числа
Целочисленные типы используются для представления как положительных, так и отрицательных чисел, не имеющих дробной части. В языке Java есть четыре целочисленных типа. Они представлены в табл. 3.1.
В большинстве случаев тип int наиболее удобен. Если нужно задать количество жителей нашей планеты, нет никакой необходимости прибегать к типу long. Типы byte и short в основном используются в специальных приложениях, например, при низкоуровневой обработке файлов или для хранения больших массивов при недостатке памяти.
Тип
Требуемый объем памятиДиапазон (включительно)
int
4 байтот -2,147483648 до 2147483647
(больше 2 миллиардов)
short
2 байтот-32768 до 32767
long
8 байтот -9223372036854775808L
до -9223372036854775807L
byte
1 байтот-128 до 127
В языке Java диапазоны целочисленных типов не зависят от машины, на которой выполняется программа. Это облегчает страдания программистов, которым необходи-
мо переносить программное обеспечение с одной платформы на другую и даже из одной операционной системы — в другую на одной и той же платформе. В противоположность этому, программы на языках С и C++ на каждом процессоре используют тип, который является наиболее эффективным именно на нем. В результате программа на языке С, которая отлично работает на 32-разрядном процессоре, может привести к целочисленному переполнению в 16-разрядной системе. Поскольку программы на языке Java одинаково работают на любых машинах, диапазоны разных типов фиксированы.
Длинные целые числа имеют суффикс L (например 4 О 00000000L). Шсстнадцате- ричные числа имеют префикс Ох (например OxCAFE). Восьмеричные числа имеют префикс 0. Например, 01С — это число 8. Естественно, это может привести к недоразумениям, и мы не рекомендуем применять восьмеричные числа.
« В языках С и C++ ключевое слово int означает целочисленный тип, зависящий от машины, для которой предназначена программа. На 16-разрядном процессоре, например процессоре 8086, целые числа занимают 2 байт. На 32-разрядном процессоре, например процессоре Sun SPARK, они занимают 4 байт. На процессоре Intel Pentium размер целого типа в языках С и C++ зависит от операционной системы: в DOS и Windows 3.1 целые числа занимают 2 байт. При использовании 32- разрядного режима работы в системе Windows целые числа занимают 4 байт. В языке Java размеры всех числовых типов не зависят от платформы, на которой выполняется программа.
Заметим, что в языке Java нет беззнаковых типов unsigned.
Типы чисел с плавающей точкой
Эти типы предназначены для представления чисел, имеющих дробную часть. В языке Java есть два типа для чисел с плавающей точкой, показанные в табл. 3.2.
Типы для чисел с плавающей точкой
ТипТребуемый объем памяти Диапазон (включительно)
float4 байтПриблизительно ±3.40282347E+38F
(6-7 значимых десятичных цифр)
double8 байтПриблизительно± 1.7976931348623157E+308F
(15 значимых десятичных цифр)
Имя double означает, что точность этих чисел вдвое превышает точность чисел типа float. (Некоторые называют их числами с двойной точностью.) В большинстве приложений наиболее удобным является тип double. Ограниченной точности чисел типа float во многих случаях просто недостаточно. Семи значимых (десятичных) цифр, возможно, хватит для того, чтобы точно выразить вашу годовую зарплату в долларах и центах, однако для зарплаты президента вашей компании этого будет явно
недостаточно. Единственная причина, по которой тип floatnce еще используется, — редкие ситуации, в которых важна скорость обработки чисел (у чисел типа floatона выше). Кроме того, тип float используется при хранении больших массивов действительных чисел.
Числа типа floatимеют суффикс F, например 3 . 402F. Числа с плавающей точкой, не имеющие суффикса F (например 3.4 02), всегда рассматриваются как числа типа double. Для их представления можно (но не обязательно) использовать суффикс D, например 3 . 402D.
Bee вычисления, производящиеся над числами с плавающей точкой, следуют стандарту IEEE 7 54. В частности, в языкеJava есть три специальных числа c плавающей точкой:
положительная бесконечность;
отрицательная бесконечность;
NaN (не число).
Они используются для обозначения переполнения и ошибок. Например, результат деления положительного числа на 0 равен положительной бесконечности. Вычисление 0/0 или извлечение квадратного корня из отрицательного числа равно NaN.
/
В языке Java существуют константы Double. POSITIVE_INFINITY, Double. NEGATIVE_INFINITY и Double . NaN (aтакже соответствующие константы типа float). Однако на практике они редко используются. В частности, для того, чтобы убедиться, что некий результат равен константе Double.NaN, нельзя Bb полнить проверку
if (х == Double.NANI // Тождественно ложное; условие.
Все величины, "не являющиеся числами", считаются разными. Однако можно вы
зывать метод Double. isNaN:
if (Double.isNaN(x)1 // Проверка, является ли х "не числом".
Символьный тип
Во-первых, для обозначения констант типа char используются одиночные кавычки. Например, 1H1 — это символ. Он отличается от "Н", строки, состоящей из единственного символа. Во-вторых, тип char обозначает символы, представленные в формате Unicode. Вы можете не знать этот формат и не беспокоиться о нем, если не разрабатываете приложения, в которых нужна поддержка языков, помимо английского. Поскольку формат Unicode был разработан для обработки практически всех символов всех письменных языков, существующих в мире, он является 2-байтсвым кодом. В нем допускается 65536 символов, из которых обычно используется около 35000. Формат Unicode намного богаче набора ASCII, представляющего собой 1-байтовый код, состоящий из 128 символов, или широко используемого расширения ISO 8859-1, с помощью которого можно закодировать 256 символов. Этот набор символов (который программисты часто называют множеством символов “Latin-1”) представляет собой подмножество формата Unicode. Точнее, эти символы являются первыми 256 СИМВОЛАМИ кодировки Unicode. Таким образом, код символов 'а' , ' 111 ' [' и 'а' в формате Unicode не превышает 256. Коды символов в формате Unicode лежат в диапазоне от 0 до 65535, однако обычно они выражаются как шестнадцатеричные величины от '\u0000' до 'uFFFF' (в то время
как в формате ISO 8859-1 их диапазон ограничивается числами \u0000 ' и '
Префикс \и означает, что символ записан в формате Unicode, а следующие за ним четыре шестнадцатеричные цифры задают сам символ. Например,— это символ
торговой марки (™). Более подробную информацию о формате Unicode вы можете найти//www.unicode.org.
Кроме префикса \и, означающего символ в формате Unicode, в языке Java есть еще несколько специальных символов (escape sequence), показанных в табл. 3.3.
Специальный символ
ИмяЗначение в кодировке Unicode
\> \t \п
Возврат на одну позицию
Табуляция
Переход на новую строкуа
\r
Возврат кареткиd
\" V
Двойная кавычкаО 2
Одинарная кавычка\u0027
\\
Обратная косая черта
Теоретически в приложении или аплете на языке Java можно использовать любой символ в формате Unicode, однако будет ли он отображаться на экране дисплея, зависит как от вашего броузера (для аплетов), так и (в конечном счете) от операционной системы (в обоих случаях). Например, на компьютерах, работающих под управлением американской версии системы Windows, с помощью языка Java невозможно отобразить японские иероглифы. Вопросы, связанные с интернационализацией, рассматриваются в главе 12 в томе 2.
Булевский тип
Переменная типа boolean имеет два значения: и true. Они используются для вычисления логических выражений. Преобразования булевских переменных в целочисленные и наоборот невозможны.
В языке C++ вместо булевских величин можно использовать числа и даже указатели. Значение 0 эквивалентно булевскому значению а ненулевые величины — значению true. В языке Java этого нет. Следовательно, программист на языке Java защищен от недоразумений, связанных с использованием таких выражений:
if (х 0) // Ой! Вместо проверки х===0 выполнили присваивание!
В языке C++ эта проверка компилируется и выполняется, причем вычисление всегда приводит к результату В языке Java такая проверка приведет к ошибке компиляции, поскольку целочисленное выражение х = о нельзя преобразовать в булевское значение.
Переменные
В языке Java каждая переменная имеет тип. При объявлении переменной сначала указывается ее тип, а затем — ее имя. Вот несколько примеров.
double salary;
int vacationDays;
long earthPopulation;
char yesChar; boolean done;
Обратите внимание на точку с запятой после каждого объявления. Она необходима, поскольку объявление в языке Java считается оператором.
Ниже приведены правила образования имен переменных.
Имя переменной должно начинаться с буквы и представ/ять собой комбинацию букв и цифр. Термины "буквы" и "цифры" в языке Java имеют более широкое значение, чем в большинстве других языков программирования. Буквами считаются символы ' А1 —' Z ', ' а ' zи любой символ в кодировке Unicode. Например, немец
кие пользователи в именах переменных могут использовать умлауты, например, ' й1. а греческие пользователи могут воспользоваться буквой я. Аналогично цифрами считаются как обычные десятичные цифры ' 0 ' - ' 9 ', так и любые символы в кодировке Unicode, использующиеся для обозначения цифры в каком-либо языке. Символы наподобие ' + ' или ' ©', а также пробел нельзя использовать в именах переменных. Все символы в имени переменной важны, причем регистр клавиатуры также имеет значение. Длина имени переменной практически не ограничена.
Если вы действительно хотите знать, какие символы в формате Unicode считаются "буквами" в языке Java, воспользуйтесь методами isJavaldentifierStart и isJavaldentifierPart из класса Character.
В качестве имен переменных нельзя использовать зарезервированные слова. (Список зарезервированных слов приведен в приложении.)
В одной строке программы можно размещать несколько объявлений, например: int i,j; // Обе переменные — целочисленные.
Однако мы не рекомендуем такой стиль. Если объявить каждую переменную в отдельной строке, читать программу станет гораздо легче.
Как видим, в языке Java различаются прописные и строчные буквы, например, имена hireday и hireDay считаются разными. Однако иногда для переменной трудно подобрать хорошее имя. Многие программисты в этих случаях дают переменной имя ее типа, например:
Box box; // -- Box — это тип, a box — имя переменной.
Однако намного лучше использовать в имени переменной префикс "а":
Box aBox;
Присваивания и инициализации 65
Присваивания и инициализации
После объявления переменной ее нужно явно инициализировать с помощью оператора присваивания, поскольку использовать переменную, которой не присвоено никакого значения, невозможно. Для присвоения ранее объявленной переменной какого-либо значения нужно указать слева ее имя, поставить знак равенства (=), а справа записать некоторое выражение на языке Java, задающее требуемое значение.
int vacationDays;// Это — объявление.
vacationDays = 12;// Это — оператор присваивания.
Ниже приведен пример присваивания символьной переменной некоего значения: char yesChar;
yesChar = 'Y';
Язык Java обладает замечательной возможностью совмещать объявление и инициализацию переменной в одной строке. Например, int vacationDays = 12; // Это — инициализация.
В языке Java объявление переменной можно размещать в любом месте кода, например, приведенный ниже код вполне допустим.
double salary = 65000.0;
System.out.printin(salary);
int vacationDays = 12; // Здесь можно объявить переменную.
Разумеется, невозможно в одной и той же области видимости объявить две переменные с одним и тем же именем.
В языках С и C++ различаются объявление и определение переменной. Вот пример определения переменной: int i = 10;
А вот пример ее объявления:
extern int i;
В языке Java объявления и определения переменных не различаются.
Константы
В языке Java для обозначения констант используется ключевое слово final, например:
public class Constants
public static void main(String(] args)
final double CM_PER_INCH = 2.54;
double paperWidth = 8.5;
double PaperHeight 11;
System.out.printIn("Размер страницы в сантиметрах: " + paperWidth * CM_PER_INCH + "на"
.+ paperheight * CM_PER_INCH);
)
Ключевое слово final означает, что присвоить какое-либо значение данной переменной можно лишь один раз и навсегда. Использовать в именах констант только прописные буквы необязательно.
В языке Java часто необходимы константы, доступные нескольким методам внутри одного класса. Обычно они называются константами класса (class constants). Константы класса объявляются с помощью ключевых слов static final. Вот пример использования константы класса.
public class Constants2
{
public static final double= 2.54;
public static void[] args)
{
double= 8.5;
double PaperHeight = 11;
страницы в сантиметрах: "
+ paperWidth *+ "на"
+*
)
Отметим тот факт, что константа класса задается вне методаТаким образом,
ее можно использовать в других методах того же класса. Более того, если (как в данном примере) константа объявлена как public, методы из других классов также могут получить к ней доступ. В нашем примере это можно сделать с помощью выражения Constants2.CM_PER_INCH.
В языке Java слово const является зарезервированным, однако сейчас оно уже не употребляется. Для объявления констант следует использовать ключевое слово final.
Операторы
Для обозначения операций сложения, вычитания, умножения и деления в языке Java используются обычные арифметические операторы + - * /. Оператор / обозначает целочисленное деление, если оба его аргумента являются целыми числами. В противном случае этот оператор обозначает деление чисел с плавающей точкой. Остаток от деления целых чисел (т.е. функция mod) обозначается символом Например, 15/2 равно 7,равно 1, а 15 . 0/2 равно 7 . 5.
Заметим, что целочисленное деление на 0 возбуждает исключительную ситуацию, в то время как результатом деления на 0 чисел с плавающей точкой является бесконечность или NaN.
Арифметические операторы можно использовать для инициализации переменных. int n = 5;
int а = 2 * п; // Значение переменной а равно 10.
В операторах присваивания удобно использовать сокращенные бинарные арифметические операторы. Например, оператор
х += 4;
эквивалентен оператору
х ■ х + • 4;. .-
(Сокращенные операторы присваивания образуются путем приписывания символа арифметической операции, например * или %, перед символом =, например *= или
Одной из заявленных целей языка Java является машинная независимость. Вычисления должны приводить к одинаковому результату, независимо от того, какая виртуальная машина их выполняет. Для арифметических вычислений над числами с плавающей точкой это неожиданно оказалось трудной задачей. Тип double для хранения числовых значений использует 64 бит, однако некоторые процессоры применяют 80-разрядные регистры с плавающей точкой. Эти регистры обеспечивают дополнительную точность на промежуточных этапах вычисления. Рассмотрим в качестве примера следующее выражение: double w = х * у / z;
Многие процессоры компании Intel вычисляют выражение х * у и сохраняют этот промежуточный результат в 80-разрядном регистре, затем делят его на значение переменной z и в самом конце округляют ответ до 64 бит. Так можно повысить точность вычислений, избежав переполнения. Однако этот результат может оказаться иным, если в процессе всех вычислений используется 64-разрядный процессор. По этой причине в первоначальном описании виртуальной машины Java указывалось, что все промежуточные вычисления должны округляться. Это возмутило компьютерное сообщество. Переполнение могут вызвать не только округленные вычисления. На самом деле они выполняются медленнее, чем более точные вычисления, поскольку операции округления занимают определенное время. В результате разработчики языка Java изменили свое мнение, стремясь разрешить конфликт между оптимальной производительностью и отличной воспроизводимостью результатов. По умолчанию разработчики виртуальной машины теперь позволяют использовать расширенную точность в промежуточных вычислениях. Однако методы, помеченные ключевым словом strictfp,должны использовать точные операции над числами с плавающей точкой, что гарантирует воспроизводимость результатов. Например, метод main можно пометить ключевыми словами, как показано ниже:
public static strictfp void main(String[' args)
В этом случае все команды внутри метода main будут выполнять точные операции над числами с плавающей точкой.
Детали выполнения этих операций тесно связаны с особенностями работы процессоров Intel. По умолчанию промежуточные результаты могут использовать расширенный показатель, но не расширенную мантиссу. (Микросхемы компании Intel поддерживают округление мантиссы без потери производительности.) Следовательно, единственное различие между вычислениями по умолчанию и точными вычислениями СОСТОИТ в том, что точные вычисления могут приводить к переполнению, а вычисления по умолчанию — нет.
Если при чтении этого замечания ваш взгляд потускнел, не волнуйтесь. Для большинства программистов этот вопрос совершенно не важен. Переполнение при вычислениях чисел с плавающей точкой в большинстве случаев не возникает. В этой книге мы не будем использовать ключевое слово strictfp.
Операторы инкрементации и декрементации
Программисты, конечно, знают, что одной из наиболее распространенных операций с числовыми переменными является добавление или вычитание единицы. В языке Java, как и в языках С и C++, есть операторы инкрементации и декрементации: оператор х++ добавляет единицу к текущему значению переменной х, а оператор X — вычитает из него единицу. Например, код
int n = 12; n*+;
делает значение переменной п равным 13. Поскольку эти операторы изменяют значение переменной, их нельзя применять к самим числам. Например, оператор 4++ является недопустимым.
Существует два вида этих операторов. Выше показана "постфиксная" форма оператора, в которой символы операции размещаются после операнда. Есть и "префиксная" форма— ++п. Оба этих оператора увеличивают значение переменной на единицу. Разница между ними проявляется, только когда эти операторы используются внутри выражений. Префиксная форма оператора инкрементации сначала добавляет единицу к значению переменной, в то время как постфиксная форма использует старое значение этой переменной. int=
int n = 7;
int a = 2 *// Теперь значение а равно 16, a — 8.
int b = 2 * n++; // Теперь значение Ь равно a n —
Мы не рекомендуем использовать оператор инкрементации ++ внутри выражений, поскольку это зачастую приводит к запутанному коду и досадным ошибкам.
(Поскольку именно оператор ++ дал имя языку C++, этоповодом к пер
вой шутке о нем. Недоброжелатели указывают, что даже имя этого языка содержит в себе ошибку: "Кроме всего прочего, этот язык следовало бы назвать ++С, потому что мы хотим использовать этот язык только после его улучшения".)
Операторы отношения и логические операторы
Язык Java содержит полный комплект операторов отношения. Чтобы проверить равенство, следует использовать символы ==. Например, значение выражения 3 == 7 равно
Для проверки неравенства используются символы ! = . Так, значение выражения 3 ! = 7 равно true.
Кроме того, в языке Java есть обычные операторы < (меньше), > (больше), <= (меньше или равно) и => (больше или равно).
Java, как и язык C++, использует символы для обозначения логического оператора "и", а также символы | | для обозначения логического оператора "или" . Как обычно, знак восклицания означает логический оператор отрицания . Операторы && и I I вычисляются по сокращенной схеме, т.е. если в ходе вычисления выражения
А && В
выясняется, что подвыражение А ложно, то подвыражение В не вычисляется. Например, в выражении
х != О> х+у // Не делить на 0.
вторая часть никогда не вычисляется, если значение переменной х равно нулю. Таким образом, подвыражение 1/х не вычисляется, если х равно нулю и деления на нуль не происходит.
Аналогично, если оказывается, что выражение А истинно, то значение выражения А | | В автоматически становится истинным, и вычислять выражение В нет необходимости.
В языке Java есть также тернарный оператор ? :, который иногда оказывается полезным. Выражение условие ? el : е2
означает, что если условие истинно, то вычисляется выражение el, а если ложно — е2. Например, выражение
х < у ? х : у
вычисляет меньшее из чисел х и у.
Побитовые операторы
Работая с любыми целочисленными типами, можно применять операторы, имеющие дело непосредственно с битами, из которых состоят целые числа. Это значит, что для получения отдельных битов числа можно использовать технику масок. В языке Java есть следующие побитовые операторы:
S ("и")S ("и")| {"или") А("исключающее или")| {"или") А("исключающее или")("не")("не")
S ("и")
S ("и")
| {"или") А("исключающее или")
| {"или") А("исключающее или")
("не")
("не")
Эти операторы работают с битами. Например, если п — это целое число, то выражение int fourthBitFromRight = (n & 8) / 8;
равно единице, если четвертый бит справа в двоичном представлении числа п равен единице, и нуль, если нет. Используя оператор & с соответствующей степенью двойки, можно замаскировать все биты, кроме одного.
Операторы & и | применяются к булевским переменным и вычисляют булевские значения. Эти операторы аналогичны операторам && иI 11 за исключением того, что побитовые операторы не вычисляются по сокращенной схеме, т.е. перед вычислением результата вычисляются оба аргумента.
В языке Java есть также операторы >> и <<, сдвигающие битовое представление числа вправо или влево. Эти операторы часто оказываются удобными, если нужно построить битовое представление на основе маски: int fourthBitFromRight = (n s (1 « 3)) » 3;
В языке есть даже оператор >>>, заполняющий старшие разряды нулями, в то время как оператор » приписывает знаковый бит к старшим разрядам. Оператора <« в языке Java нет.
Аргумент, стоящий в правой части операторов побитового сдвига, сокращается по модулю 32 (если левая часть является числом типа long, правая часть сокращается по модулю 64). Например, значение выражения 1«35 эквивалентно выражению 1<<3, или 8.
В языках C/C++ не фиксируется, какой сдвиг выполняет оператор >>: арифметический (вплоть до знакового бита) или логический (заполняя свободные разряды нулями). Разработчики средств реализации языка могут выбрать тот вариант, который покажется им более эффективным. Это значит, что оператор >> в языке C/C++ в действительности лишь определяет некое неотрицательное число. Язык Java снимает эту неопределенность.
Математические функции и константы
Класс Math содержит набор математических функций, часто оказывающихся необходимыми при решении разных задач.
Чтобы извлечь квадратный корень из числа, применяют метод sqrt.
double х = 4;
double у = Math.sqrt(x);
System.out.println(y); // Печатает число 2.0.
Между методами println и sqrt есть небольшая разница. Метод println действует на объект System. out, имея второй параметр у — число, подлежащее выводу. (Напомним, что out — это объект, определенный в классе System и представляющий собой стандартное устройство вывода.) В то же время метод sqrt в классе Math не работает ни с одним объектом. Он имеет единственный параметр х — число, из которого нужно извлечь корень. Такие методы называются статическими. Они будут изучаться в главе 4.
В языке Java нет оператора возведения в степень: для этого нужно использовать метод pow из класса Math. Оператор double у = Math.pow(x,a) ;
присваивает переменной у значение переменной х, возведенное в степень а (xa). Оба параметра метода pow, а также возвращаемое им значение имеют тип double.
Класс Math содержит обычные тригонометрические функции:
Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2
Кроме этого, в него включены экспоненциальная и обратная к ней логарифмическая функции (натуральный логарифм):
Math.exp
Math.log
В классе определены также две константы
Math.PI
Math.E,
обозначающие аппроксимации чисел Л ие.
Для повышения своей производительности функции в классе Math используют программы из встроенного модуля для вычислений с плавающей точкой. Если точность вычислений важнее скорости их выполнения, используйте класс strictMath. Он реализует алгоритмы из библиотеки "Freely Distributable Math Library" ("Свободно распространяемая библиотека математических функций") fdlibm , гарантирующей идентичность результатов на всех платформах. Исходные тексты программ, реализующих эти алгоритмы, можно найти на web-странице http://www.netlib.org/ fdlibm/index.html.(В библиотеке fdlibm дается несколько определений каждой функции, класс StrictMath следует версии IEEE 754, имена функций в которой начинаются с буквы "е".)
Преобразования числовых типов
Часто возникает необходимость преобразовать один числовой тип в другой. На рис. 3.1 показаны разрешенные преобразования.
Шесть черных стрелок на рис. 3.1 обозначают преобразования, которые выполняются без потери информации. Три серые стрелки означают преобразования, при которых может произойти потеря точности. Например, количество цифр в большом целом числе 123456789 превышает количество цифр, которое может быть представлено типом float. Число, преобразованное в тип float, имеет правильную величину, но несколько меньшую точность.
int n = 123456789;
float f = n; // Число п равно 1.234567892E8.
char
float-► double
Рис. 3.1.Разрешенные преобразования числовых типов
Если два значения объединяются бинарным оператором (например n+f, где п — целое число, a f — число с плавающей точкой), то перед выполнением операции оба операнда преобразовываются в числа, имеющие одинаковый тип.
Если хотя бы один из операндов имеет тип double, то второй тоже преобразовывается в число типа double.
В противном случае, если хотя бы один из операндов имеет типfloat, то второй тоже преобразовывается в число типа float.
В противном случае, если хотя бы один из операндов имеет тип long, то второй тоже преобразовывается в число типа long.
В противном случае оба операнда преобразовываются в числа типа int.
Приведение числовых типов
В предыдущем разделе мы видели, что при необходимости значения типа int автоматически преобразовываются в значения типа double. С другой стороны, есть несколько очевидных ситуаций, когда число типа double рассматривается как целое. Преобразования чисел в языке Java возможны, однако, разумеется, при этом может происходить потеря информации. Такие преобразования называются приведением
типа (cast). Синтаксически приведение типа задается парой скобок, внутри которых указывается желательный тип, а затем — имя переменной. Например,
double х = 9.997;
int nx = (int)x;
Теперь в результате приведения значения с плавающей точкой к целому типу переменная nx равна 9, поскольку при этом дробная часть числа отбрасывается.
Если нужно округлить число с плавающей точкой до ближайшего целого числа (что во многих случаях является намного более полезным), используется метод Math. round.
double x = 9.997;
int nx =;
Теперь переменная nx равна 10. При вызове метода round по-прежнему нужно выполнять приведение (int), поскольку возвращаемое им значение имеет тип long, и присвоить его переменной типа int можно лишь с помощью явного приведения.
При попытке привести число одного типа к другому результат может выйти за пределы допустимого диапазона. В этом случае результат будетусечен. Например, выражение (byte) 300 равно 44. Поэтому рекомендуется явно проверять заранее, будет ли результат лежать в допустимом диапазоне после приведения типов.
Приведение между булевским и целыми типами невозможно. Это предотвращает появление ошибок. В редких случаях для того, чтобы привести булевское значение к числовому типу, можно использовать условное выражение b ? 1 : 0.
Скобки и иерархия операторов
Как и во всех языках программирования, для определения порядка выполнения операторов лучше всего использовать скобки. Однако в языке Java существует иерархия операций, показанная в табл. 3.4.
ОператорыОператорыАссоциативностьАссоциативность
Операторы
Операторы
Ассоциативность
Ассоциативность
[ ] . () (вызов метода)[ ] . () (вызов метода)Слева направоСлева направо
[ ] . () (вызов метода)
[ ] . () (вызов метода)
Слева направо
Слева направо
! ~ ++ — +(унарный)-(унарный)* / %+ -« » »><<=>>= instanceof—- i =&! ~ ++ — +(унарный)-(унарный)* / %+ -« » »><<=>>= instanceof—- i =&() (приведение) new Справа налевоСлева направоСлева направоСлева направоСлева направоСлева направоСлева направоСлева направо() (приведение) new Справа налевоСлева направоСлева направоСлева направоСлева направоСлева направоСлева направоСлева направо
! ~ ++ — +(унарный)-(унарный)
* / %
+ -
« » »>
<<=>>= instanceof
—- i =
&
! ~ ++ — +(унарный)-(унарный)
* / %
+ -
« » »>
<<=>>= instanceof
—- i =
&
() (приведение) new Справа налево
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
() (приведение) new Справа налево
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
Слева направо
Окончание табл. 3.4
Операторы
Ассоциативность
I
Слева направо
&&
Слева направо
I I
Слева направо
?:
Слева направо
= += -= *= /= %= |= A = «= »= >»=
Справа налево
Если скобки не используются, операторы выполняются в указанном иерархическом порядке. Операторы, находящиеся на одном уровне иерархии, выполняются слева направо, за исключением операторов, имеющих правую ассоциативность, как показано в таблице.
В отличие от языков С и C++, язык Java не имеет оператора "запятая". Однако в операторе for в качестве первого и третьего оператора можно использовать СПИСОК выражений, разделенныхзапятой.
Строки
Строки— это последовательности символов, например "Hello". В языке Java нет встроенного типа для строк. Вместо этого стандартная библиотека языка Java содержит встроенный класс String. Каждая строка, заключенная в кавычки, представляет собой экземпляр класса String.
String e ""; // Пустая строка.
String greeting = "Hello";
Конкатенация
Язык Java, как и большинство языков программирования, дает возможность использовать знак + для объединения (конкатенации) двух строк.
String expletive = "Вставка";
String PG13 = "удаленная";
String message = expletive + PG13;
Код, приведенный выше, присваивает переменной message строку "Вставкаудаленная". (Обратите внимание на отсутствие пробела между словами: знак + объединяет две строки точно в указанном порядке.)
При конкатенации строки со значением, которое строкой не является, это значение преобразовывается в строку. Например, код int age = 13;
String rating = "PG" + age;
присваивает переменной rating строку "PG13".
Это свойство широко используется в операторах вывода; например, оператор
System.out.printIn("Ответ равен " + answer);
выводит на печать все, что требуется (причем вставляет пробел после слова "равен").
Подстроки
С помощью метода substring класса String можно выделить подстроку из заданной строки. Например, код String greeting = "Hello";
String s = greeting.substring(0, 4);
образует строку, состоящую из символов "Hell". В языке Java символы в строке подсчитываются довольно своеобразно: первый символ строки занимает нулевую позицию, как и в языках С и C++. (В языке С для этого существовала техническая причина. Однако сейчас ее уже нет, осталось лишь неудобство.)
Например, символ 1H1 в строке 'Hello' занимает нулевую позицию, а символ 'о'— четвертую. Второй параметр метода substring задает первую позицию, которую вы не хотите копировать. Допустим, что мы хотим скопировать символы из позиций 0, 1, 2 и 3 (от позиции 0 до позиции 3 включительно), т.е. метод substring будет копировать символы из позиций от 0 до 4 исключительно.
Такой способ имеет определенное преимущество: легко зычислить длину подстроки.Строкаs . substring(a,b)всегдасостоитизb-асимволов. Например,
подстрока "Hell" имеет длину 4-0 = 4.
Редактирование строки
Для определения длины строки нужно использовать метод length, например: String greeting = "Hello";
int n = greeting.length(); // Длина строки равна 5.
Поскольку тип char означает символ в формате Unicode, класс String означает последовательность символов в кодировке Unicode. Из строки можно выделить отдельный символ. Вызов метода s . charAt (n) возвращает символ в кодировке Unicode, находящийся в позиции п, где число п лежит в диапазоне от 0 до s.length () -1, например: char last = greeting.charAt(4); // Четвертый символ — 'о1 .
Однако класс String не дает методов, позволяющих изменить символ в заданной строке. Так, вы не сможете просто изменить последнюю позицию в строке greeting, поместив в нее восклицательный знак, чтобы преобразовать строку greeting в строку "Hell!",.Если вы программировали на языке С, это может вызвать у вас чувство беспомощности. Как же модифицировать строку? В языке Java это достаточно просто сделать: выделите подстроку, которую вы хотите оставить без изменения, и конкатенируйте ее с символами, которые вы хотите поставить в нужные позиции: greeting = greeting.substring(0,4; + "!";
С помощью такого оператора вы можете присвоить переменной greeting строку "Hell!".
Поскольку, программируя на языке Java, вы не можете изменять отдельные символы в строке, документация ссылается на объекты класса String как на неизменные. Число 3 всегда равно 3, а строка "Hello" всегда содержит символы 'Н', 'е', Ч', 111и 'о1 . Изменить эти значения невозможно. Однако, как мы только что убедились, можно изменить содержимое строковой переменной greeting и заставить ее ссылаться на другую строку так же, как числовой переменной, в которой хранится число 3, можно присвоить число 4.
He происходит ли при этом большой потери эффективности? Кажется, было бы намного проще изменять символы, чем создавать новую строку заново. Возможно, это и так. Действительно, неэффективно создавать новую строку, образованную с помощью конкатенации строк "Hell" и "!". Однако неизменяемые строки имеют одно большое преимущество: компилятор может делать строки совместно используемыми.
Чтобы понять этот принцип, представьте, что в некотором совместно используемом пуле находятся разные строки. Строковые переменные указывают на некоторые ячейки в этом пуле. При копировании строковой переменной и оригинал, и копия состоят из одинаковых символов, ссылаясь на одну и ту же область памяти. Кроме ТОГО, разработчики языка Java решили, что эффективность совместного использования памяти перевешивает неэффективность редактирования строк путем выделения подстрок и конкатенации.
Посмотрите на свою программу; мы подозреваем, что большую часть времени вы проводите, не изменяя строки, а сравнивая их. Разумеется, бывают случаи, когда непосредственные манипуляции со строками более эффективны. (Одна из таких ситуаций возникает, когда нужно образовать строку из отдельных символов, поступающих из файла или клавиатуры.) Для этих ситуаций язык Java предусматривает отдельный класс StringBuf fer, который будет описан в главе 12. Если эффективность обработки строк для вас неважна (как это часто случается во многих приложениях на языке Java), можете проигнорировать класс StringBuffer и просто использовать класс String.
Программисты на языке С обычно попадают в тупик, когда видят строки языка Java в первый раз, поскольку они рассматривают строки как массив, состоящий к.из символов:
char greeting!] = "Hello";
Это неверная аналогия: строка в языке Java отдаленно напоминает указатель *char:
char* greeting = Hello";
При замене строки greeting другой строкой код на языке Java выполняет примерно следующее.
char* temp = malloc(6);
strncpy(temp, greeting, 4);
strncpy(temp+ 4, "!", 2);
greeting = temp;
Разумеется, теперь переменная greeting указывает на строку "Hell!". И даже большинство "самых закоренелых" программистов на языке С должны признать, что синтаксис языка Java более элегантен, чем последовательность вызовов функции strncpy. А что будет, если мы присвоим строке greeting еще одно значение?
greeting = "Howdy";
He произойдет ли утечка памяти? Ведь исходная строка размещалась в динамической памяти. К счастью, в языке Java есть механизм автоматической сборки мусора. Если блок памяти больше не нужен, он в конце концов будет освобожден.
Если вы программируете на языке C++ и применяете класс string, определенный в стандарте ANSI C++, вам будет намного легче работать с типом string в языке Java. Объекты класса string в языке C++ также выполняют автоматическое выделение и освобождение памяти. Конструкторы, операторы присваивания и деструкторы управляют памятью явно. Однако строки в языке C++ могут изменяться —^——. отдельные символы в строке можно модифицировать.
Проверка строк на равенство
Чтобы проверить, совпадают ли две строки, следует использовать метод equals; вызов метода s. equals (t)
возвращает значение true, если строки s и t равны между собой, в противном случае он возвращает значение false. Отметим, что строки s и t могут быть как переменными, так и константами. Например, выражение
"Hello!".equals(command);
вполне допустимо. Чтобы проверить идентичность строк, игнорируя различие между прописными и строчными буквами, следует использовать метод equalsIgnoreCase. "Hello".equalsIgnoreCase("hello");
Для проверки строк на равенство нельзя применять оператор ==] Он проверяет лишь, хранятся ли обе строки в одной и той же области памяти. Разумеется, если обе строки хранятся в одном и том же месте, они должны совпадать между собой. Однако вполне возможна ситуация, при которой идентичные строки хранятся в разных местах.
String greeting = "Hello"; // Инициализирует переменную
// greeting строкой.
if (greeting = "Hello")...
// Возможно, это условие истинно.
if (greeting.substring(0, 4) == "Hell") ...
// Возможно, это условие ложно.
ввЕсли виртуальная машина всегда позволяет совместно использовать одинаковые строки, то для проверки их равенства можно применять оператор ==. Однако совместно использовать можно лишь константы, а не строки, являющиеся результатами таких операций, как + или substring. Следовательно, либо нужно навсегда отказаться от проверок строк на равенство с помощью оператора ==, либо вы получите программу, содержащую наихудшую из возможных ошибок — перемежающуюся ошибку, появление которой невозможно предсказать.
в
в
Если вы используете класс string языка C++, будьте особенно осторожны при проверке строк на равенство. В классе string оператор == перегружен и позволяет проверять идентичность содержимого строк. Возможно, в языке Java разработчики напрасно отказались от возможности работать со строками как с числовыми значениями, однако это позволило сделать строки похожими на указатели. Разработчики могли переопределить оператор == для строк так, как это они сделали с оператором +. Что ж, каждый язык имеет свои недостатки.
Программисты на языке С никогда не используют для проверки строк на равенство оператор ==, вместо этого они вызывают функцию strcmp. Метод compareTo в языке Java представляет собой точный аналог функции strcmp. Можно, конечно, использоватьвыражения вида
if (greeting.compareTo("Help") ==0) ...
Однако нам кажется, что применение метода equals делает программу яснее.
Класс String в языке Java содержит более 50 методов. Неожиданно большое количество из них оказались полезными, так что можно легко себе представить, что они используются довольно часто. Приведенное ниже замечание об интерфейсе API содержит наиболее полезные из них.
Информация об интерфейсе API разбросана по всей книге для того, чтобы дать D вам представление о интерфейсе прикладных программ на языке Java (Java
Application Programming Interface — API). Каждое такое замечание начинается с имени класса, например, java.lang.String — значение так называемого имени пакета разъясняется в главе 5. После имени класса указываются имена, пояснения и описание параметров одного или нескольких методов.
Обычно в замечании не перечисляются все методы отдельного класса. Вместо этого из них выбираются и подробно описываются наиболее часто используемые методы. Полный список методов можно найти в оперативной документации о классе.
p ^ java.lang.String 1.0
char charAt(int index)
Возвращает символ, расположенный в указанной позиции.
int compareTo(String other)
Возвращает отрицательное значение, если строка предшествует строке other в лексикографическом порядке, положительное значение — если строка other предшествует данной строке в лексикографическом порядке, и 0 — если строки идентичны.
boolean endsWith(String suffix)
Возвращает значение true, если строка заканчивается подстрокой suffix.
boolean equals(Object other)
Возвращает значение true, если строка равна строке other.
boolean equalsIgnoreCase(String other)
Возвращает значение true, если строка равна строке other без учета различий между прописными и строчными буквами.
int index(String str)
int indexOf(String str, int fromlndex)
Возвращает начало первой подстроки, совпадающей со строкой str, начиная с позиции 0 или formindex.
int lastlndexOf(String str)
int lastlndexOf(String str, int fromlndex)
Возвращает начало последней подстроки, равной строке str, начиная с позиции 0 или f ormlndex.
int length(;
Возвращаетдлинустроки.
String replace(char oldChar, char newChar)
Возвращает новую строку, полученную заменой всех символов oldChar в строке символом newChar.
boolean startWith(String prefix)
Возвращает значение true, если строка начинается подстрокой prefix.
String substring(int beginlndex)
String substring(int beginlndex, int endindex)
Возвращает новую строку, состоящую из всех символов, начиная с позиции Beginlndex и заканчивая концом строки или позицией endlndex (не включая ее).
String toLowerCase()
Возвращает новую строку, состоящую из всех символов исходной строки, при этом все прописные буквы преобразовываются в строчные.
String toUpperCase()
Возвращает новую строку, состоящую из всех символов исходной строки, при этом все строчные буквы преобразовываются в прописные.
String trim()
Возвращает новую строку, из которой исключены все предшествующие и замыкающие пробелы.
Чтение оперативнойдокументации
об интерфейсе API
Как вы только что убедились, класс String имеет много методов. Более того, в стандартной библиотеке существует несколько сотен классов, m которых еще больше методов. Совершенно невозможно запомнить все полезные классы и методы. Следовательно, очень важно уметь пользоваться оперативной документацией об интерфейсе API, чтобы при необходимости в стандартной библиотеке можно было найти все классы и методы. Документация об интерфейсе API является составной частью набора инструментальных средств Java SDK. Она записана в HTML-формате. Установите ваш броузер на подкаталог docs/api/index/html в каталоге, в котором установлен ваш пакетJava SDK. Вы увидите экран, показанный на рис. 3.2.
Экран разделен на три окна. В небольшом окне в правом левом углу приведены все доступные пакеты. Под ним в окне побольше перечислены псе классы. Щелкните кнопкой мыши на любом из имен классов, и соответствующая документация об интерфейсе API будет показана в большом окне справа (рис. 3.3). Например, чтобы получить больше информации о методах класса String, прокрутите окно, пока не увидите ссылку String. Щелкните на ней.
Затем прокрутите окно вправо, пока не увидите краткое описание всех методов, упорядоченных в алфавитном порядке (рис. 3.4). Щелкните мышью на имени этого метода, чтобы получить его детальное описание (рис. 3.5). Например, если вы щелкнете мышью на ссылке compareToIgnoreCase, то получите описание метода compareToIgnoreCase.
Ot*eTAUUev*l4S42»*.t<.0-6oc*dow4p<w>d<UwN
КТОМ^ЬяскАПР Class Use Tree Deprecated Index HelpJava*** 2 Platform
FRAMESNOFRAMES
Std Ed *1.4.6.
PREV NEXT
Java" 2 PlatforJ
Std. Ed. V1A0
AllClasses
Java"2 Platform, Standard Edition, v 1.4.0API Specification
4
All Classes5
AbstractAction Ji
AOStrgctBorder 6
AbstractButtor
9
AbstractCelFdifnrX AbstractCollectior £ AbstractColorChocv AbstractDocumant X AbstradDocumant,'S
AbstractDocumentX Abstractlntarnjptibl6 AbstractLavoixCacX Abstract!, avoir ЗасЛ AbstractList AbstractListMode1 X AbstractMao ft AbstraclfvteihodErrj
Рент ^-'- 'jr
This document is the API specification for the Java 2 Platform, Standard Ed±on. version 1.4
See:
java, applet
Description
ига
Provide) the classes necessary to create an • applet and the classes an applet uses to communicate with its applet context.
java.awt
iava,awtcolor
Contains all ofthe classes for creating user ji interfaces and for painting graphics and images.
Provides classes for color spaces.
Provides interfaces and classes for transferring data between and within
Рис. З.2. Три окна с документацией па API
CHMTAUiJwmi 4 0»e* I 4_Мое**>омр*>*еMrN
leva.lanq.Objegc
*-3»va.lang.String
Statement
Java" 2Platfo Std. Ed.V1.4.0 AllClasses
Jarilang
Class String
All Implemented Interfaces:
CharSeouence Comparable. Sen actable
public final class String
extends Object
implements Sgnal^able. Comparable. CharSeouence
Packages
s.rroamSQi irre StreamTokenizer
StrictMath
String
StnngBuffer
Overview Package PHW.. Tr«« Deprecated Index Help **" 2 Modem
PR6VCLAS1 NirTCLAtlFRAMES.. NC FRAMERSid. Ed. vl.4.6
tiiMMAJtv мватао i nei p i соната i метнпе detail fieu> i сснатяуЕТиа»
Рис. 3.3. Описание класса String
--^^П|Г
Stata~
StateEdit
SlateEditable
SlalaPactorv
SlreamsbleVglue 5 SifeatnCoiruolsdE? StreamHandler > StreamPnntServictft StreamPri ntSeMCt'» StraamResult >
The string class represents character strings Ail string literals in Java programs, suchaa "abc". are implemented as instances of this class.
SmoBuMMSti jH rserpKf/ZZ^^fil
Strings are constant; their values cannot be changed after they are created. Stnngbuffers support mutable strings. Because Stmg objects are immutable they can be shared. For example:
DafiSTALLUev*l4 ОЧ?1*-1_4_0-Ооо*еое«ме*г*е M"4
All ClasseS
Packages
State
Java” 2 Platfori I Std. Ed. V1.4.0 Д
StateEditabta StateFactoiy
Statement Slreamable StreamableValuG
'.
StreamCorruptadE? StreamHandler
StreamPrintSevic»
StreamResult 5
StreamSoijme у StreamTokenizer :<
StnctMath
String StrinaBuffer
StnnaBufferlnputS
SMI—i^ti
a
Method Sommiry
rharAt(Inc index)
Returas the character at the specified index.
соярагеТоfObiecc о)
Compares this String to another Object
ojwaiereTolSctina anothecstrlng)
Compares two strings lexicographically.
conpareToIgnoreCase(String str)
Compares two strings lexicographically, ignoring case considerations.
SHAM
£QB£dt(Str Ino 3tr)
Concatenates the specified string to the end of this : tnng.
beelew
contenttgua.!» (Str ingBuft er sb)
Returns true if and only ifthis Stringrepresents the same sequence of characters as the specified StringBuffar
•eeetc JHiin*
copyValueOf(char[] data)
Returns a String that represents the character sequence n the array specified.
Value Of (chart* data. Inc offset, inc coiAt:
Returns a String that represents the character sequence in the array specified.
beelew
endWlth(String suffix!
Tests if this string ends with the specified suffix.
beelew
equal» (Object. anCtoject)
Compares this string to the specified object
Ш
Рис. 3.4. Сводка методов класса String
•” i— we
■a
compareToIgnoreCase
public int cceeareToIonoreCaee(String str]
У Da»4ST AlLUevMl 4 Os2s*l_4_6-doo**xsMp«*1dei hrrrd
AlCalsses
Packages
§
Compares two strings lexicographically. ignoring case consideration Thismethod returas an integer whose sign is that ofthis .toUpperCase () . toLowerCaes (} . compareTc
( str.toUpperCase().toLowerCase())
5
StateEditabta
StateFactoiy
Statement '
Statement
Strsemable
Note that this method does not take locale into account, and will result in an unsatisfactory ordering for certain locales. The java.text package provides collate r to allow localesensitive ordering
Streameble Value
StreamCorruotedE*
StreamHandler a"
StreamPnntServic*/'
StreamResult StreamSourca StreamTokenizer StrictMath String
Parameters:
str - the Serinato be compared
Returns:
a negative integer, zero, or a positive integer as the the specified String is greater than, equal to, or less than this String. ignoring case considerations
1.2
See Also:
Collator.cowpgce(Str Ina. String!
-I
rexionMatches
StnnqBuifer у
StrinqBuffednp itgrj
«—™мл/-глл^а
public boolean realonKatchee(inc toffset.
Рис.З.5. Детальное описание метода CompareTolgnoreCase класса String
FСразу сделайте закладку в вашем броузере на каталогdocs/ар1/index.html.
Считывание ввода
Вы уже видели, как легко можно вывести информацю на "стандартное устройство вывода" (т.е. консольное окно), вызвав метод System.out.println. К сожалению, считать информацию со "стандартного устройства ввода" (т.е. клавиатуры) немного сложнее.
Однако создать диалоговое окно для ввода данных с клавиатуры достаточно легко. Вызов метода
JOptionPane.showInputDialog(promptstring)
выводит на экран диалоговое окно, в котором пользователь может набрать свои данные (рис. 3.6). Этот метод возвращает строку, набранную пользователем в этом окне.
Рис. 3.6. Окно ввода
Например, здесь показано, как запросить имя пользователя вашей программы.
String name = JOptionPane.showInputDialog("What is your name?");
Чтобы считать число, нужно выполнить более сложную работу. Метод JOptionPane. showInputDialog возвращает строку, а не число. Для преобразования этой строки в число нужно использовать метод Integer.parselnt или Double.parseDouble,например:
String input = JOptionPane.showInputDialog("Howold are you?"); int age = Integer.parselnt(input);
Если пользователь наберет на клавиатуре число 4 0, то строковой переменной input будет присвоена строка "40". Метод Integer .parselnt преобразовывает строку в соответствующее число, т.е. 40.
Если параметр метода parselnt содержит символы, не являющиеся цифрами, метод возбуждает исключительную ситуацию. Если ваша программа не "перехватывает" эту исключительную ситуацию, виртуальная машина прекращает ее выполнение и выводит на консоль сообщение об ошибке. В главе 11 мы покажем, как перехватывать исключительные ситуации.
Программа, показанная в листинге 3.2, запрашивает имя пользователя и его возраст, а затем выводит сообщение, например, такое:
Hello, Cay. Next year, you'll be 41.
При выполнении этой программы сначала на экране возникнет диалоговое окно, в котором нужно ввести ваше имя. Затем это окно исчезнет, и во вновь появившемся вто-
ром диалоговом окне нужно будет ввести ваш возраст. Результат работы программы будет выведен в консольное, а не диалоговое окно. Разумеется, это не очень элегантно. В следующих главах мы покажем, как создать более привлекательные пользовательские интерфейсы. Пока остановимся на методах JOptionPane. showsInputDialog и System.out.println, поскольку их легче всего использовать.
Заметим, что программа завершает свою работу вызовом метода
System.exit(0);
Каждый раз, когда программа вызывает метод JOptionPane.showsInputDialog, ее работу необходимо завершать вызовом метода System, exit(0). В основном это вызвано техническими причинами. Вывод на экран диалогового окна запускает новый поток управления. При завершении работы метода main этот новый поток управления не прекращает свою работу автоматически. Чтобы закрыть все потоки, нужно вызвать метод System.exit. (Более подробную информацию о потоках можно найти в главе 1 тома 2.)
Метод System, exit получает целочисленный параметр, представляющий собой "код выхода" из программы. По умолчанию, если работа программы завершилась нормально, ее код выхода равен 0, в противном случае этот код не равен нулю. Для индикации разных ошибочных ситуаций можно использовать разные коды выхода. Завершенная программа передает свой код выхода операционной системе. Затем сценарии и bat-файлы могут проверять этот код.
В заключение обратите внимание на строку
import javax.swing.*;,
расположенную в начале программы. Класс JOptionPane определен в пакете j swing. Используя класс, не определенный в основном пикете j ava.lang,нужно применять директиву import. Пакеты и директива import рассматриваются в главе 5.
import javax.swing.*;
public class InputTest
{
public static void main(String[] args)
{
// Первый ввод.
String name = JOptionPane.showInputDialog
("Назовите Ваше имя");
// Второй ВВОД.
String input = JOptionPane.showInputDialog
("Сколько Вам лет?");
// Преобразовать строку в целое число.
int age = Integer.parselnt(input);
// Вывести результат на консоль.
System.out.printin("Привет, " + name +
". В следующем году Вам будет " + (age + 1) );
System.exit(0);
)
)
javax.swing.JOptionPane 1.2
static String showInputDialog(Object message)
Выводит на экран диалоговое окно с сообщением, полем ввода и клавиша-
ми "Ok" и "Cancel". Метод возвращает строку, введенную пользователем.
java.lang.System 1.0
static void exit(int status)
Прекращает работу виртуальной машины и передает код выхода операционной системе. По умолчанию ненулевой код выхода означает ошибку.
Форматирование вывода
Число х можно вывести на консоль с помощью оператора System.out.println (x). Эта команда напечатает число х с максимальным количеством ненулевых цифр, допустимым для данного типа чисел. Например, код х = 10000.0 / 3.0;
System.out.print(x);
выведет на экран число 3333.3333333333335
Это создает проблемы, если вы хотите, например, вывести на экран количество долларов и центов.
Чтобы сделать ваш вывод более аккуратным, можно управлять его форматом. Класс NumberFormat в пакете j ava. text имеет три метода, предоставляющих стандартные средства форматирования для
чисел;
денежных сумм;
процентов.
Допустим, что по умолчанию вы пользуетесь американским национальным стандартом. (Национальный стандарт (local) — это множество спецификаций строк и чисел, характерных для данной страны, например, порядок следования чисел в датах, символ денежной единицы и т.д. Национальный стандарт — важное понятие при создании интернационализированных приложений— программ, доступных пользователям во всем мире. Вопросы интернационализации мы обсудим во втором томе.) Теперь значение 10000.0/3.Ocпомощью ЭТИХ трех форматов будет напечатано в следующем виде: 3,333.333 $3,333.33 333,333%
Как видим, средства форматирования добавляют запятые для разделения тысяч, символ денежной единицы ($) и знак процента.
Чтобы получить средства форматирования для национального стандарта по умолчанию, используется один из трех методов.
NumberFormat.getNumberlnstance(I
NumberFormat.getCurrencylnstance()
NumberFormat.getPercentlnstance(]
Каждый из этих методов возвращает объект, имеющий тип NumberFormat. Этот объект можно использовать для форматирования одного или нескольких чисел. Затем, чтобы получить строку, содержащую отформатированное число, к объекту типа NumberFormat нужно применить методПолучивстро
ку, возможно, вы просто выведете ее на экран.
double х - 10000.0/3.0;
NumberFormat formatter =
String s =// Строка "3,333.33".
;
Если вы захотите задать минимальное и максимальное количество цифр в целой или дробной части числа, можете воспользоваться методами setMinimumFractionDigits,и
Digits из классаFormat.
double x = 10000.0/3.0;
NumberFormat formatter =
formatter.setMaximumFraction(4);
formatter.setMinimumlntegerDigits(6);
String s =// Строка "003,333.3333".
Установка максимального количества цифр в дробной части нередко оказывается полезной. Последняя отображаемая цифра увеличивается на единицу, если отброшенная цифра равна или больше 5. Если нужно отобразить замыкающие нули, то минимальное количество цифр в дробной части должно бытьих максимальному
количеству. В противном случае следует оставить минимальное количество цифр в дробной части равным его значению по умолчанию, т.е. 0.
Установка количества цифр в целой части числа используется намного реже. Задавая это количество, пользователь вынуждает средства форматирования вставлять ведущие нули перед маленькими числами. Совершенно очевидно, что указывать максимальное количестве цифр в целой части опасно — отображаемое число будет усечено, что приведет к более красиво отформатированному, но неверному результату.
Если вы знакомы с функцией f из языка С и обожаете ее простоту, проверьте класс Format, приведенный на Web-странице
j html. Это класс из языка Java, который полностью воспроизводит работу функции f. Например, оператор printf ", 10000.0/3.0) выводит на печать строку " 3333.33" (с ведущим который чтобы ширина поля равнялась 8, и двумя цифрами после десятичной точки).
Существуют также форматы, соответствующие другим национальным стандартам. Например, рассмотрим числовые форматы, принятые в Германии, и выведем на экран тест. Существует встроенный объект под именем Locale .GERMANY, имеющий тип Locale, в котором хранятся правила форматирования чисел в соответствии с национальным стандартом Германии. Передавая объект типа Locale методу GetNumberInstance, мы получим нужные нам средства форматирования.
double х = 10000.0/3.0;
NumberFormat formatter =
System.out.printIn(formatter.format(x;);
formatter = NumberFormat.getCurrencylnstance(Locale.GERMANY);
System.out.printin(formatter.format(x));
Этот фрагмент кода выведет на печать следующие числа: 3.333,333 3.333,33 DM
Заметим, что принятые в Германии правила употребления точек и запятых в числах в точности противоположны американским: запятая используется в качестве десятичного разделителя, а точка— в качестве разделителя тысяч. Кроме того, средства форматирования учитывают, что знак денежной единицы (DM) указывается после числа.
pT^java.text.NumberFormat 1.1
e static NumberFormat getCurrencylnstance()
Возвращает объект, имеющий тип NumberFormat, для преобразования значений денежной единицы в строку, пользуясь установленным национальным стандартом.
static NumberFormat getNumberlnstance()
Возвращает объект, имеющий тип NumberFormat, для форматирования чисел в соответствии с установленным национальным стандартом.
static NumberFormat getPercentlnstance()
Возвращает объект, имеющий тип NumberFormat, для преобразования процентов в строки.
String format(double number)
Возвращает строку, содержащую отформатированное число.
void setMaximumFractionDigits(int digits)
Параметры: digitsКоличество отображаемых цифр.
Устанавливает максимальное количество цифр после десятичной точки в форматируемом объекте. Последняя отображаемая цифра округляется.
void setMaximumlntegerDigits(int digits)
Параметры: digitsКоличество отображаемых цифр.
Устанавливает максимальное количество цифр перед десятичной точкой в форматируемом объекте. Используйте этот метод с предельной осторожностью. Если вы зададите слишком мало цифр, число будет просто усечено, и результат станет совершенно неправильным!
void setMinimumFractionDigits(int digits)
Параметры: digits Количество отображаемых цифр.
Устанавливает минимальное количество цифр после десятичной точки в форматируемом объекте. Если количество цифр в дробной части числа меньше минимального, то на экран выводятся замыкающие нули.
void setMinimumlntegerDigits(int digits)
Параметры: digitsКоличество отображаемых цифр.
Устанавливает минимальное количество цифр перед десятичной точкой в форматируемом объекте. Если количество цифр в целой части числа меньше минимального, то на экран выводятся ведущие нули.
Поток управления
В языкеJava, как и в любом другом языке программирования, есть условные операторы и циклы для управления потоком. Начнем с условных операторов, а затем перейдем к циклам. Наш экскурс завершится довольно неуклюжим оператором switch, который можно применять при проверке большого количества значений одного зыражения.
Поток управления в языке Java по .троен точно так же, как и в языках С и C++, за исключением двух моментов. В нем нет оператора got з, однако существует версия оператора break с метками, который можно исгользовать для выхода из вложенного цикла (в языке С для этого пришлось бы применять оператор goto).
Область видимости блока
Перед изучением действительно управляющих структур нам необходимо поболь- шеузнатьо блоках.
Блок, или составной оператор, — это произвольное количество простых операторов языка Java, заключенных в фигурные скобки. Блоки определяют область видимости своих переменных. Блоки могут быть вложенными один в другой. Вот пример блока, вложенного внутрь другого блока в методе main.
public static void main(String[] args)
int n;
int k;
} // Переменная к определена только в этом блоке.
}
Однако в языке Java невозможно объявить одинаково названные переменные в двух вложенных блоках. Например, приведенный ниже фрагмент кода содержит ошибку и не будет скомпилирован.
public static void main(String[ args) {
int n;
( int k; int n; // Ошибка -- невозможно переопределить переменную // п во внутреннем блоке.
}
В языке C++ переменные во вложенных блоках можно переопределять. Внутреннее определение затем маскируется внешним. Это может привести к ошибкам, поэтому в языке Java такой возможности нет.
Условные операторы
Условный оператор в языке Java имеет вид:
if (условие) оператор
Условие должно указываться в скобках.
В языке Java, как и в большинстве других языков программирования, часто необходимо выполнять много операторов в зависимости от выполнения одного условия. В этом случае используется блочный оператор, имеющий следующий вид.
(.
оператор! ;
операторг;
1
Например,
if (yourSales >= target)
{
performance = "Удовлетворительно";
bonus = 100;
В этом коде все операторы, заключенные в фигурные скобки, будут выполнены, если значение переменной YourSales больше значения переменной target или равно ему (рис. 3.7).
Блок (иногда называемый составным оператором) позволяет включать несколько (простых) операторов в любую структуру языка Java, которая в ином случае состояла бы лишь из одного (простого) оператора.
Рис. 3.7. Блок-схема оператора if
Более общий условный оператор в языке Java выглядит так (рис. 3.8). if {условие) оператор] else операторз
Например,
if (yourSales >= target) {
performance = "Удовлетворительно");
bonus = 100 + 0.01 * (yourSales - target); } else {
performance = "Неудовлетворительно";
bonus =0;
Часть else не является обязательной. Группы операторов else объединяются с ближайшим оператором if. Таким образом, в коде
if (х <= 0) if (х == 0) sign = 0; else sign = -1;
оператор else соответствует второму оператору if.
Рис. 3.8. Блок-схема оператора /else
elseifelseifЧасто встречаются также повторяющиеся операторы if (рис. 3.9). Например, if (yourSales >= 2 * target) {
elseif
elseif
performance = "Великолепно";
bonus1000;
}
else if (yourSales >= 1.5 * target)
performance = "Отлично";
bonus = 500;
else if (yourSales >= target) {
performance = "Удовлетворительно";
bonus = 100;
} else
{
уволены");
Рис. 3.9. Блок-схема оператора i /else if (многовариантное ветвление)
Неопределенные циклы
В языке Java, как и во всех языках программирования, существуют управляющие структуры, позволяющие выполнять повторяющиеся операторы. Есть два вида повторяющихся циклов, которые лучше всего подходят, если вы точно не знаете, сколько повторений должно быть выполнено (они называются "неопределенными циклами").
Первый из них, цикл while, выполняет тело цикла, только пока выполняется его условие. Вот его общий вид: while (условие) оператор
Цикл while не будет выполнен ни разу, если его условие никогда не выполнится
(рис. 3.10).
Рис. 3.10. Блок-схема оператора whileРис. 3.10. Блок-схема оператора while
Рис. 3.10. Блок-схема оператора while
Рис. 3.10. Блок-схема оператора while
В листинге 3.3 показана программа, подсчитывающая, как долго нужно откладывать деньги, чтобы к выходу на пенсию накопить заданную сумму, при условии, что вы ежегодно откладываете одну и ту же сумму и размер счета возрастает в соответствии с процентной ставкой.
В этом примере мы увеличиваем счетчик и обновляем в теле цикла сумму, накопленную к данному моменту, пока общий итог не превысит заданную величину. while (balance < goal) {
balance += payment;
double interest = balance * interestRate / 100;
balance + = interest;
years++;
(He полагайтесь на эту программу при планировании своей пенсии. Мы отбросили такие детали, как инфляция и ожидаемая продолжительность вашей жизни.)
Условие цикла while проверяется в самом начале. Следовательно, возможна ситуация, когда код, содержащийся в блоке, не будет выполнен никогда. Если вы хотите, чтобы блок выполнялся хотя бы один раз, проверку условия нужно перенести в конец. Это можно сделать с помощью цикла do/while. Его синтаксис выглядит так: do оператор while (условие);
Прочитатьответ
Рис. 3.11. Блок-схема оператора do/while
Этот оператор выполняет блок и только затем проверяет условие. Затем он повторяет выполнение блока и вновь проверяет условие и т.д. Например, код в листинге 3.4 вычисляет новый баланс вашего пенсионного счета, а затем спрашивает вас, не собираетесь ли вы на заслуженный отдых.
do
(
balance += payment;
double interestbalance * interestRate / 100;
balance += interest;
year++;
// Вывести на печать текущий баланс
// Спросить, не собирается ли пользователь на пенсию, и получить ответ. }
Если пользователь отвечает "N", цикл повторяется (см. рис. 3.11). Эта программа является хорошим примером применения циклов, которые нужно выполнить хотя бы один раз, поскольку пользователю нужно видеть баланс, перед тем как решить, достаточно ли его для пенсии.
import javax.swing.*;
public class Retirement
{
public static void main(String[] args)
{
// read inputs
String input = JOptionPane.showInputDialog
("Сколько денег вам нужно для пенсии?");
double goal = Double.parseDouble(input);
input = JOptionPane.showInputDialog
("Каков объем вашего ежегодного взноса?");
double payment = Double.parseDouble(input);
input = JOptionPane.showInputDialog
("Процентная ставка, %:") ;
double interestRate = Double.parseDouble(input);
double balance0;
int years = 0;
// Обновлять баланс счета, пока не накопится нужная сумма
while (balance < goal)
{ // Добавить ежегодный взнос и процент
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
years++;
}
System.out.printin
("Можете идти на пенсию через " + years + " лет.");
System.exit(0);
}
}
б.
15.
16.
17.
18.
19.
20.
21.
22.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
import import
public {
java.text.* javax.swing
class Retirement2
public static void main(String!] args) (
String input = JOptionPane.showInputDialog
("Каков объем вашего ежегодного взноса?"); double payment = Double.parseDouble(input);
input = JOptionPane.showInputDialog
("Процентная ставка, %: ") ;
double interestRate = Double.parseDouble(input);
double balance = 0; int year = 0;
NumberFormat formatter =
// Обновлять баланс, пока пользователь не уйдет на пенсию do
(
// Добавить ежегодный взнос balance += payment;
double interest = balance * balance += interest;
и процентную ставку
interestRate / 100;
}
}
year++;
// Вывести текущий баланс System.out.printin("После " + " на вашем счету "
+ year + лет"
+ formatter.format(balance));
// Спросить, не собирается ли пользователь на пенсию input = JOptionPane.showInputDialog
("Вы собираетесь на пенсию? (Y/N)");
input = input.toUpperCase();
}
while (input.equals("N"));
System.exit(0);
Определенные циклы
Цикл for— очень распространенная конструкция для выполнения повторений, количество которых контролируется счетчиком или аналогичной переменной, обновляемой на каждой итерации. Как показано на рис. 3.12, следующий цикл выводит на экран числа от 1 до 10.
for (int i = 1; i <= 10; i++)
System.out.printIn(i);
Первый элемент оператора for обычно выполняет инициализацию счетчика, второй формулирует условие выполнения тела цикла, а третий определяет способ обновления счетчика.
Хотя в языке Java, как и в языке C++, элементами оператора цикла forмогут быть практически любые операторы, существуют неписанные правила хорошего тона, которые утверждают, что три элемента оператора for должны только инициализировать, проверять и обновлять один и тот же счетчик. Если не придерживаться этих правил, можно написать совершенно невразумительные циклы.
Даже следуя правилам хорошего тона, можно сделать многое. Например, можно написать цикл с убывающим счетчиком.
for (int i = 10; i > 0; -i)
System.out.prinln("Обратный отсчет ..." + i);
System.out.printin("Старт!);
Будьте осторожны, проверяя в цикле равенство двух чисел с плавающей точкой. Представленный ниже цикл может никогда не завершиться:
for (double x = 0; х != 10; х += 0.1)...
Из-за ошибок округления окончательный результат никогда не будет достигнут. Например, в приведенном выше цикле переменная х изменит свое значение с 9.999999999999998 на 10.099999999999998, поскольку для числа 0.1 не существует точного двоичного представления.
При объявлении переменной в первой части оператора for ее область видимости простирается до конца тела цикла.
for (int i = 1; i <= 10; i++)
}
// Здесь переменная i больше не определена.
В частности, если переменная определена внутри оператора for, ее нельзя использовать вне этого цикла. Следовательно, если вы хотите использовать конечное значение счетчика вне цикла for, эту переменную нужно объявлять до цикла!
int i;
for (i = 1; i <= 10; i+*) {
} // Здесь переменная i продолжает быть определенной.
С другой стороны, можно объявить переменные, имеющие одинаковое имя в разных циклах for.
for (int i 1; i10; i++)
{
}
for (int i = 11; i <= 20; i++) // Переопределение переменной i. {
}
Разумеется, цикл for эквивалентен циклу while. Точнее, оператор for (опера торг, выражение!, выражениег> операторг;
эквивалентен следующей конструкции.
{
оператор!;
while (выражение!)
{
оператор].;
} оператор?;
}
В листинге 3.5 показан типичный пример использования цикла for.
Эта программа вычисляет вероятность выигрыша в лотерее. Например, если нужно угадать 6 номеров от 1 до 50, количество возможных вариантов равно (50 х 49 х 48 х 47 X 46 х 45)/(1 х2хЗх4х5х6),так что шанс выиграть равен 1 из 15890700. Удачи!
В общем, если нужно угадать к номеров из п, количество возможных вариантов равно
лх(л-1)х(л-2)х„.х(л- + о.
Ix2x3x...xjt
Следующий цикл for вычисляет эту величину.
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds *(n - i + 1) / i;
import javax.swing.*;
public class LotteryOdds
{
public static void main(String(] args)
{
String input = JOptionPane.showInputDialog
("Сколько номеров нужно угадать?");
int k = Integer.parselnt(input);
input = JOptionPane.showInputDialog
("Чему равен наибольший из возможных номеров?");
int n = Integer.parselnt(input);
/*
Вычисление биномиальных коэффициентов
п* (п-1) * (п-2) * . . . * (п-к+1)
1О__
±o .■. ■.**■■ ■. ■■ .* *^ ■* — ^— ** ^™™™ ^™ ^^ ^* ^™ ^™ ^* ■■ ^™^™ e*^™ ^v ■» ^e ^e .. .e ■■ ■■ ■■ ^e ^e
1*2*3* . . . * к
*/
int lotteryOdds = 1;
for (inti=1; i<=k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / 1;
System.out.printIn
("Ваш шанс равен 1 из " + lotteryOdds + ". Удачи!")
System.exit (0);
}
}
Многовариантное ветвление — оператор switch
Конструкция if/else может оказаться неудобной, если вы стоите перед необходимостью сделать выбор из многих вариантов. В языке Java есть оператор switch, эквивалентный оператору switch из языков С и C++.
Например, создавая систему меню, состоящую из четырех альтернатив, как показано на рис. 3.13, можно использовать следующий код.
Strin"g Input = JOptionPane.showInputDialog
("Выберите вариант (1, 2, 3, 4)");
int choice = Integer.parselnt(input);
switch (choice) {
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
default:
// Неверный выбор.
break;
}
Выполнение начинается с метки case, соответствующей значению переменной choice, и продолжается до следующего оператора break или конца оператора switch. Если ни одна метка не совпадает со значением переменной, выполняется раздел default, если он предусмотрен.
Заметим, что метка case должна быть целочисленной. Нельзя проверять строки. Например, в следующем фрагменте кода сделана ошибка.
String input = . . . ;
switch (input) // ОШИБКА {
case 'A':// ОШИБКА
break;
)
Если вы забудете добавить в конце раздела case оператор break, возможно выполнение сразу нескольких разделов case, поскольку при этом поток выполнения программы проходит в следующий раздел. Совершенно очевидно, что эта ситуация представляет большую опасность и обычно приводит к ошибкам, поэтому мы никогда не используем оператор switch в своих программах.
Рис. 3.13. Блок-схема оператора switch
Прерывание потока управления
Несмотря на то что разработчики языка Java сохранили зарезервированное слово goto, они решили не включать его в язык. В принципе, применение операторов goto считается признаком плохого стиля программирования. Некоторые программисты считают, что борьба с использованием оператора goto ведется недостаточно активно (см., например, знаменитую статью Дональда Кнута "Структурное программирование с использованием операторов goto" (DonaldProgramming with
goto's"). Они считают, что неограниченное применение операторов goto может приводить к ошибкам, однако в некоторых случаях нужно выполнять преждевременный выход изСоздатели языка Java согласились с их аргументами и даже добавили в
язык новый оператор для поддержки такого стиля программирования— оператор break с меткой.
Рассмотрим сначала оператор break без метки. Для выхода из цикла можно применять тот же оператор, что использовался для выхода из тела оператора switch. Например, так.
while (years <= 100)
{
balance += payment;
double interest = balance * interestRate / 100; balance += interest;
>= goal) break;
years++;
}
Теперь выход из цикла выполняется при двух условияхв начале цикла
years>100,либовнутрицикла balance>=goal. Разумеется,тожезначение
можно было бы вычислить и без применения оператора break, например, так. while (years <= 100 balance < goal) f
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
< goal)
years++;
}
Заметим, однако, что проверка условия balance < goal а этом варианте повторяется дважды. Чтобы избежать этого, некоторые программисты используют оператор break.
В отличие от языка C++, язык Java содержит также оператор break с меткой, позволяющий производить выход из вложенных циклов. Если в глубоко вложенном цикле выполняется некоторое условие, возможно, потребуется вообще выйти из всех вложенных циклов. Программировать дополнительные условия для проверки каждого вложенного цикла просто неудобно.
Ниже приводится пример, иллюстрирующий работу оператора break с меткой. Заметим, что метка должна предшествовать самому внешнему циклу, из которого требуется выйти. За меткой нужно поставить двоеточие. int n;
read_data:
while (...)// Этот цикл помечен.
for (...) // Этот внутренний цикл не помечен.
{
String input
=число >= 0")
n =
if (n < 0) // Если это условие никогда не выполнится, // то мы никогда не выйдем из break read_data;
// Выход из цикла с меткой read_data.
)
// Этот оператор выполняется сразу после оператора break.
if (n < 0) // Проверка исключительной ситуации. {
// Обработка исключительной ситуации.
} else {
// Возвращение к нормальному выполнению программы.
}
Если в программу было введено неверное число, оператор break с меткой выполнит переход в конец помеченнного блока. При каждом применении оператора break необходимо проверять, нормально ли осуществлен выход из цикла или он произошел в результате выполнения оператора break.
Любопытно, что метку можно приписать любому оператору, даже оператору if и составному оператору, например:
метка:
{
if {условие) break label; // Выход из блока.
// При выполнении оператора break управление передается в эту точку.
Итак, если вам очень нужен оператор goto, и вы можете поместить блок, из которого нужно выйти, непосредственно перед местом, куда вы хотите перейти, можно применить оператор break! Естественно, мы не рекомендуем применять этот способ. Заметим, однако, что таким образом можно выйти из блока, но невозможно войти в него.
Кроме того, существует оператор continue, который, подобно оператору break, прерывает нормальное выполнение программы. Оператор continue передает управление в начало наиболее глубоко вложенного цикла. Например, так. while (sum < goal) {
String input =число");
n =
if (n < 0) continue;
sum += n; // выполняется, если n < 0.
Если п < 0, то оператор continue выполняет переход к заголовку цикла, пропуская оставшуюся часть текущей итерации.
Если оператор continue используется в цикле for, он передает управление оператору увеличения счетчика цикла.
for (count = 0; count < 100; count ++) {
String input =числа");
n = if (n < 0) continue; sum += n; //выполняется, если n < 0.
Если n < 0, то оператор continue выполнит переход к оператору count++.
В языке Java есть также оператор continue с меткой, передающий управление заголовку оператора цикла, помеченного соответствующей меткой.
Многие программисты считают операторы break и continue запутанными. Применять эти операторы вовсе не обязательно — ту же логику можно выразить, не прибегая к ним. В этой книге мы нигде не используем операторы break и continue.
Большие числа
Если вам недостаточно точности встроенных целочисленных типов и чисел с плавающей точкой, можно обратиться к двум полезным классам в пакетепод
названием Biglnteger и BigDecimal. Эти классы предназначены для манипуляций с числами, состоящими из произвольного количества цифр. Классы Biglnteger и BigDecimal реализуют арифметические операции произвольной точности для целых и действительных чисел соответственно.
Для преобразования обычного числа в большое используется статический метод valueOf:
Biglnteger a =
К сожалению, к большим числам нельзя применять обычные математические операторы + и *. Вместо этого программист должен использовать методы add и multiply из классов для работы с большими числами.
Biglnteger с = a.add(b); // с = а + b Biglnteger d =
// d - с * (b + 2)
В отличие от языка C++, язык Java не поддерживает перегрузку операторов. Программисты, создававшие класс Biglnteger, были лишены возможности переопределить операторы + и * для методов add и multiply в классе Biglnteger. Разработчики языка перегрузили лишь оператор + для конкатенации строк, решив не перегружать остальные операторы и не дав возможности остальным програ- мистам на языке Java перегружать операторы самостоятельно.
В листинге 3.6 показана модификация программы для подсчета шансов выиграть в лотерее, приведенной в листинге 3.5. Теперь эта программа может работать с большими числами. Например, если вам предложили сыграть в лотерее, в которой нужно угадать 60
чисел из 490 возможных, то эта программа сообщит вам, что шанс выиграть равен 1 из 716395843461995557415116222540092933411717612789263493493351013459481104668848.
Удачи!
Программа",представленнаявлистинге3.5,вычисляласледующийоператор:
lotteryOdds = lottery * (n - i + 1) / i;
При работе с большими числами эквивалентный оператор выглядит так.
lotteryOdds = lotteryOdds.multiply(Biginteger.valueOf(n-i+1)
.divide(Biginteger.valueOf(i));
import javax.swing.*;
import java.math.*;
public class BiglntegerTest{public class BiglntegerTest{9.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.}9.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.}public static void main(String[] args){public static void main(String[] args){String input = JOptionPane.showInputDialog("Сколько номеров нужно угадать?");int k = Integer.parselnt(input);String input = JOptionPane.showInputDialog("Сколько номеров нужно угадать?");int k = Integer.parselnt(input);input = JOptionPane.showlnputDialog("Чему равен наибольший из возможных номеров?"); int n = Integer.parselnt(input);input = JOptionPane.showlnputDialog("Чему равен наибольший из возможных номеров?"); int n = Integer.parselnt(input);Вычисление биномиальных коэффициентовВычисление биномиальных коэффициентовп * (п - 1) * (п - 2) • *п * (п - 1) * (п - 2) • *1*2*3*.1*2*3*...... * (п - к + 1). * (п - к + 1)Biglnteger lotteryOdds = Biginteger.valueOf(1);Biglnteger lotteryOdds = Biginteger.valueOf(1);for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds.multiply(Biginteger.valueOf(n - i + 1)) .divide(Biginteger.valueOf(i));for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds.multiply(Biginteger.valueOf(n - i + 1)) .divide(Biginteger.valueOf(i));System.out.printin("Ваш шанс равен 1 из " + lotteryOdds + ". Удачи!");System.out.printin("Ваш шанс равен 1 из " + lotteryOdds + ". Удачи!");}}System.exit(0) ;System.exit(0) ;
public class BiglntegerTest
{
public class BiglntegerTest
{
9.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
}
9.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
}
public static void main(String[] args)
{
public static void main(String[] args)
{
String input = JOptionPane.showInputDialog("Сколько номеров нужно угадать?");
int k = Integer.parselnt(input);
String input = JOptionPane.showInputDialog("Сколько номеров нужно угадать?");
int k = Integer.parselnt(input);
input = JOptionPane.showlnputDialog
("Чему равен наибольший из возможных номеров?"); int n = Integer.parselnt(input);
input = JOptionPane.showlnputDialog
("Чему равен наибольший из возможных номеров?"); int n = Integer.parselnt(input);
Вычисление биномиальных коэффициентов
Вычисление биномиальных коэффициентов
п * (п - 1) * (п - 2) • *
п * (п - 1) * (п - 2) • *
1*2*3*.
1*2*3*.
..
..
. * (п - к + 1)
. * (п - к + 1)
Biglnteger lotteryOdds = Biginteger.valueOf(1);
Biglnteger lotteryOdds = Biginteger.valueOf(1);
for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds
.multiply(Biginteger.valueOf(n - i + 1)) .divide(Biginteger.valueOf(i));
for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds
.multiply(Biginteger.valueOf(n - i + 1)) .divide(Biginteger.valueOf(i));
System.out.printin("Ваш шанс равен 1 из " + lotteryOdds + ". Удачи!");
System.out.printin("Ваш шанс равен 1 из " + lotteryOdds + ". Удачи!");
}
}
System.exit(0) ;
System.exit(0) ;
java.math.Biginteger 1.1java.math.Biginteger 1.1
java.math.Biginteger 1.1
java.math.Biginteger 1.1
Biglnteger add(Biginteger other)
Biglnteger subtract(Biginteger other)
Biglnteger multiply(Biginteger other)
Biglnteger divide(Biginteger other)
Biglnteger mod(Biginteger other)
Эти функции возвращают сумму, разность, произведение, частное и остаток от деления данного большого числа на значение переменной other.
int compareTo(Biginteger other)
Возвращает 0, если данное большое число равно значению переменной other, отрицательное число, если данное большое число меньше значения переменной other, и положительное число в противном случае.
static Biglnteger valueOf(long x)
Возвращает большое число, значение которого равно значению переменной х.
^^^3 ava • mathBigDecimal 1.1
BigDecimal add(BigDecimal other)
BigDecimal subtract(BigDecimal other)
BigDecimal multiply(BigDecimal other)
BigDecimal divide(BigDecimal other, int roundingMode’
Возвращает сумму, разность, произведение и частное от деления большого десятичного числа на значение переменной other. Чтобы вычислить частное, нужно поддерживать режим округления. Режим BigDecimal. ROUND_HALF_UF означает обычное округление (т.е. в сторону уменьшения, если последняя цифра меньше 5, и в сторону увеличения, если она больше 5). Для обычных вычислений этого достаточно. Другие режимы округления описаны в документации по интерфейсу API.
int compareTo(BigDecimal other)
Возвращает 0, если данное число типа BigDecimal равно значению переменной other, отрицательное число, если данное число меньше значения переменной other, и положительное число в противном случае.
static BigDecimal valueOf(long x)
static BigDecimal valueOf(long x, int scale)
Возвращает большое десятичное число, значение которого равно значению переменной х или x/KT*.
Массивы
Массив — это структура данных, в которой хранятся величины одинакового типа. Доступ к отдельному элементу массива осуществляется с помощью целого индекса. Например, если а — массив целых чисел, то значение выражения а [ i ] равно i-му целому числу в массиве.
Массив объявляется следующим образом: сначала указывается тип массива, т.е тип элементов, содержащихся в массиве, за которым ставится пара пустых квадратных скобок, а затем — имя переменной. Например, вот как объявляется массив, состоящий из целых чисел: int[]a;
Однако этот оператор лишь объявляет переменную а, не инициализируя ее настоящим массивом. Чтобы создать массив, нужно применить оператор new.
int I I a, = new int[100];
Этот оператор создает массив, состоящий из 100 целых чисел. Элементы этого массива нумеруются от0 до99 (а неот1до100).Послесозданиямассивзапол
нять, например, с помощью цикла.
а = new int[100];
for (int i = 0; i < 100; i++)
a[i] = i; // Заполняет массив числами от 0 до 99.
Если вы попытаетесь обратиться к элементу а [100] (или любому другому элементу, индекс которого выходит за пределы диапазона от 0 до 99), создав массив, состоящий из 100 элементов, программа прекратит работу, поскольку возникнет исключительная ситуация, связанная с выходом индекса массива за пределы допустимого диапазона.
Чтобы подсчитать количество элементов в массиве, используйте метод ва .Например,
for (int i = 0; i <i++
System.out.print in(a[i]);
После создания массива изменить его размер невозможно (хотя можно, конечно, изменять отдельные его элементы). Если в ходе выполнения программы необходимо часто изменять размер массива, лучше использовать другую структуру данных, называемую списком массивов (array list). (Подробнее этот вопрос изложен в главе 5.)
Массив можно объявить двумя способами:
int[]a;
или
int a[];
Большинство программистов на языке Java предпочитают первый стиль, поскольку в нем четче отделяется тип массива int [ ] (целочисленный массив) от имени переменной.
Инициализаторы массивов и безымянные массивы
В языке Java есть средство для одновременного создания массива и его инициализации. Вот пример такой синтаксической конструкции:
int[] smallPrimes = { 2, 3, 5, 7, 11, 13};
Отметим, что в этом случае не нужно применять оператор new.
Кроме того, можно даже инициализировать безымянный массив: new int[]19,23,29,31,37}
Это выражение выделяет память для нового массива и заполняет его числами, указанными в фигурных скобках. При этом подсчитывается их количество и, соответственно, определяется размер массива. Эту синтаксическую конструкцию удобно применять для повторной инициализации массива без образования новой переменной. Например, выражение smallPrimes = new int{ 17, 19, 23, 29, 31, 37 };
представляет собой укороченную запись выражения
int[] anonymous = { 17, 19, 23, 29, 31, 37 }; smallPrimes = anonymous;
Можно создать массив нулевого размера. Такой массив может оказаться полезным при написании метода, вычисляющего некий массив, который оказывается пустым. Массив нулевой длины объявляется следующим образом:
new типЭлементов[ ]
Заметим, что такой массив не эквивалентен объекту null. (Подробности см. в главе 4.)
Копирование массивов
Один массив можно скопировать в другой, но при этом обе переменные будут ссылаться на один и тот же массив.
int[] luckyNumbers = smailPrimes;
luckyNuimbers[5] = 12; // Теперь элемент smallPrimes[5]также равен 12.
Результат показан на рис. 3.14. Если необходимо скопировать все элементы одного массива в другой, следует использовать метод arraycopyиз класса System. Его вызов выглядит следующим образом:
System.arraycopy(from, fromlndex, to, tolndex, count);
Массив to должен иметь достаточный размер, чтобы в нем поместились все копируемые элементы.
smailPrimes =smailPrimes =luckyNumbers=luckyNumbers=Рис. 3.14. Копирование массиваРис. 3.14. Копирование массива1313
smailPrimes =
smailPrimes =
luckyNumbers=
luckyNumbers=
Рис. 3.14. Копирование массива
Рис. 3.14. Копирование массива
13
13
Например, показанные ниже операторы, результаты работы которых изображены на рис. 3.15, создают два массива, а затем копируют последние четыре элемента первого массива во второй. Копирование начинается со второй позиции в исходном массиве, а копируемые элементы помещаются в целевой массив, начиная с третьей позиции.
int[] smailPrimes = {2, 3, 5, 7, 11, 13};
int[] luckyNumbers = (1001, 1002, 1003, 1004, 1005, 1006, 1007};
System.arraycopy(smallPrimes, 2, luckyNumbers, 3, 4) ;
for (. int i = 0; i < luckyNumbers.length; i++)
System.printIn(i +.":" + luckyNumbers[i]);
Выполнение этих операторов приводит к следующему результату.
0:0:100110021003100110021003571113571113
0:
0:
1001
1002
1003
1001
1002
1003
5
7
11
13
5
7
11
13
smallPrimes=smallPrimes===
smallPrimes=
smallPrimes=
=
=
Рис. Копирование элементов массива
Массив в языке Java значительно отличается от массива в языке C++. Однако он практически совпадает с указателем на динамический массив. Это значит, что оператор
int[] a = new int[100]; //Java
эквивалентен оператору
int* = new int[100]; // C++,
а не
int a[100]; // C++
В языке Java оператор [ ] умолчанию проверяет диапазон изменения индексов. Кроме того, в языке Java нет арифметики указателей — нельзя увеличить указатель а, чтобы обратиться к следующему элементу массива.
Параметры командной строки
МыужерассматривалипримерпрограммыдляработысмассиваминаязыкеJava, которая повторялась несколько раз. ВкаждойпрограмменаязыкеJava есть метод mainспараметромString[] args. Этот параметр означает, чтометодmainполу- чает массив строк, а именно: аргументы, указанные в командной строке.
Например, рассмотрим следующую программу.
public class Message <
public static voidargs)
(
if
("Здравствуй,");
else if
System.out.print("Прощай,");
// Печатает другие аргументы командной
for (int i = 1; i <i++)
" + args[i]);
System.out.print("!”);
}
При следующем вызове программы
java Message -g жестокий мир
массив args будет состоять из таких элементов.
args[O': "-g" args[l]"жестокий"
args[2] : "мир"
Программа выведет на печать сообщение
Прощай, жестокий мир!
В методе main программы на языке Java ее имя не хранится в массиве args. Например, после запуска программы Message с помощью команды
java Message -h мир
из командной строки элемент args[0] будет равен "-h" , а не "Message" или "java".
Сортировка массива
Если нужно упорядочить массив чисел, можно применить метод sort из класса Arrays.
int[] a = new int[10000];
Arrays.sort(a);
Этот метод использует усовершенствованную версию алгоритма быстрой сортировки, которая считается наиболее эффективной для большинства множеств. Класс Arrays имеет несколько удобных методов, предназначенных для работы с массивами. Эти методы перечислены в замечании об интерфейсе API, приведенном в конце этого раздела.
Программа из листинга 3.7 создает рабочий массив. Она генерирует случайную комбинацию чисел для лотереи. Например, если нужно выиграть "6 из 49", программа может напечатать следующее сообщение.
Поставьте на следующую комбинацию. Она сделает вас богатым!
4
7
8
19
30
44
Чтобы выбрать случайные числа, массив numbers сначала заполняется числами 1,2п.
int[ ] numbers = new int[n];
for (int i = 0; i < numbers.length; i++) numbers[i[ = i + 1;
Второй массив предназначен для хранения сгенерированных чисел: int[] result = new int[k];
Теперь сгенерируем к чисел. Метод Math. random возвращает случайное число с плавающей точкой, лежащее в интервале от 0 (включительно) до 1 (не включая). Умножая результат на число п, получаем случайное число, лежащее между 0 и n—1. int r=n) ;
Присвоим i-e число i-му элементу массива. Сначала там будет помещено само число r, однако, как будет показано ниже, содержимое массива number будет изменяться после каждой новой генерации случайной комбинации чисел. result[i] =
Теперь мы должны убедиться, что ни одно число не повторится — все лотерейные номера должны быть разными. Следовательно, нужно поместить в ячейку number [ £] последнее число, содержащееся в массиве, и уменьшить п на единицу.
numbers[r" =- 1] ;
n—;
Обратите внимание на то, что при каждой генерации мы получаем индекс, а не само число. Этот индекс относится к массиву, содержащему числа, которые еще не были выбраны.
После генерации к лотерейных номеров полученный массив сортируется, чтобы результат выглядел более элегантно.
Arrays.sort(result);
for (int i = 0; i < result.length; i++)
System.out.println(result[i]) ;
import java.util.*;
import javax.swing.*;
. public class LotteryDrawing
{
public static void main(String[] args)
{
String input = JOptionPane.showInputDialog
("СКОЛЬКО номеров нужно угадать?");
int к = Integer.parselnt(input);
input = JOptionPane.showInputDialog
("Чему равен наибольший из возможных номеров?");
int n = Integer.parselnt(input);
// Заполняем массив числами 1 23. . . n.
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
numbers[i] = i + 1;
// Генерируем к чисел и помещаем их во второй массив.
int[] result = new int[k];
for (int i = 0; i < result.length; i++)
(
// Генерируем случайный индекс от 0 до n - 1.
int r = (int)(Math.random() * n) ;
// Помещаем элемент в случайную ячейку.
result[i] =
// Перемещаем последний элемент в случайную ячейку.
numbers[г] =- 1];
п—;
}
// Выводим на печать упорядоченный массив.
Arrays.sort(result);
System.out.printin
("Поставьте на следующую комбинацию — не пожалеете!")
for (int i = 0; i < result.length; i++)
System.out.printin(result[i]);
System.exit(0);
}
java.lang.System 1.1java.lang.System 1.1}
java.lang.System 1.1
java.lang.System 1.1
static void arraycopy(Object from, int fromlndex, Object to,
int tolndex, int count)
Параметры:from
Массив любого типа. (Причины, по которым этот параметр должен иметь тип Object, объясняются в главе 5.)
fromlndex
Индекс, от которого начинается копирование элементов.
to
Массив, имеющий тот же тип, что и массив from.
tolndex
Индекс, в который начинается копирование элементов.
count
Количество копируемых элементов.
Метод копирует элементы из первого массива во второй.
java.util.Arrays 1.2
static voidsort(Xxx[1 a)
Параметры: aМассив типа int, long, short, char, byte,
boolean, float или double.
Метод упорядочивает массив с помощью алгоритма быстрой сортировки.
static int binarysearch (Xxx [". a, Xxx v)
Параметры: aУпорядоченный массив типа int, long, short,
char, byte, boolean, float или double.
vЗначение того же типа, что и элементы
массива а.
Метод использует алгоритм бинарного поиска величины v. Если эта величина найдена, возвращается ее индекс. В противном случае возвращается
отрицательное значение г; значение v должно быть вставлено в ячейку -r-1 массива а.
static void fill(Xxx[] a, Xxx v)
Параметры: аМассив типаint, long, short,
char, byte, boolean, float или double.
vЗначение того же типа, что и элементы массива а.
Задает все элементы массива у.
static boolean equals(Xxx[a, Object other)
Параметры: sМассив типа int, long, short,
char, byte, boolean, float или double.
otherОбъект.
Возвращает величину true, если массив other имеет те же тип и длину, и индексы соответствующих элементов совпадают.
Многомерные массивы
Длядоступакэлементаммногомерногомассиваприменяетсянесколькоиндексов. Такие масивы используются для хранения таблиц и других более сложных упорядоченных структур данных. Если многомерные массивы в вашей работе не нужны, можете смело пропустить этот раздел.
Допустим, что вам нужно создать таблицу чисел, показывающих, как возрастет первоначальная инвестиция объемом 10000 долларов при разных процентных ставках, если прибыль ежегодно выплачивается и реинвестируется. Этот сценарий проиллюстрирован втабл. 3.5.
10%
11%
12%
13%
14%
15%
$10,000.00
$10000.00
$10,000.00
$10,000.00
$10,000.00
$10,000.00
$11,000.00
$11100.00
$11,200.00
$11,300.00
$11,400.00
$11,500.00
$12,100.00
$12321.00
$12,544.00
$12,769.00
$12,996.00
$13,225.00
$13,310.00
$13676.31
$14,049.28
$14,428.97
$14,815.44
$15,208.75
$14,641.00
$15180.70
$15,735.19
$16,304.74
$16,889.60
$17,490.06
$16,105.10
$16850.58
$17,623.42
$18,424.35
$19,254.15
$20,113.57
$17,715.61
$18704.15
$19,738.23
$20,819.52
$21,949.73
$23,130.61
$19,487.17
$20761.60
$22,106.81
$23,526.05
$25,022.69
$26,600.20
$21,435.89
$23045.38
$24,759.63
$26,584.44
$28,525.86
$30,590.23
$23,579.48
$25580.37
$26,730.79
$30,040.42
$32,519.49
$35,178.76
Очевидно, что эту информацию лучше всего хранить в двухмерном массиве (или матрице), которую мы назовем balance.
Объявить матрицу в языке Java довольно просто. Например, doubled [] balance;
Как обычно, мы не можем использовать массив, пока он не инициализирован с помощью метода new. В данном случае инициализацию можно выполнить следующим образом: balance = new double[NYEARS][NRATES];
В других случаях, если элементы массива известны заранее, можно использовать сокращенную запись для инициализации многомерного массива, в которой не используется оператор new. Например, так.
=
{
{16, 3, 2, 13},
{5, 10, 11, 8},
{9, 6, 7, 12),
{4, 15, 14, 1}
};
После инициализации массива к отдельным его элементам можно обращаться с помощью двух пар квадратных скобок, например balance [ i ] [ j ].
Программа, приведенная в качестве примера, сохраняет процентные ставки в одномерном массиве interest, а результаты подсчета баланса для каждого года и каждой процентной ставки — в двухмерном массиве balance. Первая строка массива инициализируется исходным балансом.
for (int i = 1; i < balance.length; i++) {
for (int j = 0; j < balance[ i ] .length; j++)
{
double oldBalance = balance[i-1][ j];
double interest • ...;
balanced) [j] = oldBalance + interest;
1
В листинге 3.8 эта программа показана полностью.
1. import java.text.*;
2. import javax.swing.*;
3.
4. public class Compoundinterest
5. {
6.public static void main(String[] args)
7.{
8.final int STARTRATE = 10;
9.final int NRATES = 6;
10.final int NYEARS = 10;
11.
12.// Установить процентные ставки от 10 до 15
doublet] interestRate = new double[NRATES];
for (int j = 0; j < interestRate.length; j++)
interestRate [ j ] = (STARTRATE + j) / 100.0;
doublet] 12 balance = new double[NYEARS][NRATES];
// Установить исходный баланс равным 10000.
for (int j = 0; j < balance[0].length; j++)
balance[0][j] = 10000;
// Вычислить доход для будущих лет.
for (int i = 1; i < balance.length; i++)
{
for (int j = 0; j < balance!!].length; j++)
{
// Взять баланс прошлого года из предыдущей строки.
double oldBalance = balance[i - l][j];
// Вычислить доход.
double interest = oldBalance * interestRate!j];
// Вычислить баланс текущего года.
balance(i][j] = oldBalance + interest;
}
}
// Распечатать одну строку с доходами.
NumberFormat formatter=NumberFormat.getPercentlnstance();
for (int j = 0; j < interestRate.length; j++)
{
System.out.print("");
System.out.print(formatter.format(interestRate[j]));
}
System.out.println();
// Распечатать таблицудоходов.
formatter = NumberFormat.getCurrencylnstance();
for (int i = 0; i < balance.length; i++)
{
// Напечатать строку таблицы.
for (int j = 0; j < balance[1].length; j++)
{
System.out.print(" ") ;
System.out.print(formatter.format(balance[i] [j])) ;
}
System.out.println();
}
}
}
"Неровные" массивы
До сих пор все, что мы рассматривали, не отличалось от других языков программирования. Однако в языке Java есть средства, предоставляющие совершенно новые возможности: в нем вообще нет. многомерных массивов, только одномерные. Многомерные массивы лишь имитируются как "массивы массивов"..
Например, массив balance в предыдущем примере фактически представляет собой массив, состоящий из 10 элементов, каждый из которых является массивом из шести чисел с плавающей точкой (рис. 3.16).
balance =balance =Рис. 3.16.Двухмерный массивРис. 3.16.Двухмерный массив
balance =
balance =
Рис. 3.16.Двухмерный массив
Рис. 3.16.Двухмерный массив
Выражение balance [i] относится к i-му подмассиву, т.е i-й строке таблицы. Эта строка представляет собой массив, и выражение balance [i] относится к j-му элементу этого массива.
Поскольку к строкам массива открыт самостоятельный доступ, их можно легко переставлять!
doublet] temp = balance [ i];
balance [i] = balance[i+1];
balance[1=1] = temp;
Кроме того, в языке Java легко создавать "неровные" массивы, т.е массивы, у которых разные строки имеют разную длину. Приведем стандартный пример. Создадим
массив, в котором элемент, стоящий на пересечении i-й строки и j-го столбца, равен количеству возможностей "выбрать j чисел среди i лотерейных номеров".
1
21331464151С105615201521331464151С105615201511111111111111161611
21
331
464
1
51С10
5
61520
15
21
331
464
1
51С10
5
61520
15
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
6
1
6
1
1
Поскольку число j не может превышать i, получается треугольная матрица. В i-й строке этой матрицы стоит i+1 элементов. (Мы можем выбрать и 0 элементов; сделать это можно лишь одним-единственным способом.) Чтобы создать неровный мас-
сив, сначала разместим в памяти массив, хранящий его строки.
int[] [] adds = new int[NMAX+l][];
Затем поместим туда сами строки.
for (n=0; n<=NMAX; n++) adds[nj = new int[n+l] ;
Теперь в памяти размещен весь массив, так что мы можем обращаться к его элементам, как обычно, при условии, что индексы не выходят за пределы допустимого диапазона.
for (n=0; n<odds.length; n++)
for (k=0; k<odds[n].length; k++)
// Вычисление шансов.
} adds[n][k] = lotteryOdds;
В листинге 3.9 приведен полный текст этой программы.
M В языке Java объявление doublet][] balancenew double[10[6]; // Java
не эквивалентно объявлению
double balance[10] [6]; // C++ и даже не соответствует объявлению
double (♦balance)[6] = new double[10][6]; // C++
Вместо этого в памяти размещается массив, состоящий из десяти указателей:
double** balance = new double*[10]; // C++
Затем каждый элемент в массиве указателей заполняется массивом, состоящим из 6 чисел.
for (i =0; i < 10; i++)
balance [i] = new double[6];
К счастью, этот цикл выполняется автоматически при объявлении массива с помощью оператора new double [10] [6]. Если вам нужны "неровные" массивы, размещайте массивы строк отдельно.
public class LotteryArray
{
public static voidargs)
(
final int NMAX'10;
// Размещение треугольной матрицы.
int[][] odds = new+
for (int n0; n <= NMAX; n++)
odds[n] = new+ 1];
19.39.19.39.// Заполнение треугольной
19.
39.
19.
39.
for (int n = 0; n <n++)
for (int= 0; k <k++)
{
/*
Вычисление биномиальных коэффициентов.
n * (n- 1) * (n - 2) * . . . * (n - k+ 1)
1*2*3*. . . * к
*/
int lotteryOdds = 1;
for (int i = 1; i <= k;
lotteryOdds = lotteryOdds * (n - i + 1). / i;
odds[n][k] = lotteryOdds; )
// Печать треугольной матрицы.
for (int n = 0; n <n++)
{
for (int к = 0; к <k++)
{
// Расстановка пробелов в выводе.
String output = "" + odds[n][k];
// Результат выводится в поле шириной 4
output =- 4);
System.out.print(output);
}
System, out. printlnO ;
Объектыи классыОбъектыи классыВ этой главе...
Объектыи классы
Объектыи классы
Т Введение в объектно-ориентированное программирование
Использование существующих классов
Создание собственных классов
Статические поля и методы
Параметры метода
Создание объектов
Пакеты
Комментарии
Т Советы по разработке классов
R ■■
^^ЭТОИ главе рассматриваются следующие темы.
Введение в объектно-ориентированное программирование.
Создание объектов классов из стандартной библиотеки языкаJava.
Создание собственных объектов.
Если у вас нет подготовки в области объектно-ориентированного программирования, внимательно изучите эту главу. Эта парадигма требует совершенно иного способа мышления, который в корне отличается от принципов, положенных в основу процедурно-ориентированных языков. Сделать это не всегда легко, однако для овладения языком Java необходимо хорошо знать основные понятия объектно-ориентированного программирования.
Программистам, имеющим большой опыт работы с языком C++, информация, содержащаяся в этой главе, как и в предыдущей, уже известна. Однако между Java и C++ есть довольно большое количество различий, поэтому последний раздел следует прочесть внимательно. Замечания о языке C++ облегчат программистам переход на язык Java.
Введение в объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП) в настоящее время стало доминирующей парадигмой программирования, вытеснив "структурные", процедурно-ориентированные способы программирования, разработанные в начале 1970-х годов. Язык Java представляет собой полностью объектно-ориентированный язык, и на нем невозможно программировать в процедурном стиле, к которому вы, возможно, уже привыкли. Мы надеемся, что эта глава, особенно в сочетании с примерами программ, приведенными в тексте, предоставит вам достаточный объем информации об ООП, чтобы продуктивно работать на языке Java.
Начнем с вопроса, который на первый взгляд не относится к программированию: как такие компании, как Compaq, Dell, Gateway и другие производители персональных компьютеров так быстро стали крупными фирмами? Возможно, большинство читателей ответит, что они делали хорошие компьютеры и продавали их по очень низким ценам в то время, когда спрос на компьютеры был очень высоким. Поставим вопрос несколько иначе: как они смогли в короткий срок произвести так много моделей, реагируя на постоянно изменяющиеся требования?
В основном причина успеха заключается в том, что компании поручили большую часть работы другим поставщикам. Они покупали компоненты у поставщиков с хорошей репутацией, а затем собирали их в единое целое на своих заводах. Производители компьютеров не тратили время и деньги на разработку и создание блоков питания, дисководов, материнских плат и других компонентов. Они давали возможность другим компаниям производить свою продукцию и быстро реагировать на изменения спроса, экономя свои деньги. Если бы производители компьютеров разрабатывали все блоки самостоятельно, они затратили бы намного больше средств.
Изделия, которые покупали компании— производители компьютеров, называются "полуфабрикатом" ("prepackaged functionality"). Например, покупая блоки питания, они приобретали нечто, обладающее определенными свойствами (размером, формой и т.д.) и способное выполнять определенные функции (сглаживать выходные сигналы, обеспечивать необходимое электропитание и т.д.). Компания Compaq представляет собой хороший пример высокой эффективности такого подхода. Перейдя от собственного производства компонентов компьютера к их покупке с последующей сборкой, фирма резко поднялась с последнего места, которое она занимала в то время.
В основе ООП лежит та же идея. Любая программа состоит из объектов, обладающих определенными свойствами и возможностями выполнять определенные операции. Создавать объект самому или покупать его у других программистов — решение зависит лишь от наличия денег и времени. Однако, в основном, если объекты удовлетворяют вашим требованиям, неважно, как именно они сделаны. В ООП нужно заботиться лишь о том, что представляет собой объект. Итак, точно так же, как производители компьютеров не вникают в детали внутреннего устройства блоков питания, пока эти блоки рабо-
тают так, как надо, большинство программистов на языке Java не интересует, как именно реализован тот или иной объект, пока он удовлетворяет их требованиям.
Традиционное структурное программирование заключается в разработке набора функций (или алгоритмов) для решения поставленной задачи. Определив эти функции, программист должен задать подходящий способ хранения данных. Вот почему создатель языка Pascal Никлаус Вирт (Niklaus Wirth) назвал свою знаменитую книгу по программированию "Алгоритмы + Структуры данных = Программы". Заметим, что в названии этой книги алгоритмы стоят на первом месте, а структуры данных— на втором. Это отражает образ мышления программистов того времени. Во-первых, они решали, как манипулировать данными; затем решали, какую структуру применить для организации этих данных, чтобы работать с ними было легче. ООП изменило порядок этих понятий на противоположный, поместив на первое место данные и сделав алгоритмы, предназначенные для их обработки, второстепенным вопросом.
Основной принцип, обеспечивший высокую производительность ООП, заключается в том, что каждый объект предназначен для выполнения определенных задач. Если перед объектом стоит задача, для решения которой он не предназначен, у него должен быть доступ к другому объекту, который может эту задачу решить. Затем первый объект просит второго решить эту задачу. Это— обобщенный вариант вызова функций, применяемого в процедурном программировании. (Напомним, что в языке Javaэто обычно называется вызовом метода.)
В частности, объект никогда не должен непосредственно манипулировать внутренними данными другого объекта, а также предоставлять другим объектам прямой доступ к своим данным. Все связи между ними обеспечиваются с помощью вызовов методов. Инкапсуляция (encapsulation) данных объекта максимально повышает возможность его повторного использования, уменьшает их взаимозависимость и минимизирует время отладки программы.
Разумеется, как и от модулей в процедурно-ориентированном программировании, от объектов не следует ожидать слишком многого. И разработка, и отладка программы, состоящей из небольших объектов, выполняющих небольшие задачи, намного проще по сравнению с программой, созданной из громадных объектов с крайне сложными внутренними данными и сотнями функций для манипулирования ими.
Словарь объектно-ориентированного программирования
Для дальнейшей работы вам нужно освоить терминологию ООП. Наиболее важным термином является класс, который мы уже видели в примерах программ из главы 3. Класс — это шаблон, или проект, по которому будет сделан объект. Обычно класс сравнивают с формой для выпечки печенья. Объект — это само печенье. Конструирование объекта на основе некоторого класса называется созданием экземпляра (instance) этого класса.
Как мы уже видели, все коды, которые создаются в языке Java, находятся внутри классов. Стандартная библиотека языка Java содержит несколько тысяч классов, предназначенных для решения разных задач, например, для создания пользовательского интерфейса, календарей и сетевого программирования. Несмотря на это, программисты продолжают создавать свои собственные классы на языке Java, чтобы опи-
сать объекты, характерные для разрабатываемого приложения, а также адаптировать классы из стандартной библиотеки для своих нужд.
Инкапсуляция (иногда называемая сокрытием данных) — это ключевое понятие при работе с объектами. Формально инкапсуляция — это просто объединение данных и операций над ними в одном пакете и сокрытие данных от пользователя объекта. Данные в объекте называются полями экземпляра (instance fields), а функции и процедуры, выполняющие операции над данными, — его методами (methods). В указанном объекте, т.е. экземпляре класса, поля экземпляра имеют определенные значения. Множество этих значений называется текущим состоянием (state) объекта. Применение любого метода к какому-нибудь объекту может изменить его состояние.
Еще раз подчеркнем, что основной принцип инкапсуляции заключается в запрещении прямого доступа к полям экземпляра данного класса методам других классов. Программы должны взаимодействовать с данными объекта только с помощью методов этого объекта. Это значит, что в классе можно полностью изменить способ хранения данных, сохранив методы их обработки, и при этом остальные объекты смогут работать с объектами этого класса, как прежде.
Еще один принцип ООП облегчает разработку собственных классов в языке Java: класс можно сконструировать на основе других классов. В этом случае говорят, что вновь созданный класс расширяет класс, на основе которого он построен. Язык Java, по существу, создан на основе "глобального суперкласса", называемого, что вполне естественно, Object. Все остальные объекты расширяют его. В следующей главе мы рассмотрим этот вопрос подробнее.
Новый класс содержит все свойства и методы расширяемого класса, а также новые методы и поля данных. Концепция расширения класса для создания нового называется наследованием (inheritance). Детали наследования описаны в следующей главе.
Объекты
Следуя принципам ООП, необходимо указать следующие ключевые свойства объектов.
Поведение (behavior) объекта — что с ним можно делать и какие методы к нему можно применять.
Состояние объекта — как этот объект реагирует на применение методов.
Сущность (identity) объекта— чем данный объект отличается от других, характеризующихся таким же поведением и состоянием.
Все объекты, являющиеся экземплярами одного и того же класса, ведут себя одинаково. Поведение объекта определяется методами, которые можно вызвать.
Далее, каждый объект сохраняет информацию о своем состоянии. Со временем состояние объекта может измениться, однако спонтанно это произойти не может. Состояние объекта может изменяться только в результате вызовов методов. (Если состояние объекта изменилось вследствие иных причин, значит, инкапсуляция нарушена.)
Однако состояние объекта не полностью описывает его, поскольку каждый объект имеет свою собственную сущность. Например, в системе обработки заказов два заказа могут отличаться друг от друга, даже если они запрашивают одни и те же предметы. Заметим, что индивидуальные объекты, представляющие собой экземпляры класса, всегда отличаются своей сущностью и обычно отличаются своим состоянием.
Эти основные характеристики могут влиять друг на друга. Например, состояние объекта может влиять на его поведение. (Если заказ "выполнен" или "оплачен", объект может отказаться выполнить вызов метода, требующего добавить или удалить предмет заказа. И наоборот, если заказ "пуст", т.е. ни один предмет не был заказан, он не может быть выполнен.)
В традиционной процедурно-ориентированной программе выполнение начинается сверху, с функции main. При разработке объектно-ориентированной системы понятие "верха" не существует, и новички в ООП часто интересуются, с чего начинать. Ответ таков: "Сначала найдите классы и добавьте методы в каждый класс".
Классы представляют собой аналоги имен существительных в описании решаемой задачи, а методы — используемых при этом глаголов.
Например, при описании системы обработки заказов используются следующие имена существительные:
предмет;
заказ;
адрес доставки;
оплата;
счет.
Эти имена существительные соответствуют классам Item, Order и т.д.
Рассмотрим теперь глаголы. Предметы заказываются. Заказы выполняются или отменяются. Оплата заказа осуществляется. Используя эти глаголы, можно определить объект, выполняющий такие действия. Например, если поступил новый заказ, то ответственность за его обработку должен нести объект класса Order, поскольку именно в нем содержится информация о способе хранения и видах заказываемых предметов. Следовательно, в классе Order должен существовать метод add, получающий объект Item в качестве параметра.
Разумеется, правило "имен существительных и глаголов" — это просто совет, и только опыт может помочь программисту решить, какие имена существительные и глаголы важны при создании класса.
Отношения между классами
Между классами существуют три обычных отношения.
Зависимость ("использует").
Агрегирование ("содержит").
Наследование ("является").
Отношение зависимости (dependence— "uses-a") наиболее очевидное и распространенное. Например, класс Order использует класс Account, поскольку объекты класса Order должны иметь доступ к объектам класса Account, чтобы проверить кредитоспособность заказчика. Однако класс Item не зависит от класса Account, так как объекты класса Item никогда не интересуются состоянием счета заказчика. Следовательно, класс зависит от другого класса, если его методы манипулируют объектами этого класса.
Старайтесьминимизироватьколичествовзаимозависимыхклассов. ЕсликлассА не знает о существовании класса в, он тем более ничего не знает о любых его изменениях! (Это значит, что любые изменения, внесенные в класс В, не повлияют на класс А.) В терминах программного обеспечения это означает минимизацию связей (couplings) между классами.
Отношение агрегирования (aggregation—“has-a”)довольнопростопонять,поскольку оно является вполне конкретным. Например, объект Order содержит объекты класса Item. Агрегирование означает, что класс А содержит объекты класса В.
Некоторые специалисты по методологии пренебрегают концепцией агрегирования и предпочитают использовать отношение "ассоциированности" ("association"). С точки зрения моделирования это разумно. Однако для программистов отношение "содержит" обладает большим смыслом. Мы предпочитаем использовать понятие агрегирования еще по одной причине — стандартное обозначение отношения ассоциированности менее понятно (см. табл.4.1).
Наследование (inheritance — "is-a") выражает отношение между конкретным и более общим классом. Например, класс RushOrder является производным от класса Order. Класс RushOrder имеет специальные методы для обработки приоритетов и разные методы для вычисления стоимости доставки, в то время как другие его методы, например, для заказа предмета и выписывания счета, унаследованы им от класса Order. В частности, если класс А расширяет класс В, то говорят, что класс А наследует методы класса В, имея, кроме них, еще и дополнительные возможности. (Более полно наследование описано в следующей главе.)
ОтношениеОтношениеОбозначение в языке UMLОбозначение в языке UMLМногие программисты используют символы языка UML (Unified Modeling Language) для изображения диаграмм класса (class diagrams), описывающих отношения между классами. Пример такой диаграммы приведен на рис. 4.1. Здесь классы изображены с помощью прямоугольников, а отношения между ними — различными стрелками. В табл. 4.1 показаны основные обозначения, принятые в языке UML.
Отношение
Отношение
Обозначение в языке UML
Обозначение в языке UML
Наследование>
Зависимость[>
Агрегирование
Ассоциированность
Направленная ассоциированность
На рис. 4.1 показан пример диаграммы класса.
Рис. 4.1.Диаграмма класса
Для создания диаграмм на языке UML разработано много инструментов. Некоторые поставщики предлагают очень мощные (и очень дорогие) программы, призванные стать основным средством проектирования. Отметим с реи них программы Rational Rose (http: //ww. rational. com/products/rose/index. j sp) и Together (http://www. togethersoft. com/products/controlcenter/index. j sp). В качестве альтернативы можно назвать программу ArgoUML, созданную на принципах открытого кода (http://argouml. tigris.org/). Коммерческую версию программы GentleWare можно получить на Web-странице http://gentleware.com/products/ poseidonPE.php3. Если вам нужно просто нарисовать диаграмму, приложив минимум усилий, попробуйте применить программу Violet (http: // horstmann.com/violet).
Различия между ООП и традиционным процедурным программированием
Завершим наше краткое введение в ООП анализом различий между ним и процедурной моделью. В процедурно-ориентированном программировании сначала формулируется задача, а затем:
с помощью пошагового уточнения исходная задача разбивается на все более мелкие подзадачи, пока они не станут настолько простыми, что их можно будет реализовать непосредственно (программирование "сверху вниз");
создаются процедуры, предназначенные для решения простых задач; затем эти процедуры объединяются В более сложные, пока программа не станет делать именно то, что требуется (программирование "снизу вверх").
Большинство программистов, конечно, используют смешанные стратегии "сверху вниз" и "снизу вверх". При исследовании процедур нужно придерживаться такого же правила, как и при объектно-ориентированном программировании: ищите глаголы, или действия, в описании задачи. Существенное отличие заключается в том, что при объектно-ориентированном программировании в проекте сначала выделяются классы, и лишь затем определяются их методы. Между традиционными процедурами и ООП есть еще одна существенная разница: каждый метод связан с классом и класс отвечает за их выполнение.
Рис. 4.2. Процедурное и объектноориентированное программирование
Для решения небольших задач разбиение программы на процедуры вполне оправданно. Однако при разработке больших проектов классы и методы имеют два преимущества. Классы предоставляют удобный механизм кластеризации методов. Простенький Web-броузер для своей реализации может потребовать 2000 функций и 100 классов, т.е. в среднем 20 методов в классе. Программисту намного легче понять именно последнюю структуру. Кроме того, ее легче распределить в команде программистов. Этому способствует также инкапсуляция: классы скрывают детали представления данных от любого кода, кроме своих методов. Как показано на рис. 4.2, это значит, что, если ошибка программирования искажает данные, причину легче найти среди 20 классов, чем среди 2000 процедур.
Можно возразить, что это не слишком отличается от принципа модульности (modularization). Наверняка, вам приходилось писать программы, разбивая их на модули, обменивающиеся друг с другом данными исключительно с помощью вызовов процедур, не прибегая к совместному использованию данных (sharing data). Если правильно пользоваться этим приемом, можно осуществить инкапсуляцию данных. Однако во многих языках программирования малейшее отклонение от этого подхода открывает доступ к данным из другого модуля — инкапсуляцию легко разрушить.
Есть более серьезная проблема: в то время как на основе класса можно создать несколько объектов с одинаковым поведением, в процедурно-ориентированных языках невозможно получить несколько копий одного модуля. Допустим, мы достигли модульной инкапсуляции, объединив совокупность заказов вместе с прекрасным сбалансированным бинарным деревом, обеспечивающим быстрый доступ. И вдруг выясняется, что нужно иметь две такие совокупности: одну для текущих заказов, а другую — для выполненных. Нельзя просто дважды вызвать модуль, реализующий дерево заказов. Для этого нужно скопировать и переименовать все процедуры! Классы не имеют таких ограничений. Определив однажды класс, легко создать любое количество его экземпляров (в то время как модуль имеет только один экземпляр).
Мы коснулись лишь краешка очень большой темы. В конце этой главы помещен короткий раздел "Советы по разработке классов", однако для более глубокого понимания процесса объектно-ориентированного проектирования нужно изучить книги, указанные в замечании.
Языку UML посвящено немало книг. Мы рекомендуем "Unified Modelling Landuage User Guide" by Grady Booch, IvarJacobson and James Rumbaugh (Addison-Wesley, 1999).
Использование существующих классов
Поскольку на языке Java ничего нельзя сделать без классов, в предыдущих разделах мы изучили, как работают некоторые из них. К сожалению, большинство рассмотренных выше классов довольно нетипичны для языка Java. Ярким примером такого класса является класс Math. Как мы уже видели, методы класса Math, например, метод Math. random, можно вызывать, ничего не зная о деталях их реализации. Для вызова метода достаточно знать его имя и параметры (если они есть). В этом и заключается инкапсуляция, характерная для всех классов. К сожалению, класс Math поддерживает только инкапсуляцию; в нем нет данных, а следовательно, нет их со-
крытия. Таким образом, программист может не заботиться о создании объектов и инициализации их полей — в классе Math ничего этого нет!
В следующем разделе мы рассмотрим более типичный класс Date. Мы увидим, как создаются объекты и вызываются методы этого класса.
Объекты и объектные переменные
Чтобы работать с объектами, их нужно сначала создать и задать их исходное состояние. Затем к этим объектам можно применять методы.
В языке Java для создания новых экземпляров используются конструкторы (constructors). Конструктор — это специальный метод, предназначенный для создания и инициализации объектов. Рассмотрим пример. Стандартная библиотека языка Java содержит класс Date. Его объекты описывают моменты времени, например "December 31, 1999, 23:59:59 GMP".
Может возникнуть вопрос: почему для представления дат применяются классы, а не встроенные типы (как в некоторых языках)? Например, в языке Visual Basic для описания дат есть встроенный тип, и программист может задать формат представления даты в виде #6/1/1995#. На первый взгляд, это удобно — программист может использовать встроенный тип и не заботиться о классах. Однако на самом ли деле это удобно? В некоторых странах даты записываются в виде ме- сяц/день/год, а в других — год/месяц/число. Могут ли разработчики языка предусмотреть все возможные варианты? Если они сделают это неудачно, язык станет слишком запутанным, но несчастные программисты ничего не смогут поделать. Использование классов позволяет переложить ответственность за решение этих проблемсразработчиковязыканасоздателейбиблиотек. Еслисистемныйкласс плох, то программистам легче написать свой собственный класс.
Имя конструктора всегда совпадает с именем класса. Следовательно, конструктор класса Date называется Date. Чтобы создать объект Date, конструктор нужно объединить с оператором new, как показано в следующем примере: new Date ()
Это выражение создает новый объект, который инициализируется текущими датой и временем.
При желании объект можно передать методу:
System, out .printin (new Date());
И, наоборот, метод можно применить ко вновь созданному объекту. Среди методов класса Date есть метод toString, позволяющий представить дату в виде строки. Его можно применить к только что созданному объекту следующим образом:
String = new Date().toString();
В этих двух примерах объект создавался только один раз. Обычно его необходимо как-то идентифицировать, чтобы использовать в дальнейшем. Для этого объект нужно присвоить переменной.
Date birthday = new Dated;
На рис. 4.3 показана объектная переменная birthday, ссылающаяся на вновь созданный объект.
birthday = [birthday = [Рис. 4.3. Создание нового объектаРис. 4.3. Создание нового объекта
birthday = [
birthday = [
Рис. 4.3. Создание нового объекта
Рис. 4.3. Создание нового объекта
Между объектами и объектными переменными есть существенная разница. Например, оператор
Date deadline; // Переменная Deadline не ссылается ни на один объект определяет объектную переменную deadline, которая может ссылаться на объекты типа Date. Важно понимать, что сама переменная deadline объектом не является и, по существу, даже не ссылается ни на один объект, поэтому ни один метод класса Date с помощью этой переменной вызывать пока нельзя. Оператор
s = deadline.toDtring (); // Вызывать метод еще рано приведет к сообщению об ошибке.
Сначала переменную deadline нужно инициализировать. У программиста есть две возможности. Разумеется, переменную можно инициализировать вновь созданным объектом:
deadline = new Date();
Кроме того, можно заставить переменную ссылаться на существующий объект: deadline = birthday;
Теперь обе переменные ссылаются на один и тот же объект (см. рис. 4.4).
birthday=birthday=deadline=deadline=Рис. 4.4. Объектные переменные, ссылающиеся на один и тот же объектРис. 4.4. Объектные переменные, ссылающиеся на один и тот же объект
birthday=
birthday=
deadline=
deadline=
Рис. 4.4. Объектные переменные, ссылающиеся на один и тот же объект
Рис. 4.4. Объектные переменные, ссылающиеся на один и тот же объект
Важно понимать, что объектная переменная фактически не содержит никакого объекта. Она лишь ссылается на объект.
В языке Java значение любой объектной переменной представляет собой ссылку на объект, размещенный где-то в другом месте. Оператор new также возвращает ссылку. Например, оператор Date deadline = new Date();
состоит из двух частей. Выражение new Date () создает объект типа Date, а его значение представляет собой ссылку на этот вновь созданный объект.
Объектной переменной можно явно присвоить ссылку null, чтобы отметить тот факт, что эта переменная пока не ссылается ни на один объект.
deadline = null;
if(deadline != null)
System.out.printin(deadline);
Если применить этот метод к переменной, значение которой равно null, то при выполнении программы возникнет ошибка.
birthday = null;
String s = birthday.toString(); // Ошибка!
Локальные объектные переменные не инициализируются автоматически ссылкой null. Программист должен сам инициализировать ее, либо вызвав оператор new, либо присвоив ей значение null.
Многие ошибочно полагают, что объекты в языке Java похожи на ссылки в языке C++. Однако в языке C++ нет нулевых ссылок. Кроме того, им ничего нельзя присваивать. Объектные переменные в языке Java следует считать аналогами указателей на объекты. Например,оператор
Date birthday; // Java
делает то же самое, что и оператор
Date* birthday; // C++
Такая аналогия все расставляет по своим местам. Разумеется, указатель Date* не инициализируется, пока не выполнен оператор new. Синтаксис выражения в языках C++ и Java практически совпадает.
Date* birthday = new Date(); // C++
При копировании одной переменной в другую обе переменные начинают ссылаться на один и тот же объект, т.е они являются указателями на один и тот же объект. Эквивалентом ссылки null в языке Java в языке C++ является указатель NULL.
Все объекты в языке Java располагаются в динамической памяти. Если объект содержит другую объектную переменную, она представляет собой всего лишь указатель на другой объект, расположенный в куче.
В языке C++ указатели доставляют много хлопот, так как из-за них часто возникают ошибки. Очень легко создать неверный указатель или потерять управление памятью. В языке Java этих проблем просто нет. Если вы используете неинициализированный указатель, то система поддержки выполнения программ обязательно выдаст сообщение об ошибке, а не будет продолжать выполнять ошибочную программу, выдавая случайные результаты. Программист может не заботиться об управлении памятью, поскольку механизм сборки мусора делает это за него автоматически.
В языке C++ большое внимание уделено автоматическому копированию объектов. Для этого в нем есть конструкторы копирования и операторы присваивания. Например, копией связанного списка является новый связанный список, который, имея старое содержимое, обладает совершенно новыми связями. Это позволяет разрабатывать классы, для которых копирование выполняется так же, как и для встроенных типов. В языке Java для получения полной копии объекта просто используется метод clone.
Класс GregorianCalendar из библиотеки языка Java
В предыдущих примерах мы использовали класс Date, являющийся частью стандартной библиотеки языка Java. Экземпляр класса Date имеет состояние, а именно: конкретный момент времени.
Хотя при использовании класса Date нам необязательно знать о формате даты, отметим, что время представляется количеством миллисекунд (положительным или отрицательным), отсчитанным от фиксированного момента времени, так называемой эпохи (epoch), т.е. от 00:00:00 UTC, 1 января 1970 года. Аббревиатура UTC означает Universal Coordinated Time (всеобщее скоординированное время), которое является научным стандартом времени и применяется для достижения практических целей так же, как и более известное время GMT, или Greenwich Mean Tine (время по Гринвичу).
Оказывается, однако, что класс Date не очень удобен для манипулирования с датами. Разработчики библиотеки языка Java считали, что представление даты, например "December 31, 1999, 23:59:59", является совершенно произвольным и должно зависеть от календаря. Данное конкретное представление подчиняется григорианскому календарю, самому распространенному календарю в мире. Однако тот же самый момент времени совершенно иначе представляется в китайском или еврейском лунном календаре, не говоря уже о календаре, которым пользуются ваши потенциальные заказчики с Марса.
Разработчики библиотеки решили отделить вопросы, связанные с отслеживанием моментов времени, от вопросов их представления. Таким образом, стандартная библиотека языка Java содержит два отдельных класса: класс Date, представляющий момент времени, и класс GregorianCalendar, расширяющий более общий класс Calendar, описывающий свойства календаря в целом. Теоретически можно расширить класс Calendar и реализовать китайский лунный или марсианский календари. Однако в стандартной библиотеке, кроме григорианского календаря, нет никакой другой реализации.
Отделение измерения времени от календарей представляет собой хорошее объектно-ориентированное решение. В принципе, идея использовать отдельные классы для представления разных календарных концепций является вполне удачной.
Класс Date содержит лишь небольшое количество методов, позволяющих сравнивать два момента времени. Например, методы before и after определяют, предшествует один момент времени другому или следует за ним.
if (today.before(birthday) )
System.out.printin("Еще есть время купить подарок.");
На самом деле класс Date имеет такие методы, как getDay, getMonth и getYear, но использовать эти методы без крайней необходимости не рекомендуется.
Эти методы были частью класса Date еще до того, как разработчики библиотеки поняли, что классы, реализующие разные календари, разумнее было бы отделить друг от друга. Сделав это, они пометили методы класса Date как нежелательные (deprecated). Вы можете продолжать использовать их в своих программах, получая при этом предупреждения от компилятора, а еще лучше их не применять совсем, поскольку в будущем они будут удалены из библиотеки.
Класс GregorianCalendar содержит гораздо больше методов, чем класс Date. В частности, в нем есть несколько полезных конструкторов. Выражение new GregorianCalendar()
создает новый объект, представляющий дату и момент времени, когда он был создан.
Вы можете создать объект, представляющий полночь указанной даты, год, месяц и день:
new GregorianCalendar(1999, 11, 31)
Довольно странно, что количество месяцев отсчитывается от нуля. Таким образом, число 11 означает декабрь. Для большей ясности можно использовать константу Calendar.December.
new GregorianCalendar(1999, Calendar.December, 31)
Кроме того, можно задать время:
new GregorianCalendar(1999, Calendar.DECEMBER, 31, 23, 59, 59)
Разумеется, как обычно, созданный объект нужно присвоить объектной переменной: GregorianCalendar deadline = new GregorianCalendar(...);
Класс GregorianCalendar имеет инкапсулированные поля экземпляра, в которых записана указанная дата. Не видя исходный текст, невозможно определить, какое представление даты и времени использует этот класс. Разумеется, это совершенно неважно. Важно лишь то, какие методы класс предоставляет программисту.
Модифицирующие методы и методыдоступа
У вас могут возникнуть вопросы: как получить текущий день, месяц или год из даты, инкапсулированной в заданном объекте класса GregorianCalendar? Каким образом можно изменить эти значения? Ответы на эти вопросы представлены в документации по интерфейсу API или в замечаниях, приведенных в конце этого раздела. Ниже мы рассмотрим наиболее важные методы.
Календарь должен вычислять атрибуты указанного момента времени, например, дату, день недели, месяц или год. Чтобы получить одно из этих значений, можно использовать метод get из класса GregorianCalendar. Чтобы выбрать желаемый атрибут, нужно передать методу константу, определенную в классе Calendar, например, Calendar.MONTH или Calendar.DAY_OF_WEEK:
GregorianCalendar now = new GregorianCalendar();
int month = now.get(Calendar.MONTH);
int day = now.get(Calendar.DAY_OF_WEEK);
В замечаниях об интерфейсе API перечислены все константы, которые можно использовать.
Состояние объекта можно изменить с помощью метода set:
deadline.set(Calendar.YEAR, 2001);
deadline(Calendar.MONTH, Calendar.APRIL);
deadline.set(Calendar.DAY, 15);
Существует удобный способ установить год, месяц и день с помощью одного вызова: deadline.set(2001, Calendar.APRIL, 15);
В заключение к заданной дате можно добавить количество дней, недель, месяцев и т.д. deadline.add(Calendar.MONTH, 3); // Отодвинуть момент deadline на 3 месяца.
Если добавить отрицательное число, календарь передвинется назад.
Между методом get с одной стороны и методами set и add с другой есть принципиальная разница. Метод get только просматривает состояние объекта и сообщает о нем, в том время как методы set и add модифицируют состояние объекта. Методы, которые могут изменять поля экземпляра, называются модифицирующими (mutators), a методы, которые могут лишь просматривать поля экземпляра, не изменяя их, назы- ваютсяметодами доступа (accessors).
•J В языке C++ для обозначения метода доступа используется суффикс const. Метод, не объявленный с помощью ключевого слова const, считается модифицирующим. Однако в языке Java нет специальных синтаксических средств, позволяющих различать модифицирующие методы и методы доступа.
В именах методов доступа принято использовать префикс get, а в именах модифицирующих методов— префикс set. Например, класс GregorianCalendar для получения и установки момента времени, представленного объектом, имеет методы getTime и setTime соответственно.
Date time = calendar.getTime();
calendar.setTime(time) ;
Эти методы очень полезны для преобразований объектов класса GregorianCalendar в объекты класса Date, и наоборот. Вот один пример. Допустим, нам известны год, месяц и день, и мы хотим создать объект класса Date, поля которого были бы заполнены этими значениями. Поскольку класс Date ничего не знает о календарях, создадим сначала объект класса GregorianCalendar, а затем вызовем метод getTime для получения даты.
GregorianCalendar calendar
= new GregorianCalendar(year, month, day);
Date hireDay = calendar.getTime();
И, наоборот, если нужно определить год, месяц или день, записанные в объекте класса Date, создадим объект класса GregorianCalendar, установим время, а затем
вызовем метод get.
GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(hireDay);
int year = calendar.get(Calendar.YEAR);
В конце этого раздела приведена программа, иллюстрирующая работу класса GregorianCalendar. Программа выводит на экран календарь текущего месяца.
Be29162330Be29162330Пн3 10 17 24 31Пн3 10 17 24 31Вт Ср Чт Пт Сб
Be
2
9
16
23
30
Be
2
9
16
23
30
Пн
3 10 17 24 31
Пн
3 10 17 24 31
1
45678
1112131415
1819*202122
2526272829
Текущий день помечен звездочкой, причем программа знает, как вычислить дни
недели.
Рассмотрим ключевые пункты этой программы. Во-первых, мы создаем объект класса GregorianCalendar, инициализированный текущими датой и временем. (На самом деле в данном приложении время нас не интересует.)
GregorianCalendar d = new GregorianCalendar();
Затем мы определяем текущий день и месяц, дважды вызвав метод get.
int today = d.get(Calendar.DAY_OF_MONTH); int month = d.get(Calendar.MONTH! ;
После этого мы передаем методу set объекта d параметр, задающий первый месяц, и получаем день недели, соответствующий этой дате.
d.set(Calendar.DAY_OF_MONTH,1);
int weekday = d.get(Calendar.DAY_OF_WEEK);
Переменная weekday полагается равной 1 (или Calendar. SUNDAY), если первый день месяца — воскресенье, 2 (или Calendar . MONDAY) — если понедельник, и т.д.
Затем на экран выводится заголовок и пробелы, выравнивающие первую строку календаря.
Для каждого дня мы выводим на экран пробел, если число меньше 10, затем — само число, а потом звездочку, если число соответствует сегодняшнему дню. Каждое воскресенье выводится с новой строки.
Затем мы устанавливаем объект d на следующий день:
d.add(Calendar.DAY_OF_MONTH, 1);
Когда нам следует остановиться? Нам неизвестно, сколько в месяце дней: 31, 30, 29 или 28. Вместо этого, мы продолжаем итерации, пока объект d остается в пределах текущего месяца. do I
}
while (d.get(Calendar.MONTH! == month);
Как только объект d перейдет на следующий месяц, программа завершит свою работу.
Полный текст программы приведен в листинге 4.1.
Как видим, класс GregorianCalendar позволяет легко создавать программы для работы с календарем, с учетом таких сложностей, как вычисление дней недели и переменная продолжительность месяцев. Программист не обязан ничего знать о том, как именно класс GregorianCalendar вычисляет месяцы и дни недели. Нужно просто использовать интерфейс класса — методы get, set и add.
Основное предназначение этой программы — показать, как можно использовать интерфейс класса для выполнения действительно сложных задач, не вникая в детали реализации.
1. import java.util.*;
2.
3. public class CalendarTest
4. {
5.public static void main(String[] args)
6.{
7.// Создаем объект d с текущей датой.
8.GregorianCalendar d = new GregorianCalendar();
9.
10.int today = d.get(Calendar.DAY OF MONTH);
11.int month = d.get(Calendar.MONTH);
12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.}}12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.}}// Устанавливаем объект d на первую дату месяца.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
}
}
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
}
}
d.set(Calendar.DAY_OF_MONTH, 1);
int weekday = d.get(Calendar.DAY_OF_WEEK);
// Выводим на печать заголовок таблицы.
System.out.printin(Be Пн Вт Cp Чт Пт Сб" ) ;
// Вставляем первую строку календаря.
for (int i = Calendar.SUNDAY; i < weekday; i++) System.out.print("");
do
{
// Выводим на печать день.
int day = d.get(Calendar.DAY_OF_MONTH);
if (day < 10) System.out.print(" ");
System.out.print(day);
// Отметить текущий день звездочкой. if (day == today)
"; else
System.out.print("");
// После каждой субботы начинаем новую строку.
if (weekday == calendar.SATURDAY) System.out.printIn();
// Передвинуть объект d на новый день.
d.add(Calendar.DAY_OF_MONTH, 1);
weekday = d.get(Calendar.DAY_OF_WEEK);
}
while (d.get(Calendar.MONTH) == month);
// Цикл завершается, когда объект d установлен
// на первый день следующего месяца.
// Выводим на экран последнюю строку.
if(weekday != Calendar.SUNDAY)
System.out.printlnO ;
java.util.GregorianCalendar 1.1
GregorianCalendar()
Создает календарь, представляющий текущее время в часовом поясе и стране, заданных по умолчанию.
• GregorianCalendar(int year, int month, int date) Создает григорианский календарь с заданной датой.
Параметры: year
Год
month
Месяц. Отсчет начинается с нуля. Например, 0 — это январь.
date
Число месяца.
GregorianCalendar(int year, int month, int date, int hour, int minutes, int seconds)
Создает календарь, представляющий текущее время в часовом поясе и стране, заданных по умолчанию.
Параметры: year
Год
month
Месяц. Отсчет начинается с нуля. Например, О — это январь.
date
Число месяца.
hour
Час (отОдо 23).
minutes
Минуты (от 0 до 59).
seconds
Секунды (от 0 до 59).
boolean equals(Object when)
Сравнивает заданный объект с объектом when и возвращает значение true, если эти объекты представляют один и тот же момент времени.
boolean before(Object when)
Сравнивает заданный объект с объектом when и возвращает значение true, если момент времени, представленный заданным объектом, предшествует моменту времени, определенному объектом when.
boolean after(Object when)
Сравнивает заданный объект с объектом when и возвращает значение true, если момент времени, представленный объектом when, предшествует моменту времени, определенному заданным объектом.
int get(int field)
Получает значения заданных полей.
Параметры: fieldОдно изполейCalendar. ERA,
Calendar.YEAR, Calendar.WEEK_OF_YEAR, Calendar.WEEKOFMONTH, Calendar.DAY_OF_MONTH, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.ASM_PM, Calendar.HOUR, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, Calendar.ZONE_OFFSET, Calendar.DST_OFFSET
void set(int field, int value)
Устанавливает значения заданных полей.
Параметры: fieldПараметры: fieldОдна из констант, полученных методом get.
Параметры: field
Параметры: field
valueНовое значение.
void set(int year, int month, int day)
Устанавливает дату в новом объекте.
Параметры: year month
Год.
Месяц. Отсчет начинается с нуля. Например, 0 — это январь.
day
Число месяца.
void set(int year, int month, int day, int hour, int minutes, int seconds)
Устанавливает дату и время в новом объекте.
Параметры:year month
Год.
Месяц. Отсчет начинается с нуля. Например, 0 это январь.
day hour
Число месяца.
Час (отОдо 23).
minutes
Минуты (от 0 до 59).
seconds
Секунды (от 0 до 59).
void add(int field, int amount)
Арифметический метод для работы с датами. Добавляет к полям времени заданную величину. Например, чтобы прибавить 7 дней к текущей дате, нужно сделать вызов с . add (Calendar . DAY_OF_MONTH, 7 ).
Параметры: field
Изменяемое поле (должно содержать значение одной из констант, предусмотренных для метода get).
amount
Объем добавляемого времени может быть отрицательным числом).
void setTime(Date time)
Устанавливает заданный момент времени.
Параметры:time
Момент времени.
Date getTime()
Определяет момент времени, представленный текущим значением объекта.
Создание собственных классов
В главе 3 мы уже попробовали создать простые классы. Однако все эти классы состояли из одного-единственного метода main. Теперь подошло время научиться создавать "рабочие" классы, необходимые в более сложных приложениях. Обычно в этих классах нет метода main. Вместо этого у них есть собственные поля и методы. Чтобы написать полностью законченную программу, нужно объединить классы, один из которых имеет метод main.
Класс Employee
Простейшее определение класса в языке Java имеет следующий вид.
class ИмяКласса
{
конструктор^ конструктор;
метода метод;
полег
полег
}
Мы придерживаемся стиля, в котором сначала указываются методы класса, а в конце—его поля. Это сделано для того, чтобы привлечь основное внимание к интерфейсу, а не к реализации класса.
Рассмотрим следующую, весьма упрощенную версию класса Employee, который можно использовать в бизнесе для создания платежной ведомости.
class Employee
{.
// Конструктор
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
// Метод 1
public String getName()
{ return name;
}
// Другие методы
// Поля экземпляра
private String name;
private double salary;
private Date hireDay;
}
Детали реализации этого класса мы проанализируем в следующих разделах. Тем не менее рассмотрим листинг 4.2, в котором приведен текст программы, иллюстрирующей работу класса Employee.
В этой программе создается массив Employee, в который заносятся три объекта класса Employee.
Employee!] staff = new Employee[3];
staff[О! = new Employee("Карл Крекер", ...);
staff[l] = new Employee("Гарри Хакер", . ..);
staff[2: = new Employee("Тони Тестер", ...);
Затем, для того чтобы поднять зарплату каждого сотрудника на 5%, вызывается метод класса Employee.
for (i = 0; i < staff.length; i++)
staff[i].raiseSalary(5] ;
В заключение на печать с помощью методов getName, getSalary и getHireDay выводится информация о каждом сотруднике.
for (int i = 0; i < staff.length; i++) (
Employee e = staff[i];
System.out.printIn("имя=" + getName()
+ ", зарплата=” + e.getSalary(]
+ ", дата найма на работу»" + e.getHireDay());
}
Заметим, что этот пример программы состоит из двух классов: класса Employee и класса EmployeeTest со спецификатором доступа public. Метод main с операторами, которые мы только что описали, содержится в классе EmployeeTest.
Исходный текст программы содержится в файле EmployeeTest.java, поскольку его имя должно совпадать с именем открытого класса. В исходном файле может быть только один открытый класс и сколько угодно классов с другими спецификаторами доступа.
Далее при компиляции исходного кода компилятор создает в каталоге два файла с классами: EmployeeTest. class и Employee . class.
Выполнение программы начинается, когда интерпретатор байт-кодов получает имя класса, содержащего метод main:
java EmployeeTest
Затем интерпретатор байт-кодов начинает выполнять метод main из класса EmployeeTest. Этот код в свою очередь создает три новых объекта класса Employee и демонстрирует их состояние.
import java.util.*;
public class EmployeeTest
{
public static void main(String[ args)
{
// Заполнить массив сотрудников тремя объектами.
' Employee!] staff = new Employee[3];
staff[O’ = new Employee("Карл Крекер", 75000,
1987, 12, 15);
staff[IJ = new Employee("Гарри Хакер", 50000,
1989, 10, 1);
14.17.24.25.34.35.43.47.52.53.54.55.14.17.24.25.34.35.43.47.52.53.54.55.staff[2] = new Employee("Тони Тестер", 40000, 1990, 3, 15);
14.
17.
24.
25.
34.
35.
43.
47.
52.
53.
54.
55.
14.
17.
24.
25.
34.
35.
43.
47.
52.
53.
54.
55.
// Всем поднять зарплату на 5%.
for (int i 0; i < staff.length; i++)
staff[i].raiseSalary(5);
// Вывести информацию обо всех сотрудниках.
for (int i = 0; i < staff.length; i++) {
Employee e = staff[i];
System.out.printin("имя=" + e.getName(i
+ ",зарплата=" + e.getSalary(J
+ ",дата найма на работу=" + e.getHireDay());
}
}
}
class Employee
{
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar январю соответствует 0.
hireDay = calendar.getTime();
}
public String getName(I
{
return name;
}
public double getSalary(]
{
return salary;
}
public Date getHireDay(I
{
return hireDay;
)
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
Использование нескольких исходных файлов
Программа, приведенная в листинге 4.2, содержит два класса в одном исходном файле. Многие программисты предпочитают помещать каждый класс в отдельный исходный файл. Например, класс Employee МОЖНО поместить в файл Employee. java, а класс EmployeeTest— в файл EmployeeTest.java.
Если вы придерживаетесь такой же точки зрения, у вас есть два способа компиляции программы. Можно вызвать компилятор языкаJava, используя безразличный символ: javac Employee*.java
Затем все исходные файлы, имена которых соответствуют указанному вами шаблону, будут скомпилированы в файлы классов. Кроме того, можно просто ввести команду: javac EmployeeTest.java
Возможно, вы удивитесь тому, что вторая команда будет выполнена, поскольку файл Employee .java никогда не компилируется явно. Однако, обнаружив, что в файле EmployeeTest . java используется класс Employee, компилятор языка Java станет искать файл Employee. class. Если он его не найдет, то автоматически будет найден и скомпилирован файл Employee . java. Справедливо даже более сильное утверждение: если версия обнаруженного файла Employee . j ava создана позже, чем существующий файл Employee . class, компилятор языка Java автоматически перекомпилирует файл класса.
Если вы знакомы с утилитой make операционной системы UNIX (или с одним из ее вариантов для системы Windows, например “nmake"), то можете считать,что в компилятор языка Java уже встроены возможности этих утилит.
Анализ класса Employee
В последующих разделах мы детально проанализируем класс Employee. Начнем с методов этого класса. Изучая исходный текст программы, легко видеть, что этот класс имеет один конструктор и четыре метода.
public Employee(String n, double s,int year, int month, int day)
public String getName(]•
public double getSalary()
public Date getHireDay()
public void raiseSalary(double byPercent)
Все методы этого класса помечены ключевым словом public, означающим, что эти методы могут быть вызваны любым методом любого класса. (Существуют четыре возможных уровня доступа; они рассматриваются в этой и следующей главах.)
Далее, отметим, что в классе есть три поля экземпляра для хранения данных, с которыми в объекте класса Employee производятся манипуляции.
private String name;
private double salary;
private Date hireDay;
Ключевое слово private означает, что к данным полям имеют доступ только методы самого класса Employee. Ни один внешний метод не может читать эти поля или записывать в них что-либо.
Поля экземпляра могут быть и открытыми, если перед их именем стоит спецификатор доступа public, однако это очень неудачная идея. Наличие открытых полей позволяет любой части программы читать и модифицировать их содержимое. Это полностью разрушает инкапсуляцию. Мы настоятельно рекомендуем всегда закрывать доступ к полям экземпляра с помощью ключевого клова private.
В заключение отметим, что два поля экземпляра представляют собой объекты: поля name и hireDat являются ссылками на объекты классов String и Date. Это довольно распространенное явление: классы часто содержат объекты других классов.
Первое знакомство с конструкторами
Рассмотрим конструктор, описанный в классе Employee.
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;.
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
Как видим, имя конструктора совпадает с именем класса. Этот конструктор выполняется при создании объекта класса Employee — заполняя поля экземпляра заданными значениями.
Например, при создании экземпляра класса Employee с помощью следующего выражения:
new Employee("Джеймс Бонд", 100000, 1950, 1, 1);
поля экземпляра будут заполнены такими значениями:
name = "Джеймс Бонд"; salary = 100000;
hireDay = Январь 1, 1950;
Между конструкторами и другими методами есть существенная разница: конструктор можно вызывать только в сочетании с оператором new. Конструктор нельзя применить к существующему объекту, чтобы изменить информацию в его полях. Например, вызов james.Employee("Джеймс Бонд", 250000, 1950, 1, 1); // ОШИБКА при компиляции приведет к ошибке.
Позднее в этой главе мы вернемся к конструкторам. Пока запомните следующее.
Имя конструктора совпадает с именем класса.
Класс может иметь несколько конструкторов.
Конструктор может иметь один или несколько параметров либо не иметь их совсем.
Конструктор ничего не возвращает.
Конструктор всегда вызывается оператором new.
Конструкторы в языках Java и C++ работают одинаково. Однако учтите, что все объекты в языке Java размещаются в динамической памяти, и конструкторы вызываются только вместе с оператором new. Программисты на языке C++ часто делают такую ошибку:
Employee number007("ДжеймсБонд", 10000, 1950, 1, 1); // C++, а не Java
Это выражение в C++ работает, а в Java — нет.
Будьте осторожны и не называйте локальные переменные так же, как и поля экземпляра. Например, приведенный ниже конструктор не сможет установить зарплату сотрудника.
public Employee(String n, double s, ...)
{
String name = n; // ОШИБКА double salary = s; // ОШИБКА }
В конструкторе объявляются локальные переменные name и salary. Доступ к этим переменным возможен только внутри конструктора. Они маскируют поля экземпляра с теми же именами. Некоторые программисты — например, авторы этой книги — пишут такие программы, поскольку их руки работают с клавиатурой быстрее, чем голова. Это ужасная ошибка, которую иногда крайне трудно обнаружить. Нужно быть просто внимательным и не называть переменные именами полей.
Методы класса Employee
Методы класса Employee довольно просты. Отметим, что все эти методы имеют доступ к закрытым полям экземпляра. Это очень важный момент: методы класса всегда имеют доступ к полям экземпляра своего класса.
Например, метод
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
)
устанавливает новое значение поля salary в объекте, выполняющем данный метод. (Этот конкретный метод не возвращает никакого значения.) Например, вызов метода number007.raiseSalary(5) ;
увеличивает значение поля number007 . salary в объекте number007 на 5%. Точнее говоря, этот вызов приводит к выполнению двух операторов:
double raise = number007.salary * 5 /100;
number007.salary += raise;
Метод raiseSalary имеет два параметра. Первый параметр, называемый неявным, представляет собой объект типа Employee, который указывается перед именем метода. Второй параметр, число, указанное в скобках после имени метода, называется явным.
Легко видеть, что явные параметры перечисляются в объявлении метода, например double byPercent. Неявный параметр в объявлении метода не приводится.
В каждом методе ключевое слово this ссылается на неявный параметр. При желании можно переписать метод raiseSalary следующим образом. public voidbyPercent)
( double raise =* byPercent / 100;
+= raise;
Некоторые программисты предпочитают такой стиль, поскольку в нем четче выявлены различия между полями экземпляра и локальными пременными.
В языке C++ методы обычно определяются вне класса. Например, voidbyPercent) // C++, а не Java
{
)
Если определить метод внутри класса, он автоматически станет подставляемой функцией (не всегда. — Прим. ред.).
class Employee {
int{ return name; } // Подставляемая функция в C++.
}
(В языке C++ у этого правила есть исключения. Так, если метод содержит условный оператор или цикл, компилятор не сделает его подставляемым и выдаст предупреждение. — Прим. ред.)
В языке Java все методы определяются внутри класса. Это не делает их подставляемыми.
В заключение рассмотрим подробнее довольно простые методы getName, getSalary и getHireDay.
public String
{
return
}
public double
return salary;
}
public Date
{
return hireDay;
}
Они представляют собой яркий пример методов доступа (accessor). Поскольку они просто возвращают значения полей экземпляра, иногда их называют методами доступа к полю (field accessor).
проще ли было сделать поля name, salary и hireDay открытыми и не создавать отдельные методы доступа к ним?
Дело в том, что поле name доступно лишь для чтения ("read only" field). После того как значение этого поля будет установлено конструктором, ни один метод не сможет его изменить. Следовательно, у нас есть гарантия, что ни один метод не сможет исказить информацию, хранящуюся в этом поле.
Поле salaryне является открытым только для чтения, однако изменить его значение может только метод raiseSalary. В частности, если окажется, что поле содержит неверное значение, нужно будет отладить только один метод. Если бы поле salary было открытым, причина ошибки могла бы находиться где угодно.
Иногда нужно иметь возможность читать и модифицировать содержимое поля. Для этого нужны три вещи:
закрытое поле данных;
открытый метод доступа;
открытый модифицирующий метод.
Это намного утомительнее, чем просто открыть одно-единственное поле данных, однако при этом программист получает несколько значительных преимуществ.
Внутреннюю реализацию класса можно изменять совершенно независимо от других методов.
Например, при другом способе хранения имен
String firstName;
String lastName;
метод getName должен возвращать другое значение:
firstName + " " + lastName
Это изменение совершенно не влияет на остальную часть программы. Разумеется, и методы доступа, и модифицирующие методы должны быть переработаны, чтобы учесть новое представление данных. Однако при этом возникает еще одно преимущество.
Модифицирующие методы могут выполнять проверку ошибок, в то время как метод, просто присваивающий полю некое значение, может не заметить никаких ошибок.
Например, метод setSalary может проверять, не стала ли зарплата отрицательной величиной.
Будьте осторожны при создании методов доступа, возвращающих ссылки на изменяемый объект. Создавая класс Emloyee, мы нарушили это правило: метод getHireDayвозвращает объект класса Date.
class Employee
{
public Date getHire(] ;
{ return hireDay;
}
private Date hireDay;
}
Это нарушает инкапсуляцию! Рассмотрим пример неверного кода:
Employee harry = ... ;
Date d = harry.getHireDay();
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000; d.setTime(d.getTime() - (long)tenYearsInMilliSeconds);
// Добавим к возрасту Гарри десять лет.
Причину ошибки трудно уловить. Обе ссылки d и harry.hireDayотносятся к одному и тому же объекту (рис. 4.5). Применение модифицирующего метода к объекту d автоматически изменяет закрытое состояние объекта класса Employee!
Чтобы вернуть ссылку на изменяемый объект, его нужно сначала клонировать (to clone). Клон — это точная копия объекта, расположенная в другом месте памяти. Детали клонирования будут рассмотрены в главе 3. Вот как выглядит исправленный код.
class Employee {
public Date hetHireDay(;
{ return (Date)hireDay.cloned ;
}
Эмпирическое правило: всегда используйте метод clone, когда вам нужно скопировать изменяемое поле данных.
Рис. 4.5. Возвращение ссылки на изменяемое поле данных
Метод доступа к закрытым данным
Как вы уже знаете, метод может получить доступ к закрытым данным объекта, из которого он был вызван. Многих удивляет тот факт, что метод может получить доступ к закрытым данным всех объектов своего класса'. Например, рассмотрим метод equals, сравнивающий между собой два объекта класса Employee.
class Employee
(
boolean equals(Employee other)
return name.equals(other.name);
}
}
Типичный вызов этого метода выглядит следующим образом: if (harry.equals(boss)) ...
Этот метод имеет доступ к закрытым полям объекта harry, что вовсе не удивительно. Он также имеет доступ к полям объекта boss. Это вполне допустимо, поскольку boss — объект типа Employee, а методы класса Employee могут обращаться к закрытым полям любого объекта этого типа.
В языке C++ действует такое же правило. Метод имеет доступ ко всем закрытым членам любого объекта своего класса, а не только к закрытым полям объекта, зак.данного в качестве неявного параметра.
Закрытые методы
При реализации класса мы сделали все поля данных закрытыми, поскольку открывать их весьма рискованно. А как насчет методов? Хотя большинство рассмотренных методов являются открытыми, закрытые методы встречаются довольно часто. Причина этого проста: чтобы реализовать определенный метод, может понадобиться разбить его код на несколько отдельных методов. Некоторые их этих внутренних методов не следует делать открытыми. (Например, они могут оказаться слишком тесно связанными с текущей реализацией, либо требовать специального протокола или особого вызова.) Такие методы лучше всего делать закрытыми и описывать с помощью ключевого словаprivate.
Чтобы сделать метод закрытым, просто измените ключевое слово public на private.
Сделав метод закрытым, совершенно не обязательно сообщать пользователям о его дальнейших изменениях. Может оказаться, что такой метод труднее реализовать, или способ представления данных может быть несущественным: это не имеет значения. Дело в том, что, если метод является закрытым, он никогда не будет использоваться вне класса, и разработчик класса может просто о нем не упоминать. С открытым классом дело обстоит иначе, поскольку другие части программы могут к нему обращаться.
Итак, делайте методы закрытыми, если:
метод скрыт от пользователя класса;
метод нелегко переделать при изменениях реализации класса.
Неизменяемые поля экземпляра
Поля экземпляра можно объявить с помощью ключевого слова final.Такое поле должно инициализироваться при создании объекта. Таким образом, должно быть гарантировано, что после выполнения конструктора значение этого поля задано. После этого значение такого поля изменить уже нельзя. Например, поле name класса Employee можно объявить неизменяемым, поскольку после создания объекта оно никогда не изменяется — метода setName не существует.
classEmployee {
private final String name; }
Помечать поля, остающиеся неизменными на протяжении всего времени существования объекта, — хорошая идея. Если все поля класса объявлены какfinal,то класс является неизменяемым. Например, классы String и Date представляют собой неизменяемые классы. Неизменяемые классы обладают одним преимуществом — нет необходимости заботиться о совместном использовании ссылок.
Статические поля и методы
Во всех программах, которые мы рассмотрели, метод main имел модификатор static. Обсудим подробнее смысл этого модификатора.
Статические поля
Поле, имеющее модификатор static, существует в одном экземпляре. В то же время, если поле не статическое, то каждый объект имеет его копию. Например, допустим, что нам требуется присвоить уникальный идентификационный номер каждому сотруднику. Добавим в класс Employee поле id и статическое поле nextld.
class Employee
{
private int id;
private static int nextid = 1;
}
Теперь каждый объект класса Employee имеет свое поле id, однако есть только одно поле nextid, которое относится ко всем экземплярам класса.
Попробуем объяснить это несколько иначе. Если существует тысяча объектов класса Employee, то в них есть тысяча полей id, по одному в каждом объекте. В то же время существует только один экземпляр статического поля nextid. Даже если объектов класса Employee нет совсем, статическое поле nextid существует. Оно принадлежит классу, а не конкретному объекту.
В большинстве объектно-ориентированных языков статические поля называются полями класса (class fields). Термин "статический" — бессмысленный пережиток, унаследованный от языка C++.
Реализуем простой метод.
public void setld(]
{
id = nextid;
nextld++;
}
Допустим, нам нужно задать идентификационный номер объекта harry: harry.setld();
Теперь значение поля id в объекте harry задано, а значение статического поля nextldувеличено на единицу.<
harry.id = ...; Employee.nextld++;
Константы
Статические переменнее используются довольно редко. В то же время статические константы используются гораздо чаще. Например, класс Math имеет статическую константу.
public class Math {
public static final double PI = 3.14159265358979323846;
)
Обратиться к этой константе в своей программе можно с помощью выражения Math.PI.
Если бы ключевое слово static было пропущено, константа PI была бы обычным полем экземпляра класса Math. Это значит, что для доступа к такой константе нужно было бы создать объект класса Math, причем каждый подобной объект имел бы свою копию константы PI.
Другая статическая константа, которую часто используют,— System.out. Она объявлена в классе System.
public class System
{
public static final PrintStream out = ...;
)
Как мы уже несколько раз упоминали, применять открытые поля не следует никогда, поскольку любой сможет изменить их значения. Однако открытые константы (т.е. поля, объявленные с ключевым словом final) можно использовать смело. Поскольку поле out объявлено как final, ему нельзя присвоить другой поток вывода. out = new PrintStream(...); // ОШИБКА — поле out изменить нельзя.
Посмотрев на класс System, вы увидите метод setOut, позволяющий присвоить полю System.outдругой поток. Как же этот метод может изменить переменную, описанную как final? Ответ прост: метод setOut— машинозависимый, он не реализуется в языке Java. Машинозависимые методы могут обходить механизмы контроля, предусмотренные в языке Java. Это очень необычный способ, который ни в коем случае не следует повторять в своих программах.
Статические методы
Статические методы не работают с объектами. Например, метод pow из класса Math — статический. Выражение
Math.pow()
вычисляет степень V. Этот метод не использует ни одного объекта класса Math при выполнении своей задачи. Иными словами, он не имеет неявного параметра. Это значит, что статические методы — это методы, не имеющие параметра this.
Поскольку статические методы не работают с объектами, с их помощью невозможно получить доступ к полям экземпляра. Однако статические методы имеют доступ к статическим полям других классов. Вот пример такого статического метода. public static int getNextld(J {
return nextld; // Возвращает статическое поле
Чтобы вызвать этот метод, нужно указать имя класса:
int n = Employee.getNextld();
Можно ли пропустить ключевое слово static при описании этого метода? Да, однако при этом для его вызова вам потребуется ссылка на объект типа Employee.
Для вызова статического метода можно использовать и объекты. Например, если harry— это объект класса Employee, то можно использовать вызов harry.getNextld( ) вместо Employee.getNextld(). Однако такое обозначение затемняет суть дела, поскольку метод getNextld для вычисления результата вообще не обращается к объекту harry. Мы рекомендуем для вызова статических методов использовать имена классов, а не объекты.
Статические методы следует применять в двух ситуациях.
Когда методуненужендоступк состояниюобъекта,посколькувсенеобходи
мые параметры задаются явно (например, в методе Math .pow).
Когда методу нужен доступ лишь к статическим полям класса (например, метод Employee.getNextld).
Статические поля и методы в языках Java и C++ по существу одинаковы, хотя немного отличаются синтаксически. В языке C++ для доступа к статическому полю b_или методу, находящемуся вне области видимости, можно использовать оператор
::, напримерMath: PI.
Термин "статический"— исторический курьез. Сначала ключевое слово static было введено в языке С для обозначения локальных переменных, которые не уничтожались при выходе из блока. В этом контексте слово "статический" имеет смысл: переменная продолжает существовать после выхода из блока, а также при повторном входе в него. Затем слово "статический" в языке С приобрело второе значение — глобальные переменные и функции, к которым нельзя получить доступ из других файлов. Ключевое слово static было просто использовано повторно, чтобы не вводить новое. В итоге в языке C++ это ключевое слово было использовано в третий раз, получив совершенно новую интерпретацию, для обозначения переменных и функций, принадлежащих классу, но не принадлежащих ни одному объекту этого класса. Именно это значение ключевое слово static имеет и в языке Java.
Порождающие методы
Рассмотрим еще одно широко распространенное применение статических методов. Вернемся к методам, обсужденным в главе 3.
NumberFormat.getNumberlnstance(]
NumberFormat.getCurrencylnstance()
Каждый из этих методов возвращает объект типа NumberFormat. Например, NumberFormat formatter =
System.out.printIn(formatter.format(salary));
// Выводит зарплату с символом валюты.
Как вы уже знаете, существуют статические методы — они вызываются через класс, а не с помощью объектов. Однако их цель — создать объект того же класса. Такие методы называются порождающими (factory).
Почему бы для этого не использовать конструктор? Есть две причины. Конструктору нельзя дать произвольное имя. Его имя всегда должно точно совпадать с именем класса. В примере с методом NumberFormat имеет смысл применять два разных имени для ния номера и форматирования объектов. Более того, производящий метод может вернуть объект типаили объект подкласса, производного от класса
(Наследование описано в главе 5.) Конструктор не имеет такой гибкости.
Метод main
Заметим, что статические методы можно вызывать, даже если объектов совсем нет. Например, для того чтобы вызвать методобъекты класса Math не
нужны. По той же причине метод main является статическим.
public class Application {
public static voidargs)
<
// Здесь создаются объекты.
)
Каждый класс может иметь метод main. Этот удобный способ позволяет тестировать классы независимо друг от друга. Например, метод main можно добавить в класс Employee: class Employee (
publicdoubblle s,
int year, int month, int day) { name = n; salary = s;
GregorianCalendar calendar =
newmonth - 1, day) ;
hireDay = )
public static voidargs) // Отладочный модуль.
{
Employee e = new Employee ("Romeo",50000) ;
e.raiseSalary(10);
" " +;
}
}
Если вы хотите протестировать класс Employee отдельно, просто выполните команду
java Employee
Если класс Employee представляет собой часть большого приложения, запус- титеэто приложение с помощью команды
java Application
и методmain класса Employee никогда не будет выполнен.
Метод main не работает ни с одним объектом. Фактически, когда программа начинает выполняться, еще не существует ни одного объекта. Статический метод main создает объекты, необходимые для работы программы.
Программа, показанная в листинге 4.3, содержит простую версию класса Employee со статическим полем nextld и статическим методом getNextld. Массив заполняется тремя объектами класса Employee, а затем на печать выводится информация о сотрудниках. В заключение на экран выводится присвоенный идентификационный номер.
Заметим, что класс Employee уже имеет статический метод main для модульного тестирования. Попробуйте выполнить метод main с помощью команд java Employee и
java StaticTest
public class StaticTest
{
public static void main(String(] args)
{
// Заполняем массив staff тремя объектами класса Employee.
Employee[j staff = new Employee[ 3 ];
staff[0]=new Employee("Том", 40000);
staff[1]=new Employee("Дик", 60000);
staff[2]=new Employee("Гарри", 65000);
// Выводим на печать информацию обо всех объектах.
. for (int i = 0; i < staff.length; i++)
{
Employee e = staff[i];
e.setldO ;
System.out.printIn("имя=" + e.getName()
+ ",номер=" + e.getldO
+ ",зарплата=" + e.getSalary());
}
int n = Employee.getNextld (); // Вызов статического метода.
System.out.printin("Следующий номер =" + n) ;
}
}
class Employee
{
public Employee(String n, double s)
{
name = n;
salary = s;
id = 0;
}
public String getName()
{
return name;
}
public double getSalaryO
{
return salary;
}
public int getld(J
{
return id;
}
public void setldO
{
id = nextld; // Присваивает номер следующему сотруднику.
nextld++;
}
public static int getNext!d().
{
return nextld; // Возвращает значение статического поля.
}
public static void main(String[] args) // unit test
{
Employee e = new Employee("Гарри", 50000);
System.out.printIn(e.getName() + " " + e.getSalary());
}
private String name;
private double salary;
private int id;
private static int nextld = 1;
72.}
Параметры метода
Рассмотрим термины, которые используются в программировании для описания способа передачи параметров какому-либо методу (или функции). Термин вызов по значению (call by value) означает, что метод получает значение, переданное ему вызывающим модулем. В противоположность этому, вызов по ссылке (call by reference) означает, что метод получает от вызывающего модуля адрес (location) переменной. Таким образом, метод может модифицировать значение, хранящееся в переменной, переданной по ссылке, но не может изменять значение переменной, переданной по значению. Термин "вызов по..." относится к стандартной компьютерной терминологии, описывающей способ передачи параметров в различных языках программирования, а не только в языке Java. (На самом деле, существует еще и третий способ передачи параметров— вызов по имени (call by name),— представляющий, в основном, исторический интерес, поскольку он был применен в языке Algol, одном из первых языков программирования высокого уровня.)
В языке Java всегда используется только вызов по значению. Это значит, что метод получает копии значений всех параметров. В частности, метод не может модифицировать содержимое ни одного параметра, переданного ему.
Например, рассмотрим следующий вызов.
double percent = 10;
harry.raiseSalary(percent);
He имеет значения, как именно реализован метод, мы знаем, что после его вызова значение переменной percent останется равным 10.
Изучим эту ситуацию подробнее. Допустим, что метод пытается утроить значение параметра.
public static void tripleValue(double x); // He работает. <
x = 3x;
Вызовем этот метод.
double percent = 10;
tripleValue(percent);
Однако такой способ не работает. После вызова метода значение переменной percent остается равным 10. Происходит следующее.
Переменная х инициализируется копией значения параметра percent (т.е. числом 10).
Значение переменной х утраивается — теперь оно равно 30. Однако значение переменной percent остается равным 10 (рис. 4.6).
Метод завершает свою работу, и параметр х больше не используется.
Рис. 4.6. Модификация числового параметра не имеет продолжительного эффекта
Однако существуют два вида параметров:
основные типы (числа, булевские переменные);
ссылки на объекты.
Как мы убедились, методы не могут модифицировать параметры основных типов. С объектными параметрами ситуация иная. Легко можно реализовать метод, утраивающий зарплату сотрудников.
public static void tripleSalary(Employee x) // Работает
x.raiseSalary(200);
}
При вызове
harry = new Employee(...);
tripleSalary(harry);
произойдет следующее.
Переменная х инициализируется копией значения переменной harry, т.е. ссылкой на объект.
Метод raiseSalary применяется к этой ссылке на объект. Объект класса Employee, к которому относятся ссылки х и harry, увеличивает зарплату сотрудников на 200%.
Метод завершает свою работу, и параметр х больше не используется. Разумеется, объектная переменная harry продолжает ссылаться на объект, в котором зарплата увеличена втрое (рис. 4.7).
Рис. 4.7. Модификация объектного параметра имеет продолжительный эффект
Как видим, реализовать метод, изменяющий состояние объекта, передаваемого как параметр, довольно легко. Фактически такие изменения производятся очень часто. Причина этого проста— метод получает копию ссылки на объект, поэтому и копия, и оригинал ссылки относятся к одному и тому же объекту.
Во многих языках программирования (в частности, в языках C++ и Pascal) есть два способа передачи параметров: вызов по значению и вызов по ссылке. Некоторые программисты (и, к сожалению, даже авторы некоторых книг) утверждают, что язык Java использует при передаче объектов только вызов по ссылке. Однако это не так. Поскольку это утверждение является широко распространенным заблуждением, стоит подробнее рассмотреть контрпример.
Попробуем написать метод, выполняющий обмен двух объектов класса Employee. public static void swap(Employee x, Employee у) // He работает.
{
Employee temp = x;
x = y;
у = temp;
}
Если бы в языке Java для передачи объектов в качестве параметров использовался вызов по ссылке, этот метод работал бы так.
Employee a =newEmployee("Алиса",...);
Employee b =newEmployee("Боб",...) ;
swap(a, b);
// Относится ли теперь ссылка а к Бобу, а ссылка b — к Алисе?
Однако на самом деле этот метод не меняет местами объектные ссылки, хранящиеся в переменных а и b. Параметры х и у метода swap инициализируются копиями этих ссылок. Затем метод меняет местами эти копии.
//. Ссылка х относится к Алисе, а ссылка у — к Бобу.
Employee temp « х;
х = у;
у = temp;
// Теперь ссылка х относится к Бобу, а ссылка у — к Алисе.
В конце концов следует признать, что все было напрасно. По завершении работы метода переменные х и у уничтожаются, а исходные переменные а и b продолжают ссылаться на прежние объекты.
Таким образом, в языке Java для передачи объектов не используется вызов по ссылке. Вместоэтого передаются по значению ссылки на объекты.
В приведенном ниже списке указано, что может делать метод со своими параметрами, а что — нет.
Метод не может изменять параметры основных типов (т.е. числа и булевские значения).
Метод может изменять состояние объекта, передаваемого как параметр.
Метод не может изменять ссылки и переназначать их на новые объекты.
Программа, приведенная в листинге 4.4, иллюстрирует эти утверждения. Сначала программа безуспешно пытается утроить значение числового параметра.
Проверка метода tripleValue:
До: percent=10.0
В конце метода: х=30.0
После: percent=10.0
Затем программа успешно утраивает зарплату сотрудника.
Проверка метода tripleSalary:
До: salary=50000.0
В конце метода: salary=150000.C
После: salary=15000.0
После выполнения метода состояние объекта, на который ссылается переменная harry, изменяется. Ничего невероятного здесь нет, поскольку метод модифицировал состояние объекта с помощью копии ссылки на него.
В заключение программа демонстрирует безуспешную работу метода swap.
Проверка метода swap:
До: а=Алиса
До: Ь=Боб
В конце метода: х=Боб
В конце метода: у=Алиса
После: а=Алиса
После: Ь=Боб
Как видим, переменные х и у меняются местами, однако переменные а и Ь остаются прежними.
параметров не имеет про-параметров не имеет про-Рис. 4.8. Обмен объектных должительного эффектаРис. 4.8. Обмен объектных должительного эффекта
параметров не имеет про-
параметров не имеет про-
Рис. 4.8. Обмен объектных должительного эффекта
Рис. 4.8. Обмен объектных должительного эффекта
В языке C++ применяются как вызов по значению, так и вызов по ссылке. Например, легко можно реализовать методы
void tripleValue(doubles x)
ИЛИ
void swap(Employee& x, Emloyees y)
Эти методы модифицируютсвои параметры, являющиеся ссылками.
Листинг 4.
ava
1. public class ParamTest
(
4.
15.
16.
17.
18.
19.
25.
26.
27.
28.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
public static void main(String(] args) {
/*
Тест 1: методы не могут изменять числовые параметры. */
System.out.printin("Проверкаметода tripleValue:");
double percent = 10;
System.out.printin("До: percent=" + percent);
tripleValue(percent; ;
System.out.printin("После: percent=" + percent);
Тест 2: методы могут изменять состояние объектов, передаваемых в качестве параметров
*/
System.out.printIn("ХпПроверкаметода tripleSalary:");
Employee harry = new Employee("Гарри", 50000);
System.out.printin("До: lary="+harry.getSalary());
tripleSalary(harry) ;
System.out.println("После: salary="+harry.getSalary()]
Тест З: методы не могут приписывать новые объекты параметрам, являющимся ссылками на объекты.
*/
System.out.printIn("ХпПроверкаметода swap:");
Employee a = new Employee("Алиса", 70000);
Employee b = new Employee("Боб", 60000);
System.out.printin("До: а= System.out.printIn("До: b= swap(a, b);
System.out.printIn("После:
+ a.getName()); + b.getName ());
а=
}
System.out.printin("После: b=
+ a.getName());
+ b.getName());
public static void tripleValue(double x) // He {
работает.
x = 3 *
x;
System.out.printIn("B конце метода: х=" + x)
public static void tripleSalary(Employee x) // {
}
x.raiseSalary(200);
System.out.printin("0 конце метода: salary= + x.getSalaryO );
public static void swap(Employee x, Employee y)
{
}
;
Работает.
Employee temp = x; x = y; у = temp;
System.out.printin("В конце метода: х=" + x.getName()) System.out.printin("0 конце метода: у=" + у.getName ())
7075.83.7075.83.
70
75.
83.
70
75.
83.
}
class Employee // Упрощенный класс Employee
{
public Employee(String n, double s)
{
name = n;
salary = s;
}
public String getName(;
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100; salary += raise;
}
private String name;
private double salary;
}
Создание объектов
Мы уже знаем, как можно писать простые конструкторы, определяющие начальные состояния объектов. Однако, поскольку создание объектов — очень важный вопрос, в языке Java имеется достаточно много разнообразных механизмов для разработки конструкторов. Эти механизмы рассматриваются ниже.
Перегрузка
Напомним, что класс GregorianCalendar имеет несколько конструкторов. Создать объект этого класса можно с помощью оператора
GregorianCalendar today = new GregorianCalendar();
или
GregorianCalendar deadline
= new GregorianCalendar(2099, Calendar, DECEMBER, 31);
Такая возможность называется перегружай (overloading). Перегрузка возникает, если несколько методов имеют одинаковое имя (в нашем случае несколько конструкторов имеют имя GregorianCalendar), но разные параметры. Компилятор должен сам определить, какой метод вызвать, сравнивая типы параметров, описанных в заголовках методов, с типами значений, указанных в вызове. Если ни один метод не соответствует вызову, либо одному вызову одновременно соответствует несколько вариантов, возникает ошибка компи- ляции.(Этотпроцессназывается разрешением перегрузки (overloadingresolution).)
В языке Java можно перегрузить любой метод, а не только конструкторы. Таким образом, чтобы полностью описать метод, нужно указать его имя и типы параметров. Такая конструкция называется сигнатурой метода (signature). Например, в классе String есть четыре метода indexOf. Они имеют следующие сигнатуры: indexOf(int) indexOf(int, int) indexOf(String] indexOf(String, int)
Тип возвращаемого методом значения не входит в его сигнатуру. Следовательно, нельзя использовать два метода, имеющих одинаковые имена и типы параметров и отличающихся лишь типом возвращаемого значения.
Инициализация полей по умолчанию
Если значение поля не задано в конструкторе явно, ему автоматически присваивается значение по умолчанию: числам — нули, булевским значениям — false, а ссылкам на объект — ссылка null. Однако полагаться на это — дурной тон. Если поля инициализируются неявно, программа становится менее понятной.
Между полями и локальными переменными есть существенная разница. Локальные переменные всегда должны явно инициализироваться в методе. Однако, если поле в классе не инициализируется явно, ему автоматически присваивается значение, задаваемое по умолчанию (нуль, значение falseили ссылка null).
Рассмотрим в качестве примера класс Employee. Допустим, в конструкторе значения некоторых полей не заданы. По умолчанию поле salary должно инициализироваться нулем, а поля name и hireDay — ссылкой null.
Однако полагаться на это не следует. При вызове методов getName или getHireDay ссылка null может оказаться совершенно нежелательной.
Date h = harry.getHireDay ();
calendar.setTime(h); // Если h null, возбуждается исключительная ситуация.
Конструкторы по умолчанию
Конструктор по умолчанию (default constructor) не имеет параметров. (Этот конструктор иногда называется конструктором без аргументов (no-arg constructor).) Например, конструктор по умолчанию класса Employee имеет следующий вид.
public Employee(] (
name
salary = 0;
hireDay = new Date ();
Если в классе вообще нет конструкторов, вызывается конструктор по умолчанию. Этот конструктор присваивает всем полям экземпляра их значения, предусмотренные по умолчанию. Так, все числовые значения, содержащиеся в полях экземпляра, окажутся равными нулю, булевские значения — false, а ссылки на объекты — null.
Если в классе есть хотя бы один конструктор, и не предусмотрен конструктор по умолчанию, невозможно создать объекты, не задавая параметры конструктора. Например, класс Employee в листинге 4.2 имеет один конструктор:
name, double salary, int y, intint d)
В этом случае нельзя создать объект, поля которого имели бы значения по умолчанию. Это значит, что вызов е = new;
является ошибочным.
Учтите, что конструктор по умолчанию вызывается, только если в классе вообще нет ни одного другого конструктора. Если в классе есть хотя бы один собственный конструктор и нужно дать его пользователям возможность создавать экземпляр этого класса с помощью вызова new(),
следует самому создать конструктор по умолчанию (без параметров). Разумеется, если значения по умолчанию вас вполне устраивают, можно просто написать: public (
Явная инициализация полей
Поскольку конструкторы в классе можно перегружать, есть несколько способов задать начальное состояние полей его экземпляров. Всегда следует быть уверенным, что, независимо от вызова конструктора, все поля экземпляра имеют некие разумные значения.
Можно просто присвоить каждому полю соответствующее значение в определении класса. Например, class Employee {
private String name = "";
}
Это присваивание выполняется до вызова конструктора. Такой синтаксис особенно полезен, если все конструкторы класса должны задать одно и то же значение конкретного поля.
Значение, задаваемое при инициализации, не должно быть константным. Ниже приведен пример, в котором поле инициализируется с помощью вызова метода. Рассмотрим класс Employee, каждый объект которого имеет поле id. Это поле можно инициализировать следующим образом.
class Employee {
static int
{ int r = nextld++; return r;
>
private int id =
В языке C++ нельзя инициализировать поля экземпляра непосредственно в описании класса. Значения всех полей должны задаваться в конструкторе. Однако в языке C++ есть синтаксическая конструкция, называемая списком инициализации (initializer list).
Employee::Employee(String n, double s, int y, int m, int d) // C++
:
salary(s), hireDayty, m, d) ( )
В языке C++ эта синтаксическая конструкция используется для вызова конструкторов полей экземпляра. В языке Java делать это нет никакой необходимости, поскольку объекты не могут содержать другие объекты. Вместо этого разрешается иметь только ссылки на них.
Имена параметров
Создавая даже самый тривиальный конструктор (а большинство из них таковыми и являются), трудно выбрать подходящие имена для его параметров.
Обычно в качестве имен параметров выбираются буквы:
public Employee(String n, double s)
name = n;
salary = s;
)
Однако недостаток этого подхода заключается в том, что, читая программу, невозможно понять, что означают параметры п и s.
Некоторые программисты добавляют к осмысленным именам параметров префикс "а":
public Employee(String aName, double aSalary) {
name ■ aName;
salary = aSalary
}
Такой способ вполне ясен. Любой читатель программы может сразу понять, в чем заключается смысл параметра. Есть еще один широко распространенный прием. Он основан на том, что параметры маскируют (shadow) поля экземпляра с одинаковыми именами. Например, если вызвать метод с параметром salary, то эта переменная будет ссылаться на параметр, а не на поле экземпляра. Доступ к полю экземпляра осуществляется с помощью выражения this . salary. Напомним, что ключевое слово this означает неявный параметр, т.е. создаваемый объект. Рассмотрим следующий пример. public Employee(String name, double salary) (
this.name = name;
this.salary = salary;
}
В языке C++ перед именами полей экземпляра обычно приписывают префиксы, представляющие собой либо символ подчеркивания, либо фиксированную букву. кНапример, поле, в котором записан размер зарплаты, может называться salary
или mSalary. В программах на языке Java это обычно не делается.
Вызов другого конструктора
Ключевое слово this ссылается на неявный параметр метода. Однако у этого слова есть еще одно значение.
Если первый оператор конструктора имеет вид this (...), то вызывается другой конструктор этого же класса. Вот типичный пример.
public Employee(double s) {
.// Вызывает конструктор Employee(String, double).
this("Сотрудник#" + nextld, s] ;
nextld++;
}
При вызове оператора new Employee (60000) конструктор Employee (double) вызывает конструктор Employee (String, double).
Применять ключевое слово this для вызова другого конструктора очень полезно — при этом нужно лишь один раз написать общий код, создающий объект.
Объект this в языке Java идентичен указателю this в языке C++. Однако в языке C++ невозможно вызвать один конструктор с помощью другого. Для того чтобы k.разработать общий код инициализации объекта в языке C++, нужно создавать от
дельный метод.
БЛОКИ инициализации
Ранее мы видели два способа инициализации поля.
Задать его значение в конструкторе.
Присвоить значение в объявлении.
На самом деле в языке Java существует и третий механизм; он называется блоком инициализации (initialization block). Эти блоки выполняются каждый раз, когда создается объект данного класса. Например, class Employee {
public Employee(String n, double s)
name = n;
salary = s;
public Employee ()
/ Блок инициализации объекта { id = nextld++;
}
private int id;
private static int nextld;
}
В этом примере начальное значение поля id задается в блоке инициализации объекта, причем неважно, какой именно конструктор используется для создания объекта. Блок инициализации выполняется первым, а тело конструктора — после него.
Этот механизм является совершенно необязательным и обычно не используется. В основном применяются более ясные способы задания начальных значений полей внутри конструктора.
Имея столько способов инициализации полей, немудрено запутаться. Рассмотрим детальнее, что произойдет при вызове конструктора.
Все поля данных инициализируются своими значениями, предусмотренными по умолчанию (0,или null).
Инициализаторы всех полей и блоки инициализации выполняются в порядке их перечисления в объявлении класса.
Если в первой строке конструктора вызывается другой конструктор, то выполняется вызванный конструктор.
Выполняется тело конструктора.
Естественно, нужно организовать код инициализации так, чтобы в нем было легко разобраться. Например, было бы странным, если бы вызов конструкторов класса зависел от порядка объявления полей. Это могло бы привести к ошибкам.
Инициализировать статическое поле следует, либо задав начальное значение, либо используя статический блок инициализации (static initialization block). Первый механизм мы уже рассматривали: static int nextld1;
Если для инициализации статических полей класса нужен сложный код, используйте статический блок инициализации.
Разместите код внутри блока и пометьте его ключевым словом static. Пусть, например, нам нужно, чтобы идентификационные номера сотрудников начинались со случайного числа и не превышали 10000.
// Статический блок инициализации. static
{
Random generator = new
nextld =;
}
Статическая инициализация выполняется, когда класс загружается впервые. Аналогично полям экземпляра статические поля принимают значения 0, false или null, если программист не задал другие значения явным образом. Все операторы, задающие начальные значения статических полей, и статические блоки инициализации выполняются в порядке их перечисления в объявлении класса.
Программисты на языке Java часто развлекаются, создавая программу "Привет, мир", не использующую методmain. ' public class hello {
static
{
System.out.printin("Привет,мир!");
}
}
При выполнении команды java Hello класс загружается, статический блок инициализации выводит на печать строку "Привет, мир!", и только затем появляется сообщение о том, что методmain не определен. Этот недостаток можно ликвидировать с помощью вызова метода System.exit(0) в конце статического блока инициализации.
Программа, приведенная в листинге 4.5, иллюстрирует многие темы, которые обсуждались в этой главе.
Перегрузка конструкторов.
Вызов другого конструктора с помощью оператораthis (...).
Конструктор по умолчанию.
Блок инициализации объекта.
Статический блок инициализации.
Инициализация поля экземпляра.
import java.util.*;
. {public class ConstructorTest
public static void main(String (" args)
{
// Заполняет массив staff тремя объектами класса Employee.
Employee[' staff = new Employee[3];
staff[0" = new Employee("Гарри", 40000);
staff[i; = new Employee(60000);
staff[2] = new Employee();
// Выводит на печать информацию обо всех сотрудниках.
for tint i = 0; i < staff.length; i++)
{
Employee e = staff [i];
System.out.printin("имя=" + e. getName (
+ ",id=" + e.getldd
+ ",salary=" + e.getSalaryl));
}
}
}
27.32.37.42.52.53.54.64 .7077.27.32.37.42.52.53.54.64 .7077.class Employee
27.
32.
37.
42.
52.
53.
54.
64 .
70
77.
27.
32.
37.
42.
52.
53.
54.
64 .
70
77.
{
// Три перегруженных конструктора. public Employee(String n, double s) {
name = n; salary = s;
}
public Employee(double s)
{
// Вызов конструктора Employee(String, double). this("Сотрудник #" + nextld, s);
}
// Конструктор ПС умолчанию.
public Employee(I
(
// Поле name инициализируется как ""-- см. ниже.
// Поле salary задается неявно -- инициализируется нулем.
// Поле id задается в блоке инициализиации.
}
public String getName(]
{ return name;
}
public double getSalaryO
{ return salary;
}
public int getld()
{ return id;
}
private String name = ""; // Инициализация поля name.
private double salary;
private int id;
private static int nextld;
// Блок инициализации объекта.
{
id = nextld;
nextld++; •
}
// Статический блок инициализации. static
{
Random generator = new Random));
// Задаем nextld как случайное число от 0 до 9999. nextld = generator.nextlnt(10000);
}
}
java.util.Random 1.0
Random(I
Создает новый генератор случайных чисел.
• int nextlnt(int n)
Возвращает случайное число от 0 до n-1.
Уничтожение объекта и метод finalize
В некоторых объектно-ориентированных языках, особенно в языке C++, есть явные деструкторы для уничтожения объектов, которые больше не нужны. В основном, деструкторы освобождают память, занятую объектами. Поскольку в языке Java есть механизм автоматической сборки мусора, освобождать память вручную нет никакой необходимости, поэтому в языке Java нет деструкторов.
Разумеется, некоторые объекты используют, кроме памяти, и другие ресурсы, например файлы, или обрабатывают другие объекты, которые, в свою очередь, обращаются к системным ресурсам. В этом случае очень важно, чтобы ресурсы освобождались и возобновлялись вовремя.
В любой класс можно добавить метод finalize. Этот метод будет вызван перед тем, как сборщик мусора уничтожит объект. Не следует полагаться на метод finalize, если вам нужно возобновить ресурсы и сразу использовать их повторно — вы просто не можете знать, когда именно этот метод будет вызван.
Существует метод System.runFinalizerOnExit(true), гарантирующий, что метод finalizeбудет вызван до того, как программа завершит свою работу. Однако этот метод крайне ненадежен и объявлен нежелательным.
Если ресурс должен быть освобожден сразу после его использования, нужно написать свой код. Добавьте метод dispose, который нужно явно вызвать для очистки памяти. Если класс имеет метод dispose, нужно вызвать его по завершении работы с объектом этого класса. В частности, если класс содержит поле, имеющее метод dispose, нужно предусмотреть этот метод для всех полей.
Пакеты
Язык Java позволяет объединять классы в коллекции, называемые пакетами (packages). Пакеты облегчают организацию работы и позволяют отделить вашу собственную работу от классов, разработанных другими программистами.
Стандартная библиотека языка Java распространяется в виде большого количества пакетов, включая пакеты j ava . lang, j j net и др. Стандартные пакеты языка Java являются иерархическими. Подобно каталогам на вашем диске, пакеты могут быть вложены один в другой. Все стандартные пакеты включены в иерархии пакетов j ava и j avax.
В основном, пакеты используются для обеспечения уникальности имен классов. Допустим, двух программистов осенила блестящая идея создать класс Employee. Если оба класса будут находиться в разных пакетах, конфликт не возникнет. Чтобы обеспечить абсолютную уникальность имени пакета, компания Sun рекомендует использовать доменное имя вашей компании в Интернет (которое по определению уникально), запи-
санное в обратном порядке. Впоследствии вы сможете использовать подпакеты в разных проектах. Например, один из авторов этой книги имеет зарегистрированный домен horstmann. com. Записав это имя в обратном порядке, можно использовать его для названия пакета com. horstmann. В дальнейшем этот пакет можно разделить на подпакеты, например com. horstmann. corejava.
Единственная цель вложенных пакетов— гарантия уникальности имен. С точки зрения компилятора между вложенными пакетами нет абсолютно никакой связи. Например, пакеты java.util и java . util.jar никак не связаны друг с другом. Каждый из них представляет собой независимую коллекцию классов.
Использование пакетов
Класс может использовать все классы из собственного пакета и все открытые классы из других пакетов.
Доступ к открытым классам из других пакетов можно получить двумя путями. Во- первых, можно просто добавить полное имя пакета перед именем каждого класса. Например, java.util.Date today = new java.util.Date();
Очевидно, что этот способ слишком утомителен. Более простой и распространенный способ использовать ключевое слово import. Оператор import допускает более короткие имена для ссылок на классы, находящиеся в пакете. В этом случае не нужно указывать полные имена классов.
Можно импортировать как один конкретный класс, так и весь пакет. Операторы import следует поместить в начало исходного файла (но после всех операторов package). Например, все классы из пакета j ava.util можно импортировать с помощью оператора import java.util.*;
Затем можно использовать следующий оператор, не указывая префикса, задающего имя пакета.
Date today = new Date();
Можно также импортировать отдельный класс из пакета. import java.util.Date;
Импортировать все классы из пакета проще. Негативного влияния на размер кода это не оказывает, поэтому нет никаких причин не делать этого.
Однако следует заметить, что оператор import со звездочкой * можно применять для импортировгшия только одного пакета. Нельзя использовать обозначение import java. * или import java. * . *, чтобы импортировать все пакеты, имена которых содержат префикс j ava.
Импортировать можно только классы, но не объекты. Например, невозможно импортировать объект System.out.
В большинстве случаев импортируется • весь пакет, независимо от его размера. Единственный вариант, при котором на пакет следует обратить особое внимание, — конфликт имен. Например, и пакет java.util, и пакет java.sql содержат класс Date. Допустим, вы пишете программу, импортирующую оба этих пакета.
import java.util.*;
import java.sql.*;
Если теперь попытаться использовать класс Date, возникнет ошибка компиляции: Date today; // ОШИБКА — java.util.Date или java.sql.date?
Компилятор не может определить, какой класс Date вам нужен. Решить эту проблему можно, добавив конкретный оператор import:
import java.util.*;
import java.sql.*;
import java.util.Date;
А если вам на самом деле нужны оба класса Date? Для этого нужно указывать полное имя пакета для каждого имени класса.
java.util.Date deadline = new java.util.Date ();
java.sql.Date today = new java.sql.Date ();
Обнаружение классов в пакетах является задачей компилятора. Байт-коды в файлах классов всегда используют полные имена пакетов для ссылок на другие классы.
Программисты на языке C++ обычно пугают оператор import с директивой #include. Между ними нет ничего общего. В языке C++ директиву N include нужно применять для объявления внешних свойств, поскольку компилятор C++ не заглядывает внутрь файлов, за исключением файла, который он компилирует, и тех файлов, которые указаны в директиве #include. Компилятор языка Java "видит" содержимое всех файлов при условии, что вы указали, где их искать.
В языке Java можно вообще избежать применения механизма импортирования, явно назвав все пакеты, например java.util.Date. В языке C++ избежать использования директивы #include нельзя.
Единственное преимущество оператора import — удобство. Он позволяет использовать более короткие имена классов, не указывая полное имя пакета. Например, после оператора import java.util.* (или import java.util.Date) на класс java.util. Date можно ссылаться просто no имени Date.
Аналогичный механизм работы с пакетами в языке C++ реализован в виде директивы namespace. Ключевые слова package и import в языке Java можно считать аналогами директив namespace и using в языке C++.
Добавление класса в пакет
Чтобы поместить класс в пакет, нужно указать имя пакета в начале исходного файла перед кодом, определяющим класс. Например, файл Employee . j ava в листинге 4.7 начинается следующими строками.
package com.norstmann.corejava;
public class Employee
}
Если оператор package в исходном файле не указан, то классы, описанные в этом файле, принадлежат пакету по умолчанию (default package). Пакет по умолчанию не имеет имени. До сих пор все рассмотренные нами классы принадлежали пакету по умолчанию.
Пакеты следует помещать в подкаталог, имя которого соответствует полному имени пакета. Например, все файлы классов в пакете com.horstmann.corejavaдолжны находиться в подкаталоге com/horstmann/corejava (соответственно com\horstmann\ core j ava в системе Windows). Это простейшая установка — много других вариантов будут рассмотрены в этой главе позднее.
Программы, приведенные в листингах 4.6 и 4.7, распределены по двум пакетам: класс PackageTest принадлежит пакету по умолчанию, а класс Employee принадлежит пакету com.horstmann.corejava. Следовательно, файл Employee . class должен содержаться в подкаталоге com/horstmann/corejava. Другими словами, структура каталогов должна выглядеть следующим образом.
(рабочий каталог)
PackageTest.java
PackageTest.class com/
horstmann/
corejava/
Employee.java
Employee.class
Чтобы скомпилировать эту программу, просто перейдите в каталог, содержащий файл PackageTest. j ava, и выполните команду javac Package.java
Компилятор автоматически найдет файл com/horstmann/corejava/Employee . j ava и скомпилирует его.
Компилятор не проверяет, содержатся ли исходные файлы в каталогах. Напри- мер,допустим, что исходный файл начинаетсясдирективы package com.mycompany;
Этот файл можно скомпилировать, даже если он не находится в каталоге com/mycompany. Исходный файл будет скомпилирован без ошибок, однако при попытке выполнитьэтупрограмму виртуальная машина не найдетклассы, полученные в результате компиляции. Следовательно, иерархия исходных файлов должна совпадать с иерархией файлов классов.
import com.horstmann.corejava.*;
//• В этом пакете определен класс Employee.
public class PackageTest
{
public static void main(String[ args)
{
// Поскольку использован оператор import, имя
// com.horstmann.corejava.Employee использовать
// необязательно
Employee harrynew Employee("Гарри Хакер", 50000,
1989, 10, 1);
// Поднять зарплату на 5%.
harry.raiseSalary(5);
// Вывести информацию о Гарри.
System.out.printin("имя=" + harry.getName()
+ ",зарплата=" + harry.getSalaryO);
}
}
1 . package com. ho rstmann.corejava^
// Классы в этом файле являются частью пакета.
import java.util.*;
// Операторы import помещаются после оператора package
public class Employee'
{
public Employee(String n, double s,
int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendarcalendar
= new GregorianCalendar (year, month - 1, day);
// В классе GregorianCalendar январю соответствует 0.
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
Как виртуальная машина определяет, где находятся классы
Как мы уже видели, классы хранятся в подкаталогах файловой системы. Путь к классу должен совпадать с именем пакета. Кроме того, чтобы поместить классы в архив, можно использовать утилиту JAR. В одном архивном файле содержится много файлов классов и подкаталогов, что позволяет экономить память и сокращать время доступа к ним. (Файлы JAR более детально обсуждаются в главе 10.)
Например, тысячи классов рабочей библиотеки содержатся в файле rt. jar. Этот файл можно найти в подкаталоге j re/lib набора инструментальных средствJava SDK.
Файлы JAR для организации файлов и подкаталогов используют формат ZIP. Для работы с файлом rt.jar и другими JAR-файлами можно использовать любую утилиту для работы с ZIP-файлами.
В программах, рассмотренных нами ранее, каталог пакета com/horstmann/ corej ava представлял собой подкаталог каталога программ. Однако такое расположение является не очень удобным. Обычно доступ к файлам пакета нужен многим программам. Чтобы обеспечить им совместный доступ к пакетам, необходимо сделать следующее.
Поместить классы в один или несколько специальных каталогов, скажем /home/user/classdir. Заметим, что этот каталог является базовым (base) по отношению к дереву пакета. Если потребуется добавить класс com. horstmann. corejava . Employee, то файл класса следует поместить в подкаталог /home/user/classdir/com/horstmann/corejava.
Установить путь к классу (class path). Путь к классу — это набор всех базовых каталогов, которые могут содержать файлы классов.
Установка пути к классу зависит от среды, в которой компилируется программа. Если используется пакет Java SDK, есть две возможности: указать компилятору и интерпретатору байт-кодов опцию -classpath или задать значение переменной окружения CLASSPATH.
Детали зависят от операционной среды. В среде UNIX элементы пути к классу отделяются друг от Друга двоеточиями.
/home/user/classdir:.:/home/user/archives/archive.jar
В среде Windows они разделяются точками с запятой.
c:\classes;.;c:\archives\archive.jar
В обоих случаях точка означает текущий каталог.
Приведенный выше путь к классу содержит:
имя базового каталога /home/user/classdir илиc: \classes;
обозначение текущего каталога (.);
имяфайла JAR/home/user/archives/archive. jar или с: \archives\archive.jar.
Поиск классов всегда выполняется в файлах рабочей библиотеки (r t .j ar и другие JAR-файлы в каталогах jrelib и j re/lib/ext). Имена этих файлов не следует явно указывать в пути к классам.
Это свойство отличается от версий 1.0 и 1.1 набора инструментальных средств Java Software Development Kit. В старых версиях системные классы хранились в файле classes . zip, представлявшем собой часть пути к классам.
Например, рассмотрим, как устанавливается путь в классам с помощью опции компилятора.
javac -classpath /home/user/classdir:.:/home/user/archives archive.jar MyProg.java
(Вся команда должна быть набрана в одной строке. В среде Windows для отделения элементов пути друг от друга используются точки с запятой.)
При работе с интерпретатором байт-кодов java (но не с компилятором javac) можно использовать опцию -cp,а не -classpath.
В пути к классам перечисляются все каталоги и архивные файлы, в которых классы следуетискать в первую очередь. Рассмотримпростой путькклассам:
/home/user/classdir:.:/home/user/archives/archive.jar
Допустим, что интерпретатор выполняет поиск файла, содержащего класс com.horstmann.corejava. Employee. Сначала он ищет его в системных файлах, которые хранятся в архивах, находящихся в каталогах jre/lib и jre/lib/ext. В этих файлах искомого класса нет, поэтому интерпретатор возвращается к анализу пути к классу. Он просматривает следующие файлы:
/home/user/classdir/com/horstmann/corejava/Employee.class;
com.horstmann/core j ava/Employee.class, начиная с текущего каталога;
com.horstmann/corejava/Employee.class в каталоге
/home/user/archives/archive.jar.
На поиск файлов компилятор затрачивает больше времени, чем виртуальная машина. Если вы указали файл, не назвав пакета, которому он принадлежит, компилятор сначала должен сам определить, какой именно пакет содержит данный файл. Он рассматривает все директивы import как возможные источники классов. Допустим, что исходный файл содержит директивы import java.util.*
import com.horstmann.corej ava.*;
При этом исходный код относится к классу Employee. Компилятор пытается найти классы java.lang.Employee (поскольку пакет java.lang всегда импортируется по умолчанию), java.util.Employee, com.horstmann.corej ava.Employee и Employee в текущем пакете. Он ищет каждый из этих файлов во всех каталогах, указанных в пути к классам. Если найдено несколько таких классов, возникает ошибка компиляции. (Поскольку классы должны быть уникальными, порядок следования директив import значения не имеет.) Компилятор делает еще один шаг. Он просматривает исходные файлы, чтобы определить, являются ли варианты класса, содержащиеся в этих файлах, более свежими, чем файл класса. Если это так, исходный файл автоматически перекомпилируется. Напомним, что из других пакетов можно импортировать лишь открытые классы. Исходный файл может содержать только один открытый класс, а имена файла и открытого класса должны совпадать. Следовательно, компилятор может легко определить, где находятся исходные файлы, содержащие открытые классы. Однако из текущего пакета можно импортировать также классы, не являющиеся открытыми. Эти классы могут быть определены в исходных файлах с разными именами. При импортировании класса из текущего пакета компилятор ищет все исходные файлы, чтобы выяснить, в каком из них определен данный класс.
Компилятор javac всегда ищет файлы в текущем каталоге, а интерпретатор java обращается к текущему каталогу, только если в пути к классам указана точка ".". Если путь к классам не указан, никаких проблем не возникает — путь к классам, задаваемый по умолчанию, содержит обозначение текущего каталога ".". Но если вы задали путь к классам и забыли указать точку ".", то ваша программа будет скомпилирована без ошибок, однако выполниться не сможет.
Как задать путь к классам
Путь к классам можно задать с помощью опции -classpath программ javac и java. Однако такой способ может оказаться слишком утомительным. Можно воспользоваться альтернативной возможностью и задать значение переменной окружения CLASSPATH. Ниже указано, какэто сделать в операционной среде UNIX/Linux и Windows.
В среде UNIX/Linux нужно отредактировать файл, в котором хранятся параметры оболочки.
Если используется оболочка С, добавьте в файл . cshrc, находящийся в рабочем каталоге, строку
setnv CLASSPATH /home/user/classdir: .
Если используется оболочка Bourne Again, или bash, добавьте в файлы . bashrc или .bash_profile,находящиеся в рабочем каталоге, строку
SET CLASSPATH=c:\user\classdir;.
Убедитесь, что вокруг символа = нет пробелов.
В среде Windows NT/2000 откройте панель управления. Затем выберите ПИКТОграмму System и вкладку Environment. В поле Variable наберите слово CLASSPATH. В поле ввода значений наберите соответствующий путь к классам, например с: \user\classdir;.
Область видимости пакета
Мы уже встречались с модификаторами доступа public и private. Сущности, помеченные ключевым словом public, могут использоваться любым классом. Закрытые сущности могут использоваться только определившим их классом. Если ни один модификатор доступа не указан, то сущность (т.е. класс, метод или переменная) является доступной всем методам в том же самом пакете.
Рассмотрим программу, приведенную в листинге 4.2. Класс Employee не определен как открытый. Следовательно, любой другой класс из того же самого пакета — в данном случае пакета, заданного по умолчанию,— например класс EmployeeTest. может получить доступ к иему\ Для классов такое умолчание является вполне разумным решением. Однако для переменных такой способ доступа неудачен. Переменные должны быть явно помечены как закрытые, или по умолчанию их область видимости будет ограничена пакетом. Это, конечно, разрушает инкапсуляцию. Проблема заключается в том, что забыть указать слово private очень просто. Вот пример из класса Window, принадлежащего пакету java.awt, представляющего собой часть исходного кода, поставляемого с пакетом SDK.
public class Window extends Container
{
String warningString;
}
Заметим, что переменная warningString не имеет модификатора доступа private! Это значит, что методы всех классов в пакете j ava . awt имеют к ней доступ и могут изменить ее значение (например, присвоить ей строку "Trust me ! " ("Верь мне!")). Фактически все методы, имеющие доступ к переменной warningString, принадлежат классу Window, поэтому эту переменную смело можно было бы объявить как закрытую. Мы подозреваем, что программисты, писавшие этот код, торопились и просто забыли указать модификатор доступа private.
Забавно, что эта проблема так и не была исправлена, даже после того, как мы указали на нее в пяти изданиях этой книги — очевидно, что программисты, создававшие эту библиотеку, не читают "Основы языка Java". Более того, со временем в класс были добавлены новые поля, и снова половина из них не объявлена как private.
А может это и вовсе не проблема? Однозначно ответить на этот вопрос нельзя. По умолчанию пакеты не являются закрытыми сущностями. Это значит, что каждый может добавлять в пакеты свои классы. Разумеется, злонамеренные или невежественные программисты могут написать код, модифицирующий переменные, область видимости которых ограничена пакетом. Например, в ранних версиях языка Java можно было легко проникнуть в другой класс пакета j ava . awt. Для этого было достаточно начать свой класс со строки package java.awt;
Затем нужно было поместить полученный файл класса в подкаталог j avaXawt, и доступ к содержимому пакета j ava . awt открыт. С помощью такого трюка можно было изменить строку предупреждения, которая должна была выводиться на экран (рис. 4.9).
Начиная с версии 1.2, создатели пакета SDK запретили загрузчику классов загружать классы, определенные пользователем, если их имя начиналось с "java."! Разумеется, эта защита не распространяется на ваши собственные классы. Вместо этого вы можете
использовать другой механизм, называемый герметизацией пакета (package sealing), который упорядочивает доступ к пакету. Запечатав пакет, вы запрещаете добавлять к нему классы. В главе 10показано, как создать JAR-файл, содержащий запечатанные пакеты.
Комментарии
Пакет Java SDK содержит очень полезный инструмент под названием j avadoc, генерирующий по вашим исходным файлам документацию в формате HTML. Фактически документация по интерфейсу API, описанная в главе 3, является результатом применения утилиты j avadoc к исходному коду стандартной библиотеки языка Java.
Добавив к своему исходному коду комментарии, начинающиеся со специального разделительного знака /**, вы тоже можете легко создать документацию, имеющую профессиональный вид. Это очень удобный способ, поскольку он позволяет хранить и код, и документацию к нему в одном месте. Если документацию поместить в отдельный файл, то со временем ее согласованность с кодом нарушится. В то же время, поскольку коментарии являются неотъемлемой частью исходного файла, документацию легко обновить, повторно выполнив утилиту j avadoc.
Как вставить комментарии
Утилита j avadoc извлекает информацию о следующих сущностях.
Пакеты.
Открытые классы и интерфейсы.
Открытые и защищенные методы.
Открытые и защищенные поля.
Защищенные сущности будут рассмотрены в главе 5, а интерфейсы — в главе 6.
Вы можете (и должны) комментировать каждую из перечисленных сущностей. Каждый комментарий помещается непосредственно перед сущностью, к которой он относится. Комментарий начинается с разделителя /** и заканчивается символами */.
Каждый комментарий, имеющий вид /** ... */, содержит текст произвольной формы, завершающийся дескриптором (tag). Дескриптор начинается символом @, например, @authorили @param.
Первое предложение произвольного текста должно представлять собой краткое резюме (summary statement). Утилита avadoc автоматически генерирует страницы, содержащие резюме, извлеченные из этих предложений. -
В самом тексте можно использовать модификаторы языка HTML, например, <em>. . . </em>— для выделения текста курсивом, <code>. . . </code> для установки моноширинного шрифта (как у пишущей машинки), <strong>. . .</strong>— для выделения текста жирным шрифтом и даже <im. . . > — для вставки рисунков. Следует, однако, избегать модификаторов заголовков <hl> и правил <hr>, поскольку они могут повлиять на форматирование документа.
Если комментарии содержат ссылки на другие файлы, например, рисунки (диаграммы или компоненты интерфейса), поместите эти файлы в подкаталоги, начинающиеся словом doc. Утилита avadoc скопирует эти каталоги и файлы, содержащиеся в них, из исходного каталога в каталог для документации.
Комментарии к классу
Комментарии к классу должны помещаться до первого оператора import, непосредственно перед определением класса.
Вот пример такого комментария.
/**
Объект класса <code> Card</code> имитирует игральную карту, например, "червонную даму". Карта имеет масть (бубны, черви, пики или трефы) и ранг (1=туз, 2.. .10, 11=валет, 12=дама, 13=король).
*/
public class Card (
}
Многие программисты начинают каждую строку документации звездочкой, например. /**
* Объект класса <code> Card</code> имитирует игральную карту,
* например, "червонную даму". Карта имеет масть (бубны,
* черви, пики или трефы) и ранг (1=туз, 2 10,
* 11=валет, 12=дама, 13=король).
*/
Мы этого не делаем, поскольку это мешает программистам обновлять комментарии. Никто не захочет заново расставлять звездочки после того, как порядок строк окажется нарушенным. Однако некоторые текстовые редакторы помогают выполнять эту тяжелую работу. Если вы уверены, что все потенциальные пользователи вашей программы будут применять именно такие текстовые редакторы, можете разукрасить ваши комментарии звездочками.
Комментарии к методам
Каждый такой комментарий должен непосредственно предшествовать методу, который он описывает. Кроме дескрипторов общего назначения можно использовать специальные дескрипторы: @param переменная описание
Этот дескриптор добавляет раздел "parameters" ("параметры") в описание заданного метода. Этот раздел можно растянуть на несколько строк. Кроме того, можно использовать дескрипторы языка HTML. Все дескрипторы @param, относящиеся к одному методу, должны быть сгруппированы вместе. @return описание
Данный дескриптор добавляет раздел "returns" ("возвращаемое значение") в описание заданного метода. Этот раздел можно растянуть на несколько строк. Кроме того, можно использовать дескрипторы языка HTML. @throws описание класса
Этот дескриптор означает, что данный метод может возбуждать исключительную ситуацию. Исключительные ситуации изучаются в главе 11.
Рассмотрим пример комментария к методу.
/**
Поднимает зарплату сотрудников.
Переменная byPercent хранит процент,
на который повышается зарплата (т.е. 10 = 10%).
Размер повышения
*/
public doublebyPercent) ,
{
double raise = salary &* byPercent / 100;
salary += raise;
return raise;
}.
Комментарии к полям
Документировать нужно лишь открытые поля — обычно они представляют собой статические константы. Например, /•*
Масть "Черви".
*/
public static final int HEARTS = 1;
Комментарии общего характера
В комментариях к классу можно использовать следующие дескрипторы. имя
Этот дескриптор создает раздел "author" ("автор"). В комментариях может быть несколько таких дескрипторов — по одному на каждого автора.
текст
Этот дескриптор создает раздел "version" ("версия"). Слово "текст" означает любое описание текущей версии.
При создании любого комментария, содержащегося в документации, можно использовать также следующие дескрипторы.
текст
Этот дескриптор создает раздел "since" ("начиная с..."). Слово "текст" означает любое описание версии, в которой впервые было реализовано данное свойство. Напримерversion 1.7.1.
текст
Этот дескриптор добавляет комментарий, означающий, что класс, метод или переменная больше использоваться не должны. Предполагается, что слово "текст" заменяется некоторым выражением. Например, Sdeprecated Useinstead
В документации с помощью дескрипторов @see и @link можно использовать гипертекстовые ссылки на соответствующие внутренние части или внешние документы. ссылка
Этот дескриптор добавляет гиперссылку в раздел "See also" ("См. также"). Его можно использовать в комментариях как к классам, так и к методам. Здесь под словом подразумевается один из следующих вариантов.
пакет.класс#сущность дескриптор
<а href="...">дескриптор</а>
"текст"
Первый вариант распространен больше других. Здесь нужно указать имена класса, метода или переменной, а утилита javadocвставит в документацию соответствующую гипертекстовую ссылку. Например, команда
@see com.horstmann.corejava.Employee#raiseSalary(double]
создает ссылку на метод raiseSalary (double) класса
com. horstmann. corejava.Employee. После этого ссылка помещается в текущий пакет или класс.
Подчеркнем, что здесь для отделения имени класса от имени метода или переменной нужно использовать символ #, а не точку. Компилятор javaс хорошо различает значения точки, выступающей в качестве разделителя между именами пакетов, подпакетов, классов, внутренних классов, методов и переменных. Однако утилита j avadoc не настолько изощренна, поэтому следует проявлять осторожность.
Если вслед за дескриптором @seeстоит символ <, то программист должен указать гипертекстовую ссылку. Ссылаться можно на любой URL-адрес. Например, @" see <a"страница книги
"Основы языка Java"
В каждом их этих случаев можно указать необязательный дескриптор, который играет роль якоря для ссылки. Если этот дескриптор не указать, то якорем ссылки будет считаться имя кода или URL-адрес.
Если вслед за дескриптором @see стоит символ ", то текст отображается в разделе "see also". Например,
@see "Основы языка Java, том 2"
Для одной и той же сущности можно использовать несколько дескрипторов @see, однако их следует группировать вместе.
При желании можно поместить гипертекстовые ссылки на другие классы или методы где угодно, в любом комментарии. Для этого нужно вставить специальный дескриптор вида {link пакет.класс#сушност дескриптор} в любое место комментария. Описание сущности подчиняется тем же правилам, что и дескриптор @see.
Комментарии к пакетам и обзорам
Комментарии к классам, методам и переменным помещаются обычно непосредственно в исходных файлах. При этом комментарии выделяются символами / * * .. . * /. Однако для генерации комментариев к пакету в каждый каталог, где находится пакет, нужно добавить файл package.html. Весь текст, содержащийся между дескрипторами <body> . . . </body>, извлекается утилитой javadoc.
Кроме того, все исходные файлы можно сопроводить обзорными комментариями. Они размещаются в файле под названием overview.html, размещенном в родительском каталоге, содержащем все исходные файлы. Весь текст, содержащийся между дескрипторами <body> . . . </body>, извлекается утилитой javadoc. Эти комментарии отображаются на экране, когда пользователь выбирает опцию меню "Overview" ("Обзор").
Как извлечь комментарии
Будем считать, что docDirectory— это имя каталога, в котором должны храниться HTML-файлы. Выполним следующие действия.
Перейдем в каталог, содержащий исходные файлы, подлежащие документированию. Если документированию подлежат вложенные пакеты, например com. horstmann. core j ava, нужно перейти в каталог, содержащий подкаталог com. (Именно в этом каталоге должен храниться файлoverview.html.)
Для документирования одного пакета выполним команду
jacadoc -d docDirectory имяПакета
Для документирования нескольких пакетов выполним команду
jacadoc -d docDirectory имяПакета! имяПакета2 ...
Если файлы находятся в пакете, заданном по умолчанию, вместо первых двух нужно выполнить команду
javadoc -d docDirectory *.java
Если пропустить опцию -d docDirectory, HTML-файлы будут извлечены в текущий каталог, что вызовет путаницу, и мы не рекомендуем делать это.
Программу j avadoc можно очень хорошо настроить с помощью многочисленных командных опций. Например, чтобы включить в документацию дескрипторы @author и @version, можно использовать опции -author и -version. (По умолчанию они пропускаются.) Документацию об утилите javadoc можно найти на Web- странице http:/ / java.sun.com/products/jdk/javadoc/index.html.
Если вам требуется еще более тонкая настройка, например, создать документацию в другом формате, отличающемся от HTML, можно разработать СВОЙ собственный доклег (doclet), позволяющий генерировать документацию произвольного формата. Очевидно, это очень специфическое требование, поэтому мы рекомендуем читателю получить более детальную информацию на Web-странице http://java.sun.eom/products/jdk/l.3/docs/tooldocs/javadoc /overview.html.
Советы ПО разработке классов
He желая показаться слишком занудными и надоедливыми, мы хотели бы завершить эту главу некоторыми советами, которые облегчат вам объектно-ориентированную разработку классов.
Всегда держите все данные закрытыми.
Первое и главное требование: всеми средствами избегайте нарушения инкапсуляции. Иногда приходится писать методы доступа к полю или модифицирующие методы, однако лучше держать все поля закрытыми. Горький опыт показал, что способ представления данных может изменяться, однако способ их использования изменяется гораздо реже. Если данные закрыты, способ их представления не влияет на пользовательский класс, поэтому ошибки искать легче.
Всегда инициализируйте данные.
Язык Java не требует инициализировать локальные переменные, однако поля в объектах инициализируются обязательно. Не полагайтесь на установки по умолчанию, инициализируйте переменные явным образом с помощью конструкторов.
Не используйте в классе слишком много основных типов.
Следует объединять несколько связанных между собой полей основных типов в новый класс. Такие классы легче понимать и модифицировать. Например, четыре поля основных типов в классе Customer
private String street;
private String city;
private String state;
private int zip;
следует объединить в новый класс Address. Таким образом, адреса легче изменять, особенно если приходится иметь дело с иностранными адресами.
Не все поля должны иметь индивидуальные методы доступа и модификации.
Например, может понадобиться определить и задать зарплату некоего сотрудника. Очевидно, изменять скрытые данные сразу после создания объекта совершенно не нужно. Довольно часто объекты содержат поля, к которым никто больше не обращается и не изменяет их содержимое. К таким объектам относится массив аббревиатур названия штатов США в классе Address.
Используйте стандартную форму определения класса.
Содержимое класса всегда перечисляется в следующем порядке:
открытые сущности;
сущности, область видимости которых ограничена пакетом;
закрытые сущности.
Внутри каждого раздела перечисляются:
методы;
статические методы;
поля;
статические поля.
Помимо прочего, пользователей вашего класса больше интересует открытый интерфейс, чем детали закрытой реализации. Кроме того, их больше интересуют методы, чем данные.
Однако в настоящее время общего мнения об оптимальном стиле программирования еще нет. Стиль программирования на языке Java, принятый в компании Sun, рекомендует сначала перечислять поля, а затем — методы.
Независимо от того, какого стиля вы придерживаетесь, главное— быть последовательным.
Разбивайте на части слишком большие классы.
Этот совет, конечно, слишком неопределен: то, что кажется "слишком большим" одному программисту, кажется нормальным другому. Однако, если есть очевидная возможность разделить один сложный класс на два класса попроще,
воспользуйтесь ею. (Только не бросайтесь в другую крайность. Десять классов, в каждом из которых есть только один метод, — это уже слишком.)
Приведем пример плохого стиля.
class CardDeck // Плохой пример.
{
public CardDeck() { ... }
public void shuffled (...)
public int getTopValue() {...}.
public int getTopSuit() { ...}
public void draw() { ... }
private int[] value;
private int[] suit;
На самом деле в этом классе реализованы две разные концепции: во-первых, карточный стол с методами shuffle (тасование) и draw (раздача) и, во- вторых, игральная карта с методами для проверки ранга и масти карты. Разумнее было бы ввести класс Card, представляющий собой отдельную игральную карту. Тогда мы имели бы два класса, каждый со своей задачей.
class CardDeck
(,
public CardDeck(] { ... }
public void shuffle(] { ... }
public int getTopValue() { ... }
public int getTopSuit() { ... } public void draw() { ... }
private Card[] cards;
}
class Card
{
public Card (int aValue, int aSuit) ( .. . }
public int getvalue(; { ... }
public int getSuit(; { ... }
private int [" value; private int[] suit;
}
Давайте вашим классам и методам осмысленные имена.
Классы, как и переменные, следует называть именами, отражающими их смысл. (В стандартной библиотеке есть, конечно, двусмысленные примеры, скажем, класс Date описывает время, а не дату.)
Удобно принять следующее соглашение: имя класса должно быть именем существительным (Order) или именем существительным, которому предшествует имя прилагательное (RushOrder), или герундием (словом, заканчивающимся на "-ing", например BillingAddress). Как правило, методы доступа должны начинаться словом get, набранным строчными буквами (getSalary), а модифицирующие методы— словом set, набранным строчными буквами (setSalary).
^5^5НаследованиеНаследованиеВ этой главе...
^5
^5
Наследование
Наследование
Расширение классовРасширение классовТ Object: глобальный суперкласс
Расширение классов
Расширение классов
• Класс Class
Т Отражение
Как применять наследование
1^ главе 4 мы изучали классы и объекты. Данная глава посвящена наследованию, еще одному фундаментальному понятию объектно-ориентированного программирования. Идея, лежащая в основе наследования, заключается в том, что новые классы можно создавать из уже существующих. При наследовании методы и поля существующего класса используются повторно (наследуются) вновь создаваемым классом, причем для адаптации нового класса к новым ситуациям в него добавляют дополнительные поля и методы. Этот прием играет в языке Java весьма важную роль.
Если вы имеете опыт работы лишь с такими процедурно-ориентированными языками, как C++, Visual Basic или COBOL, тщательно изучите эту главу. Для программистов, работавших на языке C++ или других объектно-ориентированных языках, например языке Smaltalk, сведения, изложенные в этой главе, покажутся очень знакомыми. Следует, однако, учесть, что механизмы наследования, реализованные в языке Java и в других объектно-ориентированных языках, значительно отличаются друг от друга.
В конце главы мы изучим механизм отражения, позволяющий исследовать свойства классов в ходе выполнения программы. Отражение — мощный, но очень сложный механизм. Поскольку отражение больше интересует разработчиков программных инструментов, чем прикладных программистов, при первом чтении этот раздел можно просто бегло просмотреть.
Расширение классов
Вернемся к классу Employee, рассмотренному нами в предыдущей главе. Допустим (увы!), что вы работаете в компании, менеджеры которой отличаются от остальных сотрудников. Разумеется, менеджеры во многих отношениях являются обычными наемными сотрудниками. Однако сотрудникам за их работу выплачивается заработная плата, в то время как менеджеры за свои достижения получают премии. В этой ситуации следует применять наследование. Почему? Потому что нужно определить новый класс Manager, добавив в него новые методы и поля. Однако некоторые из этих методов и полей уже определены в классе Employee, причем все поля этого класса подходят и для класса Manager. Говоря более абстрактно, между классами Manager и Employee существует очевидное отношение "является" ("is-a"). Каждый менеджер является сотрудником: это отношение "является"—признак наследования.
Ниже приводится пример определения класса Manager, производного от класса Employee. Для обозначения наследования в языке Java используется ключевое слово extends.
class Manager extends Employee {
Дополнительные методы и поля
ММеханизмы наследования в языках Java и C++ похожи. В языке Java вместо лек
семы : : используется ключевое слово extends. Любое наследование в языке к.Java является открытым, причем аналога закрытому и защищенному наследова
нию, существующему в языке C++, в языке Java нет.
Ключевое слово extends означает, что создается новый класс, производный от существующего. Существующийклассназывается суперклассом, базовым илиродительским, а вновь создаваемый — подклассом, производным или дочерним. В среде программистов на языке Java наиболее широко распространены термины "суперкласс" и "подкласс", хотя некоторые специалисты предпочитают пользоваться генеалогическими понятиями "родительский/дочерний", более тесно связанными с темой "наследования".
Класс Employee является суперклассом. Это не значит, что он имеет превосходство над своим подклассом или обладает более широкими функциональными во'змож- ностями. На самом деле все наоборот: подкласс шире, чем суперкласс. Например, анализируя код класса Manager, легко увидеть, что он инкапсулирует больше данных и содержит больше методов, чем его суперкласс Employee.
Префиксы супер- и под- пришли в программирование из теории множеств, являющейся частью компьютерных наук и математики. Множество всех сотрудников содержит в себе множество всех менеджеров. В этом случае говорят, что множество сотрудников является супермножеством (superset) по отношению к множеству менеджеров. Иначе говоря, множество всех менеджеров является подмножеством (subset) множества всех сотрудников.
Класс Manager имеет новое поле, в котором хранится размер премии, и новый метод, позоляющий задавать эту величину.
class Manager extends Employee {
public voidb)
{ bonus = b;
}
private double bonus;
}
В этих методах и полях нет ничего особенного. Имея объект класса Manager, можно просто применять метод setBonus.
Manager boss = ... ;
;i
Разумеется, с помощью объекта класса Employee метод setBonus вызвать невоз- его нет среди методов, определенных в этом классе. Однако методы и getHireDay можно вызывать, пользуясь объектами класса Manager, поскольку они автоматически наследуются от суперкласса Employee.
Аналогично поля name, salary и hireDay также наследуются от суперкласса. Каждый объект класса Manager имеет четыре поля: name, salary, hireDay и bonus. Определяя подкласс, нужно указать лишь отличия между подклассом и суперклассом. Разрабатывая классы, следует помещать методы общего назначения в суперкласс, а более специальные методы — в подкласс. Такое выделение функциональных свойств общего назначения путем их размещения в суперклассе широко распространено в объектно-ориентированном программировании.
Однако некоторые методы суперкласса не подходят для подкласса Manager. В частности, метод getSalary должен возвращать сумму базовой зарплаты и премии. Следовательно, нужно реализовать новый метод, замещающий (overriding) метод суперкласса. class Manager extends Employee {
public void
Как реализовать этот метод? На первый взгляд это просто — нужно лишь вернуть сумму полей salary и bonus.
public void
{
return salary + bonus; //работает.
}
Однако выясняется, что этот способ не работает. Метод getSalary класса Manager не имеет прямого доступа к закрытым полям суперкласса. Это значит, что метод getSalary класса Manager не может прямо обратиться к полю salary, несмотря на то, что каждый объект класса Manager имеет поле с именем salary. Только методы класса Employee имеют доступ к закрытым полям суперкласса. Если методам класса Manager нужен доступ к закрытым полям, они должны сделать то же, что и любой другой метод— использовать открытый интерфейс, в данном случае— метод get- Salary класса Employee.
Итак, попробуем все сначала. Вместо прямого обращения к полю salary нужно вызвать метод getSalary.
public void getSalary()
{
double baseSalary = getSalary(); // Все еще не работает. return baseSalary + bonus;
}
Проблема заключается в том, что метод getSalary просто вызывает сам себя, поскольку в классе Manager есть свой метод getSalary (именно его мы пытаемся реализовать). Вследствие этого возникает бесконечная цепочка вызовов одного и того же метода, что приводит к краху программы.
Нужно явно указать, что мы хотим вызвать метод getSalary из суперкласса Employee, а не из текущего класса. Для этой цели используется специальное ключевое слово super. Оператор super.getSalary()
вызывает метод getSalary именно из класса Employee. Вот как выглядит правильный вариант метода getSalary для класса Manager.
public void getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
Некоторые программисты считают ключевое слово super аналогом ссылки this. Однако эта аналогия не вполне точна — ключевое слово super не означает ссылку на объект. Например, его нельзя присвоить другой объектной переменной. Это слово просто сообщает компилятору, что нужно вызвать метод суперкласса.
Как видим, подкласс может добавлять поля, а также добавлять и замещать методы суперкласса, причем при наследовании ни одно поле или метод из классов не удаляется.
Для вызова метода суперкласса в языке Java используется ключевое слово super. В языке C++ для этого можно указывать имя суперкласса вместе с оператором разрешения области видимости : :. Например, метод getSalary класса Manager в языке C++ можно вызвать с помощью выражения Employee: : getSalary ().
В заключение напишем конструктор.
public Manager(String n, double s, int year, int month, int day) (
super(n, s, year, month, day);
bonus = 0;
Здесь ключевое слово super имеет другой смысл. Оператор super(n, s, year, month, day);
означает "вызов конструктора суперкласса Employee с параметрами n, s, year, month и day".
Поскольку конструктор класса Manager не имеет доступа к закрытым полям класса Employee, он должен инициализировать их, вызывая другой конструктор с помощью ключевого слова super. Вызов, содержащий слово super, должен быть первым оператором в конструкторе подкласса.
Если конструктор подкласса не вызывает явно ни один конструктор суперкласса, то автоматически вызывается конструктор по умолчанию, принадлежащий данному суперклассу. Если в суперклассе нет конструктора по умолчанию, а конструктор подкласса не вызывает явно другой конструктор суперкласса, то компилятор языка Java выдаст сообщение об ошибке.
Напомним, что ключевое слово this применяется в двух случаях: для указания ссылки на неявный параметр и для вызова другого конструктора того же класса. Аналогично ключевое слово super используется в двух ситуациях: для вызова метода суперкласса и конструктора суперкласса. При вызове конструкторов ключевые слова this и super имеют почти одинаковый смысл. Вызов конструктора должен быть первым оператором в другом конструкторе. Параметры конструктора передаются либо другому конструктору того же класса (this), либо конструктору суперкласса (super).
В языке C++ вызов конструктора super не применяется. Вместо этого для создания суперкласса используется список инициализации. В языке C++ конструктор класса Manager выглядел бы следующим образом.
Manager::Manager(String n, double s, int year, int month, int day)
:s, year, MONTH, DAY)
{ bonus = 0;
}
После переопределения метода getSalary для объектов класса Manager все менеджеры получат премии.
Рассмотрим конкретный пример: создадим объект класса Manager и установим размер его премии.
Manager boss = new Manager("Карл Крекер", 80000, 1987, 12, 15);
boss.setBonus(5000);
Образуем массив, в котором должны храниться три объекта класса Employee.
Employee[] staff = new Employee[ 3 ];
Заполним его смесью объектов классов Manager и Employee.
staff[0] = boss;
staff[Ij =newEmployee("Гарри Хакер",50000,1989,10,1);
staff[2] =newEmployee("Тони Тестер",40000,1990,3,15);
Выведем на печать зарплату каждого сотрудника.
for (int i = 0; i < staff.length; i++) {
Employee e = staff[i];
System.out.printin(e.getName(; + " " + e.getSalary());
}
Этот цикл выводит на печать следующие строки.
Карл Крекер 85000.0
Гарри Хакер 50000.0
Тони Тестер 40000.0
Теперь объекты staff[ 1 ] и staff[ 2 ] выводят на печать базовые зарплаты соответствующих сотрудников, поскольку они являются объектами класса Employee. Однако объект staff[0] относится к классу Manager, и его метод getSalary добавляет к базовой зарплате премию.
Обратите особое внимание на вызов
e.getSalary(]
Здесь вызывается правильный метод getSalary. Заметим, что объявленным типом переменной е является класс Employee, однако фактическим типом объекта, на который может ссылаться переменная е, может быть как класс Employee (когда i=l или 2), так и класс Manager (когда i=0).
Когда переменная е ссылается на объект класса Employee, оператор e.get- Salary вызывает метод getSalary класса Employee. Однако, если переменная е ссылается на объект класса Manager, вызывается метод getSalary класса Manager. Виртуальной машине известен фактический тип объекта, на который ссылается переменная е, поэтому вызывается правильный метод.
Способность объектной переменной (например, переменной е) ссылаться на объекты, имеющие разные фактические типы, называется полиморфизмом (polymorphism). Автоматический выбор нужного метода во время выполнения программы называется динамическим связыванием (dynamic binding). Обе темы обсуждаются в этой главе позднее.
В языке Java нет необходимости объявлять виртуальный метод. Динамическое связывание выполняется по умолчанию. Если вы не хотите, чтобы метод был виртуальным, следует пометить его ключевым словом final. (Смысл этого слова обсуждается ниже.)
Листинг 5.1 .Л^огр^ла Mahage^est.java j
import java.util.*;
public class ManagerTest
{
public static void main(String[] argsl
{
// Создаем объект класса Manager.
Manager boss = new Manager("Карл Крекер", 80000,
1987, 12, 15);
boss.setBonus(5000);
Employee[ staff = new Employee[3];
// Заполняем массив staff объектами классов Manager и Employee
staff[0] = boss;
staff[1] = new Employee("Гарри Хакер", 50000,
1989, 10, 1);
staff[2] = new Employee("Тони Тестер", 40000,
1990, 3, 15);
// Выводим информацию обо всех объектах класса Employee
for (int i = 0; i < staff.length; i++)
{
Employee e = staff[i];
System.out.printin("имя=" + e.getName()
+ ",salary=" + e.getSalary());
}
}
}
class Employee
{
public Employee(String n, double s,
int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendarcalendar
= new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar январь обозначен нулем.
hireDay = calendar.getTime();
}
public String getName(1
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay(;
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
class Manager extends Employee
{
/**
Sparam n имя сотрудника
@param s зарплата
Sparam year год найма на работу
Sparam month месяц найма на работу
Sparam день найма на работу
*/
public Manager(String n, double s,
int year, int month, int day)
82.83.84.85.86.87.88.89.90.82.83.84.85.86.87.88.89.90.}}super(n, s, year, month, day);
82.
83.
84.
85.
86.
87.
88.
89.
90.
82.
83.
84.
85.
86.
87.
88.
89.
90.
}
}
bonus = 0;
p{ublic double getSalary(]
double baseSalary = super.getSalary()'
) return baseSalary + bonus;
public void setBonus (double b)
{
bonus = b;
}
private double bonus;
Иерархия наследования
Наследование не обязано ограничиваться одним уровнем классов. Например, от класса Manager можно в свою очередь произвести класс Executive. Совокупность всех классов, производных от общего суперкласса, называется иерархией наследования (inheritance hierarchy), как показано на рис. 5.1. Путь от конкретного класса к его потомкам в иерархии наследования называется цепочкой наследования (inheritance chain).
Рис. 5.1. Иерархия наследования класса EmployeeРис. 5.1. Иерархия наследования класса EmployeeEmployee
Рис. 5.1. Иерархия наследования класса Employee
Рис. 5.1. Иерархия наследования класса Employee
Обычно от потомка к предку существует несколько цепочек. Из класса Employee можно вывести класс Programmer или класс Secretary, причем они будут совершенно независимыми как от класса Manager, так и друг от друга. Этот процесс можно продолжать сколь угодно долго.
Полиморфизм
Есть простое правило, позволяющее определить, стоит ли в конкретной ситуации применять наследование или нет. Правило "является" ("is-a") утверждает, что каждый объект подкласса является объектом суперкласса. Например, каждый менеджер является сотрудником. Следовательно, имеет смысл сделать класс Manager подклассом класса Employee. Естественно, обратное утверждение неверно— не каждый сотрудник является менеджером.
Другой способ формулировки правила "является" — принцип подстановки (substitution principle). Этот принцип гласит, что объект подкласса можно использовать вместо любого объекта суперкласса.
Например, объект подкласса можно присвоить переменной суперкласса.
Employee e;
е = new Employee(...); // Объект класса Employee.
е = new Manager(...);// Можно и так.
В языке Java объектные переменные являются полиморфными (polymorphic). Переменная типа Employee может ссылаться как на объект типа Employee, так и на объект любого подкласса класса Employee (например, подклассов Manager, Executive, Secretary и т.п.).
Преимущество этого принципа проиллюстрировано в листинге 5.1.
Employee!] staff = new Employee[3];
Manager boss = new manager(...); staff[0] = boss;
Здесь переменные staff[0] и boss ссылаются на один и тот же объект. Однако переменная staff[0] рассматривается компилятором только как объект класса Employee. Это значит, что допускается вызов boss.setBonus(5000); // OK и запрещается вызов
staff[0] .setBonus(5000); // ОШИБКА
Это происходит, поскольку переменная staff[0] объявлена как объект класса Employee, а метода setBonus в этом классе нет.
Однако присвоить ссылку на объект суперкласса переменной подкласса невозможно. Например, следующий оператор является недопустимым.
Manager m = staf [i]; // ОШИБКА
Причина очевидна: не все сотрудники являются менеджерами. Если бы это присваивание оказалось выполненным, и переменная m могла бы ссылаться на объект класса Employee, который менеджером не является, то впоследствии оказался бы возможным вызов метода m. setBonus (...), который привел бы к ошибке при выполнении программы.
В языке Java множественное наследование не поддерживается. (Способы реализации функциональных возможностей множественного наследования обсуждают- к.ся в следующей главе в разделе "Интерфейсы".)
Динамическое связывание
Важно понимать, что происходит, когда вызывается метод, принадлежащий некоему объекту. Рассмотрим детали этого процесса.
Компилятор проверяет объявленный тип объекта и имя метода. Допустим, происходит вызов метода x.f (args), причем неявный параметр х объявлен как объект класса С. Заметим, что могут существовать несколько методов с именем f, имеющих разные типы параметров (например, метод f (int) и метод f (String)). Компилятор пронумерует все методы с именем f в классе С и все открытые методы с именем f в суперклассах класса С.
Теперь компилятор знает всех возможных кандидатов при вызове метода.
Затем компилятор определяет типы параметров, указанных при вызове метода. Если среди всех методов с именем f есть только один метод, типы параметров которого совпадают с указанными, происходит его вызов. Этот процесс называется разрешением перегрузки (overloading resolution). Например, при вызове x.f("Hello") компилятор выберет метод f(String), а не метод f (int) . Ситуация может осложниться вследствие преобразования типов (int — в double, Manager — в Employee и т.д.). Если компилятор не находит ни одного метода с подходящим набором параметров, или в результате преобразования типов возникает несколько методов, соответствующих данному вызову, выдается сообщение об ошибке.
Теперь компилятор знает имя и типы параметров метода, подлежащего вызову.
Напомним, что имя и список типов параметров метода образуют его сигнатуру (signature). Например, методы f (int ) и f(string ) имеют одинаковые имена, но разные сигнатуры. Если в подклассе определен метод, сигнатура которого совпадает с сигнатурой некоторого метода суперкласса, то метод подкласса замещает его собой. Однако тип возвращаемого значения в сигнатуру не входит. Следовательно, невозможно одновременно определить метод int f (String) в суперклассе и метод void f(String ) в подклассе.
Если метод является закрытым (private), статическим (static), терминальным (final) или конструктором, компилятор точно знает, какой метод вызвать. (Модификатор final описывается в следующем разделе.) Такой процесс называется статическим связыванием (static binding). В противном случае метод, подлежащий вызову, определяется по фактическому типу неявного параметра, и во время выполнения программы должно использоваться динамическое связывание. В нашем примере компилятор сгенерировал бы команду вызова метода f (String) с помощью динамического связывания.
Если при выполнении программы для вызова метода используется динамическое связывание, виртуальная машина должна вызвать версию метода, соответствующую фактическому типу объекта, на который ссылается переменная х. Допустим, что объект имеет фактический тип D , являющийся суперклассом класса С. Если в классе D определен метод f (String), то вызывается именно он. Если нет, то поиск метода f (String), подлежащего вызову, выполняется в суперклассе и т.д.
На поиск вызываемого метода уходит слишком много времени, поэтому виртуальная машина заранее создает таблицу методов (method table) для каждого класса, в которой перечисляются сигнатуры всех методов и фактические методы, подлежащие вызову. При вызове метода виртуальная машина просто просматривает таблицу методов. В нашем примере виртуальная машина проверяет таблицу методов класса D и обнаруживает метод f (String), подлежащий вызову. Такими методами могут быть D. f (String) или X. f (String), если X — некоторый суперкласс класса D.
В этом сценарии есть одна особенность. Если вызывается метод super. f (args), то компилятор просматривает таблицу методов суперкласса неявного параметра.
Рассмотрим подробнее этот процесс на примере вызова метода е . getSalary () в листинге 5.1. Переменная е имеет тип Employee. В этом классе есть только один метод getSalary, у которого нет параметров. Следовательно, в данном случае можно не беспокоиться о разрешении перегрузки.
Поскольку метод getSalary не является ни закрытым, ни статическим, ни константным, он связывается динамически. Виртуальная машина создает таблицу методов классов Employee и Manager. Таблица класса Employee показывает, что все методы определены в самом классе.
Employee:
getName() -> Employee.getName(1
getSalary() -> Employee.getSalary(J
getHireDday() -> Employee.getHireDay(;
raiseSalary(double! -> Employee.raiseSalary(double!
На самом деле это не совсем так. Как мы увидим позднее, класс Employee имеет суперкласс Object, от которого он наследует большое количество методов, которые мы пока будем игнорировать.
Таблица методов класса Manager имеет несколько иной вид. В этом классе три метода наследуются, один метод — переопределяется и один — добавляется.
Manager:
getName(I -> Employee.getName(]
getSalary(] -> Manager.getSalary()•
getHireDday(; -> Employee.getHireDay(]
raiseSalary(double) -> Employee.raiseSalary(double;
setBonus(double; -> Manager.setBonus(double)
Во время выполнения программы вызов метода е . getSalary () выполняется следующим образом.
Сначала виртуальная машина находит в таблице методов класса Employee, Manager или другого подкласса фактический тип переменной е.
Затем виртуальная машина определяет класс, которому принадлежит метод getSalary с соответствующей сигнатурой. Теперь метод, подлежащий вызову, известен.
Виртуальная машина вызывает этот метод.
Динамическое связывание обладает одной важной особенностью: оно позволяет модифицировать программы без перекомпиляции их кодов. Это делает программы
динамически расширяемыми (extensible). Допустим, в программу добавлен новый класс Executive, и переменная е может ссылаться на объект этого класса. Код, содержащий вызов метода e.getSalary (), заново компилировать не нужно. Если переменная е ссылается на объект типа Executive, автоматически вызывается метод Executive.getSalary().
При замещении область видимости метода подкласса должна быть не меньше области видимости замещаемого метода суперкласса. В частности, если метод суперкласса открыт, метод подкласса также должен быть объявлен открытым. Программисты часто ошибаются, забывая указать спецификатор public при описании метода подкласса. В этих случаях компилятор выражает свое недовольство тем, что привилегии доступа к данным ограничены.
Предотвращение наследования: терминальные классы и методы
Иногда наследование является нежелательным. Классы, которые нельзя расширить, называются терминальными (final).Для того чтобы отметить этот факт, в определении класса используется модификатор final. Например, допустим, что мы хотим предотвратить создание подклассов класса Executive. В этом случае этот класс просто объявляется с помощью модификатора final.
final class Executive extends Manager {
}
Отдельный метод класса также может быть терминальным. Такой метод не может замещаться никакими методами подклассов. (Все методы терминального класса автоматически являются терминальными.) Например, class Employee {
public final String getName()
{ return name;
}
Напомним, что с помощью модификатора final могут также описываться константные поля. После создания объекта содержание константного поля нельзя изменить. Однако, если класс объявлен терминальным, его поля от этого не становятся константными.
Для того чтобы некий класс или метод сделать терминальным, может быть две причины.
L Эффективность.
Динамическое связывание сложнее статического, следовательно программы с динамическими вызовами работают медленнее. Важнее другое — компилятор не может заменить обычный метод подставляемой функцией, поскольку этот метод может замещаться методом подкласса. В то же время терминальные методы компилятор может сделать подставляемыми. Например, если метод е . getName () является терминальным, компилятор сам заменит его вызов полем e. name.
Вызовы процедур нарушают стратегию обнаружения и декодирования последующих инструкций при обработке текущей команды, выполняемой центральным процессором. Замена вызовов обычных процедур подставляемыми функциями — большое достижение. Естественно, это — проблема компилятора, а не интерпретатора байт-кодов.
Безопасность.
Гибкость механизма динамического связывания означает, что программист не может управлять процессом вызова метода. При передаче сообщения, например e.getName(), может оказаться, что переменная е представляет собой объект подкласса, в котором метод getName () переопределяется и возвращает совершенно другую строку. Описывая этот метод с помощью модификатора final, можно избежать таких недоразумений.
Например, класс String является терминальным. Это значит, что определить подкласс этого класса невозможно, а компилятор и виртуальная машина могут оптимизировать его вызовы методов.
В языке C++ методы по умолчанию динамически не связываются, поэтому их можно объявлять подставляемыми (inline) и заменять их вызовы соответствующими кодами. Однако механизма, позволяющего предотвратить замещение метода суперкласса методом подкласса, в языке C++ нет. В программе на этом языке можно создать класс, из которого нельзя вывести производные классы, но для этого нужно выполнить маловразумительный и практически бесполезный трюк. (Можете реализовать его в качестве упражнения. Подсказка: воспользуйтесь виртуальным базовым классом.)
Приведение типов
В главе 3 мы рассмотрели процесс принудительного преобразования одного типа в другой, называемый приведением типов (casting). Для этого в языке Java существует специальное обозначение. Например, операторы double х = 3.4 05;
int nx = (int)x;
преобразовывают значение выражения х в целое число, отбрасывая дробную часть.
Иногда требуется преобразовать число с плавающей десятичной точкой в целое. Аналогично этому в языке следует предусмотреть возможность преобразовывать ссылку на объект одного класса в ссылку на объект другого класса. Чтобы выполнить приведение объектной ссылки, используются такие же синтаксические конструкции, как и для числовых выражений. Для этого поместите имя требуемого класса в скобки и поставьте перед объектной ссылкой, которую нужно преобразовать. Например, Manager boss = (Manager)staff[0];
Для приведения типов существует только одна причина — чтобы использовать все возможности объекта после того, как его фактический тип будет на время забыт. Например, в классе Manager массив staffпредставляет собой массив объектов класса Employee, поскольку некоторые его элементы хранят информацию об обычных сотрудниках. Чтобы получить доступ ко всем новым полям этого типа, нам может понадобиться привести тип некоторых элементов этого массива к типу Manager. (Заметим, что в примере, рассмотренном в первом разделе, мы предприняли специальные меры, чтобы избежать приведения типов. Мы инициализировали переменную boss объектом класса Manager, перед тем как поместить ее в массив. Для того чтобы задать размер премии, нужно знать правильный тип объекта.)
Как известно, в языке Java каждая объектная переменная имеет тип, описывающий разновидность объекта, на который ссылается переменная, и все, что он может делать. Например, переменная staff[1] ссылается на объект класса Employee (поэтому она может ссылаться и на объекты класса Manager).
На основе описания классов компилятор проверяет, сужает или расширяет возможности класса программист, объявляющий переменную. Если переменной суперкласса присваивается объект подкласса, возможности класса сужаются, и компилятор без проблем позволяет программисту сделать это. Если, наоборот, объект суперкласса присваивается переменной подкласса, возможности класса расширяются, поэтому программист должен подтвердить это с помощью обозначения, предназначенного для приведения типов, указав в скобках имя подкласса (Subclass).
Что произойдет, если попытаться осуществить приведение типов вниз по цепочке наследования и попробовать обмануть компилятор?
Manager boss = (Manager)staff[1]; // ОШИБКА
При выполнении программы система обнаружит несоответствие и возбудит исключительную ситуацию. Если ее не перехватить, работа программы будет прекращена. Итак, перед приведением типов следует проверить его корректность. Для этого нужно использовать оператор instanceof. Например, if (staff[i] instanceof Manager) {
boss = (Manager)staff[1];,
}
В заключение заметим, что компилятор не позволит выполнить некорректное приведение типов. Например, приведение типов
Date с = (Date)staff[1];
приведет к ошибке на этапе компиляции, поскольку класс Date не является подклассом класса Employee.
Подытожим все вышесказанное.
Приведение типов можно выполнять только внутри иерархии наследования.
Чтобы проверить корректность приведения суперкласса к подклассу, используйте оператор instanceof.
• -
Проверка
х instanceof С
не возбуждает исключительную ситуацию, если X — нулевая ссылка null. Она просто вернет значение false. Это вполне разумно. Поскольку нулевая ссылка не относится ни к одному объекту, она, очевидно, не относится ни к одному объекту класса С.
На самом деле преобразование типа объекта с помощью приведения — не слишком хорошая идея. В нашем примере выполнять такое преобразование объекта класса Employee в объект класса Manager не обязательно. Метод getSalary прекрасно работает с обоими типами, поскольку динамическое связывание автоматически определяет правильный метод.
В языке Java для приведения типов используется устаревший синтаксис языка С, однако работает он как безопасная операция динамического приведения типов dynamic_cast языка C++. Например, оператор
Manager boss = (Manager)staff[1]; //Язык Java эквивалентен оператору
Manager* boss = dynamic_cast<Manager*>(staff[1)); // Язык C++
Однако существует одно отличие. Если приведение типов выполняется неверно, оно не порождает нулевой объект, а возбуждает исключительную ситуацию. В этом смысле приведение типов в языке Java напоминает приведение ссылочных типов (references) в языке C++. Это очень значительный недостаток. В языке C++ и контроль, и преобразование типов можно выполнить в одном операторе
Manager* boss = dynamic_cast<Manager*>(staff[1]); // Язык C++ if (boss != NULL) . . .
В языке Java используется комбинация оператора instanceof и оператора приведения ТИПОВ.
if (staff[1j instanceof Manager)
{
Manager boss = (manager)staff[1];
}
Единственная причина для приведения типов — желание использовать уникальный метод, который есть только в классе Manager, например setBonus. Если по какой-либо причине возникает необходимость вызвать метод getSalary из объекта Employee, задайте себе вопрос — не свидетельствует ли это о недостатках суперкласса? Возможно, следует пересмотреть устройство суперкласса и добавить в него метод setBonus. Запомните, чтобы вызвать крах программы, достаточно одного-единственного непра-
ПИЛЬНОГО приведения типов. В общем, следует по возможности избегать приведения типов и применения оператора instanceof.
Приведение типов обычно используется в настраиваемых контейнерах, например, в классе ArrayList, который рассматривается в этой главе ниже. При извлечении значения из контейнера его тип известен лишь как настраиваемый тип Object, и, чтобы преобразовать его к типу объектов, находящихся в контейнере, нужно выполнить приведение.
Абстрактные классы
Поднимаясь по иерархии наследования, классы становятся все более абстрактными. В некотором смысле класс-предок становится настолько абстрактным, что о нем начинают думать скорее как об основе для разработки других классов, чем как о классе, позволяющем создавать конкретные объекты. Рассмотрим, например, расширение* иерархии класса Employee. Сотрудник— это человек, а человек может быть студентом. Расширим нашу иерархию классов, добавив в нее классы Person и Student.
Рис. 5.2. Диаграмма наследования для класса Person и его подклассов
Какие проблемы возникают при таком высоком уровне абстракции? Существуют определенные атрибуты, характерные для каждого человека, например имя. И студенты, и сотрудники имеют имена, и при создании общего суперкласса понадобится перенести метод getName на более высокий уровень в иерархии наследования.
Добавим теперь новый метод getDescription, предназначенный для создания краткой характеристики человека, например:
Сотрудник, зарплата: $50,000.00
Студент, специализация: компьютерные науки
Для классов Employee и Student этот метод довольно просто реализуется. Однако какую информацию можно поместить в класс Person? В нем ничего нет, кроме имени. Разумеется, можно было бы реализовать метод Person. getDescription () , возвращающий пустую строку. Однако есть способ получше. Используя ключевое слово abstract, можно не реализовывать этот метод вообще.
public abstract String getDescription();
// Реализация не требуется.
Для большей ясности класс, содержащий один или несколько абстрактных методов, можно объявить абстрактным.
abstract class Person
I
public abstract String getDescription();
}
Кроме абстрактных методов, абстрактные классы могут содержать конкретные данные и методы. Например, класс Person хранит имя человека и содержит конкретный метод, возвращающий это имя.
abstract class Person
public Person(String n)
name = n;
public abstract tring getDescription();
return name;
private String name;
Многие программисты полагают, что абстрактные классы должны содержать только абстрактные методы. Это далеко не так. В суперкласс следует помещать как можно больше функциональных возможностей, независимо от того, абстрактный он или нет. В частности, общие поля и неабстрактные методы нужно помещать в абстрактный суперкласс.
Абстрактные методы представляют собой прототипы методов, реализованных в подклассах. Расширить абстрактный класс можно двумя способами. Например, оставить некоторые или все абстрактные методы неопределенными. Затем каждый подкласс сделать абстрактным. Кроме того, можно определить все методы. Тогда подклассы больше не будут абстрактными.
Определим класс Student, расширяющий абстрактный класс Person и реализующий метод getDescription. Поскольку ни один метод в классе Student не является абстрактным, нет никакой необходимости объявлять сам класс абстрактным.
Класс может быть объявлен абстрактным, даже если он не содержит ни одного абстрактного метода.
Создать объекты абстрактного класса невозможно. Например, выражение new Person("Винс By");
ошибочно. Однако можно создать объекты конкретного подкласса.
Заметим, что можно создавать объектные переменные абстрактных классов, однако такие переменные должны ссылаться на объект неабстрактного класса. Например, Person р = new Student ('"Вине By", "Экономика");
Здесь p —переменная абстрактного типа Person, ссылающаяся на экземпляр неабстрактного подкласса Student.
В языке C++ абстрактный метод называется чисто виртуальной функцией (pure virtual function) и сопровождается окончанием =0.
class Person // Язык C++
public:
virtual string getDescription() = 0;
Класс в языке C++ является абстрактным, если он содержит хотя бы одну чисто виртуальную функцию. В языке C++ нет специального ключевого слова, означающего абстрактные классы.
конкретный подкласс Student, расширяющий абстрактный класс Person. class Student extends Person {
publicn, String
(
super(n);
major = }
public String
{
return "Студент, специализация: " + major;
}
private String major;
}
В этом подклассе определяется метод getDescription. Следовательно, все методы в классе Student являются конкретными, и класс больше не является абстрактным.
В программе, представленной в листинге 5.2, определен абстрактный суперкласс Person и два конкретных подкласса Employee и Student. Заполним массив типа Person ссылками на объекты классов Employee и Student.
[] people = new Person[2];
= new
= new
Затем выведем на печать имена и характеристики этих объектов.
for (int =0; i <i++)
{
Person p =[i];
+ "," +
}
Некоторых читателей приведенный ниже вызов getDescription () может озадачить. Не относится ли он к неопределенному методу? Учтите, что переменная р никогда не ссылается ни на один объект класса Person, поскольку создать такой объект попросту невозможно. Переменная р всегда ссылается на объект конкретного подкласса, например Employee или Student. Для этих объектов метод getDescriprion определен.
Можно ли пропустить все абстрактные классы из суперкласса Person и просто определить метод getDescription в подклассах Employee и Student? В этом случае
метод getDescription нельзя вызвать с помощью переменной р. Компилятор гарантирует, что вызываются только методы, определенные в классе.
Абстрактные методы представляют собой важное понятие языка Java. Гораздо чаще они применяются при создании интерфейсов, которые будут детально рассмотрены в главе 6.
import java.text.*;
public class PersonTest
{
public static void main(String[] args)
{
Person[j people = new Person [2];
// Заполняем массив объектами классов Student и Employee.
people[0]
= new Employee("Гарри Хакер", 50000);
people[l]
= new Student("Мария Моррис", "компьютерные науки");
// Напечатать имена и описания всех объектов класса Person
for (int i = 0; i < people.length; i++)
{
Person p = people[i^ ;
System.out.printIn(p.getName() + ", "
+ p.getDescription());
}
}
}
abstract class Person
{
public Person(String n)
{
name = n;
}
public abstract String getDescription();
public String getName(]
{
return name;
}
private String name;
}
class Employee extends Person
{
public Employee(String n, double s)
{
// Передать имя конструктору суперкласса.
8 .super(n);
salary = s;
}
51.51.52.52.53.53.54 .54 .55.55.56.56.public double getSalary(] } return salary;public double getSalary(] } return salary;57.57.58.58.59.59.60.60.61.62.63.64.65.66.67.68.69.70.71.61.62.63.64.65.66.67.68.69.70.71.public String getDescription(]NumberFormat formatter= NumberFormat.getCurrencylnstance(); return "Сотрудник, зарплата: "+ formatter.format(salary);}public String getDescription(]NumberFormat formatter= NumberFormat.getCurrencylnstance(); return "Сотрудник, зарплата: "+ formatter.format(salary);}72.73.74.72.73.74.75.76.77.78.79.80.81.82.83.84 .85.86.87.88.89.90.91.92.93.75.76.77.78.79.80.81.82.83.84 .85.86.87.88.89.90.91.92.93.public void raiseSalary(double byPercent) {double raise = salary * byPercent / 100; salary += raise;}private double salary;}class Student extends Person{/**@param n имя студентаSparam m специализация студента*/public Student(String n, String m){// Передать строку п конструктору суперкласса super(n);major = m;}public String getDescription(){return "Студент, специализация: " + major;}private String major;>public void raiseSalary(double byPercent) {double raise = salary * byPercent / 100; salary += raise;}private double salary;}class Student extends Person{/**@param n имя студентаSparam m специализация студента*/public Student(String n, String m){// Передать строку п конструктору суперкласса super(n);major = m;}public String getDescription(){return "Студент, специализация: " + major;}private String major;>
51.
51.
52.
52.
53.
53.
54 .
54 .
55.
55.
56.
56.
public double getSalary(] } return salary;
public double getSalary(] } return salary;
57.
57.
58.
58.
59.
59.
60.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
public String getDescription(]
NumberFormat formatter
= NumberFormat.getCurrencylnstance(); return "Сотрудник, зарплата: "
+ formatter.format(salary);
}
public String getDescription(]
NumberFormat formatter
= NumberFormat.getCurrencylnstance(); return "Сотрудник, зарплата: "
+ formatter.format(salary);
}
72.
73.
74.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84 .
85.
86.
87.
88.
89.
90.
91.
92.
93.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84 .
85.
86.
87.
88.
89.
90.
91.
92.
93.
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100; salary += raise;
}
private double salary;
}
class Student extends Person
{
/**
@param n имя студента
Sparam m специализация студента
*/
public Student(String n, String m)
{
// Передать строку п конструктору суперкласса super(n);
major = m;
}
public String getDescription()
{
return "Студент, специализация: " + major;
}
private String major;
>
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100; salary += raise;
}
private double salary;
}
class Student extends Person
{
/**
@param n имя студента
Sparam m специализация студента
*/
public Student(String n, String m)
{
// Передать строку п конструктору суперкласса super(n);
major = m;
}
public String getDescription()
{
return "Студент, специализация: " + major;
}
private String major;
>
Защищенный доступ
Как известно, поля в классе желательно делать закрытыми, а методы — открытыми. Любые закрытые сущности в других классах невидимы. Это утверждение справедливо и для подклассов: подкласс не имеет доступа к закрытым полям суперкласса.
Иногда нужно ограничивать доступ к некоторому методу и открывать его лишь для подклассов. Гораздо реже возникает необходимость предоставлять методам подкласса доступ к полям суперкласса. В этом случае сущность в классе объявляется защищенной с помощью ключевого слова protected. Например, если в суперклассе Employee поле hireDay объявлено защищенным, а не закрытым, методы подкласса Manager смогут обращаться к нему непосредственно.
Однако методы класса Manager имеют доступ лишь к полям hireDay, принадлежащим объектам самого класса Manager, но не класса Employee. Это ограничение введено для того, чтобы программисты не могли злоупотреблять механизмом защищенного доступа и создавать подклассы лишь для получения доступа к защищенным полям.
На практике пользоваться атрибутом protected нужно очень осторожно. Допустим, что ваш класс используется другими программистами и в нем есть защищенные поля. Без вашего ведома другие программисты могут создавать классы, производные от вашего, и тем самым получать доступ к защищенным полям вашего класса. Теперь вы уже не сможете изменять реализацию своего класса, не уведомив других программистов. Это противоречит духу объектно-ориентированного программирования, поощряющего инкапсуляцию данных.
Применять защищенные методы полезнее, чем защищенные поля. Метод класса можно объявить защищенным, чтобы ограничить его использование. Это значит, что методы подклассов (предки которых известны по определению) могут вызывать этот метод, а методы других классов — нет.
Метод clone класса Object представляет собой хороший пример такого механизма (детали см. в главе 6).
Защищенные сущности в языке Java видимы во всех подклассах, а также во всех других классах пакета, которому эти подклассы принадлежат. Этим язык Java немного отличается от языка C++, в котором ключевое слово protected имеет несколько иной смысл. Таким образом, понятие защищенности в языке Java менее безопасно, чём в языке C++.
Итак, в языке Java есть четыре модификатора доступа, управляющих областью видимости.
Область видимости ограничена классом (private).
Область видимости не ограничена (public).
Область видимости ограничена пакетом и всеми подклассами (protected).
Область видимости ограничена пакетом (к сожалению— по умолчанию). Никакого модификатора не нужно.
Object: глобальный суперкласс
Класс Objectявляется предком всех классов— каждый класс в языке Java расширяет класс Ob j ect. Однако писать
class Employee extends Oblects
не нужно. Если суперкласс явно не указан, им считается класс Object. Поскольку в языке Java каждый класс расширяет класс Object, очень важно знать, какими возможностями обладает сам класс Object. В этой главе данный вопрос не рассматривается. Некоторые аспекты класса Object будут изучены в других главах, а остальную информацию читатели могут найти в документации, помещенной в Интернет. (Некоторые методы класса Ob j ect можно понять лишь после изучения потоков — см. том 2.)
Переменную типа Ob j ect можно использовать в качестве ссылки на объект любо-
го типа:
Object obj = new Employee("Гарри Хакер", 35000);
Разумеется, переменная этого класса полезна лишь в качестве настраиваемой переменной, предназначенной для хранения значений произвольного типа. Чтобы сделать с этим значением что-то конкретное, нужно знать его исходный тип, а затем выполнить приведение типов:
Employee е = (Employee) obj ;
В языке C++ аналогичного глобального базового класса нет, хотя, разумеется, лю- бойуказатель можно преобразовать в указатель типаvoid*. Программисты наязы- ь.ке Java часто применяют ссылки типаObjectдля создания структур данных и алго
ритмов, поддерживающих разные типы данных. В языке C++ для этого в основном используются шаблоны. Однако в языке Java шаблонов нет, поэтому программисты используют механизм, позволяющий конкретизировать тип объекта во время выполнения программы, и создают код, манипулирующий ссылками типа Obj ect.
Методы equals и toString
Метод equals класса Object проверяет, одинаковы ли два объекта. Поскольку метод equals реализован в классе Object, он определяет лишь, занимают ли они одну и ту же область памяти. Это практически бесполезная проверка. Если нужно на самом деле убедиться, что объекты эквивалентны, необходимо заменить метод equals более осмысленным методом сравнения. Например, так.
class Employee
( / / ...
public boolean equals(Object otherObject)
(
// Быстрая проверка идентичности объектов.
if (this == otherObject) return true;
// Если явный параметр — null, возвращается значение false.
if (otherObject == null) return false;
// Если классы не совпадают, они не эквивалентны.
if (getClass () != otherObject.getClass()) return false;
// Теперь мы знаем, что объект otherObject
// имеет тип Employee и не является нулевым.
Employee other = (Employee) otherObject;
// Проверим, хранятся ли в полях объектов идентичные значения.
return name.equals(other.name I
&& salary = other.salary
48 hireDay.equals(other.hireDay);
)
}.
..••
Метод getClass определяет тип объекта. Этот метод подробно обсуждается в этой главе позднее. Чтобы объекты были равны между собой, они должны для начала быть объектами одного и того же класса.
Как должен работать метод equals, если неявные и явные параметры не принадлежат одному и тому же классу? К сожалению, разные программисты поступают в этой ситуации по-разному. Мы рекомендуем, чтобы метод equals возвращал значение false, если классы не совпадают. Однако многие программисты используют следующую проверку.
if (!(otherObject instanceof Employee)) return false;
При этом остается возможность, что объект класса Object принадлежит его подклассу. Некоторые программисты вообще не пользуются проверкой. Тогда, если привести тип объекта класса Object к типу Employee невозможно, метод equals возбуждает исключительную ситуацию. С технической точки зрения оба подхода неверны, и вот почему. Спецификация языка Java требует, чтобы метод equals обладал следующими возможностями.
Рефлексивность. Для любой ненулевой ссылки х вызов x.equals (x) должен возвращать значение true.
Симметричность. Для любых ссылок х и у вызов x.equals (у) возвращает значение true тогда и только тогда, когда у. equals (x) возвращает значение true.
Транзитивность. Для любых ссылок х, у и z, если вызовы X . equals (у) и у. equals (z) возвращают значение true, то вызов X.equals (z) возвращает значение true.
Непротиворечивость. Если объекты, на которые ссылались переменные х и у не изменяются, то повторные вызовы x.equals (у) должны возвращать одно и то же значение.
Для любой ненулевой ссылки х вызов x.equals (null) должен возвращать значение false.
Правило 5 требует, чтобы в методе equals содержалась проверка
if (otherObject == null) return false;
Менее очевидно, что правило 2 требует выполнения проверки эквивалентности классов. Рассмотрим вызов
e.equals(mJ
В нем объект е принадлежит классу Employee, а объект m — классу Manager, причем каждый из них содержит одинаковые имена, зарплату и дату приема на работу. Если не выполнить проверку эквивалентности классов, которым принадлежат объекты m и е, вызов вернет значение true. Однако это значит, что и обратный вызов
m.equals (el
также должен возвращать значение true — правило 2 не позволяет ему возвращать значение false или возбуждать исключительную ситуацию.
К сожалению, в спецификации языка Java об этом говорится очень мало, и большинство программистов просто не знают об этом. Стандартная библиотека языка Java содержит более 150 реализаций методов equals, некоторые из которых представляют собой мешанину операторов instanceof, вызовов метода getClass и перехватов исключительной ситуации ClassCastException, а некоторые вообще ничего не делают. Только небольшая часть реализаций удовлетворяет правилу 2. Лучше всего создать свою, более совершенную реализацию метода equals.
Вот рецепт для создания более совершенного метода equals.
Вызвать явный параметр— впоследствии его тип нужно будет
привести к типу другой переменной, которую назовем other.
Проверить, идентичны ли ссылки this иj ect:
== otherObject) return true;
На практике чаще всего так и делают. Проще проверить идентичность ссылок, чем сравнивать поля объектов.
Проверить, является ли ссылка otherObj ect нулевой (null). Если да, вернуть значениеЭту проверку делать нужно обязательно.
if (otherObject == null) return false;
Проверить, принадлежат ли объекты this и other одному и тому же классу. Эта проверка является обязательной по "правилу симметричности".
if!=return false;
Преобразовать объект otherOb j ect в переменную требуемого класса: ClassName other =
Теперь нужно сравнить между собой все поля. Для полей основных типов используется оператор ==, для объектных полей — метод equals. Если все поля двух объектов совпадают друг с другом, возвращается значение true, в противном случае — значение return==
field2)
Внутри подкласса нужно сначала вызвать метод equals из суперкласса. Если эта проверка возвращает значение false, значит, объекты не совпадают. Если же проверка завершается успешно, можно приступать к сравнению полей подкласса. class Manager extends Employee {
publicotherObject)
{
ifreturn false;
=
// Методпроверяет, принадлежат ли
// объекты this и otherObject одному и тому же классу. return bonus ==
} }
Другим важным методом классаявляется методвозвращающий
значение объекта в виде строки. Этот метод замещается почти во всех классах и предназначен для вывода на печать текущего состояния объекта. Вот типичный пример. Метод toString класса Point возвращает следующую строку: java.awt.Point[x=10,y=20■
Большинство (но не все) методы toString состоят из имени класса, за которым указываются значения его полей в квадратных скобках. Ниже приведена реализация метода toString для класса Employee.
publicStringtoString (',
{
return "Employee[name" + name
+=" + salary
+ ",hireDay =" + hireDay
)'
На самом деле этот метод можно усовершенствовать. Не будем встраивать имя класса в метод toString, а просто вызовем метод getClass () . getName () и получим строку, содержащую имя класса.
public String toString(] <
return getClass().geName()
+ "[name=" + name
+ ", salary=" + salary
+ ",hireday=" + hireDay
} , '
Теперь метод toString работает и с подклассами.
Разумеется, программист, создающий подкласс, должен определить свой собственный метод toString и добавить поля подкласса. Если в суперклассе используется вызов getClass () . getName(), подкласс просто вызывает метод super.ToString() . Вот пример метода toString из класса Manager.
class manager extends Employee {
public String toString(1
{
return super.toString([
+ "[bonus=" + bonus + " ]" ;
}
Теперь состояние объекта класса Manager выводится на печать следующим образом:
Manager(name=...,salary=..., hireDay=. ..][bonus=]
Метод toString вездесущ. Для этого есть важная причина: как только объект конкатенируется со строкой с помощью оператора "+", компилятор языка Java автоматически вызывает метод toString, чтобы получить представление его текущего состояния. Например,
Point p = new Point (10, 20);
String message = "Текущая позиция = " + р;
// Автоматически вызывает метод p.String().
• Вместо оператора x.toString () можно писать выражение " " + х. Эта конкатенация пустой строки с представлением объекта х в виде строки эквивалентна вызову метода x. toString ().
Допустим, что х — произвольный объект, и программист вызывает метод System.out.printin(x[ ;
206Глава5.Наследование
В этом случае метод println просто вызовет метод X . toString () и выведет строку результата.
Метод toString, определенный в классе Object, выводит на печать имя класса и адрес объекта. Например, при вызове System.out.printin(System.out);
возникает следующая строка
java.io.PrintStreams2f6684
Это происходит потому, что разработчики класса Printstreamне позаботились о замещении метода toString.
Этот метод — отличное средство отладки. Во многих классах из стандартной библиотеки метод toString определен так, чтобы с его помощью можно было получать полезную отладочную информацию. Некоторые отладчики позволяют вызывать метод toString для отображения на экране текущего состояния объектов, поэтому при трассировке программы можно всегда пользоваться выражениями System.out.printin("Текущее положение = " + position);
Мы настоятельно рекомендуем добавлять метод toString в каждый создаваемый класс. И вы, и программисты, использующие ваш класс, получите выгоду от этого.
В программе, приведенной в листинге 5.3, реализованы методы equals и toString для классов Employee и Manager.
2 import java.util.*;
. publicclassEqualsTest
{
public static void main(String[j args)
{
Employee alicel = new Employee("Алиса Адамс", 75000,
1987, 12, 15);
Employee alice2 =alicel;
Employee alice3 =newEmployee ("Алиса Адамс", 75000,
1987, 12, 15);
Employee bob = new Employee("БоС Брандсон", 50000,
13.1989, 10, 1);
System.out.printIn("alicel == alice2: "
+ (alicel == alice2));
System.out.printin("alicel == alice3: "
+ (alicel == alice3));
: "
+ alicel.equals (alice3));
"
+ alicel.equals(bob));
System.out.printIn("bob.toString(): " + bob);
.
28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.Manager carl = new Manager("Карл Крекер", 80000, .1987, 12, 15);
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
Manager boss = new Manager("Карл Крекер", 80000, 1987, 12, 15);
boss.setBonus(5000);
System.out.printIn("boss.tostring(): " + boss);
System.out.printIn("carl.equals(boss): " + carl.equals(boss));
}
}
class Employee
{
public Employee(String n, double s,
int year, int month, int day) {
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public String getName()
{ return name;
}
public double getSalaryO
{ return salary;
}
public Date getHireDay(;
{
return hireDay;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
public boolean equals(Object otherObject) {
// Быстрая проверка идентичности объектов
if (this == otherObject) return true;
// Если явный параметр — null, возвращается значение false. if (otherObject == null) return false;
// Если классы не совпадают, они не равны.
if (getClassO != otherObject.getClass()] return false;
// Теперь мы знаем, что объект otherObject — ненулевой.
Employee other = (Employee)otherObject;
// Проверка идентичности значений, записанных в полях.
return name.equals(other.nameI
s& salary == other.salary
&& hireDay.equals(other.hireDay) ;
}
public String toStringO
{
return getClass().getName(]
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]";
}
private String name;
private double salary;
private Date hireDay;
}
class Manager extends Employee
{
public Manager(String n, double s,
int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalaryO
{
double baseSalary = super.getSalaryO ;
return baseSalary + bonus;
}
public void setBonus(double b)
{
bonus = b;
}
public boolean equals(Object otherObject)
{
if (!super.equals(otherObject); return false;
Manager other = (Manager)otherObject;
// Метод super.equals проверяет, принадлежат ли объекты
// this и other одному и тому же классу.
return bonus == other.bonus;
}
public String toStringO
{
return super.toString(]
'+ " [bonus=" + bonus
+ "]" ;
}
private double bonus;
}
Обобщенное программирование
В переменных типа Ob j ect могут храниться любые значения переменных любого класса, в частности, класса String:
Object obj = "Привет"; // Правильно.
Однако числа, символы и булевские переменные не являются объектами.
obj =5;// Неправильно.
obj = false;// Неправильно.
Ниже в этой главе мы покажем, как эти типы можно преобразовать в объекты с помощью таких интерфейсных классов (wrapper classes), как Integer и Boolean.
Более того, все типы массивов, независимо от того, хранятся в них объекты или переменные основных типов, относятся к классам, производным от класса Object. Employee!] staff = new Employee[10];
Object arr= staff; // Правильно.
arr = new int[10];// Правильно.
Массив объектов, относящихся к какому-либо классу, можно преобразовать в массив объектов класса Object. Например, массив класса Employee [ ] можно передать в качестве параметра методу, ожидающему массив класса Object[ ]. Это преобразование весьма полезно для обобщенного программирования (generic programming).
Вот простой пример, иллюстрирующий концепцию обобщенного программирования. Допустим, вы хотите определить индекс элемента в массиве. Это ситуация, в которой можно применять обобщенное программирование, используя вместо переменных любого класса переменные типа Object.
static int find(Object[] a, Object key)
(
int i;
for (i =0; i < a.length; i++)
if (a[i].equals(key)] return i;
return -1 ; // Индекс не найден.
}
Например,
Employee!] staff = new Employee[10];
Employee harry;
int n = find(staff, harry);
Заметим, что в массив типа Obj ect [ ] можно преобразовать лишь массив объектов некоего класса. Преобразовать массив типа int[] в массив типа Obj ect [] невозможно. (Однако, как указывалось ранее, элементы обоих массивов можно преобразовать в элементы типа Ob j ect.)
Если массив, состоящий из объектов некоего класса, преобразовывается в массив типа Object [ ], обобщенный массив продолжает хранить информацию о своем исходном типе на протяжении всего времени выполнения программы. Поместить в этот массив элемент постороннего типа не удастся.
Employee!] staff = new Employee[10];
... // Заполняется объектами класса Employee.
Object[ arr = staff;
arr[O' =newDate();
// Нельзя, но очень хочется!
for (i =0; i < n; i++)
// Ура, теперь наша зарплата увеличилась.
Разумеется, во время выполнения программы такие ситуации должны отслеживаться. Показанный выше код будет скомпилирован без ошибок, поскольку компилятор разрешает присваивать значение типа Date элементуимеющему тип
Object. Однако при выполнении программы информация об исходном типе массива хранится постоянно, позволяя отслеживать типы всех объектов, содержащихся в нем. Если вы попробуете поместить в массив переменную недопустимого типа, возникнет исключительная ситуация.
Программисты на языке C++ могут удивиться, узнав, что в языке Java допускается приведение типа Employee!] к типу[]. Ведь в языке C++, даже если
класс object является суперклассом класса Employee, соответствующее преобразование указателя, имеющего тип Employee**, в указатель типане
разрешается. (Хотя, конечно, преобразование указателя типа Employee* в указатель типавполне допустимо.)
Это ограничение введено для большей безопасности. Если бы приведение "Подкласс** —> Суперкласс**" разрешалось, содержимое массива могло бы подвергнуться риску. Рассмотрим следующий код.
Employee ** staff; // Язык C++.
Object** arr = staff;
// Нельзя, но очень хочется!
arr[0]new Date();
// Можно, поскольку класс Date также является производным // от класса for (i = 0; i < n; i++)
// Ура, теперь наша зарплата выросла!
В языке Java эта проблема предотвращается тем, что исходные типы всех массивов запоминаются на все время выполнения программы и используются для отслеживания типов переменных, присваиваемых элементам массивов.
1.0
Class getClass()
Возвращает класс объекта. Как мы увидим далее в этой главе, в языке Java существует представление классов, инкапсулированное в классе Class.
boolean otherObject)
Сравнивает два объекта на равенство; возвращает значение true, если объекты занимают одну и ту же область памяти, в противном случае возвращается значение false. Создавая собственные классы, этот метод следует замещать.
Object
Создает копию (клон) объекта. Система поддержки выполнения программ, написанных на языке Java, выделяет память для нового экземпляра и копирует туда содержимое области памяти, в которой хранится текущий объект.
Клонирование объектов имеет большое значение, однако это довольно сложный процесс, изобилующий ловушками, в которые могут попадать неопытные программисты. В главе 6 метод clone будет рассмотрен подробнее.
String toString()
Возвращает строку, представляющую значение объекта. Создавая собственные классы, этот метод следует замещать.
java.lang.Class 1.0java.lang.Class 1.0String getName ()
java.lang.Class 1.0
java.lang.Class 1.0
Возвращает имя объекта.
Class getSuperclass ()
Возвращает имя суперкласса данного класса в виде объекта класса Class.
Списки массивов
Во многих языках программирования размер всех массивов должен задаваться еще на этапе компиляции программы. Программисты ненавидят это ограничение, поскольку оно вынуждает их идти на неприятные компромиссы. Сколько сотрудников будет работать в отделе? Однозначно не более 100 человек. А что, если департамент огромен, и в нем работают 150 сотрудников? А если департамент очень мал, и в нем работают только 10 человек? Ведь в этом случае 90 процентов массива не используется!
В языке Java ситуация намного лучше — размер массива можно задавать уже во время выполнения программы.
int actualSize = ... ;
Employee!] staff = new Employee[actualsize];
Разумеется, этот код не решает полностью проблему динамической модификации массивов во время выполнения программы. Задав размер массива, его потом нелегко изменить. Проще всего разрешить эту ситуацию, используя объекты другого класса, имитирующие массивы, но способные автоматически уменьшать и увеличивать свой размер. Этот класс называется ArrayList.
В старых версиях языка Java для автоматического изменения размера массивов программисты использовали класс Vector. Однако класс ArrayList эффективнее. Более подробную информацию о классе Vector читатель найдет в главе 2 тома 2.
Между массивами и списками массивов (array lists) есть существенная разница. Массивы являются встроенным свойством языка Java, причем массивы, состоящие из элементов типа Т, имеют тип Т [ ]. В то же время класс ArrayList является библиотечным и определен в пакете java.util. Это единственный тип, объекты которого могут содержать элементы типа Object. В частности, при извлечении любого элемента из списка массивов программист должен выполнить приведение его типа.
Для добавления новых элементов в список массивов используется метод add. Например, посмотрим, как создать список массивов и заполнить его объектами класса Employee.
ArrayList staff = new ArrayList();
staff.add(new Employee());.
staff,add(new Employee(...));
Класс ArrayList управляет внутренним массивом ссылок, имеющих тип Object. В конце концов этот массив окажется заполненным. Здесь на помощь приходят списки массивов: если метод add вызывается при заполненном внутреннем массиве, объект класса ArrayList автоматически создает массив большего размера и копирует в него все объекты.
Если вы заранее знаете, сколько элементов нужно хранить, то перед заполнением списка массивов вызовите метод ensureCapacity:
staff.ensureCapacity (100);
Этот метод выделит память для внутреннего массива, состоящего из 100 объектов. Затем можно вызвать метод add, который не будет иметь проблем с перераспределением памяти.
Количество элементов, которые будут храниться в списке массивов, можно передать конструктору класса ArrayList в качестве параметра:
ArrayList staff = new ArrayList(100);
□s□sВыделение памяти для списка массивов с помощью оператора
□s
□s
new ArrayList(100) // Емкость списка массивов равна 100.
не эквивалентно выделению памяти для нового массива
new Employee(100"// Размер массива равен 100.
Между емкостью списка массивов и размером массива есть существенное различие. Выделяя память для массива из 100элементов, вы резервируете ИХдля дальнейшего использования. В то же время емкость списка массивов — это всего лишь возможная величина. В ходе работы она может быть даже увеличена (за счет выделения дополнительной памяти), но сразу после создания список не содержит ни одного элемента вообще.
Если вы применяете выравнивание памяти, добавление новых элементов может привести к перемещению блока памяти, на что потребуется время. Метод trimToSize нужно применять только тогда, когда вы полностью уверены, что добавлять новые элементы в список массивов не придется.
Класс ArrayList во многих отношениях отличается от шаблона vector, применяемого в языке C++. Наиболее важное отличие состоит в том, что vector — это шаблон, в список можно вставлять только элементы соответствующего типа, а при извлечении элемента из списка массивов никакого приведения делать не нужно. Например, компилятор просто откажется вставить объект класса Data в вектор vector<Employee>. Векторный шаблон языка C++ перегружает оператор [ ], обеспечивая удобный доступ к элементам. В языке Java нужно применять явный вызов метода, поскольку в нем нет перегрузки операторов. Векторы в языке C++ копируются по значению. Если а и Ь — два вектора, то присвоение а = b; создает новый вектор, длина которого совпадает с длиной вектора b. и все элементы копируются из векторов b в вектор а. Аналогичное присвоение в языке Java делает переменные а и b ссылками на один и тот же список массивов.
Метод size возвращает фактическое количество элементов в списке массивов. Например, вызов staff.size(;
возвращает текущее количество элементов в списке staff. Эквивалентный оператор для определения размера массива а выглядит так:
a.lenght
Если вы уверены, что список массивов будет иметь постоянный размер, можете вызвать метод trimToSize. Этот метод выравнивает размер области памяти, чтобы он точно соответствовал количеству элементов, подлежащих хранению. Механизм сборки мусора предотвращает неэффективное использование памяти, освобождая ее излишки.
java.util.ArrayList 1.2
ArrayList()
Создает пустой список массивов.
ArrayList(int initialCapacity)
Создает пустой список массивов, имеющий заданную емкость.
Параметры: initialCapacity Первоначальная емкость списка массивов.
boolean add(Object obj]
Добавляет элемент в конец списка. Всегда возвращает значение true.
Параметры: okjДобавляемый элемент.
int size()
Возвращает количество элементов, хранящихся в списке массивов. (Он отличается от емкости списка и, конечно, никогда не превосходит ее.)
void ensureCapacity (int capacity)
Гарантирует, что список массивов имеет емкость, достаточную для хранения заданного количества элементов без изменения внутреннего массива ссылок.
Параметры: capacityТребуемая емкость списка массивов.
void trimToSize(;
Сокращает емкость списка массивов до его текущего размера.
Доступ к элементам списка массивов
К сожалению, ничто не дается бесплатно. Удобство, предоставляемое автоматическим регулированием размера списка массивов, компенсируется более сложным синтаксисом, который требуется для доступа к его элементам. По этой причине класс ArrayList не является частью языкаJava; это просто вспомогательный класс, кем-то запрограммированный и помещенный в стандартную библиотеку.
Вместо приятных скобочек [ ], используемых для доступа к элементу массива, программисты вынуждены применять методы get и set.
Например, чтобы задать i-й элемент, используется выражение:
staff.set(i, harry);
Это эквивалентно оператору a[i] = harry;, где а — это массив.
Извлечь элемент из списка массивов сложнее, так как значение, возвращаемое методом get, имеет тип Ob j ect. Программист должен привести извлекаемый элемент к желаемому типу.
Employee e = (Employee)staff.get(i);
Это выражение довольно громоздко. В этом можно убедиться, сравнив его с эквивалентным выражением для массивов.
Employee e = a[i) ;
Нумерация в списках массивов, как и в массивах, начинается с нуля.
Иногда можно достичь и гибкости, и удобства доступа к элементам, пользуясь следующим трюком. Во-первых, создайте список массивов и добавьте в него все нужные элементы.
ArrayList list = new ArrayList(); while (...)
{ x= ... list.add(x);
}
Затем, используя метод toArray, скопируйте все элементы в массив.
Х[] а = new X [list.size() ]; list.toArray(a);
He вызывайте метод list.set(i, x), если размер списка массивов меньше i. Например, следующий код содержит ошибку.
ArrayList list = new Arraylist(100);
// Емкость равна 100, размер — 0.
list.set(0, х); // Элемента с номером 0 еще нет.
Заполняя массив, вместо метода set используйте метод add, а метод set применяйте только для замены уже существующего элемента.
Списки массивов по своей природе небезопасны. В список можно случайно добавить элемент неправильного типа.
Date birthday = ... ;
staff.set(i, birthday) ;
Компилятор no этому поводу не выражает никакого неудовольствия. Легко преобразовать объект класса Date в объект класса Object, но после извлечения такого объекта из списка его случайно можно привести к типу Employee. Это неверное приведение типов вызовет крах программы. Вот это — действительно проблема. Она возникает потому, что в списке массивов хранятся объекты класса Object. Если бы в массиве staffхранились ссылки на объекты класса Employee, то компилятор просто запретил бы вставлять в него объекты класса Date.
Employee!] a = new Employee[100];
a[i] = calendar; // Ошибка.
В очень редких случаях списки массивов оказываются полезными для хранения неоднородных объектов (heterogeneous collections). При неободимости в список можно добавлять объекты совершенно разных классов. Извлекая из списка массивов некий элемент, нужно проверять его тип с помощью следующего кода.
ArrayList list;
list.add(new Employee(...));
list.add(new Date!...));
Object obj = list.get(n);
if (obj instanceof Employee)
{
Employee e = (Employee)obj;
}
Однако такой стиль программирована^ считается плохим, потому что сначала игнорировать информацию о типе объекта, а потом извлекать его из списка массивов — не слишком удачная идея.
java.util.ArrayList 1.2
void set(int index, Object obj)
Помещает значение в элемент списка массивов с указанным индексом, за-
меняя его предыдущее содержимое.
Параметры: index Позиция
(число между 0 и size () -1). objНовое значение.
Object get(int index)
Извлекает значение с указанным индексом.
Параметры: index Индекс извлекаемого элемента (число между 0 и size () -1).
Вставка и удаление элементов из середины списка массивов
Элементы можно добавлять не только в конец списка массива, но и в его середину. int n = staff.size() / 2;
staff.add(n, e);
Элемент, имеющий индекс п, и расположенные за ним элементы сдвигаются, чтобы освободить место для нового элемента. Если новый размер списка массивов после вставки элемента превышает его емкость, массив копируется в новое место.
Аналогично можно удалить элемент из середины списка массивов.
Employee e = (Employee)staff.remove(n);
Элементы, расположенные после удаленного элемента, копируются влево, а размер списка массивов уменьшается на единицу.
Вставка и удаление элементов не слитком эффективны. Это не проблема для маленьких списков. Однако, если при работе с длинным списком приходится часто вставлять и удалять его элементы, попробуйте вместо списка массивов применить связанные списки. Способы программирования связанных списков излагаются в томе 2.
В листинге 5.4 показана модификация программы EmployeeTest из главы 4. Массив Employee [ ] заменен классом ArrayList. Обратите внимание на следующие изменения.
Задавать размер массива не нужно.
С помощью метода add можно добавлять сколько угодно методов.
Вместо метода length для подсчета количества элементов используется метод size ().
Вместо выражения а [ i ] для доступа к элементу массива используется метод (Employee)a.get (i).
1.import java.util.*;1.import java.util.*;2.2.
1.import java.util.*;
1.import java.util.*;
2.
2.
public class ArrayListTest
{
15.16.17.18.19.15.16.17.18.19.public static void main(String[ args)
15.
16.
17.
18.
19.
15.
16.
17.
18.
19.
{
//Заполним список staff тремя объектами класса Employee.
ArrayList staff = new ArrayList() ;
staff,add(new Employee ("Карл Крекер", 75000, 1987, 12, 15));
staff .add (new Employee ("ГарриХакер", 50000, 1989,10,1));
staff .add (new Employee ("Тони Тестер", 40000, 1990, 3, 15));
// Поднять зарплату на 5%.
for (int i = 0; i < staff.size(); i++) {
Employee e = (Employee)staff.get(i; ;
. e.raiseSalary(5);
// Вывести информацию обо всех сотрудниках.
for (int i = 0; i < staff.size(); i++) {
Employee e = (Employee)staff.get(i) ;
System.out.printIn("name=" + e.getNarne() + ",salary=" + e.getSalary() + ",hireDay=" + e.getHireDay());
)
class Employee
{
public Employee(String n, double s,
int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendarcalendar
new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar январь обозначается нулем.
hireDay = calendar.getTime();
}
public String getName(;
{
return name;
}
public double getSalary(;
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
java.util.ArrayList 1.2
void add(int Index, Object obj) Сдвигает элементы для вставки.
Параметры: indexПараметры: indexob job jПозиция вставляемого элемента (число между 0 и size () -1).
Параметры: index
Параметры: index
ob j
ob j
Новый элемент.
Позиция удаляемого элемента (число между 0 и size () -1).
Интерфейсы объектов
Иногда нужно преобразовать переменную основного типа в объект. Все основные типы имеют аналоги в виде классов. Например, существует класс Integer, соответствующий основному типу int. Эти классы обычно называются интерфейсными (object wrappers). Они имеют очевидные имена: Integer, Long, Float, Double, Short, Byte, Character, Void и Boolean. (Первые шесть классов являются производными от класса Number.) Интерфейсные классы являются терминальными. (Следовательно, вы не можете заместить метод toString в классе Integer, чтобы отобразить число, например, с помощью римских цифр.) Кроме того, изменить значение, хранящееся в объекте интерфейсного класса, также невозможно.
Допустим, мы хотим, чтобы в списке массивов хранились числа с плавающей точкой. Как указывалось ранее, непосредственно добавить числа в список невозможно. ArrayList list = new ArrayList();
list.add(3.14); //Ошибка
Число с плавающей точкой не является объектом класса Object. Здесь нужен интерфейсный класс Double.
list.add(new Double(3.14));
Разумеется, чтобы извлечь число из списка массивов, содержащего объекты типа Double, нужно извлечь фактическое значение из объекта интерфейсного класса, используя метод doubleValue () класса Double.
double x = ((Double)list.get(n)).doubleValue();
Уф! Вот реальная цена за то, что мы определили класс DoubleArrayList, скрывающий все это уродство раз и навсегда.
class DoubleArrayList
public DoubleArrayList()
list = new ArrayList();
}
public void set(int n, double x)
list.set(n. new Double(x));
)
public void add(double x)
(
list.add(new Double(x) ) ;
}
public double get(int n)
( return ((Double)list.get(n)).doubleValue();
}
public int size()
(
return list.size(0;
)
private ArrayList list;
}
Некоторые программисты считают, что с помощью интерфейсных классов можно реализовать методы, модифицирующие свои числовые параметры. Однако это неправильно. Напомним (глава 4), что на языке Java невозможно написать метод, увеличивающий целое число, передаваемое ему в качестве параметра, поскольку все параметры передаются в методы по значению.
public static voidx) // Неправильно.
{
x++; // Увеличивает локальную копию.
}
public static voidargs)
{
int a 3;
increment(a);
}
Изменение переменной х не влияет на значение переменной а. Можно ли преодолеть это препятствие, используя классвместо типа int?
public static voidx) // Неправильно.
}
public static voidargs)
(
Integer a = new increment(a);
}
Кроме всего прочего, теперь переменные а и х ссылаются на один и тот же объект. Если изменяется значение переменной х, изменяется и значение переменной а. Проблема заключается в том, что объект классаявляется неизменяемым
(immutable): информация, содержащаяся в объекте интерфейсного класса, не может изменяться. В частности, в языке Java нет аналога оператора х++ для объектов классаИтак, с помощью интерфейсных классов невозможно создать ме
тод, модифицирующий числовые параметры.
Если вам необходимо создать метод, изменяющий свои числовые параметры, используйте один из вспомогательных типов (holder types), определенных в классе этим типам относятся IntHolder, BooleanHolder др. Каждый такой тип содержит открытое (!) поле value, через которое можно получить доступ к хранящемуся в нем числу.
public static voidx)
{
x.value++;
public static voidargs)
{
IntegerHolder a = new
increment(a);.
int result = a.value;
Интерфейсные классы будут часто встречаться по другой причине. Разработчики языкаJava считают эти классы удобным местом хранения базовых методов, например, для преобразования строк, состоящих из цифр, в числа.
Чтобы преобразовать строку в целое число, нужно использовать следующий оператор: int х = Integer.parselnt(s);
Это никак не влияет на объекты типа Integer, поскольку методparselnt является статическим. Однако класс Integer — подходящее место для этого метода.
Аналогично .МОЖНО использовать метод Double.parseDouble для разбора числа на цифры.
В версиях, предшествовавших языку Java 2, в классе Double не было метода ParseDouble. Вместо этого программисты использовали неуклюжие конструкции double х = new Doublets).doubleValue();
или
double x = Double.valueOf(s).doubleValue();
Эти операторы создают объект класса Double из строки s, а затем извлекают его значение. Статический метод parseDouble более эффективен, поскольку не вынуждает создавать какие-либо объекты.
Для разбора чисел на цифры есть другой метод, хотя он не проще. Можно применять ме- Dтод parse из класса Number Format. Если переменная s является строкой, а переменная
formatter— объектом типа NumberFormat, то вызов метода formatter.parse(s) возвращает объект типа Number.
NumberFormat formatter = NumberFormat.getNumberlnstance();
Number n = formatter.parse(s);
На самом деле класс Number является абстрактным, а возвращаемый объект имеет тип либо Long, либо Double, в зависимости от содержимого строки s. Для определения типа возвращаемого значения можно воспользоваться оператором instanceof:
if (n instanceof Double) Double d = (Double)n;
Однако на практике тип возвращаемого значения никто не проверяет. Метод doubleValue определен в классе Number и возвращает число с плавающей точкой, эквивалентное объекту типа Number, который в свою очередь может быть типом Long или Double. Таким образом, можно использовать следующее выражение:
х = formatter.parse(s.trim()).doubleValue();
Использование класса NumberFormat имеет одно преимущество: строка может содержать группы разделителей тысяч, например "12,301.4".
В замечании об интерфейсе прикладных программ, приведенном ниже, показаны некоторые более важные методы класса Integer. В остальных многочисленных классах также реализуются аналогичные методы.
.0
int
Возвращает значение объекта типа Integer в виде числа типа int (замещая метод intValue из класса Number).
static String toString(int i, int radix)
Возвращает новый объект типа String, представляющий десятичное число.
static ints)
Возвращает целое значение, предполагая, что объект типа String представляет целое десятичное число.
static ints, int radix)
Возвращает целое значение, предполагая, что объект типа String представляет целое число в позиционном счислении, основа которого задается параметром radix.
static Integers)
Возвращает новый объект типа Integer, инициализированный целым значением, предполагая, что объект типа String представляет целое десятичное
static Integers, int radix)
Возвращает новый объект типа Integer, инициализированный целым значением, предполагая, что объект типа String представляет целое число в позиционном счислении, основа которого задается параметром radix.
Numbers)
Возвращает числовое значение, предполагая, что объект типа String
представляет число.
Класс Class
Во время выполнения программы система языка Java всегда осуществляет проверку типов всех объектов любых классов. Эта информация используется виртуальной машиной для выбора правильного метода, подлежащего вызову.
Получить доступ к этой информации можно иначе— с помощью специального класса, который носит довольно странное имя Class. Метод getClass() класса Ob j ect возвращает экземпляр типа Class.
Employee e;
Class =
Аналогично тому, как объект типа Employee описывает свойства конкретного сотрудника, объект класса Class описывает свойства конкретного класса. Вероятно, наиболее популярным методом класса Class является методвозвращаю
щий имя класса. Например, оператор
+ " " + выводит на печать
Employee Гарри Хакер,
если объект е относится к классу Employee, или
Manager Гарри Хакер,
если объект е относится к классу Manager.
Используя статический метод forName, можно также получить объект класса Class, соответствующий строке.
String className = "Manager";
Class cl = Class . forName(className);
Если имя класса хранится в строке, содержимое которой во время выполнения программы изменяется, нужно применять именно этот метод. Он работает правильно, если переменная className является именем класса или интерфейса. В противном случае метод forName возбуждает контролируемую исключительную ситуацию (checked exception). Правила применения обработчика исключительной ситуации (exception handler) при работе с этим методом описаны во врезке.
Сначала загружается ваш метод main. Он загружает все нужные классы. Каждый из этих классов в свою очередь загружает необходимые ему классы и т.д. Если приложение достаточно велико, этот процесс может занять значительное время, вызвав у пользователя глубокое разочарование. Для того чтобы вызвать у пользователя вашей программы иллюзию быстрого старта, можно воспользоваться следующим трюком. Убедитесь, что класс, содержащий метод main, не ссылается явно на другие классы. Затем изобразите на экране всплеск и вручную загрузите остальные классы с помощью вызовов метода Class.forName.
Третий способ получения объекта класса Class краток и удобен. Если Т— это произвольный тип, то T. class — объект соответствующего класса. Например, Class ell = Class cl2 = int.class;
Class cl3 = Doublet).class;
Отметим, что объект класса Class фактически описывает тип, который не обязательно является классом. Например, тип int — это не класс, однако, несмотря на это, int.class— это объект типа Class.
Исторически сложилось так, что метод getName возвращает довольно странные имена, соответствующие типам массивов:
System.out.printin(Double[].class.getName());
// Выводит на печать строку [Ljava.lang.double;
System.out.printIn(int(].class.getName());
// Выводит на печать строку [I
Перехват исключительных ситуаций
Обработка исключительных ситуаций детально рассматривается в главе 111 однако в более ранних главах нам будут иногда встречаться методы, порождающие ошибки при выполнении программы.
Если во время выполнения программы возникает ошибка, программа "возбуждает исключительную ситуацию". Этот процесс более гибок, чем простое
прекращение выполнения программы, поскольку дает возможность создавать обработчики (handlers) исключительных ситуаций и продолжать работу.
Если обработчик не предусмотрен, система прекращает работу программы и выводит на экран сообщение о типе возникшей исключительной ситуации. Возможно, вы уже сталкивались с сообщениями об исключительных ситуациях, например, если пытались использовать ссылку null или выходили за пределы массива.
Существуют два типа исключительных ситуаций: неконтролируемые (uncheked) и контролируемые (checked). При возникновении контролируемой исключительной ситуации компилятор проверяет, предусмотрен ли для нее обработчик. Однако большинство исключительных ситуаций являются неконтролируемыми. К ним относится, например, исключительная ситуация, возникающая при обращении к ссылке null. Компилятор не проверяет, предусмотрен ли программистом обработчик для этой исключительной ситуации. В конце концов, нужно беречь силы и стараться избегать подобных ошибок, а не программировать их обработку.
Однако не всех ошибок можно избежать. Если, несмотря на все ваши усилия, возникла исключительная ситуация, компилятор будет настаивать на том, чтобы вы создали ее обработчик. Например, метод Class.forName возбуждает проверяемую исключительную ситуацию. В главе 11 мы рассмотрим несколько стратегий обработки исключительных ситуаций. А пока изучим реализацию лишь простейших обработчиков.
Поместим один или несколько методов, которые могут возбудить проверяемую исключительную ситуацию, в блок try, а код обработчика — в раздел catch.
try
{
Операторы, которые могут возбуждать исключительную ситуацию.
)
catch(Exception e)
{
Код обработчика.
}
Рассмотрим пример.
try
{
String name =// Определить имя класса.
Class cl =// Может возбуждать
// исключительную ситуацию.
...// Что-то делает с классом cl.
}
catch(Exception e)
{
e.printStackTrace() ;
}
Если имя класса не существует, оставшаяся часть блока, расположенного в блоке try, игнорируется, и программы входят в блок catch. (Здесь выводится содержимое стека с помощью метода StackTrace класса Throwable. Этот класс являет-
ся суперклассом класса Exception.) Если в блоке try ни один метод не возбуждает исключительную ситуацию, то код обработчика в разделе catch игнорируется.
От программиста требуется лишь предусмотреть обработчик для проверяемой исключительной ситуации. Метод, возбудивший исключительную ситуацию, обнаружить легко. Если происходит вызов метода, который может возбуждать исключительную ситуацию, а обработчика для нее нет, то компилятор выдаст соответствующее предупреждение.
Для каждого типа виртуальная машина создает уникальный объект класса Class. Следовательно, для сравнения объектов этого класса можно применять оператор ==, например, if (e.getClass(; ==...
Еще один полезный метод позволяет программисту создавать экземпляр класса Class "на лету". Этот метод имеет довольно естественное имя newlnstance(). Например, метод e.getClass().newlnstance ();
создает новый экземпляр класса, которому принадлежит объект е. Для инициализации вновь создаваемого объекта метод newlnstance вызывает конструктор по умолчанию (не имеющий параметров).
Используя комбинацию методов forName и newlnstance, можно создать объект, имя класса которого хранится в строке.
String s = "Manager";
Object m = Class.forName(s).newlnstance();
Если при создании объекта на основе имени класса нужно передать конструктору какие-либо параметры, таким способом пользоваться нельзя. Вместо этого нужно применять метод newlnstance класса Constructor. (Это один из нескольких классов, определенных в пакете java. lang.reflect. Отражение обсуждается в следующем разделе.)
Метод newlnstance является аналогом виртуального конструктора в языке C++. Хотя в языке C++ на самом деле нет виртуальных конструкторов в чистом l.виде, такое выражение употребляется для обозначения подобных методов,
реализуемых с помощью специализированных библиотек. Класс class соответствует классу type_info в языке C++, а метод getClass — оператору typedef. Класс Class в языке Java более гибок, чем его аналог в языке C++. Класс type_inf о может только отображать строку с именем типа, но не способен создавать объекты этого типа.
java.lang.Class 1.0
static Class forName(String className)
Возвращает объект класса Class, представляющий класс с именем className.
Object newlnstance()
Создает новый экземпляр класса.
java.lang.reflect.Constructor 1.1
Object newlnstance(Object[' args)
Создает новый экземпляр класса, в котором объявлен конструктор.
Параметры: args Параметры, передаваемые конструктору. Более подробно процесс передачи параметров описан в разделе об отражении.
java.lang.Throwable 1.0
void printStackTrace(;-
Выводит в стандартный поток ошибок объект класса Throwable и содер-
жимое стека отладки.
Отражение
Класс Class предоставляет программисту мощные средства для создания программ, динамически манипулирующих кодом. Это свойство широко применяется в компонентах JavaBeans (см. TOM 2). С помощью отражения язык Java предоставляет пользователю такие же возможности, как, например, язык Visual Basic. В частности, при создании нового класса, независимо от того, когда он создается — при разработке или в ходе выполнения программы, — средства быстрой разработки программ, предоставляемые компонентной архитектурой JavaBeans, должны уметь определять его потенциальные возможности. (Это очень похоже на процесс, который выполняется при добавлении нового компонента в Visual Basic.)
Программа, анализирующая свойства классов, называется отражением (reflection). Пакет, обеспечивающий эти функциональные возможности, называется, соответственно, java.lang. reflect. Отражение — чрезвычайно мощный механизм. С его помощью можно делать следующее.
Анализировать потенциальные возможности классов в ходе выполнения программ.
Инспектировать объекты в ходе выполнения программы, например, создавать единый метод toString, работающий со всеми классами.
Реализовывать код для работы с настраиваемыми массивами.
Пользоваться преимуществами объектов класса Method, аналогичных указателям на функции из языка C++.
Отражение — мощный и сложный механизм. Однако он интересен, в основном, разработчикам программного инструментария, а не прикладным программистам; если вы пишете прикладные программы, а не средства создания программы на языке Java, можете спокойно пропустить оставшуюся часть главы и вернуться к ней позднее.
Использование отражения для анализа свойств классов
Ниже приводится краткий обзор важных составляющих механизма отражения, позволяющего программисту анализировать устройство класса.
Три класса Field, Method и Constructor в пакете java.lang.reflect описывают поля, методы и конструкторы класса соответственно. Все три класса имеют метод getName, возвращающий имя анализируемого класса. Класс Field содержит метод getType, возвращающий объект типа Class, описывающий тип поля. Классы Method и Constructor имеют методы, определяющие тип возвращаемого значения и типы параметров, передаваемых этим методам. Все три класса содержат метод getModifiers, возвращающий целое число, соответствующее использованным модификаторам, например, public или static. Для анализа этого числа применяются статические методы класса Modifiers из пакета java.lang.reflect. Например, в классе Modifiersесть методы isPublic, isPrivate и isFinal, определяющие, является тот или иной метод либо конструктор открытым, закрытым или терминальным. Все, что нужно сделать — применить соответствующий метод класса Modifierк целому числу, которое возвращается методом getModifiers. Для вывода на печать самих модификаторов используется метод Modifier. toString.
Методы getFields, getMethods и getConstructors класса Class возвращают массивы открытых полей, методов и конструкторов, принадлежащих анализируемому классу. К ним относятся и открытые поля суперклассов. Методы getDeclaredFields, getDeclaredMethods и getDeclaredConstructors класса Class возвращают массивы, состоящие из всех полей, методов и конструкторов, объявленных в классе. К ним относятся закрытые и защищенные члены, но не члены суперклассов.
В листинге 5.5 показано, как вывести на печать всю информацию о классе. Программа предлагает пользователю ввести имя класса, а затем выводит на печать сигнатуры всех методов и конструкторов вместе с именами всех полей класса. Например, при вводе в программу строки java.lang.Double
на экране появится следующий текст.
class java.lang.Double extends java.lang.Number {
public java.lang.Double(java.lang.String);
public java.lang.Double(double) ;
public int hashCode();
public int compareTo(java.lang.Object);
public int compareTo(java.lang.Double);
public boolean equals(java.lang.Object);
public java.lang.String toStringO;
public static java.lang.String toString(double);
public static java.lang.Double valueOf(java.lang.String);
public static boolean isNaN(double) ;
public boolean isNaN();
public static boolean islnfinite(double);
public boolean islnfinite();
public byte buteValue(] ;
public short shortvalue() ;
public int intValue();
public long longValue();
public float floatvalue();
public double doubleValue();
public static double parseDouble(java.lang.String);
public static native long doubleToLongBits(double);
public static native long doubleToRawLongBits(double);
public static native double LongBitsToDouble(long);
publid static final double POSITIVE INFINITY; public static final double NEGATIVE_INFINITY; public static final double NaN;
public static final doubleMAXVALUE;
public static final double MIN_VALUE;
public static final java.lang.Class.Type;
private static final long serialVersionUID;
Эта программа замечательна тем, что она может анализировать любой класс, загружаемый интерпретатором языка Java, а не только классы, доступные при компиляции. В следующей главе мы применим ее для анализа внутренних классов, автоматически генерируемых компилятором языка Java.
import java.lang.reflect.*;
import javax.swing.*;
public class ReflectionTest
{
public static void main(String[] args)
{
// Имя класса считывается из командной строки или вводится.
String name;
if (args.length >0)
name = args(0);
else
name = JOptionPane.showInputDialog
("Имя класса (например java.util.Date): ");
try
{
// Выводит имя класса и суперкласса (if != Object).
Class cl =
Class supercl = cl.getSuperclass();
System.out.print("Class " + name);
if (supercl != null && supercl != Object.class]
System.out.print(" extends " + supercl.getName());
System.out.print("\n(\n") ;
printconstructors(cl) ;
System.out.printIn() ;
printMethods(cl) ;
System.out.printin() ;
printFields(cl] ;
System.out.printIn("}");
}
catch(ClassNotFoundExceptione)(e.printStackTrace(;}
System.exit(0] ;
}
/**
Выводит на печать сигнатуры всех конструкторов класса.
@param cl класс
*/
public static void printConstructors (Class cl)
(
Constructor!] constructors = cl.getDeclaredConstructors() : 44.
for (int i= 0; i <constructors.length; i++)
{
Constructor с = constructors[i];
String name = c.getName();
System.out.print(Modifier.tostring(c.getModifiers ())),
System.out.print("" + name + "(");
// Выводит на печать типы параметров.
Class[] paramTypes =
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.printC, ");
.System.out.print(paramTypes(j ].getName());
}
System, out. printlnf); ");
}
}
/**
Выводит на печать сигнатуры всех методов класса.
Sparam cl a class
*/
public static void printMethods(Class cl)
{
Method[ methods = cl.getDeclaredMethods();
for (int i = 0; i < methods.length; i++)
{
Method m=;:
Class retType = m.getReturnTypeO;
String name = m.getName ();
// Выводит на печать модификаторы,
// тип возвращаемого значения и имя метода.
System.out.print(Modifier.tostring(m.getModifiers()));
System.out.print ("" + retType.getName() + " " +
+"(") ;
// Выводит на печать типы параметров.
Class [ ] paramTypes = m.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[ j ] .getName());
}
System.out.println(");");
}
}
/**
Выводит на печать все поля класса.
@param cl класс
*/
public static void printFields(Class cl)
{
Field[j fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
Field f = fields[i];
Class type = f.getTypeO;
String name = f.getName();
System.out.print(Modifier.tostring(f.getModitiers( ) ) ;
System.out.printin(" " + type.getName(] + " " + name
+ ;");
}
)-
}
java.lang.Class 1.0
Field[] getFields()1.1
Field [] getDeclaredFields()1.1
Метод getFields возвращает массив, содержащий объекты класса Field,
соответствующие открытым полям анализируемого класса или его суперкласса. Метод getDeclaredFields возвращает массив, содержащий объекты класса Field, соответствующие всем полям анализируемого класса. Метод возвращает массив нулевой длины, если таких полей нет, или если объект класса Class представляет собой основной тип или массив.
Method[] getMethods1.1
Method[] gteDeclaredMethods() 1.1
Возвращают массив, содержащий объекты класса Method, соответствующие либо только открытым методам, включая наследуемые (getMethods), либо всем методам, за исключением унаследованных (getDeclaredMethods), анализируемого класса или интерфейса.
Constructor[j getConstructors(J 1.1
Constructor[] getDeclaredConstructors1.1
Возвращает массив, содержащий объекты класса Constructor, соответст-
вующие либо только открытым (getConstructors), либо всем конструкторам (getDeclaredMethods) класса, представленного объектом типа Class.
java.lang.reflect.Field 1.1
java.lang.reflect.Method 1.1
java.lang.reflect.Constructor1.1
Class getDeclaringClass()
Возвращает объект класса Class, соответствующий классу, в котором оп- ределензаданныйконструктор, методилиполе.
Class[] getExceptionTypes () (в классах Constructor или Method) Возвращает массив объектовтипаClass,представляющих собой типы исключи- тельныхситуаций, возбуждаемыхзаданнымметодом.
int getModif iers (
Возвращаетцелоечисло, соответствующее модификаторузаданного конструктора, метода или поля. Для анализа возвращаемого значения использует методы класса Modifier.■
String getName()
Возвращает строку, в которой записано имя конструктора, метода или поля.
Class [] getParameterTypes () (в классах Constructor или Method) Возвращает массив объектов типа Class, представляющих собой типы параметров.
Class getReturnType () (в классе Method)
Возвращает объект класса Class, соответствующий возвращаемому типу.
java.lang.reflect.Modifier 1.1
static String toString(int modifiers)
Возвращает строку с модификаторами, соответствующую множеству битов в целом числе modifiers.
static booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanstatic booleanisAbstract (int modifiers) isFinal (int modifiers) islnterface (int modifiers) isNative (int modifiers) isPrivate (int modifiers) isProtected (int modifiers) isPublic (int modifiers) isStatic(int modifiers) isStrict (int modifiers) isSynchronized (int modifiers) isVolatile (int modifiers)
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
static boolean
Применение отражения для анализа объектов
в ходе выполнения программы
В предыдущем разделе мы показали, как определяются имена и типы полей любого объекта.
Получить соответствующий объект типаClass.
Вызвать метод getDeclaredFields из объекта типа Class.
В этом разделе мы сделаем шаг вперед и попробуем определить содержимое поля. Разумеется, определить содержимое конкретного поля объекта, имя и тип которого известны при создании программы, не представляет никакого труда. Однако отражение позволяет сделать это и для объектов, неизвестных на этапе компиляции.
Ключевым в этом процессе является метод get класса Field. Если объект f имеет тип Field (например, если он получен методом getDeclaredFields), а объект obj относится к томужеклассу,чтои объектf,товызовf .get(obj )вернетобъект,
значение которого будет текущим значением поля в объекте obj. Все это выглядит довольно абстрактно, поэтому рассмотрим конкретный пример.
Employee harry = new Employee ("Гарри Крекер", 3500, 10, 1, 1989); Class cl =
// Объект типа Class, представляющий класс Employee.
Field f = cl ,getDeclaredField("name");
// Имя поля класса Employee.
Object V = f.het(harry);
// Значение поля name объекта harry,
// т.е. объект типа String "Гарри Крекер".
Фактически в этом коде скрыта одна проблема. Поскольку поле name закрыто, метод get возбудит исключительную ситуацию IllegalAccessException. Метод get можно применять только для доступных полей. Механизм безопасности языка Java позволяет определить, какие поля имеет объект, но не даст прочитать их содержимое, если эти поля недоступны.
По умолчанию механизм отражения следует правилам доступа, установленным в языке Java. Однако, если программа не контролируется менеджером безопасности, эти правила можно обойти. Чтобы сделать это, вызовите метод setAccessible для объектов классов Field, Method или Constructor, например:
f.setAccessible(true); // Теперь можно вызывать f.get(harry);
Метод setAccessible относится к классу AccessibleObject, общему суперклассу классов Field, Method и Constructor. Это свойство предусмотрено для обеспечения работы отладчиков, постоянной памяти и тому подобных механизмов. Позднее в этой главе мы применим его для создания настраиваемого метода toString.
С методом get связана еще одна проблема. Имя name имеет тип String, поэтому можно вернуть его значение как объект класса Object.Допустим, однако, что нам нужно определить значение поля salary. Оно имеет тип double, а в языке Java нет объектов числового типа. Чтобы решить эту проблему, можно использовать либо метод getDouble класса Field, либо метод get, который с помощью механизма отражения поместит поле в соответствующий интерфейсный класс, в данном случае — Double.
Разумеется, значение поля можно не только определять, но и задавать. Вызов f.set(obj, value) задает новое значение поля, представленного переменной f в объекте obj.
В листинге 5.6 показано, как создать обобщенный метод toString, работающий с любым классом. Сначала он использует метод getDeclaredFields для получения всех полей. Затем применяется метод setAccessible, делающий все эти поля доступными. Далее определяется имя и значение каждого поля. Каждое значение помещается в строку с помощью рекурсивного вызова метода toString из соответствующего объекта. Этот метод проверяет поля всех суперклассов, вплоть до класса Ob j ect.
class ObjectAnalyzer
{
public static String toString(Object obj]
{
Class cl =
String r = cl.getName();
// Проверка полей класса и всех его суперклассов. do
{
r+= "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
// Определяем имена и значения всех полей. for (int i = 0; i < fields.length; i++) {
Field f = fields[ i ];
if(!Modifier.isStatic(f.getModifiers())) {
if(!r.endsWith("[")) r+= "," r += f.getName() + try {
Object val =
r += val.toString();
}
catch (Exception e) { e.printStackTrace(); } )
r += "]";
cl =;
}
while (cl != Object.class);
return r;
)
}
Код, представленный в листинге 5.6, порождает немало трудностей. Циклические ССЫЛКИ могут образовать бесконечную рекурсию. Следовательно, объект класса ObjectAnalyzerдолжен отслеживать объекты, которые он уже просмотрел. Кроме того, для просмотра массивов необходим другой подход. Детали этого подхода мы рассмотрим в следующем разделе.
Для просмотра содержания любого объекта можно воспользоваться методом toString. Например, вызов
Object obj = NumberFormat.getCurrencylnstance ();
System.out.printIn(new ObjectAnalyzer().toString(obj))
порождает следующий впечатляющий вывод.
java.text.DecimalFormat[digitList=java.text.digitList[decimalAt=O,count=0, digits=byte(]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}],positivePrefix=$,pos itiveSuffix=,negativePrefix=($,negativeSuffix=),posPrefixPattern='?,posSuf fixPattern=...,negativePrefixPattern(1 ?,negSuffixPattern=..., multipliers, groupingSize=3,decimalSeparatorAlwaysShown=false,isCurrencyFormat=true,sym bols=java.textDecimalFormatSymbols[zeroDigit=0,groupingSeparator=,,decimal Separators,perMi 11=?, percents, degit=#,patternseparators-,infinity=?,MaN= ?,minusSign=~,currencySymbol=...,intlCurrencySymbol=USD,monetarySeparator= .,exponential=E,locale=java.util.Locale[language=en, country=US,variants.. ,hashcode=1591],currency=java.util.Currency[currencycodes.., defaultFracti onDigits=2],serialVersionOnStream=2],useExponentialNotation=false,positive PrefixFieldPositions=null, positiveSuffixFieldPositions=null,negativePrefix FieldPosition=null, negativeSuffixFieldPositions=null, negativePrefixFieldPc sitions=null,negativeSuffixFieldPositions=null,minExponentDig!ts=0,seriaiv ersion0nStream=2][groupingUsed=true,max.IntegerDigits=40,minIntegerDigits=l ,maxFractionDigits=3,minFractionDigits=l,maximumFractionDigits=2,minimumFr actionDigits=2,serialVersionOnStream=l][]
Обобщенный метод toString можно использовать для реализации метода toString в собственных классах. Например, так.
public String toString(1 (
return new ObhectAnalyzer().toString(this);
Это позволяет легко использовать метод toString для отладки программ.
Листинг5.6. Программа ОЛистинг5.6. Программа Ог^7|£,?7*^"7Ч7’'*'*'Лг^7|£,?7*^"7Ч7’'*'*'Л
Листинг5.6. Программа О
Листинг5.6. Программа О
г^7|£,?7*^"7Ч7’'*'*'Л
г^7|£,?7*^"7Ч7’'*'*'Л
import java.lang.reflect.*;
import java.util.*;
import java.text.*
public class ObjectAnalyzerTest
{
public static void main(String[] args)
{
Object obj = NumberFormat.getCurrencylnstance();
System.out.printIn(new ObjectAnalyzer().toString(obj));
}
}
class ObjectAnalyzer
{
/**
Преобразует объект в строку,
в которой перечислены все его поля.
@param obj объект
@return строка, в которой указаны имя объекта,
имена всех полей и их значения.
*/
public static String toString(Object obj)
24.{
if (obj == null) return "null";
if (visited.contains(obj) 1 return "...";
visited.add(obj);
Class cl =
if (cl == String.class)return (String)obj;
if (cl.isArrayt));
{
String r = cl.getComponentType () + ".[](};
for (int i = 0; i < Array.getLength(obj); i++)
{
if (i > 0) r +=",";
Object val =i);
if(cl.getComponentType().isPrimitive()) r += val;
else r += toString(val);
}
return r + " } ";
}
String r = cl.getName();
// Проверка полей класса и всех его суперклассов.
do
{
47.48.49.50.51.52.53.54 .55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.}47.48.49.50.51.52.53.54 .55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.}г+= "[";
47.
48.
49.
50.
51.
52.
53.
54 .
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
}
47.
48.
49.
50.
51.
52.
53.
54 .
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
}
Field[] fields = cl .getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
// Определяем имена и значения всех полей.
for (int i = 0; i < fields.length; i++)
{
Field f = fieldsti];
if(IModifier.isStatic(f.getModifiers()))
{
if(!r.endsWith("[")1 r+= ","
r +=+ "-";
try
{
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r+= val;
else r += val.toString();
}
catch (Exception e){ e.printStackTrace(]; }
}
}
r+= "]" ;
cl = cl.getSuperclass();
}
while (cl != null);
return r;
}
private ArrayList visited = new ArrayList();
java.lang.reflect.AccessibleObject 1.2
void setAccessible(boolean flag)
Устанавливает признак доступности заданного объекта отражения. Значе-
ние true свидетельствует, что проверка доступа к полю отменена и закры-
тые поля теперь доступны.
boolean isAccessible(AccessibleObject[] array, boolean flag) Получает значение признака доступности заданного объекта отражения.
static void setAccessible(AccessibleObject[] array, boolean flag)
Удобный метод для задания признака доступности массива объектов.
Применение отражения для работы с настраиваемыми массивами
Класс Array из пакета java.lang.reflect позволяет создавать динамические массивы. Например, используя это свойство вместе с методом arrayCopy из главы 3, можно динамически увеличить уже существующий массив, полностью сохранив его предыдущее содержание.
Проблема, которую мы собираемся решить, весьма типична. Допустим, что у нас есть заполненный массив заданного типа, и нам требуется увеличить его. Кроме того, предположим, что нам надоело вручную создавать код, решающий эту задачу. Мы хотим написать обобщенный метод, позволяющий увеличивать размер массива, сохраняя его предыдущее содержание.
а = new
// Массив полон. а =
Как написать такой метод? Нам поможет тот факт, что массив типа Employee [ ] можно преобразовать в массив типа j ect [ ]. Звучит многообещающе. Итак, предпримем первую попытку создать обобщенный метод. Мы просто увеличим массив наэле
ментов (поскольку одного увеличения на 10% для маленьких массивов недостаточно). statica); // Бесполезно.
{
newLength =* 11 / 10 + 10;
newArray = new
0, newArray, 0 , return newArray;
}
Однако существует проблема, связанная с фактическим использованием полученного в результате массива. Этот массив имеет тип Ob j ect [ ], поскольку он создан оператором new
Массив класса [ ] не может быть преобразован в массив класса Employee [ ]. При попытке сделать это возникнет исключительная ситуация ClassCast. Проблема в том, что, как мы уже указывали, в ходе выполнения программы система запоминает первоначальный тип элементов массива, т.е. тип, указанный в операторе new. Можно временно преобразовать массив типа[ ] в массив класса j ect [ ] и обратно,
но массив, изначально созданный как массив класса[ преобразовать в массив
класса Employee [ ] невозможно. Чтобы создать подходящий обобщенный метод, нужно научиться создавать новый массив, тип которого совпадал бы с типом исходного массива. Для этого нам нужны методы класса Array из пакетаreflect, особенно методсоздающий новый массив. Нужно задать тип элементов мас
сива и требуемую длину в качестве параметров этого метода.
Object newArray =newLength);
Чтобы сделать это, нам нужно уметь определять длину и тип элементов нового массива.
Длину можно получить с помощью методаgetLength (). Статический метод getLength класса Arrayдлину любого массива. Чтобы определить
тип элементов нового массива, нужно выполнить следующие действия.
Во-первых, определить, какому классу принадлежит объект а.
Подтвердить, что он действительно является массивом.
Использовать метод getComponentType класса Class (определенный лишь для объектов, представляющих собой массивы) и получить правильный тип массива.
Почему метод getLength относится к классу Array, a ComponentType — к классу Class? Науке это не известно. Распределение по классам методов, реализующих механизм отражения, иногда выглядит довольно случайным.
Итак, мы получаем следующий код.
static Object arrayGrow(Objecta); // Правильный код. { Class cl =
if (!cl.isArray(); return null;
Class componentType =;
int length = Array.getLength(a);
int newLength = length * 11/10 +10;
Object newArray =newLength);
System.arraycopy(a, 0, newArray, 0 , length); return newArray;
}
Заметим, что метод arrayGrow можно применять для увеличения массива любого типа, а не только массива объектов класса Ob j ect. int[] ia = { 1, 2, 3, 4}; ia = (int[]) arrayGrow(ia);
Для этого параметр метода arrayGrow объявляется как объект класса Object, а не как массив объектов класса Object (т.е. Object[ ]). Массив типа int [ ] можно преобразовать в объект типа Ob j ect, но не в массив, состоящий из объектов класса Ob j ect!
В листинге 5.7 показано, как следует увеличивать размеры массивов обоих типов. Отметим, что приведение типа значения, возвращаемого методом badArrayGrow, возбудит исключительную ситуацию.
import java.lang.reflect.*;
import java.util.*;
. public class ArrayGrowTest
{
public static void main(String[" args)
1{
8 .int [] a = { 1, 2, 3 };
a = (int[])goodArrayGrow(a);
arrayPrint(a);
String[] b = { "Том", "ДИК", "Гарри" };
b = (String[])goodArrayGrow(b);
arrayPrint(b);
System.out.printin
("Следующий вызов возбудит исключительную ситуацию.');
b = (String(])badArrayGrow(b);
}
/**
Этот метод пытается увеличить размер массива, создавая
новый массив и копируя туда все элементы старого.
24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.@param увеличиваемый массив
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
@return увеличенный массив, содержащий старые элементы.
Однако возвращаемый массив имеет тип Object[], который не совпадает с типом массива а.
*/
static Object[] badArrayGrow(Object[] a) {
int newLength =11 / 10 + 10;
Object[' newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0, a.length);
return newArray;
}
/**
Этот метод увеличивает размер массива, создавая новый массив того же типа и копируя туда старые элементы.
@param увеличиваемый массив. Может состоять из объектов или значений основных типов.
return увеличенный массив, состоящий из элементов массива а.
*/
static Object goodArrayGrow(Object a) {
Class cl =
if (!cl.isArray()] return null;
Class componentType =
int length = Array.getLength(a) ;
int newLength = length * 11 / 10 + 10;
Object newArray = Array.newlnstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, length); return newArray;
}
/**
Удобный метод вывода на печать всех элементов массива.
@param Распечатываемый массив. Может состоять из объектов
или значений основных типов.
*/
static void arrayPrint(Object a)
{
Class cl = a.getClass();
if (!cl.isArray()] return;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
System.out.print(componentType.getName(]
+ "[" + length + "] = { ") ;
for (int i = 0; i < Array.getLength (a); i++)
System.out.print(Array.get(a, i)+ " ") ;
System.out.printin("}”);
75}
}
java.lang.reflect.Array 1.1
int index)int index)public static Object get(Object array,
int index)
int index)
public static xxx getXxx(Object array, int index)
(Символы xxx обозначают основные типы boolean, byte, char, double, float, int, long, short.) Эти методы возвращают значение элемента заданного массива, имеющегоуказанный индекс.
public static void set(Object array, int index, Object newValue)
public static xxx setXxx(Object array, int index, xxx newValue)
(Символы xxx обозначают основные типы boolean, byte, char, double, float,int, long, short.) Эти методы записывают новое значение в ячей- кузаданного массива, имеющую указанный индекс.
public static int getLength(Object array) Возвращает длину заданного массива.
Object newlnstance(Class componentType, int length)
Object newlnstance(Class componentType, int [ ] lengths) Возвращает новый массив, состоящий из заданных компонентов и имеющий ука- заннуюразмерность.
Указатели на методы!
На первый взгляд в языке Java нет указателей на методы, т.е. способа передать одному методу адрес другого, чтобы второй затем вызвал первого. Разработчики языка Java заявили, что указатели на методы небезопасны и часто порождают ошибки, а потому интерфейсы (обсуждаемые в следующей главе) — это то, что нужно. Однако на самом деле в языке Java есть указатели на методы. Они являются побочным (возможно, случайным) продуктом механизма отражения.
Среди нестандартных расширений языка Java, которые компания Microsoft внесла в свой язык J++ (и в его наследник — язык С#), есть еще один тип указателей на методы, отличающийся от класса Method, обсуждаемого в этом разделе. Однако, как мы увидим в главе 6, внутренние классы — более полезный и универсальный механизм.
Чтобы понять, как работают указатели на методы, вспомните, что поле объекта можно проверить с помощью метода get класса Field. Аналогично класс Method содержит метод invoke, позволяющий вызывать метод, содержащийся в текущем объекте этого класса. Сигнатура метода invoke имеет следующий вид.
Object invoke(Object obj. Object[] args)
Первый параметр является неявным, а явный параметр представляет собой массив объектов класса Object. Если метод статический, первый параметр игнорируется— его можно приравнять к нулевой ссылке null. Если метод не имеет явных параметров, в качестве параметра args можно передать ссылку null или массив нулевой длины. Например, если объектт1 позволяет вызвать метод getName класса Employee, можно написать String n = (String)ml.invoke(harry, null);
Как это уже было с методами get и set класса Field, если параметр или возвращаемое значение относятся к основным типам и не являются объектами какого-либо класса, воз-
пикает проблема. Нужно сначала преобразовать все базовые типы в соответствующие типы интерфейсных классов, ауж затем вставлять элементы в массив args. И, наоборот, метод invoke возвращает объект интерфейсного класса, а не значение основного типа. Допустим, что объект т2 содержит метод raiseSalary класса Employee. Затем нам нужно поместить параметр типа double в объект интерфейсного класса Double.
Object{" args = {new Double(5.51 }; m2.invoke(harry, args);
Как получить объект класса Method? Можно, конечно, вызвать метод getDeclaredMethods и выполнить поиск среди возвращаемого массива объектов типа Method. Кроме того, можно вызвать метод getMethod, получающий на вход строку с указанием имени поля и возвращающий объект класса Field. Однако методов с одним и тем же именем может быть несколько, поэтому при их выборе следует проявлять осторожность. По этой причине нужно предусмотреть массив, содержащий правильные типы параметров. Посмотрим, например, как можно получить указатели на методы getName и raiseSalary класса Employee.
Method ml = Employee.class.getMethod("getName", null);
Method m2 = Employee.class.getMethod("raiseSalary",
new Class[j { double.class) );
Второй параметр метода getMethod представляет собой массив объектов класса Class. Поскольку метод raiseSalary имеет один параметр типа double, нужно предусмотреть массив, состоящий из одного элемента double.class. Обычно проще всего создать такой массив“налету”, как это сделано в предыдущем примере. Выражение new Class[] { double.class }
означает массив, состоящий из единственного объекта типа Class — объекта double.class.
Теперь, изучив синтаксис объектов класса Method, посмотрим, как они работают. В листинге 5.8 приведена программа вывода на печать таблицы значений математической функции, например Math. sqrt или Math. sin. Результат работы программы выглядит следующим образом.
public ssttaattiicc
nnaattiivvee
ddoouubbllee
java.lang.Math.sqrt(double
1.0000
!
1.0000
2.0000
1.4142
3.0000
1
1.7321
4.0000
2.0000
5.0000
2.2361
6.0000
1
2.4495
7.0000
1
2.6458
8.0000
1
2.8284
9.0000
3.0000
10.0000
1
3.1623
Код, осуществляющий вывод таблицы на экран, разумеется, не зависит от табулируемой функции.
double dx = (t0 - from) / (n - 1); for (double x = from; x <= to; x+= dx;
{
double у = f(x);
// Здесь f - табулируемая функция
// Реальную программу см. ниже
+" |"+y) ;
}
Мы хотим написать обобщенный метод printTable, позволяющий протабулиро- вать любую фунукцию. Для этого достаточно передать функцию в качестве параметра, имеющего тип Method.
static voidfrom, double to, int n, Method f)
Разумеется, параметр f — это объект, а не функция, поэтому мы не можем просто написать fВместо этого мы должны передать объект х в качестве массива парамет
ров (запакованного в интерфейсный класс Double), вызвать метод invoke и распаковать возвращаемое значение.
args = {new};
Double d =args);
double у =
Первый параметр метода invoke является нулевой ссылкой, поскольку мы вызываем статический метод.
Рассмотрим пример вызова метода printTable, использованного для табуляции квадратного корня.
10
java.lang.Math.class.getMethod("sqrt", new{})) ;
Самое трудное — получить доступ к методу объекта. Сейчас мы имеет доступ к методу пакета j ava .Math с именем sqrt, список параметров которого состоит только
из одного типа — double.
Полный код методаприведен в листинге 5.8.
import
import java.text. *;
public class MethodPointerTest
{
public static voidargs) throws Exception
{
// Получаем указатели на методы square и sqrt.
Method square =
MethodPointerTest.class.getMethod("square",
new});
Method sqrt =
new{});
// Выводим таблицу значений х и у.
10, 10, square);
10, 10, sqrt);
}
/**
Возвращает квадрат числа.
x Число
24.25.26.27.34.37.47.50.51.52.72.7576.76.77.78.24.25.26.27.34.37.47.50.51.52.72.7576.76.77.78.@return x квадрат числа
24.
25.
26.
27.
34.
37.
47.
50.
51.
52.
72.
75
76.
76.
77.
78.
24.
25.
26.
27.
34.
37.
47.
50.
51.
52.
72.
75
76.
76.
77.
78.
*/
public static double square(double x) {
return x * x;
}
/**
Выводит таблицу значений х и у метода.
@param from нижняя граница значений х
@param toверхняя граница значений х
@param n количество строк в таблице
@param f метод, получающий и возвращающий значения типа double
return value
*/
public static void printTable(double from, double to, int n, Method f)
{
// Вывод заголовка таблицы.
System.out.printin(f; ;
// Форматирование вывода (точность: 4 знака)
NumberFormat formatter =
NumberFormat.getNumberInstance() ;
formatter.setMinimumFractionDigits(4);
formatter.setMaximumFractionDigits(4);
double dx = (to - from) / (n - 1) ;
for (double x = from; xto; x += dx)
{
// Вывод значения х.
String output = formatter.format(x; ;
// Расстановка полей шириной 10 знаков.
for (int i = 10 - output.length(); i > 0; i--’
System.out.print(1 ');
System.out.print(output+ " |");
try
{
// Вызов метода и вывод значения у.
Object[] args = { new Double(x] };
Double d = (Double)f.invoke(null, args);
double у = d.doubleValue ();
output = formatter.format(y);
// Расстановка полей шириной 10 знаков.
for (int i = 10 - output.length(); i > 0;
System.out.print(1 ');
System.out.printin(output);
}
catch (Exception e) { e.printStackTrace(); }
}
}
}
Таким образом, с объектами класса Method можно делать то же, что и с указателями на функции в языке C++. Как и в языке C++, такой стиль программирования обычно неудобен и часто приводит к ошибкам. Что случится, если мы вызовем метод с неправильными параметрами? Метод invoke возбудит исключительную сиутацию.
Кроме того, и параметры, и возвращаемое значение метода invoke обязательно должны иметь тип Object. Это значит, что нам понадобится делать обратное приведение типов. В результате компилятор не станет проверять программу. Следовательно, ошибки всплывут только при ее тестировании, когда исправить их будет значительно сложнее. Более того, программа, использующая механизм отражения для получения указателей на методы, работает гораздо медленнее, чем программа, непосредственно вызывающая эти методы.
По этой причине мы рекомендуем использовать объекты Method только в случае крайней необходимости. Намного лучше применять интерфейсы и внутренние классы (тема следующей главы). В частности, вслед за разработчиками языка Java мы повторим, что использовать объекты класса Method для обратного вызова функций не следует. Для этой цели хорошо подходят интерфейсы (см. следующую главу), позволяющие создавать программы, которые работают намного быстрее и надежнее.
java.lang.reflect.Method 1.1
^^■i, public Object invoke(Object implicitParameter, Object[] explicit(parameters)
Вызывает метод, описанный в данном объекте, передавая ему заданные параметры и возвращая значение, вычисленное этим методом. Для статических методов в качестве неявного параметра передается ссылка null. Элементарные типы следует передавать только внутри интерфейсных классов. Значения элементарных типов должны возвращаться распакованными.
Как применять наследование
В заключение дадим несколько советов по правильному применению очень полезного механизма наследования.
Размещайте общие операции и поля в суперклассе.
Вот почему мы поместили поле name в класс Person и не стали повторять его в классах Employee и Student.
Heиспользуйте защищенные поля.
Некоторые программисты полагают, что следует "на всякий случай" объявлять большинство полей защищенными, чтобы при необходимости подклассы могли к ним обратиться. Однако есть две причины, по которым этот механизм не гарантирует достаточной защиты. Во-первых, множество подклассов неограниченно — любой программист может создать подкласс вашего класса и написать программу, получающую непосредственный доступ к защищенным полям экземпляра, нарушая инкапсуляцию. Во-вторых, в языке Java к защищенным
полям имеют доступ все классы, размещенные в том же пакете, независимо от того, являются они подклассами данного класса или нет.
Однако специализированные методы, которые переопределяются в подклассах, защищенными объявлять полезно. Ярким примером служит метод clone ().
Используйте наследование для моделирования отношения "является ".
Наследование позволяет экономить силы, поэтому некоторые программисты им злоупотребляют. Допустим, нам нужен класс Contractor. Подрядчик имеет имя и дату найма, однако у него нет оклада. Вместо этого его работа оплачивается почасово, причем он работает не настолько долго, чтобы возникла необходимость повышать оплату его труда. Ниже приведен шаблон, по которому класс Contractor можно вывести из класса Employee, добавив поле hourlyWage.
class Contractor extends Employee {
private double hourlyWage;
}
Эта идея не слишком удачна, поскольку при этом получается, что каждый подрядчик имеет и оклад, и почасовую оплату. Если вы попробуете реализовать методы для распечатки чеков или ведомостей, этот факт повергнет вас в бесконечное уныние. Программа, которую вам придется написать, будет гораздо длиннее той, которую вы могли бы использовать, не прибегая к наследованию.
Отношение "подрядчик/сотрудник" не удовлетворяет отношению "является". Подрядчики вовсе не являются разновидностью сотрудников.
Не используйте наследование, если не все наследуемые методы имеют смысл. Допустим, что нам нужно написать класс Holiday. Разумеется, праздники — это разновидность календарных дней, а дни можно представить в виде объектов класса GregorianCalendar, поэтомуможноприменятьнаследование.
class Holiday extends GregorianCalendar()
К сожалению, множество праздников при наследовании не закрыто. Среди открытых методов класса GregorianCalendar есть метод add, который может превратить праздники в будни.
Holiday Christmas;
cristmas.add(GregorianCalendar.DAY, 12);
Итак, наследование в этом примере не подходит.
Используйте полиморфизм, а не информацию о типе.
Вспомните о полиморфизме, как только увидите код, имеющий следующий вид.
if (x имеет тип 1)
;
else if (x имеет тип 2)
;
Носят ли действие!и действие2 общий характер? Если да, поместите соответствующие методы в общий суперкласс или интерфейс обоих типов. Тогда можно просто вызвать метод
x. действие I;
и с помощью механизма динамического связывания, присущего полиморфизму, выполнить правильное действие.
Код, использующий полиморфизм или реализацию интерфейса, намного легче поддерживать и расширять, чем код, использующий многочисленные проверки типов.
Не злоупотребляйте механизмом отражения.
Механизм отражения позволяет создавать программы поразительного уровня абстракции, определяющие поля и методы в ходе выполнения программы. Эта возможность чрезвычайно полезна для системного программирования, но для прикладного — практически не нужна. Отражение — очень хрупкий механизм, поскольку компилятор не может помочь программисту обнаружить ошибки. Все ошибки всплывают во время выполнения программы и возбуждают исключительные ситуации.
Интерфейсы и внутренние классы
В этой главе...
Интерфейсы
Клонирование объектов Внутренние классы
Заместители
Т.еперь вы знаете все основные инструменты, предназначенные для объектноориентированного программирования на языке Java. В этой главе мы рассмотрим еще два широко распространенных способа программирования. Несмотря на то что эти способы далеко не очевидны, владеть ими должен каждый профессиональный программист.
Первый из них называется программированием интерфейсов (interfaces). Этот способ позволяет указывать, что должны делать классы, не описывая, как они это должны это делать. Классы могутреализовывать один или несколько интерфейсов. Если возникает необходимость использовать интерфейс, применяются объекты этих классов. Изучив интерфейсы, мы рассмотрим клонирование объектов (которое иногда называется глубоким копированием). Клоном объекта называется новый объект, имеющий точно такое же состояние, но другую индивидуальность. В заключение мы перейдем к механизму, основанному на применении внутренних классов (inner classes). С технической точки зрения внутренние классы довольно сложны — они определяются внутри других классов, а их методы имеют доступ к полям окружающего класса. Внутренние классы полезны при разработке совокупности взаимосвязанных классов. В частности, они позволяют создавать лаконичные, высокопрофессиональные коды для реализации событийно-ориентированного графического пользовательского интерфейса.
Глава завершается обсуждением заместителей (proxies), объектов, реализующих произвольные интерфейсы. Заместители представляют собой весьма специализированную конструкцию, полезную для создания инструментов системного программирования. Новичок этот раздел может спокойно пропустить.
Интерфейсы
Интерфейс в языке Java не является классом. Он представляет собой множество требований (requirements), предъявляемых к соответствующему классу.
Обычно программист, отвечающий за решение какой-либо задачи, заявляет: "Если ваш класс будет соответствовать определенному интерфейсу, я смогу выполнить свою работу". Рассмотрим конкретный пример. Метод sort класса Array позволяет упорядочивать массив объектов при одном условии: объекты должны принадлежать классам, реализующим интерфейс Comparable.
Вот как выглядит этот интерфейс.
public interface
{ compareTo (Object other);
:
Это значит, что любой класс, реализующий интерфейс Comparable, должен иметь метод compareTo, получающий параметри возвращающий целое число.
Все методы интерфейса автоматически объявляются открытыми, поэтому, объявляя метод в интерфейсе, не обязательно указывать модификатор public.
Разумеется, существует и неявное требование: при вызове compareTo () метод compareTo должен на самом деле уметь сравнивать два объекта и возвращать признак того, что один из них больше другого. Предполагается, что метод возвращает отрицательное значение, если объект х меньше объекта у, нуль — если они равны, и положительное число — в противном случае.
Интерфейсы могут иметь один или несколько методов. Как мы увидим далее, интерфейсы могут объявлять константы. Однако еще важнее то, что интерфейсы не могут реализовываться в виде объектов. В интерфейсах нет ни полей, ни реализаций методов, которые являются прерогативой классов, реализующих соответствующие интерфейсы. Таким образом, может показаться, что интерфейс похож на абстрактный класс, лишенный каких-либо полей. Однако между двумя этими понятиями есть существенное различие — позднее мы подробно его проанализируем.
Теперь допустим, что нам требуется реализовать метод sort класса Array для сортировки объектов класса Employee. В этом случае класс Employee должен
Comparable.
Для того чтобы класс реализовал интерфейс, нужно выполнить два действия.
Объявить, что класс предназначен для реализации интерфейса.
Поместить в класс определения всех методов,-указанных в интерфейсе.
Чтобы объявить, что класс реализует интерфейс, используется ключевое слово implements:
class Employee implements Comparable
Разумеется, теперь нужно реализовать метод compareTo. Например, сравним зарплату сотрудников. Тогда метод toCompare будет возвращать -1 , если зарплата первого сотрудника меньше зарплаты второго, 0, если они равны, и 1 в противном случае.
public int compareTo(Object otherObject) {
Employee other = (Emloyee; otherObject;
if (salary < other.salary; return -1 ;
if (salary > other.salary; return 1;
return 0;
}
В объявлении интерфейса метод toCompare не объявлен открытым, поскольку все методы интерфейса автоматически являются открытыми. Однако при реализации интерфейса метод должен быть объявлен с помощью модификатора public. В противном случае компилятор (по умолчанию для класса) предположит, что область видимости этого метода ограничивается пакетом. В этом случае компилятор выдаст предупреждение, что вы пытаетесь понизить уровень доступа.
Метод toCompare интерфейса Comparatible возвращает целое число. Если объекты не равны между собой, знак возвращаемого числа не имеет значения. Такая гибкость может оказаться полезной при сравнении целочисленных полей. Допустим, что каждый сотрудник имеет уникальный номер id, по которому мы хотим их упорядочить. Тогда можно просто вернуть число id - other.id. Это значение будет отрицательным, если номер первого сотрудника меньше номера второго, 0, если они равны, и некоторое положительное число в противном случае. Учтите только, что диапазон изменения целых чисел должен быть достаточно малым, чтобы вычитание не привело к переполнению. Если известно, что идентификационный номер лежит в пределах от 0 до (Integer.Max_Value -1) / 2, то ничего плохого не случится.
Разумеется, этот прием с вычитанием не работает для чисел с плавающей точкой. Разность salary - other.salary может округляться до 0, если зарплаты очень близки, но не идентичны.
Теперь ясно, что нужно сделать, чтобы класс смог сортировать объекты, — реализовать в нем метод toCompare. Это очень резонно. Должен существовать какой-то способ использовать метод sort для сравнения объектов. Однако почему нельзя в классе Employee просто предусмотреть метод toCompare, не реализуя интерфейс Comparable? Причина заключается в том, что язык Java является строго типизированным (strongly typed). Вызывая некий метод, компилятор должен убедиться, что этот метод действительно существует. Где-нибудь внутри метода sort могут находиться операторы вроде следующих.
if (a[).compareTo(a[j)) > 0) {
// Поменять местами объекты a[i] и a[j].
}
Компилятор дожен знать, что объект a[i] действительно имеет метод compareTo. Если переменная а представляет собой массив объектов, реализующих интерфейс Comparable, то существование такого метода гарантируется, поскольку каждый класс, реализующий этот интерфейс, по определению должен содержать этот метод.
Может показаться, что метод sort класса Array работает только с массивами типа Comparable[], и компилятор выдаст предупреждение, как только обнаружит вызов метода sort для массива, элементы которого не реализуют интерфейс Comparable. Увы, это не так. Вместо этого метод sort получает на вход массивы типа Object[] и применяет неуклюжее приведение типов.
// Фрагмент кода из стандартной библиотеки — не рекомендуется.
if (((Comparable)a(i)).compareTot(Comparable)a(ij > 0) {
// Поменять местами объекты a[i] и a[j].
}
Если объект а [ i ] не принадлежит классу, реализующему интерфейс Comparable, виртуальная машина возбуждает исключительную ситуацию. (Заметим, что второе приведение типа Comparable совершенно излишне, поскольку явный параметр метода compareTo имееттип Object, а не Comparable.)
В листинге 6.1 приведена полная программа сортировки массива, состоящего из объектов класса Employee.
import java .util.*;
public class EmployeeSortTest
{
public static void main(String[ ] args)
{
Employee[ ] staff = new Employee[3];
staff[0=newEmployee("Гарри Хакер",35000);
staff[1=newEmployee("Карл Крекер",75000);
staff[2]=newEmployee("Тони Тестер",38000);
Arrays.sort(staff);
// Выводит на экран информацию обо всех сотрудниках.
for (int i = 0; i < staff.length; i++)
{
Employee e = staff(i1;
System.out.printin("имя=” + e.getNameO
+ ",зарплата=" + e.getSalary());
}
}
}
class Employee implements Comparable
{
public Employee(String n, double s)
{
name = n;
salary = s;
}
public String getName(;
{
35.36.39.40.41.44.47.48.49.52.53.54.57.58.59.64.65.35.36.39.40.41.44.47.48.49.52.53.54.57.58.59.64.65.return name; }return name; }public double getSalary(] } return salary;
35.
36.
39.
40.
41.
44.
47.
48.
49.
52.
53.
54.
57.
58.
59.
64.
65.
35.
36.
39.
40.
41.
44.
47.
48.
49.
52.
53.
54.
57.
58.
59.
64.
65.
return name; }
return name; }
{public void raiseSalary(double byPercent) double raise = salary * byPercent / 100; } salary += raise;
/**
Сравнивает зарплату сотрудников.
@param otherObject другой объект класса Employee §return отрицательное число, если зарплата данного сотрудника меньше, чем зарплата сотрудника, представленного объектом otherObject; 0, если их зарплаты равны, и положительное число в противном случае.
*/
public int compareTo(Object otherObject)
{
(Employee)otherObject;(Employee)otherObject;Employee otherEmployee otherif (salary < other.salary) return -1; if (salary > other.salary) return 1; return 0;
(Employee)otherObject;
(Employee)otherObject;
Employee other
Employee other
}
private String name;
private double salary;
}
java.lang.Comparable 1.0java.lang.Comparable 1.0int compareTo(Object otherObject)
java.lang.Comparable 1.0
java.lang.Comparable 1.0
Сравнивает текущий объект с объектом otherObject и возвращает отрицательное целое число, если текущий объект меньше, чем объект otherObject, нуль, если они равны между собой, и положительное число в противном случае.
В языке Java есть стандартное требование, которое звучит следующим образом: "Автор реализации метода должен гарантировать, что для всех объектов х и у выполняется условие sgn(x. compareTo (у)) = -sgn(y. compareTo(x)). (Следовательно, если вызов y.compareTo(x) возбуждает исключительную ситуацию, то и вызов x.compareTo (у) также должен возбуждать ее.)" Здесь sgn означает функцию "сигнум", т.е. знак числа: sgn(n) равна — 1,если число п отрицательно, 0, если число п равно нулю, и 1. если число п положительно. В переводе на человеческий язык это означает, что если поменять параметры метода compareTo местами, знак возвращаемого числа (но не обязательно его фактическое значение) также должен измениться на противоположный. Это не проблема, но упоминание исключительных ситуаций усложняет дело. Допустим, класс Manager имеет свой собственный метод для сравнения двух менеджеров. Он может начинаться следующим образом.
public int compareTo(Object otherObject) {
Manager other = (Manager)otherObject;
}
Это нарушает правило "антисимметричности". Если объект х относится к классу Employee, ay —к классу Manager, то вызов х. compareTo (у) не возбудит исключительной ситуации — он просто сравнивает объекты х и у как объекты класса Employee. Однако обратный вызов у .compareTo (х) возбудит исключительную ситуацию ClassCastException.
Аналогичная ситуация возникает при программировании метода equals. Однако в этом случае мы просто проверяем, идентичны ли два класса, и, если нет, возвращаем значение false. Однако, если объекты х и у относятся к одному и тому же классу, непонятно, что должен возвращать вызов X . compareTo (у) — положительное или отрицательное число? Возможно, менеджеры вообще считают себя выше любого рядового сотрудника, независимо от их зарплаты. Но тогда нужно явно реализовать эту проверку.
Если вы не хотите оставить решение этой задачи на совести разработчика подклассов вашего класса, можно объявить метод compareTo терминальным (final).Тогда проблема решится сама собой, поскольку ни в одном подклассе невозможно будет реализовать другую версию этого метода. И наоборот, если вы реализуете метод compareTo в подклассе, нужно реализовать полную проверку.
if (otherObject instanceof Manager) {
Manager other = (Manager)otherObject;
} else if (otherObject ionstanceof Employee) {
return 1; // Менеджеры всегда лучше :-( } else
return -((Comparable)otherObject).compareTo(this);
java.util.Array 1.2
static void sort (Object[] a)
Сортирует элементы массива а, используя улучшенный алгоритм сортировки слиянием. Все элементы массива должны принадлежать классу, реализующему интерфейс Comparable, и быть сравнимыми друг с другом.
Свойства интерфейсов
Интерфейсы — это не классы. В частности, с помощью оператора new невозможно создать экземпляр интерфейса.
х = new Comparable(...); // Неправильно.
Однако, несмотря на это, можно объявлять интерфейсные переменные.
Comparable x; // Правильно.
Интерфейсные переменные должны ссылаться на объект класса, реализующего данный интерфейс.
х = new Employee (); // Правильно, если класс Employee
// реализует интерфейс Comparable.
Как известно, оператор instanceof проверяет, принадлежит ли объект заданному классу. Однако с помощью этого оператора можно также проверять, реализует ли объект заданный интерфейс.
if (anObject instanceof Comparable) { ... }
Аналогично классам интерфейсы также могут образовывать иерархию наследования. Это позволяет создавать цепочки интерфейсов в направлении от более абстрактных к более специализированным. Допустим, что у нас есть интерфейс Moveable.
public interface Moveable
{
void move(double x, double y) ;
}
Затем представьте себе интерфейс Powered, расширяющий интерфейс Moveable. public interface Powered extends Moveable {
double milesPerGallon();
}
Хотя в интерфейсе может не быть ни полей, ни статических методов, в нем можно объявлять константы.
public interface Powered extends Moveable {
double milesPerGallon();
// Открытая статическая терминальная константа.
double SPEED_LIMIT = 95;
}
Напомним, что все методы в интерфейсе автоматически объявляются открытыми. Аналогично константы в интерфейсе всегда являются открытыми статическими и терминальными.
В некоторых интерфейсах объявляются только константы и ни одного метода. Например, в стандартной библиотеке содержится интерфейс SwingConstants, определяющий константы NORTH, SOUTH, HORIZONTAL и т.д. Любой класс, реализующий интерфейс SwingContants, автоматически наследует эти константы. Его методы могут непосредственно ссылаться на константы NORTH, не используя громоздкое обозначение SwingConstants.NORTH.
В языке Java любой класс может иметь только один суперкласс, но при этом любой класс может реализовывать несколько интерфейсов. Это позволяет максимально гибко определять поведение класса. Например, в языке Java есть очень важный интерфейс Cloneable. (Этот интерфейс детально изучается в следующем разделе.) Если некий класс реализует интерфейс Cloneable, то для создания точных копий его объектов можно применять метод clone класса Object. Допустим далее, что мы хотим не
только создавать клоны объектов нашего класса, но и сравнивать их между собой. Тогда нужно просто реализовать в классе оба интерфейса Clonable и Comparable. class Employee emplements Cloneable, Comparable
Для разделения имен интерфейсов, задающих свойства классов, используется запятая.
Интерфейсы и абстрактные классы
Если вы помните содержание раздела, посвященного абстрактным классам из главы 5, у вас, возможно, возник вопрос, зачем разработчики языка Java создали концепцию интерфейсов. Почему бы интерфейс Comparable не сделать абстрактным классом? abstract class Comparable // Почему бы и нет? (
public abstract int compareTo(Object other);
В этом случае класс Employee мог бы просто расширять абстрактный класс и предусматривать свой собственный метод toCompare.
class Employee extends Comparable // Почему бы и нет?
(
public int compareTo(Object other) { ... } }
К сожалению, это породило бы массу проблем, связанных с использованием абстрактного базового класса для выражения обобщенного свойства. У класса может быть только один родитель. Допустим, что класс Employee уже расширил какой-то другой класс, скажем, Person. Значит, другого родителя у него быть не может.
class Employee extends Person, Comparable // Неправильно.
В то же время каждый класс может реализовывать сколько угодно интерфейсов. class Employee extends Person implements Conparable // Правильно.
В других языках программирования, в частности в языке C++, классы могут иметь несколько суперклассов. Это свойство называется множественным наследованием (multiple inheritance). Разработчики языка Java решили не поддерживать множественное наследование, поскольку оно делает язык либо слишком сложным (как язык C++), либо менее эффективным (какязык Eiffel).
В то же время интерфейсы предоставляют большинство возможностей множественного наследования, не усложняя язык и не снижая эффективность.
В языке C++ есть множественное наследование. Это вызывает много сложностей, связанных с виртуальными базовыми классами, правилами доминиро- к.вания и обратным приведением типов указателей. Множественное наследо
вание используют лишь немногие программисты на языке C++. Некоторые никогда его не используют, остальные рекомендуют использовать множественное наследование только в “смешанном" виде. Это значит, что первичный базовый класс описывает родительский объект, а дополнительные базовые классы (так называемые миксеры (mix-ins)) описывают вспомогательные свойства. Этот стиль напоминает классы из языка Java, имеющие только один базовый класс и дополнительные интерфейсы. Однако в языке C++ миксеры могут добавлять некоторые свойства по умолчанию, в то время как интерфейсы в языке Java этого делать не могут.
Компания Microsoft долгое время была сторонником применения интерфейсов вместо множественного наследования. Фактически интерфейсы, предложенные разработчиками языка Java, эквивалентны интерфейсам, используемым в COM-технологии, разработанной компанией Microsoft. В результате этого невероятного совпадения взглядов СОМ-объекты (например, компоненты управления ActiveX) легко можно создавать с помощью инструментальных средств языка Java. Это сделано (причем очень ясно и понятно) в языке J++ фирмы Microsoft. Кроме того, этот подход положен в основу инструментальных средств JavaBeans-to-ActiveX компании Sun.
Интерфейсы и обратные вызовы
Обратный вызов (callback) широко распространен в программировании. При обратном вызове программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. Например, можно задать действие, которое должно быть выполнено, если будет нажата конкретная кнопка или выбран определенный пункт меню. Однако, поскольку вы еще не умеете создавать пользовательский интерфейс, мы рассмотрим похожую, но более простую ситуацию.
Класс j avax.swing содержит класс Timer, который можно использовать для отсчета интервалов времени. Например, если в программе предусмотрены часы, то с помощью класса Timer можно отсчитывать каждую секунду и обновлять циферблат часов.
Устанавливая таймер, мы задаем интервал времени и указываем, что должно произойти по его истечении. Как указать таймеру, что он должен делать? Во многих языках программирования задается имя функции, которую таймер должен периодически вызывать. Однако классы из стандартной библиотеки языка Java используют объектно-ориентированный подход. Программист должен передать таймеру объект некоторого класса. После этого таймер вызывает один из методов данного объекта. Передача объекта — более гибкий механизм, чем вызов функции, поскольку объект может нести с собой дополнительную информацию.
Разумеется, таймер должен знать, какой метод он должен вызвать. Для этого таймеру нужно указать объект класса, реализующего интерфейс ActionListener из пакета j ava . awt. event. Вот как выглядит этот интерфейс.
public interface ActionListener
void actionPerformed(ActionEvent event);
}
По истечении заданного интервала времени таймер вызывает метод actionPerformed.
Как указано в главе 5, в языке Java есть аналоги указателей на функции ^^^^^^e объекты класса Methods. Однако их трудно использовать, они медленнее работают и не могут проверять типы во время выполнения программы. Если вы хотите использовать указатель на функцию в языке C++, рассмотрите возможность применения интерфейса из языка Java.
Допустим, нам нужно каждые десять секунд выводить на экран сообщение "Текущее время: ...", сопровождаемое звуковым сигналом. Для этого необходимо определить класс, реализующий интерфейс ActionListener. Затем поместите операторы, которые нужно выполнить, внутрь метода ас tionPerformed.
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event)
{
Date now = new Date ();
System.out.printin.("Текущее время : " + now);
Toolkit.getDefaultToolkit(),beep();
} }
Обратите внимание на параметр ActionEvent метода actionPerformed. Он содержит информацию о событии, например, об объекте, его породившем, — более подробно этот вопрос рассматривается в главе 8. Однако детальная информация о событии в данной программе не важна, поэтому можно просто проигнорировать этот параметр.
Затем следует создать объект данного класса и передать его конструктору класса Timer. ActionListener listener = new TimePrinter();
Timer t = new Timer(10000, listener);
Первый параметр конструктора Timer представляет собой интервал времени между точками отсчета, измеренный в миллисекундах. Сообщение должно выдаваться на экран каждые десять секунд. Второй параметр является объектом класса ActionListener.
Итак, запустим таймер.
t.start ()
Каждые десять секунд на экране будет появляться следующее сообщение, сопровождаемое звуковым сигналом.
Текущее время: Thu Apr 13 23:29:08 PDT 2000
В листинге 6.2 приведена программа, реализующая описанный алгоритм. После запуска таймера программа выводит на экран окно сообщения И ждет, пока пользователь не щелкнет на кнопке "Ok" , при этом на экран каждые десять секунд выводится текущее время.
Будьте терпеливы. После того как диалоговое окно с вопросом "Выйти из программы?" исчезнет с экрана, первое сообщение таймера появится только через десять секунд.
Заметим, что в дополнение к пакетам javax. swing. * и java . util. * программа импортирует класс javax. swing. Timer по имени. Это позволяет избежать неоднозначности, которая могла бы возникнуть между пакетами javax.swing.Timer и j ava . util.Timer, которые к нашей задаче отношения не имеют.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.♦;
import javax.swing.Timer;
// Чтобы разрешить конфликт с классом java.util.Timer.
7.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
public class TimerTest
public static void main(String[] args)
{
ActionListener listener = new TimePrinter();
// Создает таймер, вызывающий блок прослушивания // каждые 10 секунд.
Timer t = new Timer(10000, listener);
t. start();
JOptionPane.showMessageDialog(null, "Выход?");
System.exit(0);
}
}
class TimePrinter implements ActionListener
(
public void actionPerformed(ActionEvent event)
(
Date now = new Date ();
System.out.printin("Текущее время ... " + now);
Toolkit.getDefaultToolkit().beep();
javax.swing.Timer
Timer(int interval,
javax.swing.JOptionPane 1.2
static void showMessageDialog(Component parent,Object
message)
Отображает на экран окно диалога со строкой сообщения и кнопкой "Ok". Окно диалога выравнивается по центру компонента parent. Если ссылка parent является нулевой, окно диалога выравнивается по центру экрана.
1.2
ActionListener listener)
Создает таймер, передающий сообщение объекту listener по истечении интервала времени, продолжительность которого задается параметром interval.
void start(J
Запускает таймер, который затем вызывает метод actionPerformed, передавая ему объект listener.
void stop ()
Останавливает таймер. После остановки таймер больше не вызывает метод actionPerformed.
java.awt.Toolkit 1.0
static Toolkit getDefaultToolkit ()
Подключает инструментальный набор, используемый по умолчанию. Этот набор содержит информацию о среде пользовательского графического интерфейса.
void beep()
Включает звуковой сигнал.
Клонирование объектов
После создания копии переменной и оригинал и копия представляют собой ссылки на один и тот же объект (рис. 6.1). Это значит, что изменение одной переменной повлечет изменение другой.
Копирование
оригинал= [оригинал= [копия=копия=name = |"Джон Паблик"name = |"Джон Паблик"salary = |50000.0 ~|salary = |50000.0 ~|EmployeeEmployeeКлонированиеКлонированиеРис. 6, LКопирование и клонированиеРис. 6, LКопирование и клонирование
оригинал= [
оригинал= [
копия=
копия=
name = |"Джон Паблик"
name = |"Джон Паблик"
salary = |50000.0 ~|
salary = |50000.0 ~|
Employee
Employee
Клонирование
Клонирование
Рис. 6, LКопирование и клонирование
Рис. 6, LКопирование и клонирование
Employee original = new Employee("Джон Паблик", 50000);
Employee copy = original;
copy.raiseSalary(10); // Ой: Оригинал тоже изменился!
Если нужно, чтобы переменная copy представляла собой новый объект, в первый момент своего существования идентичный объекту original, но совершенно независимый от него, используйте метод clone ().
Employee copy = (Employee)original.clone();
// Приведение типов необходимо —
// метод clone возвращает объекты класса Object.
copy.raiseSalary(10); // OK! Оригинал не изменился!
Однако не все так просто. Метод clone является защищенным (protected) методом класса Object, т.е. его нельзя вызвать непосредственно. Только класс Employee может клонировать объекты своего класса. У этого ограничения есть существенная причина. Подумайте о способе, которым класс Object может реализовать метод clone. Ему вообще ничего не известно об объекте, поэтому он может копировать лишь поля. Если все поля класса являются числовыми или имеют другой основной тип, их копирование выполняется прекрасно. Однако если объект содержит ссылку на подобъект, то и оригинал, и клонированные объекты будут совместно использовать одну и ту же информацию.
Чтобы проиллюстрировать это явление, рассмотрим класс Employee, в котором есть еще одно поле payDay. На рис. 6.2 показано, что происходит, когда метод clone класса Objectприменяется к объекту класса Employee. Как видим, операция клонирования по умолчанию является "поверхностной" — она не клонирует объекты, на которые есть ссылки в других объектах.
Рис. 6.2. Поверхностная копияРис. 6.2. Поверхностная копияоригинал=оригинал=копиямкопиям
Рис. 6.2. Поверхностная копия
Рис. 6.2. Поверхностная копия
оригинал=
оригинал=
копиям
копиям
Хорошо ли это? Зависит от ситуации. Если подобъект, используемый совместно как оригиналом, так и поверхностным клоном, является неизменяемым (immutable), это вполне безопасно. Такое происходит в двух ситуациях: 1) подобъект может принадлежать неизменяемому классу, например, классу String; 2) подобъект может просто оставаться постоянным на протяжении всей жизни содержащего его объекта, не подвергаясь воздействию модифицирующих методов либо методов, вычисляющих ссылку на него.
Достаточно часто, однако, подобъекты являются изменяемыми, поэтому приходится переопределять метод clone, чтобы выполнить глубокое копирование (deep copying), которое позволяет клонировать подобъекты наряду с содержащими их объектами. В нашем примере поле payDay относится к изменяемому классу Gregorian- Calendar.
Для каждого класса нужно ответить на следующие вопросы.
Достаточно ли метода clone по умолчанию?
Можно ли исправить метод clone по умолчанию, применяя его к изменяемым объектам?
Следует ли применять метод clone?
Третий пункт выбирается по умолчанию. Для того чтобы программист мог выбрать первый или второй путь, класс должен выполнить следующее.
Реализовать интерфейс Cloneable.
Переопределить метод clone с модификатором доступа public.
Метод clone в классе Objectобъявлен защищенным, поэтому его нельзя просто вызвать с помощью выражения anObject.clone(). Однако разве не доступны защищенные методы для любого подкласса и не является ли каждый класс подклассом класса Object? К счастью, правила защищенного доступа менее строги (см. главу 5). Подкласс может вызвать защищенный метод clone только для клонирования своих собственных объектов. Чтобы клонировать другие объекты, метод clone следует переопределить и сделать открытым.
В данном случае интерфейс Cloneable используется как обычно. В частности, он не уточняет метод clone — этот метод наследуется от класса Object. Интерфейс служит просто в качестве дескриптора, указывающего на то, что в данном случае разработчик класса понимает, как выполняется процесс клонирования. Объекты настолько настороженно относятся к клонированию, что возбуждают проверяемую исключительную ситуацию, если объект требует выполнения клонирования, но не реализует интерфейс Cloneable.
Интерфейс Cloneable — один из немногих помеченных интерфейсов (tagged interfaces) в языке Java. Напомним, что такие интерфейсы, как Comparable, должны гарантировать, что некий класс реализует конкретный метод или набор методов. Помеченные интерфейсы не имеют методов; их единственное предназначение — позволить использовать оператор instanceof для проверки типа.
if (obj instanceof Cloneable) . . .
Даже если применение метода clone по умолчанию (поверхностное копирование) вполне допустимо, в любом случае нужно реализовать интерфейс Cloneable, переопределить метод clone, сделав его открытым, вызвать метод super . clone () и перехватить исключительную ситуацию CloneNotSupportedException. Рассмотрим пример.
class Person implements Cloneable (
public Object// Повышает уровень видимости.
{ try { return
}
catch (CloneNotSupportedException e) { return null; } // Этого не должно случиться, // поскольку класс реализует интерфейс Clonable.
)
Программисты, которые будут использовать ваш метод clone, должны будут по- прежнему выполнять приведение типа. Метод clone всегда возвращает объект класса
Метод clone класса Object угрожает возбудить исключительную ситуацию CloneNotSupportedException — это произойдет, если метод clone будет вызван из объекта, класс которого не реализует интерфейс Cloneable. Разумеется, класс Person является клонируемым, поэтому исключительная ситуация возбуждаться не будет. Однако компилятор об этом не знает. Следовательно, нужно по-прежнему перехватывать исключительную ситуацию и возвращать фиктивное значение.
Метод clone не придает новых функциональных возможностей методу
clone, создающему поверхностную копию. Он просто делает этот метод открытым. Для глубокого копирования нужно приложить намного больше усилий и клонировать изменяемые поля экземпляра.
Вот пример метода clone, выполняющего глубокое копирование. class Employee implements Cloneable {
public Object
•{
try { // Вызов метода Employee cloned =
// Клонирование изменяемых полей. =
return cloned;
}
catch (CloneNotSupportedException e) { return null; }
}
Очевидно, что клонирование — дело рискованное, поэтому вполне резонно метод clone сделан защищенным. (В главе 12 описан элегантный метод клонирования объектов с помощью сериализации.)
Определяя открытый метод clone, вы снижаете уровень безопасности программы. Этот метод может быть унаследован каким-нибудь подклассом, независимо от того, разумно это или нет. Например, определив метод clone для класса Employee, можно клонировать объекты класса Manager. Может ли метод clone класса Employee справиться с этим заданием? Это зависит от полей класса Manager. В нашем случае никаких проблем не возникает, поскольку поле bonus имеет основной тип. Однако в общем случае нужно проверить, как работают методы clone в каждом классе, который расширяет ваш класс.
Программа, приведенная в листинге 6.3, клонирует объекты класса Employee, затем вызывает два модифицирующих метода. Метод raiseSalary изменяет значение поля salary, а метод addPayDay— состояние поля payDay. Ни один из модифицирующих объектов не действует на исходный объект, поскольку метод clone переопределен и выполняет глубокое копирование.
-—~ ~ г~~-~~ТТ^Тд^ГТТТ"1
Листинг 6.3. Программа CloneTest.Java £2$
- import java.util.*;
public class CloneTest
{
public static void main(String[ args)
{
Employee original = new Employee("Джон Паблик", 50000);
original.setPayDay(2000, 1, 1);
Employee copy = (Employee) original. clone (');
copy.raiseSalary(10);
copy.setHireDay(2002, 12, 31);
System.out.printIn("Оригинал=" + original);
System.out.printIn("Копия=" + copy);
}
/**
Класс Employee.
*/
class Employee implements Cloneable
20.{
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Object clone()
{
try
{
// Вызов метода Object.clone().
Employee cloned = (Employee)super.clone() ;
//• Клонирование изменяемых полей.
cloned.hireDay = (GregorianCalendar)hireDay.clone(.;
.return cloned;
}
catch (CloneNotSupportedException e) { return null; }
}
/**
Задать конкретную дату найма на работу.
@param year год найма
Sparam month месяц найма
Sparam day день найма
*/
public void setHireDay(int year, int month, int day)
{
hireDay = new GregorianCalendar(
year, month - 1, day).getTimeO;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toStringO
{
63.return "Employee(имя=" + name
64.
+
",зарплата=" + salary
65.
+
",день выдачи зарплаты=" + getPayDay()
66.
+"]
И .
;
67.
}
68.
69.
private
String name;
70.
private
double salary;
71.
private
GregorianCalendar payDay;
72. }
Внутренние классы
Внутренним (inner) называется класс, определенный внутри другого класса. Зачем он нужен? Назовем четыре причины.
Объект внутреннего класса имеет доступ к реализации объекта, который его создал, включая закрытые данные.
Внутренний класс можно скрыть от других классов того же пакета.
Безымянный(anonimous) внутренний класс удобен, если нужно на лету уточнить обратные вызовы.
Внутренние классы очень удобны при создании событийно-управляемых программ.
В языке C++ есть вложенные классы (nested classes). Вложенный класс находится внутри области видимости охватывающего его класса. Вот типичный пример: L.внутри класса для связанного списка определен класс, содержащий связи, и
класс, в котором определяется позиция итератора.
class LinkedList {
public:
class Iterator // Вложенный класс.
Ч
public:
voidinsert(intx); inr erase();
};
private:
class Link // Вложенный класс.
{
public:
Link* next; int data;
)
Вложение представляет собой отношение между классами, а не между объектами. Объект класса LinkedList не содержит подобъектов типа Iterator или Link.
У вложения классов есть два преимущества: управление именами (name control) и управление доступом (access control). Поскольку имя Iterator вложено внутрь класса LinkedList, оно известно исключительно как LinkedList:: Iterator и не может конфликтовать с другими классами, имеющими имя Iterator. В языке Java это преимущество не играет такой роли, поскольку в нем такое же управление именами осуществляют пакеты. Отметим, что класс Link находится в закрытом разделе класса LinkedList. Он полностью скрыт от остальной части программы. По этой причине можно спокойно объявлять его поля открытыми. Тогда методы класса LinkedList получат к ним доступ (который им совершенно необходим), причем для остальных методов эти поля останутся невидимыми. В языке Java такой вид управления доступом невозможен, если не применять внутренние классы.
Однако внутренние классы в языке Java имеют еще одно достоинство, которое делает их более полезными, чем вложенные классы в языке C++. Объект внутреннего класса содержит неявную ссылку на объект внешнего класса, который создал его. С помощью этой ссылки объект внутреннего класса получает доступ ко всем полям и методам внешнего объекта. Этот механизм более подробно рассматривается ниже в этой главе.
Эту дополнительную ссылку не имеют лишь статические внутренние классы. Именно они представляют собой полный аналог вложенных классов языка C++.
Использование внутренних классов для доступа к состоянию объекта
Синтаксис внутренних классов довольно сложен. Поэтому, чтобы продемонстрировать применение внутренних классов, мы рассмотрим простой, но несколько надуманный пример. Мы напишем программу, в которой банковский счет управляется тайме-
ром. Объект класса Listener, отслеживающий действия таймера, каждую секунду добавляет к сумме, лежащей на счету, начисленные на нее проценты. Однако для манипулирования с банковским балансом мы не будем использовать открытые методы (такие как deposit или withdraw), поскольку любой может вызвать эти методы и изменить состояние счета без нашего ведома. Поэтому мы применим внутренние классы, методы которых могут непосредственно манипулировать банковским балансом.
Рассмотрим схему класса BankAccount.
class BankAccount
{
public BankAccount (double initialBalance) { ... }
public void start(double rate) { ... }
private double balance;
private class InterestAdder implements ActionListener // Внутренний класс.
{
}
Обратите внимание на класс InterestAdder, расположенный внутри класса BankAccount. Из этого не следует, что каждый объект класса BankAccount содержит поле типа InterestAdder. Разумеется, мы будем создавать объекты внутреннего класса, но эти объекты не являются полями внешнего класса. Просто по отношению к методам внешнего класса они локальны.
Класс InterestAdder является закрытым внутренним классом (private inner class) класса BankAccount. Этот механизм гарантирует безопасность. Поскольку объекты класса InterestAdder могут порождаться только методами класса BankAccount, можно не беспокоиться о нарушении инкапсуляции. Область видимости обычных классов всегда ограничена либо пакетом, либо не ограничена вообще.
Класс InterestAdder имеет конструктор, устанавливающий уровень процентной ставки, которая должна применяться на каждом шаге. Поскольку данный внутренний класс реализует интерфейс ActionListener, он содержит также метод action- Performed. Этот метод и увеличивает на самом деле баланс счета. Ниже приведено более подробное устройство внутреннего класса.
class BankAccount
{
public BankAccount(double initialBalance)
{
balance = initialBalance;
}
private double balance;
private class interestAdder implements ActionListener
{
public interestAdder(double aRate) {
rate = aRate;
public voidevent) { .. . }
private double rate;
}
Метод start класса BankAccount создает объекта класса InterestAdder при заданном уровне процентной ставки, затем порождает объект класса ActionListener и запускает таймер.
public voidrate)
ActionListener adder = new
Timer t = newadder);
t.start О;
В результате метод actionPerformed класса InterestAdder будет вызываться каждую секунду. Рассмотрим его внутреннее устройство подробнее.
public voidevent)
{
double interest = balance * rate / 100;
balance += interest;
formatter = =
= +
}
Имя rate ссылается на поле InterestAdder, что неудивительно. Однако в классе InterestAdder нет поля balance. Вместо этого переменная balance ссылается на поле объекта BankAccount, созданного объектом InstanceAdder. Это новаторское решение. Обычный метод может обращаться к полям данных объекта, который его вызвал. Метод внутреннего класса получает доступ как к полям своего собственного класса, так и к полям данных внешнего класса, создавшего его. Для этого, естественно, объект внутреннего класса всегда получает неявную ссылку на создавший его объ- ект(рис.6.3).
Рис. 6.3. Объект внутреннего класса содержит ссылку на объект внешнего класса
В определении внутреннего класса эта ссылка невидима. Для того чтобы продемонстрировать, как она работает, вызовем ссылку на объект внешнего класса outer. Тогда метод actionPerformedэквивалентен следующим операторам.
public void actionPerformed(ActionEventevent)
{
double interest = outer.balance * this.rate / 100;
// "outer" — не настоящее имя
outer.balance += interest;
NumberFormat formatter =
System.out.printIn("Баланс = "
+ formatter.format(outer.balance)) ;
}
Ссылка на объект внешнего класса задается в конструкторе. Это значит, что компилятор добавляет в конструктор еще один параметр, генерируя код, подобный следующему. public InterestAdder(BankAccount account, double aRAte]
{
outer = account;
// Автоматически сгенерированный код.
rate = aRate;
}
Обратите, пожалуйста, внимание на то, что слово outer не является ключевым. Оно используется просто для иллюстрации механизма, задействованного при работе с внутренними классами.
После того как метод start создаст объект класса InterestAdder, компилятор передаст ссылку this конструктору текущего объекта класса BankAccount.
ActionListener added = new InterestAdder(this, rate);
// Автоматически сгенерированный код.
В листинге 6.4 показана законченная программа, иллюстрирующая работу с внутренними классами. Рассмотрим еще раз управление доступом. Объекту класса Timer нужен объект какого-либо класса, реализующего интерфейс ActionListener. Если бы этот класс был обычным, то доступ к полям и методам класса BankAccount должен был бы осуществляться с помощью открытого метода. В результате в классе BankAccount пришлось бы реализовывать методы для всех классов. Объекты внутреннего класса InterestAdder обладают доступом к объектам класса BankAccount, однако ни один другой класс таких привилегий не имеет.
Листинг6.4. Прог
import java.awt.event. *;
import java.text.*;
import javax.swing.*;
A.
public class InnerClassTest
{
public static void main(String[] args)
{
// Создаем банковский счет, зачисляя на него $10,000.
BankAccount account = new BankAccount(10000);
14.17.23.24 .25.33.34.35.42.43.44.47.52.53.54.57.14.17.23.24 .25.33.34.35.42.43.44.47.52.53.54.57.// Начинаем начислять проценты, по 10% каждую секунду. account.start(10);
14.
17.
23.
24 .
25.
33.
34.
35.
42.
43.
44.
47.
52.
53.
54.
57.
14.
17.
23.
24 .
25.
33.
34.
35.
42.
43.
44.
47.
52.
53.
54.
57.
// Программа выполняется, пока не нажата кнопка "Ok" JOptionPane.showMessageDialog(null, "Выход?");
System.exit(0);
}
}
class BankAccount
{
/**
Создает банковский счет с начальным балансом.
@param initialBalance начальный баланс
*/
public BankAccount(double initialBalance) {
balance = initialBalance;
}
/**
Начинаем моделировать доход, увеличивая его каждую секунду.
@param rate процентная ставка
*/
public void start(double rate) {
ActionListener adder = new InterestAdder(rate);
Timer t = new Timer(1000, adder);
t.start();
}
private double balance;
/**
Этот класс начисляет проценты на банковский счет.
Метод actionPerformed вызывается таймером.
*/
private class InterestAdder implements ActionListener {
public InterestAdder(double aRate) {
rate = aRate; }
public void actionPerformed(ActionEvent event) {
// Вычислить доход.
double interest = balance * rate / 100;
balance += interest;
// Вывести на печать текущий баланс. NumberFormat formatter
=
System.out.printIn("Баланс="
+ formatter.format(balance));
}
private double rate;
}
}
Специальные синтаксические правила
для внутренних классов
В предыдущем разделе мы называли ссылку на внешний класс именем outer. На самом деле синтаксис, связанный с использованием внешней ссылки, несколько сложнее. Выражение
Outerclass.this
означает ссылку на внешний класс. Например, во внутреннем классе InterestAdder можносоздатьметодactionPerformed.
public void actionPerformed(ActionEvent event)
{
double interest = BankAccount.this.balance * this.rate / 100;
BankAccount.this.balance += interest;
}
И, наоборот, можно создать конструктор внутреннего класса более явным способом, используя следующий синтаксис.
outerobject .new InnerClass (параметры',
Например,
ActionListener adder = this.new InterestAdder(rate);
Теперь ссылке на внешний класс, содержащейся во вновь созданном объекте класса InterestAdder, присвоена ссылка this метода, создавшего объект внутреннего класса. Этот случай является наиболее распространенным. Как всегда, квалификатор this излишен. Однако ссылку на объект внешнего класса можно установить и на другой объект, явно указав его. Например, если класс InterestAdder является открытым внутренним классом, можно создать его объекты для любого банковского счета.
BankAccount mySavings = new BankAccount(10000);
BankAccount.IntegerAdder adder = =
Отметим, что, если ссылка
Outerclass.InnerClass
находится внутри области видимости внешнего класса, она относится к внутреннему классу. Например, если класс InterestAdder является открытым, в любом месте программы на него следует ссылаться с помощью выражения BankAccount. InterestAdder.
Чем полезны внутренние классы? Действительно ли они необходимы и безопасны?
Внутренние классы представляют собой большой шаг вперед. Изначально язык Java должен был быть проще языка C++. Однако внутренние классы далеко не просты.
Их синтаксис сложен. (Он сложнее даже синтаксиса безымянных внутренних классов, рассматриваемых в этой главе.) Взаимодействие внутренних классов с другими средствами языка не совсем прозрачно, например, управление доступом и безопасность внутренних классов далеко не очевидны.
Зачем же разработчики языка Java пожертвовали преимуществами, которыми он выгодно отличался от других языков, в пользу элегантного и интересного механизма, польза которого настолько неочевидна?
У нас нет окончательного ответа на этот вопрос. Стоит уточнить, что внутренние классы относятся к компилятору, а не к виртуальной машине. И преобразуются они в обычные классы с помощью символа $ (доллар), разделяющего имена внешних и внутренних классов, поэтому виртуальная машина их не различает.
Например, класс InterestAdder внутри класса преобразуется в класс
Чтобы увидеть, как работает этот механизм, попробуйте провести следующий эксперимент: запустите программу ReflectionTest, приведенную в главе 5, и выполните отражение класса BankAccount$ InterestAdder. На экране появятся следующие строки.
class {
public double)
public void
private double rate;
private final BankAccount this$0;
Если вы программируете в среде Unix, не используйте символ $, если имя класса указывается в командной строке. Это значит, что программу ReflectionTest следует запускать с помощью команды
ReflectionTest
Как видим, компилятор генерирует дополнительное поле0 для ссылки на
внешний класс. (Имя this$0 синтезируется компилятором, поэтому сослаться на него невозможно.) Кроме того, количество параметров конструктора увеличивается.
Если компилятор может выполнять такие операции, почему программист не в состоянии сделать это вручную? Попробуйте. Можно сделать класс InterestAdder обычным классом, а затем передать ему ссылку this на создавший его объект. class BankAccount {...
public voidrate)
{
ActionListener adder = newrate);
Timer t = new Timer(1000, adder);
(); } } class InterestAdder implements ActionListener {
publicaccount, double
outer = account;
rate = aRAte;
private BankAccount outer;
private double rate;
}
Рассмотрим теперь метод actionPerformed. Ему нужен доступ к полю outer.balance.
public void actionPerformed(ActionEvent event) {
double interest = outer.balance * rate / 100; // ОШИБКА.
outer.balance += interest; // ОШИБКА.
)
Здесь возникает проблема. Внутренний класс имеет доступ в закрытым данным лишь внешнего класса, который его содержит, но класс InterestAdder ДЛЯ него закрыт. Следовательно, внутренние классы намного мощнее, чем обычные, поскольку обладают более высокими привилегиями доступа.
Может возникнуть вопрос, каким образом внутренние классы получают дополнительные привилегии доступа, преобразовываясь в обычные, а виртуальной машине вообще о них ничего не известно? Чтобы раскрыть эту тайну, снова воспользуемся программой ReflectionTest, отслеживающей класс BankAccount.
classBankAccount {
public BankAccount(double);
static double access$000(BankAccount);
public void start(double);
static double access$018(BankAccount, double);
private double balance;
}
Обратите внимание на методы access$00C и access$018, добавленные компилятором во внешний класс. Они вызываются методами внутреннего класса. Например, оператор balance += interest
в методе actionPerformedкласса InterestAdder на самом деле выполняет вызов access$018(outer, access$000(outer) + interest);
He опасно ли это? Еще бы, конечно, опасно! Кто-нибудь другой может легко вызвать метод access$018 и прочесть закрытое поле balance или, что еще хуже, вызвать метод access$000 и установить новое значение этого поля. В стандарте языка Java символ $ зарезервирован для системных переменных и методов. Однако хакеры, знакомые со структурой файлов, содержащих классы, легко могут (если не поленятся) создать свой аналогичный файл и вызвать данный метод с помощью соответствующих команд виртуальной машины. Разумеется, такой файл должен генерироваться вручную (например, с помощью редактора текстов, записанных в шестнадцатеричном коде). Поскольку область видимости секретных методов доступа
ограничена пакетом, атакующий код должен размещаться в том же самом пакете, что и атакуемый класс.
Итак, если внутренний класс обладает доступом к закрытым полям, можно создать другой класс, добавить его в тот же пакет и получить доступ к закрытым данным. Правда, для этого нужен опыт и решительность. Программист не может получить такой доступ случайно, не создавая для этих целей специальный файл, содержащий модифицированные классы.
Локальные внутренние классы
Если внимательно изучить код класса BankAccount, обнаружится, что имя класса используется лишь однажды: при создании объекта этого типа в методе start.
Втакихситуацияхклассможноопределитьлокально в отдельном методе. public voidrate)
class InterestAdder implement ActionListener
{
public
{ rate = aRate;
} public voidevent)
{ double interest = balance * rate / 100; balance += interest;
formatter
=
= "
+
}
private double rate;
}
ActionListener adder = new
Timer t = newadder);
t.start ();
}
Локальные классы никогда не объявляются с помощью модификаторов доступа (т.е. public и protected). Их области видимости всегда ограничиваются блоком, в котором они объявлены.
Локальные классы имеют огромное преимущество — они полностью скрыты от внешнего мира— даже от остальной части класса BankAccount. Ни один метод, за исключением метода start, ничего не знает о классе InterestAdder. Кроме того, локальные классы выгодно отличаются от внутренних тем, что они имеют доступ не только к полям своего внешнего класса, но и к локальным переменным! Рассмотрим типичный пример.
p{ublic void start(final double rate)
class InterestAdder implements ActionListener
p{ublic void actionPerformed(ActionEvent event)
double interest = balance * rate / 100; balance += interest;
NumberFormat formatter
=;
System.out.printIn("Баланс = "■*
+ formatter.format(balance));
}
}
ActionListener adder = new InterestAdder(rate);
Timer t = new Timer(1000, adder);
t.start(1 ;
}
Обратите внимание на то, что классу IntegerAdder больше не нужно хранить переменную rate. Он просто ссылается на параметр метода, содержащего определение данногокласса.
Возможно, это выглядит не очень неожиданно. В конце концов строка double interestbalance * rate / 100;
находится внутри метода start, так почему бы ей не иметь доступа к переменной rate?
Чтобы понять, почему все это не так просто, рассмотрим поток управления поближе.
Вызывается метод start .
С помощью вызова конструктора внутреннего класса InterestAdder инициализируется объектная переменная adder.
Ссылка adder передается конструктору класса Timer, таймер запускается, и метод start прекращает свою работу. В этот момент параметр rate метода start больше не существует.
Впоследствии метод actionPerformedвыполняет оператор double interest = balance * rate / 100;
Чтобы метод actionPerformed был успешно реализован, класс IntegerAdder должен создать копию поля rate до того, как оно перестанет существовать в качестве локальной переменной метода start . Именно это и происходит. В нашем примере компилятор синтезирует имя BankAccount$InterestAdder для локального внутреннего класса. Применив снова программу ReflectionTest для изучения устройства класса BankAccount$l$InterestAdder, мы получим следующий результат.
class BankAccount$l$InterestAdder
{
BankAccount$l$InterestAdder(BankAccount, double)
public void actionPerformed(java.awt.event.ActionEvent);
private final double val$rate;
private final BankAccount this$0;
Обратите внимание на дополнительный параметр конструктора, имеющий тип double, и переменную val$rate. При создании объекта переменная rate передается конструктору и размещается в поле val$rate. Для разработчика компилятора это выглядит довольно обременительным. Компилятор должен обнаруживать доступ к локальным переменным, создавать для каждой из них соответствующие поля, а затем копировать локальные переменные в конструктор, чтобы поля данных инициализировались именно этими копиями.
С программистской точки зрения, однако, доступ к локальным переменным выглядит привлекательно. Он делает внутренние классы проще, уменьшая количество их полей, которые должны программироваться явным образом.
Как уже указывалось, методы локального класса могут ссылаться только на локальные переменные, объявленные с помощью ключевого слова final. По этой причине параметр rate в нашем классе был объявлен терминальным. Терминальная локальная переменная не может быть модифицирована. Это гарантирует, что локальная переменная и ее копия, созданная внутри локального класса, всегда имеют одно и то же значение.
Мы уже видели, что терминальные переменные можно использовать в качестве констант:
public static final double SPEEDLIMIT = 55;
Ключевое слово final можно применять и к локальным переменным, и к статическим. В обоих случаях это означает одно и то же: данной переменной значение можно присваивать только один раз, сразу после ее создания. Впоследствии это значение изменить будет невозможно. Однако инициализировать терминальную переменную одновременно с ее определенем не обязательно. Например, параметр rate, являющийся терминальной переменной, инициализируется сразу после создания при вызове метода start. (Если этот метод вызывается неоднократно, при каждом вызове будет создаваться новый параметр rate.) Значение переменной val$rate во внутреннем классе BankAccount$l$InterestAdder устанавливается лишь один раз, в конструкторе внутреннего класса. Терминальная переменная, которая не была инициализирвоана во время ее описания, часто называется пустой терминальной переменной (blank final variable).
Безымянные внутренние классы
Используя локальные внутренние классы, можно добиться еще большего. Если нужно создать один-единственный объект данного класса, этому классу можно даже имени не давать. Такой класс называется безымянным внутренним классом (anonimousinner class).
public void start(final double rate)
{
ActionListener adder = new
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
double interest = balance * rate / 100;
balance += interest;
NumberFormat formatter
=
= " +
}
};
Timer t = newadder);
start ();
}
Этот синтаксис и вправду выглядит криптограммой. На самом деле он означает вот что: создается новый объект класса, реализующего интерфейс ActionListener, в котором внутри фигурных скобок {} определен требуемый метод
Любые параметры, необходимые для создания объекта, задаются внутри фигурных скобок {}, за которыми следует имя супертипа. В общем случае синтаксис выглядит следующим образом.
newнеобходимые для создания объекта)
{
Методы внутреннего класса и данные.
}
Здесь СуперТип может быть интерфейсом, например ActionListener; затем внутренний классданный интерфейс. СуперТип может быть и классом; то
гда внутренний класс расширяет данный суперкласс.
Безымянный внутренний класс не может иметь конструкторов, поскольку имя конструктора должно совпадать с именем класса, а в данном случае класс не имеет имени. Вместо этого параметры, необходимые для создания объекта, передаются конструктору суперкласса. В частности, если внутренний класс реализует какой-либо интерфейс, он не может иметь никаких параметров для создания объекта. Несмотря на это, такие параметры можно задать следующим образом. new{ методы и данные }
Следует тщательно изучить различие между созданием нового объекта некоторого класса и созданием объекта безымянного внутреннего класса, расширяющего данный класс. Если вслед за скобками, содержащими список параметров, необходимых для создания объекта, указана открытая скобка, определяется безымянный внутренний класс. Person queen = new
// Объект класса Person.
Person count = new{...};
// Объект внутреннего класса, расширяющего класс Person.
Идея ввести в язык безымянные внутренние классы оказалась удачной, или это только великолепное средство создавать зашифрованные, никому не понятные программы? Возможно, и то, и другое. Если внутренний класс невелик и сводится всего к нескольким строчкам простого кода, можно сэкономить время на проверке типа, но именно это может увести программиста на скользкий путь создания темных и запутанных программ.
Досадно, что разработчики не предприняли никаких попыток улучшить синтаксис безымянных внутренних классов, так как в целом синтаксис языка Java намного проще, чем языка C++. Разработчики внутренних классов могли бы помочь программистам разобраться с такими синтаксическими конструкциями, как, например, следующая.
Person count » new class extends{...};
// Синтаксис, не свойственный языку Java.
Однако они этого не сделали. Поскольку многие программисты считают, что коды, содержащие безымянные внутренние классы, слишком сложны для понимания, мы рекомендуем ограничивать их применение.
В листинге 6.5 приведена законченная программа для работы с банковским счетом, в которой используется безымянный внутренний класс. Сравнив эту программу с кодом, содержащимся в листинге 6.4, вы можете убедиться, что применение безымянного внутреннего класса сделало программу несколько короче и, как мы надеемся, легче для понимания.
4.13.34.35.42.43.4.13.34.35.42.43.import 3ava.awt.event.*;
4.
13.
34.
35.
42.
43.
4.
13.
34.
35.
42.
43.
import java.text.*;
import javax.swing.*;
public class AnonymousInnerClassTest
public static void main(String[I args) (
// Создает банковский счет, зачисляя на него $10,000.
BankAccount account = new BankAccount(10000);
// start accumulating interest at 10% account.start(10) ;
// Программа выполняется, пока не нажата кнопка "Ok".
JOptionPane.showMessageDialog(null, "Выход?");
System.exit(0) ;
}
}
class BankAccount
{
/**'
Создает банковский счет с начальным балансом.
@param initialBalance начальный баланс
*/
public BankAccount(double initialBalance) {
balance = initialBalance; }
/*
Начинает моделировать доход, увеличивая его каждую секунду. @param rate процентная ставка
*/
public void start(final double rate) I
ActionListener adder = new
ActionListener(1
public void actionPerformed(ActionEvent event) {
// Вычислить доход.
double interest = balance * rate / 100;
47.53.54.}47.53.54.}balance += interest;
47.
53.
54.
}
47.
53.
54.
}
// Вывести на печать текущий баланс. NumberFormat formatter
=
System.out.printIn("Баланс="+ formatter.format(balance));
}
};
Timer t = new Timer(1000, adder);
t. start ();
}
private double balance;
Статические внутренние классы
Может случиться так, что вам нужно будет использовать внутренний класс просто для того, чтобы скрыть его внутри другого класса, а ссылка на объект внешнего класса окажется ненужной. Подавить генерацию этой ссылки можно, объявив внутренний класс статическим (static).
Вот типичный пример, показывающий, как это сделать. Допустим, нам нужно вычислить максимум и минимум в массиве чисел. Разумеется, для этого мы напишем две функции: одну— для вычисления максимума, а другую— для вычисления минимума. При вызове обеих функций массив просматривается дважды. Было бы намного эффективнее просматривать массив только один раз, одновременно вычисляя и максимум, и минимум.
double min= d[0];
double max = d[0] ;
for (int i = 1; i < d.length; i++)
{
if (min > d[i]) min = d[i];
if (min < d[i]) max = d[i];
}
Однако в таком случае функция должна возвращать два значения. Достичь этого эффекта можно, определив класс Pair, содержащий два числа.
class Pair {
public Pair(double f; double s) ;
first = f; second = s;
)
public double getFirst()
{
return First;
}
publicdoublegetSecond()
return Second;
private double first;
private double second;
Тогда функция minmax сможет вернуть объект класса Pair.
classArrayAlg {
public static Pair minmax(double[] d)
I return new Pair(min, max);
)
При вызове функции minmax для получения ответов используются методы getFirst и getSecond.
Pair p = ArrayAlg.minmax(d) ;
System.out.printin("min = " + p.getFirst());
System.out.printIn("min = " + p. getSecond());
Разумеется, имя Pair слишком неконкретно, и при выполнении большого проекта у другого программиста может возникнуть такая же блестящая идея, вот только класс Pair у него будет содержать не числа, а строки. Эту потенциальную проблему можно решить, сделав класс Pair открытым внутренним классом внутри класса ArrayAlg. Тогда настоящим именем класса будет не Pair, a ArrayAlg. Pair.
ArrayAlg.Pair p = ArrayAlg.minmax(d);
Однако в отличие от внутренних класов, использованных в предыдущих примерах, в данном случае ссылка на любой другой объект внутри класса Pairне нужна. Эту ссылку можно подавить, объявив внутренний класс статическим.
class ArrayAlg {
public static class Pair
{
}
)
Разумеется, только внутренние классы можно объявлять статическими. Статический внутренний класс ничем не отличается от любого другого внутреннего класса, за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса. В нашем примере следует применять статический внутренний класс, поскольку объект внутреннего класса создается внутри статического метода.
public static Pair minmax(double[] d) {
return new Pair(min, max);
1. public class StaticInnerClassTest1. public class StaticInnerClassTest2. {2. {7.8 .9.12.13.17.18.19.20.21.22.23.24.25.26.27.28.29.30.40.41.42.7.8 .9.12.13.17.18.19.20.21.22.23.24.25.26.27.28.29.30.40.41.42.
1. public class StaticInnerClassTest
1. public class StaticInnerClassTest
2. {
2. {
7.
8 .
9.
12.
13.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
40.
41.
42.
7.
8 .
9.
12.
13.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
40.
41.
42.
Если бы класс Pair не был объявлен статическим, компилятор выдал бы предупреждение, что при инициализации объекта внутреннего класса неявный объект ти- паArrayAlg не доступен.
D
Статический внутренний клас используется тогда, когда доступ к объекту внешнего класса не нужен. Некоторые программисты для обозначения статических внутренних классов используюттермин вложенные классы (nested classes).
В листинге 6.6 приведен полный код классаArrayAlg и вложенного класса Pair.
——■— —'।
Листинг 6.6. Программа StaticlnnerClassTest.java ' ' * ■*<#, ■ ^уб.ег
L_:!J
public static void main(String[] args) {
double!] d = new double[20];
for (int i = 0; i < d.length; i++)
d[i] = 100 * Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.printIn("min = " + p.getFirst());
System.out.printIn("max = " + p.getSecond()); }
class ArrayAlg
{
/**
Пара чисел с плавающей точкой.
*/
public static class Pair
/**
Создает пару, состоящую из чисел с плавающей точкой. Sparam f первое число Sparam s второе число
*/
public Pair(double f, double s)
first = f; second = s;
/**
Возвращает первое число пары.
@return первое число
*/
public double getFirst()
{ return first;
/*
Возвращает второе число пары.
@return второе число
public double getSecond()
{
return second;
}
private double first;
private double second;
)
/**
Вычисляет минимум и максимум чисел в массиве.
@param d массив чисел с плавающей точкой
Sreturn пара, в которой первый элемент
является минимумом чисел, содержащихся в массиве,
а второй элемент — максимумом.
*/
public static Pair minmax(double[] d)
{
if (d.length== 0) return new Pair(0, 0);
double min = d[0];
*double max = d[0];
for(inti=1;i<d.length; i++)
{
if (min > d[i]) min = d[i];
if (max < d[i]) max = d[i];
}
returnnewPair(min,max);
}
}
Заместители
В заключительном разделе этой главы мы обсудим концепцию заместителей (proxies), новое свойство, ставшее доступным в версии 1.3 пакета Java SDK. Заместители используются для создания во время выполнения программы новых классов, реализующих заданное множество интерфейсов. Они необходимы, только если на этапе компиляции программист еще не знает, какие интерфейсы ему следует реализовать. В прикладном программировании эта ситуация представляет собой исключительный случай. Однако в некоторых приложениях системного программирования гибкость, предоставляемая заместителями, может оказаться очень важным обстоятельством. Используя эту концепцию, часто можно избежать механической генерации и компиляции "фиктивного" кода.
Допустим, у нас есть массив объектов класса Class, представляющих интерфейсы (возможно, содержащих лишь один интерфейс), точная природа которых во время компиляции нам не известна. Теперь предположим, что нам нужно создать объект класса, реализующего эти интерфейсы. Это трудная проблема. Если объект класса Class представляет собой настоящий класс, мы можем применить метод newlnstance или задействовать механизм отражения, чтобы найти конструктор этого класса. Однако создать объект интерфейса невозможно. Следовательно, определить новый класс во время выполнения программы мы не можем.
В ранних версиях пакета Java SDK (до версии 1.2 включительно) фиктивный код обнаруживался очень часто. При использовании удаленного вызова метода (remote method invocation — RMI) специальная утилита rmic порождает фиктивные классы, которые нужно добавлять в программу. (Более подробно удаленный вызов метода рассматривается в главе 4 тома 2.) При использовании компонентов bean box фиктивные классы генерируются и компилируются "на лету" при попытке связать эти компоненты друг с другом. (Более подробно о компонентах JavaBeans можно прочесть в главе 7 тома 2.) Ожидается, что в ближайшем будущем этот механизм будет обновлен и получит те же преимущества, которыми обладают заместители.
Чтобы преодолеть эту преграду, определенные программы, например, BeanBox в ранних версиях пакета Bean Development Kit, генерируют код, помещают его в файл, вызывают компилятор, а затем загружают полученный файл, содержащий классы. Естественно, это очень медленный процесс, а кроме того, для выполнения такой программы постоянно нужен компилятор. Механизм заместителей предоставляет более удачное решение. Класс-заместитель может создавать во время выполнения программы совершенно новые классы. Такой класс реализует интерфейсы, которые указывает программист. В частности, в классе- заместителе содержатся следующие методы.
Все методы, требуемые указанными интерфейсами.
Все методы, определенные вк, лассе Ob j ect (toString, equals и т.д.).
Однако определить новый код для этих методов в ходе выполнения программы нельзя. Вместо этого программист должен создать обработчик вызовов (invocation handler), представляющий собой объект, реализующий интерфейс InvocationHandler. Этот интер фейс имеет один-единственный метод:
Object invoke(Object proxy, Method method, Object[j args)
Как только объект класса-заместителя вызывает какой-либо метод, автоматически вызывается метод invoke из обработчика вызовов, получающий на вход объект класса Method и параметры исходного вызова.
Чтобы создать объект класса-заместителя, используется метод newProxyInstance класса Proxy. Этот метод имеет три параметра.
Загрузчик класса (class loader). Модель безопасности языка Java позволяет использовать разные загрузчики классов: для системных классов, для классов, загружаемых из Интернет, и т.д. Загрузчики классов обсуждаются в томе 2. Пока мы будем указывать ссылку null, чтобы использовать загрузчик классов, предусмотренный по умолчанию.
Массив объектов класса Class по одному для каждого интерфейса, подлежащего реализации.
Обработчик вызовов.
Остается два вопроса. Как определить обработчик? Что можно сделать с полученным в результате объектом класса-заместителя? Разумеется, ответы на эти вопросы зависят от задачи, которую программист хочет решить с помощью механизма заместителей. Их можно использовать для достижения разных целей, например:
переадресации вызовов методов на удаленный сервер;
установки связи между событиями пользовательского интерфейса с определенными действиями при выполнении программы;
отслеживания вызовов методов при отладке.
В нашем примере мы применим заместители и обработчики вызовов для отслеживания вызовов методов. Мы определим интерфейсный класс TraceHandler, в котором хранится некий объект. Его метод invoke просто выводит на экран имя и параметры вызванного метода, а затем вызывает его, задавая в качестве неявного параметра объект, хранящийся в классе TraceHandler.
class TraceHandler {
publict)
{ target = t;
}
public Objectproxy, Methodargs)
throws Throwable
{
// Выводит на печать имя метода и его параметры.
// Вызывает фактический
returnargs);
}
private Object target;
}
Ниже показано, как создать объект класса-заместителя, позволяющий отслеживать вызов метода.
Object value = . .. ;
// Создает объект интерфейсного класса.
InvocationHandler handler = new
// Создает объекты класса-заместителя для всех интерфейсов.
Class[] interfaces =
Object proxy =interfaces, handler);
Теперь каждый раз, когда вызывается метод из объекта proxy, его имя и параметры выводятся на печать, а затем этот метод вызывается из объекта
В программе, приведенной в листинге 6.7, мы применили объекты класса- заместителя для отслеживания бинарного поиска. Сначала заполняется массив, состоящий из объектов-заместителей для целых чисел от 1 до 1000. Затем для поиска случайного целого числа в массиве вызывается метод binarySearch класса Arrays. В заключение на экран выводится соответствующий элемент.
elements = new
// Заполняем массив объектами-заместителями для целых чисел от 1 до 1000.
for (inti=0; i<elements.length; i++)
{
Integer value = new Integer(i+1);
elementspi] = ...; // Прокси-объект для объекта value. }
у
// Генерируем случайное число.
Random generator = new Random();
int r = generator.nextlnt(elements.length);
Integer key = new Integer(r+ 1);
// Поиск числа key.
int result = Arrays.binarysearch(elements, key);
// Выводим на экран найденное число (если оно есть в массиве). if (result > = 0)
System.out.printin(elements[result]);
Класс Integer реализует интерфейс Comparable. Объекты-заместители принадлежат классу, определяемому во время выполнения программы. (Его имя $ProxyO.) Этот класс также реализует интерфейс Comparable. Однако его метод compareTc вызывает метод invoke из обработчика вызовов в объекте-заместителе.
Метод binarySearch вполняет вызов наподобие следующего.
if (elements[i].compareTo(key) < 0) ...
Поскольку массив заполнен объектами-заместителями, метод compareTo вызывает метод invoke класса TraceHandler. Этот метод выводит на печать имя вызванного метода и его параметры, а затем вызывает метод compareTo из объекта Integer.
В конце программы выводятся результаты ее работы.
System.out.printIn(elements[result]);
Метод println вызывает метод toString из объекта-заместителя, а затем этот вызов также перенаправляется обработчику вызовов.
Вот как выглядят результаты полной трассировки программы.
500.compareTo(2881
250.compareTo(288)
375.compareTo(288)
312.compareTc (288)
281.compareTo(288]
296.compareTo(288)
288.compareTo(288]
288.toString(]
Как видим, алгоритм бинарного поиска последовательно приближается к искомому числу, на каждом шаге вдвое уменьшая интервал поиска.
import java.lang.reflect.*;
import java.util.*;
. public class ProxyTest
{
public static void main (String [", args)
{
Object[] elements = new Object[1000] ;
// Заполняем массив объектами-заместителями целых чисел.
for (int i = 0; i < elements.length; i++)
{
Integer value = new Integer(i + 1);
Class[ interfaces = Value.getClass().getlnterfaces();
InvocationHandler handler = new TraceHandler(value);
Object proxy = Proxy.newProxylnstance(null,
interfaces, handler);
elements[i] = proxy;
}
// Генерируем целое число.
Random generator = new Random();
int r = generator.nextlnt(elements.length);
Integer key = new Integer(r + 1);
// Выполняем поиск числа key.
int result = Arrays.binarysearch(elements, key);
// Если число найдено, выводим его на экран.
if (result >= 0)
System.out.printin(elements[result]);
}
}
/**
Обработчик вызова, выводящий имя метода и параметры,
а затем вызывающий исходный метод.
*/
class TraceHandler implements InvocationHandler
{
/**
Создает объект класса TraceHandler.
@param t неявный параметр при вызове метода
*/
public TraceHandler(Object t)
(
target = t;
}
public Object invoke(Object proxy, Method m, Object[ args)
throws Throwable
{
/ / Выводим на экран неявный параметр.
System.out.print(target) ;
// Выводим на экран имя метода.
System.out.print("." + m.getName() + "(");
// Выводим на экран явные параметры.
if (args != null)
{
for (int i0; i < args.length; i++)
{
72.72.System.out.print(args[i]);if (i < args.length - 1)
72.
72.
System.out.print(", ");
}
}
System.out.println(")");
// Вызываем фактический метод.
.return m.invoke(target, args);
}
private Object target;
}
Свойства классов-заместителей
Теперь, увидев классы-заместители в действии, вернемся к анализу некоторых их свойств. Напомним, что классы-заместители создаются во время выполнения программы "на лету". Однако затем они становятся обычными классами, как и все остальные классы в виртуальной машине.
Все классы-заместители расширяют класс Proxy. Класс-заместитель имеет только одну переменную — обработчик вызовов, определенный в суперклассе Proxy. Любые дополнительные данные, необходимые для выполнения задач, поставленных перед прокси-объектами, должны храниться в обработчике вызовов. Например, при создании объектов классов-заместителей, реализующих интерфейс Comparable, в программе, приведенной в листинге 6.7, обработчик TraceHandler является интерфейсным классом для фактических объектов.
Все классы-заместители замещают методы toString, equals и hashCode класса Object. Как и все методы-заместители, эти методы просто вызывают метод invoke, принадлежащий обработчику событий. Другие методы класса Object (например, методы clone и getClass) не переопределяются.
Классы-заместители имеют неопределенные имена. Класс Proxy из пакета Java 2 SDK генерирует имена классов, начинающиеся строкой s Proxy.
Для конкретного загрузчика классов и заданного набора интерфейсов может существовать только один класс-заместитель. Это значит, что, вызывая метод new- Proxylnstance дважды для одного и того же загрузчика классов и массива интерфейсов, программист получает два объекта одного и того же класса. Имя этого класса можно получить с помощью метода getProxyClass.
Class proxyclass = interfaces);
Класс-заместитель всегда является открытым и терминальным. Если все интерфейсы, которые реализуются классом-заместителем, открыты, он не принадлежит ни одному конкретному пакету. В противном случае все неоткрытые интерфейсы, а следовательно, и сам класс-заместитель, должны были бы принадлежать тому же самому пакету.
Вызвав метод isProxyClass класса Proxy, можно проверить, относится ли конкретный объект класса Class к некоему классу-заместителю.
Эта глава завершает наше описание основ языка Java. Интерфейсы и внутренние классы представляют собой часто встречающиеся понятия. Однако, как мы уже гово-
рили, заместители — это новаторский прием, представляющий интерес, в основном, для разработчиков средств программирования, а не для прикладных программистов. Теперь можно переходить к изучению графики и пользовательских интерфейсов, описание которых начинается в главе 7.
java.lang.reflect.InvocationHandler 1.3
Object invoke(Object proxy, Method method, Object[args)
Этот метод определяется для того, чтобы задать действие, которое должно быть выполнено, как только из объекта-заместителя будет вызван какой- либо метод.
java.lang.reflect.Proxy 1.3
static Class getProxy(ClassLoader loader, Class [ ] interfaces)
Возвращает имя класса-заместителя, реализующего заданные интерфейсы.
• static Object newProxylnstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
Создает новый экземпляр класса-заместителя, реализующего заданные интерфейсы. Все методы вызывают метод invoke из заданного объекта обработчика ВЫЗОВОВ.
♦ static boolean isProxyClass(Class с)
Возвращает значение true, если с является классом-заместителем.
Программирование
графики
в этой главе...
Т Введение в пакет Swing
Создание фреймов
Позиционирование фреймов
Отображение информации на панели
Т Двухмерные фигуры
Цвета
Текст и шрифты
Рисунки
д< сих пор мы учились писать программы, входные данные для которых вводились с клавиатуры. Затем программа обрабатывала их и выводила результаты на экран консоли. Сейчас такие программы уже не соответствуют запросам большинства пользователей. Современные программы, например, Web-страницы, работают совершенно иначе. В этой главе описываются программы, использующие графический пользовательский интерфейс (Graphics User Interface— GUI). В частности, вы узнаете, каким образом можно изменить размер и положение окон на экране, отобразить в окне текст, набранный разными шрифтами, вывести на экран рисунок и т.д. Эти навыки окажутся весьма полезными при создании интересных программ, описанных в дальнейших главах.
Следующие две главы посвящены обработке таких событий, как нажатие клавиши и щелчок мыши, а также средствам создания элементов пользовательского интерфейса, например, меню и кнопок. Прочитав эти три главы, вы сможете создавать незави-
самые (stand-alone) графические приложения. В главе 10 показано, как программировать аплеты, использующие возможности графических приложений, и внедрять их в Web-страницы. Более сложные вопросы, связанные с программированием графики, рассматриваются в томе 2.
Введение впакет Swing
Первая версия языка Java 1.0 содержала библиотеку классов Abstract Window Toolkit (AWT), обеспечивавших основные средства для программирования графического пользовательского интерфейса. Создание элементов пользовательского интерфейса на конкретной платформе (Windows, Solaris, Macintosh и т.д.) библиотека AWT поручала соответствующему пакету встроенных инструментальных средств. Например, если с помощью библиотеки AWT на экран нужно было вывести текстовое окно, то фактически ввод текста обрабатывался встроенным "эквивалентом". В основе программ, использующих библиотеку AWT, лежит простая идея: нужно указать лишь положение и поведение элементов пользовательского интерфейса, а язык Java сам создаст их эквиваленты. Теоретически такие программы должны работать на любых платформах, поскольку компания Sun всегда заявляла: "Написано однажды, работает везде".
Подход, основанный на использовании встроенных компонентов, очень хорош для простых приложений. Однако вскоре стало ясно, что с его помощью крайне трудно создавать высококачественные графические библиотеки, не зависящие от платформы. Элементы пользовательского интерфейса, например, меню, панели прокрутки и текстовые поля, на разных платформах могут вести себя по-разному. Следовательно, на основе этого подхода трудно создавать непротиворечивые программы с предсказуемым поведением. Кроме того, некоторые графические среды программирования (например Xll/Motif) не имеют такого богатого набора компонентов пользовательского интерфейса, как операционные системы Windows или Macintosh. Это, в свою очередь, еще больше ограничивало независимость библиотек, созданных методом "наименьшего общего знаменателя". В результате графические приложения, созданные с помощью библиотеки AWT, по сравнению с программами для Windows или Macintosh выглядели не так привлекательно и не имели таких функциональных возможностей. Что еще хуже, в библиотеке AWT на разных платформах обнаруживались разные ошибки. Программистам приходилось тестировать каждое приложение на каждой платформе. Такая практика иронично называлась "Написано однажды, ломается везде!".
В 1996 году компания Netscape создала библиотеку программ для создания графического пользовательского интерфейса и назвала ее IFC (Internet Foundation Classes). Эта библиотека была основана на совершенно других принципах. Элементы пользовательского интерфейса, такие как меню, кнопки и тому подобное, изображались в пустом окне. Единственная встроенная функциональная возможность, которая требовалась для работы с этой библиотекой, заключалась в способе, которым окно выводилось на экран. Таким образом, элементы графического пользовательского интерфейса, созданные с помощью библиотеки IFC, выглядели и вели себя точно так же, но не зависели от платформы, на которой запускалась программа. Компании Sun и Netscape объединили свои усилия и усовершенствовали этот подход, создав библиотеку под кодовым названием "Swing". С тех пор слово Swing стало официальным названием набора инструментальных средств для создания машинонезависимого поль-
зовательского интерфейса. Этот набор представляет собой часть библиотеки JFC (Java Foundation Classes), в которую входят также средства для создания интерфейса прикладных программ (API).
DDБиблиотека Swing не может полностью заменить собой библиотеку AWT. Она просто дает пользователю больше возможностей для создания пользовательского интерфейса. Основная архитектура библиотеки AWT, в частности, обработка событий, остается той же, что и в версии Java 1.1. (Средства обработки событий, предусмотренные библиотеками AWT в версиях Java 1.0 и Java 1.1, значительно отличаются друг от друга.) В библиотеке Swing используется модель обработки событий, появившаяся в версии Java 1.1, причем один из вариантов этой библиотеки можно даже загрузить с Web-страницы компании Sun и применить для программ, написанных на языке Java 1.1. Итак, хотя компоненты пользовательского интерфейса из библиотеки AWT в настоящее время все еще вполне доступны, программисты все реже и реже применяют их при разработке своих приложений. В дальнейшем, употребляя слово "Swing", мы будем иметь в виду машинонезависимые классы для создания пользовательского интерфейса, а говоря "AWT", — соответствующий механизм для создания окон, например, обработку событий.
D
D
Разумеется, элементы пользовательского интерфейса библиотеки Swing появляются на экране немного медленнее, чем компоненты, предусмотренные в библиотеке AWT. Однако на современных машинах это практически незаметно. С другой стороны, у библиотеки Swing есть несколько весьма существенных достоинств.
Она содержит более богатый и более удобный набор элементов пользовательского интерфейса.
Эта библиотека намного меньше зависит от платформы, на которой будет выполняться программа. Следовательно, она содержит меньше ошибок, характерных для отдельной платформы.
Библиотека Swing дает возможность пользователям работать на разных платформах.
Все это значит, что библиотека Swing в конце концов позволит выполнить обещание компании Sun: "Написано однажды, работает везде!".
Третье достоинство библиотеки Swing может оказаться и недостатком: если элементы пользовательского интерфейса на разных платформах выглядят одинаково, то (по крайней мере на некоторых платформах) они обязательно будут отличаться от ее собственных компонентов, т.е. пользователи будут в них хуже разбираться.
В библиотеке Swing эта проблема решена очень элегантно. Программисты, использующие библиотеку Swing, могут придать своей программе внешний вид в заданном стиле. Например, на рис. 7.1 и 7.2 показано, как выглядит одна и та же программа в средах Windows и Motif. (По очевидным причинам, связанным с авторскими правами, средства, позволяющие адаптировать программу для платформ Windows и Macintosh, доступны лишь в системах поддержки выполнения программ на этих платформах.)
Рис. 7. 1. Внешний вид программы в среде Windows
Рис. 7.2. Внешний вид программы в среде Motif
Несмотря на то что в книге нет места, чтобы подробно описать, как это делается, программисты сами могут изменить стиль интерфейса своих приложений и даже разработать новые средства для этого. Это утомительный процесс, при котором нужно указать, какие именно компоненты библиотеки Swing должны быть изображены на экране. Правда, некоторые разработчики уже сделали это при переносе языка Java на такие нетрадиционные платформы, как киоски или портативные терминалы (handheld terminals). Более подробную информацию об этом можно найти в разделе "Swing Connection" на Web-странице http://java.sun.com/products/jfc/tsc.
Более того, компания Sun разработала платформнонезависимый стиль, получивший название "Metal". Интерфейс, выполненный в этом стиле, выглядит весьма привлекательно (рис. 7.3).
Рис. 7.З. Внешний вид программы в стиле Metal
Итак, библиотека Swing более надежна, функциональна и независима, чем библиотека AWT. Кроме того, ее легче использовать, чем компоненты пользовательского интерфейса из библиотеки AWT. Мы считаем, что в будущем программирование пользовательского интерфейса на языке Java будет осуществляться только на основе библиотеки Swing, и она будет все шире применяться при создании новых программ на языке Java.
Если вы уже создавали графические пользовательские интерфейсы, используя для этого среды Visual Basic, Delphi или Visual C++, то знаете, насколько легко применять
готовые компоненты, предоставляемые этими средами. Эти средства позволяют визуально программировать интерфейс и сами генерируют нужный для этого код. Хотя некоторые интегрированные среды для создания интерфейса на языке Java уже существуют, они еще не так развиты, как соответствующие средства визуального программирования в среде Windows. В любом случае, чтобы полностью понимать, как среда визуального программирования генерирует код для графического пользовательского интерфейса, и эффективно использовать нужные для этого средства, необходимо знать, как этот интерфейс создается вручную. Естественно, в большинстве случаев дляэтого нужнонаписать очень большую программу.
Создание фреймов
Окно верхнего уровня (т.е. окно, не содержащееся внутри другого окна) в языке Java называется фреймом (frame). В библиотеке AWT для такого окна предусмотрен класс Frame. В библиотеке Swing аналог этого класса называется JFrame. Класс JFrame расширяет класс Frame и является одним из немногих компонентов в библиотеке Swing, которые не изображаются на канве. Итак, украшения (кнопки, строки заголовков, пиктограммы и т.д.) программируются с помощью пользовательской системы окон, а не библиотеки Swing.
У большинства компонентов библиотеки AWT в библиотеке Swing есть аналоги, имена которых образованы путем приписывания буквы "J" к именам соответствующих классов библиотеки AWT, т.е. JButton, JPanel и т.д.
Фрейм— это пример контейнера (container). Это значит, что фрейм может содержать в себе другие компоненты пользовательского интерфейса, такие как кнопки или поля для ввода текста. В этом разделе мы рассмотрим самые распространенные методы работы с классом JFrame.
В листинге 7.1 приведена простая программа, отображающая на экране пустой фрейм, как показано на рис. 7.4.
Рис. 7.4. Простейший видимый фреймРис. 7.4. Простейший видимый фрейм
Рис. 7.4. Простейший видимый фрейм
Рис. 7.4. Простейший видимый фрейм
import javax.swing. *;
public class SimpleFrameTest
" {
..}}public static void main(String[" args) {
.
.
}
}
SimpleFrame frame - new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show();
}
class SimpleFrame extends JFrame
{
public SimpleFrame(]
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public static final int DEFAULT_WIDTH - 300;
public static final int DEFAULT_HEIGHT = 200;
}
Проанализируем эту программу построчно.
Классы библиотеки Swing находятся в пакете j avax. swing. Имя j avax означает, что данный пакет является расширением языка Java и не входит в число основных пакетов. Классы библиотеки Swing на самом деле представляют собой расширение языка Java 1.1. Поскольку эти классы не входят в иерархию основных классов языка, их можно загружать в броузеры, совместимые с версией Java 1.1. (Менеджер безопасности такого броузера не позволит загрузить пакет, имя которого начинается со слова “java”.) В языке Java 2 пакет Swing больше не имеет расширения, входя при этом в иерархию основных пакетов. Эти классы поддерживаются любой реализацией языка Java, совместимой с версией Java 2. Несмотря на это, имя j avax в языке осталось, чтобы сохранить совместимость с версией Java 1.1. (На самом деле пакет Swing сначала назывался com. sun . j ava . swing, затем в ранних бета-версиях языка Java 2 это имя сократилось до j ava . awt. swing, в предыдущих бета-версиях пакет Swing снова назывался com. sun.jswing, но после многочисленных протестов программистов
было выбрано окончательное имя — j avax. swing.)
По умолчанию фрейм имеет совершенно бесполезный размер 0x0 пикселей. Затем определяется подкласс SimpleFrame, конструктор которого устанавливает размер 300x200 пикселей. В методе main класса SimpleFrameTest сначала создается объект класса SimpleFrame. ,
Далее определяется, что должно произойти, если пользователь закроет фрейм. В данном случае программа должна завершить свою работу. Для этого используется оператор
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
В других программах, имеющих несколько фреймов, завершать программу только потому, что пользователь закрыл один из них, совершенно не обязательно. Обычно после своего закрытия фрейм исчезает с экрана, а программа продолжает работу.
Параметр EXIT_ON_CLOSE метода setDefaultCloseOperation впервые появился в версии 1.3 пакета Java 2 SDK. Если вы используете предыдущие версии, эту строку придется удалить из программы. (Это придется сделать и в большинстве программ, приведенных в этой книге.)
Разумеется, если вызов метода setDefaultCloseOperation будет удален, программа не завершит свою работу после закрытия окна. Чтобы выйти из программы, ее придется "убить" (to kill). В среде X Windows в меню обычно есть опция, позволяющая "убить", "аннигилировать" или "уничтожить" программу. Детали зависят от диспетчера окна. В среде Windows можно вызвать на экран список задач (с помощью комбинации клавиш <CTRL+ALT+DEL>) и завершить выполнение соответствующей программы.
Разумеется, это — сильнодействующее средство. Вместо этого можно заменить вызов метода setDefaultCloseOperationследующим кодом.
frame.addWindowListener(new
WindowAdapter(I
{
public void windowclosing(WindowEvent e) {
I System.exit(0);
});
В главе 8 вы узнаете, почему этот фрагмент кода прекращает работу приложения при закрытии фрейма.
В предыдущем замечании описывался "официальный" способ закрытия фрейма при выходе из программы в версиях пакета Java 2 SDK, предшествующих версии 1.3. Однако в некоторых из них вызов
farame. setDefaultCloseOperation (3 )*;
волшебным образом работает как надо. 'Очевидно, еще до того, как в класс JFrame была включена константа EXIT_ON_CLOSE, В пакет SDK были добавлены функциональные возможности для обработки "выхода из программы".
Простое создание фрейма не приводит к его автоматическому появлению на экране. В начале своего существования все фреймы невидимы. Это дает программисту возможность добавлять к фрейму компоненты еще до того, как он впервые появится на экране. Чтобы показать фрейм на экране, метод main вызывает метод show из соответствующего объекта класса JFrame.
В конце концов, метод main завершает свою работу. Обратите внимание на то, что это не приводит к прекращению работы программы. Завершается лишь ее основной поток. Поток пользовательского интерфейса, обеспечивающий работу программы, активируется выводом фрейма на экран.
Класс JFrame наследует метод show от суперкласса window. У класса window есть суперкласс Component, который также имеет метод show. Метод Component. show объявлен нежелательным (deprecated), поэтому для того, чтобы сделать компонент видимым, мы рекомендуем вызывать метод setvisible (true). Для окон и фреймов имеет смысл вызывать метод show, а не setvisible, так как метод show одновременно делает окно ВИДИМЫМ и выводит его на передний план.
Запуск программы проиллюстрирован на рис. 7.4. Это действительно совершенно непривлекательное окно верхнего уровня. Как видно на рисунке, строка заголовка и такие окружающие украшения, как пиктограммы для изменения размеров окна, изображаются операционной системой, а не компонентами библиотеки Swing. При запуске этой программы в среде XWindows эти элементы будут совершенно другими. Все, что находится внутри фрейма, изображается с помощью библиотеки Swing. В данной программе фрейм просто заполняется фоном, цвет которого задается по умолчанию.
Работая с пакетом SDK 1 4, вы можете отключить все украшения фреймов, вызвав метод frame.setUndecorated(true; .
D
Приведенная выше программа содержит два класса, один — для определения фрейма, а второй — для изображения фрейма на экране. Часто в подходящий для этого класс помещают метод main.
class SimpleFrame extends JFrame
{
public static void main(String(] args)
{
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show();
)
public SimpleFrame(1
{
setSize(DEFAULT WIDTH, DEFAULT HEIGHT); }
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200; }
С одной стороны, если метод main принадлежит классу, описывающему фрейм, то вспомогательный класс создавать не обязательно. С другой стороны, довольно много программистов сочтут такой стиль несколько замысловатым. Следовательно, предпочтительнее отделять класс, запускающий программу на выполнение, от класса, в котором определяется пользовательский интерфейс.
Позиционирование фреймов
В самом классе JFrame есть лишь несколько методов, позволяющих изменить внешний вид фрейма. Разумеется, с помощью волшебной силы наследования большинство методов, задающих размер и местоположение фрейма, можно взять у различных суперклассов класса JFrame. Вероятно, к числу наиболее важных методов следует отнести следующие.
Метод dispose, закрывающий окно и освобождающий все системные ресурсы, использованные при его создании.
Методseticonimage, получающий объект класса Image для применения в качестве пиктограммы при минимизации окна (в терминологии языка Java такое окночастоназывают свернутым (iconized)).
Метод setTit 1е для изменения текста в строке заголовка.
Метод setResizable, получающий булевский параметр и определяющий, можно ли впоследствии изменять размеры фрейма.
Иерархия наследования для класса JFrame показана на рис. 7.5.
Рис. 7.5. Иерархиянаследования для
классов JFrame и JPanel
Как указано в замечаниях об интерфейсе прикладных программ (API), именно в классе Component (являющемся предком всех объектов графического пользовательского интерфейса) и в классе Window (родительском по отношению к классу Frame) следует искать методы для изменения размеров и формы фреймов. Например, метод show, который используется для отображения фрейма на экране, находится в классе Window, а в классе Component есть метод setLocation, позволяющий изменить местоположение компонента. При вызове setLocation(х,у!
левый верхний угол фрейма задается в точке, находящейся на расстоянии х пикселей вправо и у пикселей вниз по отношению к точке (0,0) — левому верхнему углу экрана. Аналогично метод setBounds класса Component позволяет одновременно изменить и размер, и местоположение компонента (в частности, объекта класса JFrame) с помощью вызова
setBounds(х, у, width, height)
Координаты фрейма, задаваемые методами setLocation и setBounds, вычисляются по отношению ко всему экрану. Как будет показано в главе Э, координаты других компонентов внутри контейнера вычисляются по отношению к границам самого контейнера.
Помните: если размер не задан явно, все фреймы по умолчанию имеют размер 0x0 пикселей. Чтобы не усложнять наш пример, мы задали размеры фреймов так, чтобы они были приемлемы для большинства дисплеев. Однако в профессиональных приложениях сначала нужно проверить разрешающую способность экрана и написать код, изменяющий размер фрейма в соответствии с полученной величиной: окно, которое прекрасно смотрится на экране портативного компьютера, на экране дисплея с большой разрешающей способностью будет выглядеть как почтовая марка. Как мы вскоре убедимся, получить размеры экрана в пикселях совсем несложно. Затем эту информацию можно использовать для вычисления оптимального размера окон в вашей программе.
Замечания об интерфейсе прикладных программ, помещенные в этом разделе, дают представление о наиболее важных методах, позволяющих настраивать фреймы. Некоторые из них определены в классе JFrame. Иногда для того, чтобы найти подходящий метод, нужно перелистать всю документацию об интерфейсе прикладныхпрограмм. Ксожалению, сделатьто же самое сдокументацией по пакету SDK намного сложнее. Для подклассов в документацию по интерфейсу прикладных программ помещена только информация о замещаемых методах. Например, метод show можно применять к объектам класса JFrame, но, поскольку он просто наследуется от класса Window, в документации по классу JFrame о нем ничего не сказано. Если вы считаете, что некий метод должен существовать, а в документации по классу, с которым вы работаете, его нет, попробуйте просмот- ретьописаниеметодов суперклассов этогокласса. Вначалекаждойстраницыдо- кументации по интерфейсу прикладных программ есть гиперссылки на суперклассы. Кроме того, после описания новых и замещаемых методов приводится список всех наследуемых методов.
Для того чтобы продемонстрировать, что можно делать с окнами, закончим этот раздел простой программой, в которой устанавливаются размеры фреймов. При этом должны выполняться следующие условия.
Фрейм должен занимать четверть экрана.
Фрейм должен находиться в центре экрана.
Например, если экран имеет размер 800x600 пикселей, нужен фрейм размером 400x300 пикселей, верхний левый угол которого находится в точке (200, 150).
Для этого нам нужен метод, позволяющий определять разрешающую способность экрана. Очевидно, он должен взаимодействовать с соответствующей операционной системой, поскольку только она может хранить такую информацию. В языке Java системно-зависимую информацию обычно можно получить с помощью инструментария (toolkit). В классе Toolkit есть метод getScreenSize, возвращающий размер экрана в виде объекта класса Dimension. Объект d типа Dimension одновременно хранит ширину и высоту фрейма в открытых (!) переменных width и height.
Вот фрагмент, с помощью которого можно определить размер экрана.
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize(); int screenwidth = int screenHeight = screensize.height;
Кроме того, для фрейма предусмотрена пиктограмма. Поскольку процесс изображения рисунков на экране также зависит от операционной системы, нам снова нужно средство для загрузки рисунка. Затем этот рисунок устанавливается в качестве пиктограммы для фрейма.
Image img = set Icon(img);
В зависимости от операционной системы эту пиктограмму можно увидеть в разных местах. Например, в системе Windows пиктограмма отображается в левом верхнем углу окна и ее можно увидеть в списке активных задач, если нажать клавиши <ALT+TAB>.
В листинге 7.2 приведена полная программа. Запустив ее, обратите внимание на пиктограмму "Core Java".
Обычно главный фрейм программы имеет максимальный размер. Для того чтобы максимально увеличить размер фрейма, работая с пакетом SDK 1.4, достаточно вызвать метод
frame.setExtendedState(Frame.MAXIMIZED BOTH);
Если написанное вами приложение работает c несколькими дисплеями, то, чтобы определить размеры экранов, следует применять классы GraphicsEnvironment и GraphicsDevice. Пакет SDK 1.4 позволяет также выполнять приложения в полноэкранном режиме.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CenteredFrameTest
{
public static void main(String[; args)
(
S.CenteredFrame frame = new CenteredFrame();
frame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE;;
frame.show(] ;
}
}
class CenteredFrame extends JFrame
16.17.18.19.20.16.17.18.19.20.21.22.23.24.-25.26.27.21.22.23.24.-25.26.27.28.29.30.31.32.28.29.30.31.32.33.34.35.36.37.33.34.35.36.37.public CenteredFrame(;
16.
17.
18.
19.
20.
16.
17.
18.
19.
20.
21.
22.
23.
24.
-25.
26.
27.
21.
22.
23.
24.
-25.
26.
27.
28.
29.
30.
31.
32.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
33.
34.
35.
36.
37.
(
// Определяем размеры экрана.
Toolkit kit = Toolkit. getDefaultToolk.it ();
Dimension screenSize = kit.getScreenSize(); int screenHeight = screensize.height; int screenWidth = screenSize.width;
// Помещаем фрейм в центре экрана.
setsize(screenwidth/ 2, screenHeight / 2);
setLocation(screenwidth/ 4, screenHeight / 4);
// Задаем пиктограмму и заголовок фрейма.
Image img = kit.getImage("icon.gif"); seticonimage(img);
setTitle("CenteredFrame");
java.awt.Component 1.0
boolean isVisible()
Проверяет, можно ли данный компонент сделать видимым. Компоненты являются видимыми с самого начала, за исключением таких компонентов верхнего уровня, как JFrame.
void setVisible(boolean b)
Показывает или скрывает компонент в зависимости от значения парамет- pab (true или false).
boolean isShowing()
Проверяет, можно ли показать компонент на экране. Для этого он должен быть видимым и находиться внутри контейнера, который также выводится наэкран.
boolean isEnabled()
Проверяет, можно ли сделать компонент доступным. Доступный компонент может получать информацию с клавиатуры. Компоненты являются доступными с самого начала.
void setEnabled(boolean b)
Делает объект доступным или недоступным.
Point getLocation(]1.1
Возвращает местоположение левого верхнего угла данного компонента по отношению к левому верхнему углу окружающего его контейнера. (Объект р класса Point инкапсулирует координаты хп у В ПОЛЯХ р . х и р . у.)
Point getLocationOnScreen(]1.1
Возвращает местоположение левого верхнего угла данного компонента по отношению к координатам экрана.
void set.Bonus(int x, int у, int width, int height)1.1
Перемещает и изменяет размеры компонента. Местоположение компонента задается параметрами х и у, а новый размер — параметрами width и height.
void setLocation(int x, int y)1.1
void setLocation(Point p)1.1
Перемещает компонент в новую точку. Координаты х 11 у (или р . х и p • у) являются координатами контейнера, если данный компонент не является компонентом верхнего уровня, в противном случае (например, для объектов класса JFrame) используются координаты экрана.
1.11.1• Dimension getSize(int width, int height) get setsize(Dimension d)1.1
1.1
1.1
Задает новые размеры компонента.
java.awt.Window 1.0
void toFront()
Выводит окно на экран поверх всех других окон.
void toBack()
Помещает данное окно позади всех окон, выведенных на экран и, соответственно, переупорядочивает остальные окна.
|AP11 java.awt.Frame 1.0
void setResizable(boolean b)
Определяет, можно ли изменить размеры фрейма.
void setTitle(String s)
Помещает заголовок фрейма в строку s.
void seticonimage(Image image)
Параметры: image Изображение, которое вы хотите использовать в качестве пиктограммы для фрейма.
void setUndecorated(boolean b) 1.4
Если параметр b имеет значение true, украшения фрейма удаляются.
void getExtendedState() 1.4
void setExtendedState(int state) 1.4
Определяет или устанавливает размер окна. Переменная state может принимать значения
Frame.NORMAL
Frame.ICONIFIED
Frame.MAXIMIZEDHORIZ
Frame.MAXIMIZED_VERT
Frame.MAXIMIZED BOTH
java.awt.Toolkit 1.0
static Toolkit getDefaultToolkit(]
Возвращает объект класса Toolkit, предусмотренный no умолчанию.
Dimension getScreenSize()
Определяет размеры экрана.
Image getlmage(string filename)
Загружает изображение из файла filename.
Отображение информации на панели
В этом разделе мы покажем, как выводить информацию внутри фрейма. Например, в главе 3 мы выводили строку "Not a Hello, World program" на консоль в текстовом режиме. Теперь мы выведем это сообщение внутри фрейма, как показано на рис. 7.6.
I siI siHRBHRBNot a Mato, Work? programNot a Mato, Work? program
I si
I si
HRB
HRB
Not a Mato, Work? program
Not a Mato, Work? program
Рис. 7.6. Простая графическая программа
Строку сообщения можно вывести и просто поверх фрейма, однако так никто из профессиональных программистов не делает. В языке Java фреймы предназначены именно для того, чтобы быть контейнерами для компонентов (например, меню или других элементов пользовательского интерфейса). Обычно рисунки выводятся в другом компоненте, называемом панелью (panel), которая добавляется к фрейму.
Структура класса JFrame неожиданно оказалась сложной. Его внутреннее устройство показано на рис. 7.7.
Как видим, класс JFrame состоит из четырех слоев, каждый из которых представляет собой окно (pane). Корневая (root), слоеная (layered) и прозрачная (glass) панели не представляют для нас интереса. Они нужны лишь для создания меню и панели содержимого (content pane), выдержанных в определенном стиле. Наиболее интересной с точки зрения использования библиотеки Swing является панель содержимого. При разработке фрейма компоненты добавляются в окно содержания с помощью следующего кода.
Container contentPane = getContentPane();
Component с = ... ; contentPane.add(c);
В качестве альтернативы, если вы хотите просто увидеть компонент пакета Swing внутри фрейма, можно просто связать панель содержимого с этим компонентом: JComponent с = ... ;
frame.setContentPane(c);
При работе с библиотекой AWT вызов метода add приводит к тому, что компоненты добавляются непосредственно внутрь фрейма. Работая с библиотекой Swing, это сделать невозможно — все компоненты необходимо помещать внутрь панели содержимого.
Допустим, что мы хотим добавить в панель содержимого одну панель, на которой мы отобразим наше сообщение. Панели реализуются классом JPanel. Они представляют собой элементы пользовательского интерфейса и обладают двумя полезными свойствами.
На их поверхности можно рисовать.
Они сами по себе являются контейнерами.
Таким образом, на панели можно размещать любые элементы пользовательского интерфейса (кнопки, бегунки и т.п.).
Однако добавлять плоскую панель JPanel к панели содержимого нет■ смысла-— она не делает ничего интересного. Чтобы придать программе осмысленность, следует использовать наследование и создать новый класс, а затем заместить или добавить методы, обеспечивающие дополнительные функциональные возможности, которых мы хотим достичь.
В частности, для того, чтобы рисовать на панели, предварительно нужно выполнить следующие действия.
Определить класс, расширяющий класс JPanel.
Заместить методpaintcomponent в этом классе.
На самом деле метод paintComponent находится в классе JComponent — родительском классе всех неоконных компонентов библиотеки Swing. Он получает один параметр типа Graphics, который представляет собой набор установок для изображения рисунков и текста, например, в нем задается шрифт и цвет текста. Все рисование в языке Java осуществляется с помощью объектов класса Graphics. В этом классе предусмотрены шаблоны для рисования, рисунки и текст.
Параметр типа Graphics напоминает контекст устройства в среде Windows или графический контекст в средеХ11.
Вот как создается панель, на которой можно рисовать.
class MyPanel extends JPanel {
public void paintcomponent(Graphics g)
{
... // Код, выполняющий рисование.
}
Каждый раз, когда возникает необходимость перерисовать окно, неважно по какой причине, обработчик событий уведомляет об этом данный компонент, что приводит к выполнению метода paintComponent всех компонентов этого типа.
Метод никогда не следует вызывать самостоятельно. Когда нужно перерисовать окно вашего приложения, он вызывается автоматически, и вмешиваться в этот процесс не нужно.
Какого рода действия при этом выполняются? Например, рисование, поскольку пользователь может увеличивать или уменьшать размер окна. Если пользователь открыл новое окно, которое перекрыло существующее, сделав его невидимым, окно приложения оказывается поврежденным и также должно быть перерисовано. (Графическая система не сохраняет в памяти пиксели нижележащего окна.) Наконец, когда окно отображается на экран впервые, нужно выполнить код, указывающий, как и где должны быть отображены его первоначальные элементы.
Если вам нужно принудительно перерисовать экран, вместо метода paintComponent вызывайте метод repaint. Метод repaint вызовет метод paintComponent для всех компонентов, у которых заданы аргументы типа Graphics.
Как видно из приведенного выше фрагмента программы, метод paintComponent имеет один параметр типа Graphics. Размеры объекта типа Graphics при отображении на экран задаются в пикселях. Координата (0,0) соответствует левому верхнему углу компонента, на поверхности которого выполняется рисование.
Отображение текста на экране (обычно называемое рендерингом текста (rendering text)) считается особой разновидностью рисования. Класс Graphics содержит метод имеющий следующий синтаксис: х, у)
В нашем случае строка "Not a Hello, World Program" выводится на экран внутри исходного окна, занимающего приблизительно четверть экрана по ширине и половину — по высоте. Несмотря на то что нам пока неизвестно, как задается размер строки, начнем ее в точке с координатами (75, 100). Это означает, что вывод первого символа строки начинается с точки, отстоящей на 75 пикселей от правого края и на 100 пикселей — от верхнего. (На самом деле базовая линия строки находится на 100 пикселей ниже верхнего края.) Итак, метод paintComponent выглядит примерно так.
classextends J{anel
< public voidg)
{ ... //См. a Hello, World program", Message_X, } public static final int= 75;
public static final int= 100;
}
Однако этот метод еще не закончен. Класс NotHelloWorldPanel расширяет класс JPanel, имеющий собственную информацию о том, каким образом следует изображать панель, а именно: какой цвет фона для этого нужно выбрать. Для того чтобы убедиться, что суперкласс действительно выполняет эту часть работы, перед началом рисования нужно вызвать метод super
class NotHelloWorldPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
... // Код для рисования.
}
Если вы программируете с помощью старой библиотеки AWT, то заметите небольшие различия. В старой библиотеке AWT рисование происходило на компоненте Canvas, объекте подкласса Component. В библиотеке Swing нет специального класса, аналогичного классу Canvas. Рисовать можно только на компоненте библиотеки Swing. Однако, если нужно как-то отделить область рисования от остальной части пользовательского интерфейса, рисовать следует на поверхности объекта подкласса JPanel.
Более важное отличие состоит в том, что для рисования не применяется метод paint. Действительно, если метод paint в вашей программе замещается, то она не будет правильно работать. Теперь нужно вызывать метод JComponent.paint, выполняющий множество сложных операций, например, установку контекста графики и буферизацию изображений. В библиотеке Swing всегда следует использовать метод paintComponent.
В старой библиотеке AWT иногда нужно было определять метод update, чтобы не очищать окно и избежать мерцания экрана при вызове метода paint. При работе с пакетом Swing этого не требуется. Компоненты библиотеки Swing применяют буферизацию, что позволяет рисовать, не опасаясь мерцания экрана.
Полный код программы приведен в листинге 7.3.Полный код программы приведен в листинге 7.3.1. import javax.swing.*;import java.awt.*;2. import java.awt.*;3.4. public class NotHelloWorld1. import javax.swing.*;import java.awt.*;2. import java.awt.*;3.4. public class NotHelloWorld101112101112public static void main(String[] args) {NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.serDefaultCloseOperation(JFrame,EXIT_ON_CLOSE]' , frame.show();1public static void main(String[] args) {NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.serDefaultCloseOperation(JFrame,EXIT_ON_CLOSE]' , frame.show();113.14151617181920212213.141516171819202122/**Фрейм, содержащий панель сообщения.*/class NotHelloWorldFrame extends JFrame/**Фрейм, содержащий панель сообщения.*/class NotHelloWorldFrame extends JFramepublic NotHelloWorldFrame() (public NotHelloWorldFrame() (setTitle("NotHelloWorld"; ;setSize(DEFAULT WIDTH, DEFAULT HEIGHT);setTitle("NotHelloWorld"; ;setSize(DEFAULT WIDTH, DEFAULT HEIGHT);
Полный код программы приведен в листинге 7.3.
Полный код программы приведен в листинге 7.3.
1. import javax.swing.*;import java.awt.*;
2. import java.awt.*;
3.
4. public class NotHelloWorld
1. import javax.swing.*;import java.awt.*;
2. import java.awt.*;
3.
4. public class NotHelloWorld
10
11
12
10
11
12
public static void main(String[] args) {
NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.serDefaultCloseOperation(JFrame,EXIT_ON_CLOSE]' , frame.show();
1
public static void main(String[] args) {
NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.serDefaultCloseOperation(JFrame,EXIT_ON_CLOSE]' , frame.show();
1
13.
14
15
16
17
18
19
20
21
22
13.
14
15
16
17
18
19
20
21
22
/**
Фрейм, содержащий панель сообщения.
*/
class NotHelloWorldFrame extends JFrame
/**
Фрейм, содержащий панель сообщения.
*/
class NotHelloWorldFrame extends JFrame
public NotHelloWorldFrame() (
public NotHelloWorldFrame() (
setTitle("NotHelloWorld"; ;
setSize(DEFAULT WIDTH, DEFAULT HEIGHT);
setTitle("NotHelloWorld"; ;
setSize(DEFAULT WIDTH, DEFAULT HEIGHT);
// Добавляем панель внутрь фрейма.
NotHelloWorldPanel panel = new NotHelloWorldPaneli);
Container contentPane = getContentPane();
contentpane.add(panel);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
/**
Панель, на которой отображается сообщение.
*/
class NotHelloWorldPanel extends JPanel
{
public void paintcomponent (Graphics g)
{
super.paintcomponent(g);
g.drawstring("Not a Hello, World program",
MESSAGE_X, MESSAGE_Y);
}
public static final int MESSAGE_X = 75;
public static final int MESSAGE_X = 75;
}
javax.swing.JFrame 1.2
Container getContentPane(I
Возвращает панель содержимого для данного объекта класса JFrame.
j
void repaint()
Вызывает перерисовку компонента "при первой возможности".
public void repaint(int x, int y, int width, int height) Вызывает перерисовку части компонента "при первой возможности".
javax.swing.JComponent 1.2
void paintcomponent(Graphics g)
Замещает метод и описывает, как нужно нарисовать компонент.
Двухмерные фигуры
Начиная с версии 1.0, в классе Graphics содержатся методы для рисования линий, прямоугольников, эллипсов и т.д. Однако эти операции предоставляют слишком ограниченный набор возможностей для рисования. Например, в них нельзя изменить толщину линии и повернуть фигуру на произвольный угол.
В версии 2.0 и последующих версиях пакета SDK появилась библиотека Java 2D, реализующая более мощный набор графических операций. В этой главе мы рассмотрим лишь ее основные свойства — более подробная информация представлена во втором томе в главе об усовершенствованной библиотеке AWT.
Для того чтобы нарисовать фигуру с помощью библиотеки Java 2D, нужно создать объект класса Graphics2D. Этот класс является подклассом класса Graphics. Если используется пакет SDK, содержащий библиотеку Java 2D, то такие методы, как paintComponent, автоматически получают объекты класса Graphics2d. Для этого нужно просто применить приведение типов, как показано ниже.
public void paintcomponent(Graphics g) {
Graphics g2 = (Graphics2D)g;• ,
)
Библиотека Java 2D создает геометрические фигуры с помощью объектноориентированного подхода. В частности, в ней есть классы для рисования линий, прямоугольников и эллипсов.
Line2C
Rectangle2E
Ellipse2D
Эти классы реализуют интерфейс Shape.
Библиотека Java 2D поддерживает рисование и более сложных фигур, в частности, дуг, квадратичных и кубических кривых, а также произвольных линий. Более подробная информация об этом содержится в томе 2.
Для того чтобы нарисовать фигуру, сначала нужно создать объект некоторого класса, реализующего интерфейс Shape, а затем вызвать метод draw класса Graphics2D. Например:
Rectangle2D rect = ...; g2.draw(rect);
До появления библиотеки Java 2D программисты использовали для рисования фигур методы класса Graphics, например, метод drawRectangle. На первый взгляд старый способ вызова методов выглядит немного проще. Однако, используя библиотеку Java 2D, программист имеет больше свободы действий— впоследствии он может уточнить свой рисунок с помощью многочисленных средств, предусмотренныхвбиблиотекеJava2D.
Есть одна трудность, связанная с использованием классов библиотеки Java 2D при рисовании фигур. В отличие от методов рисования, предусмотренных в версии 1.0, в которых применялись цело Ч ИСЛ С! 1НЫС координаты, выраженные в пикселях, в библиотеке Java 2D используются координаты, представленные числами с плавающей точкой. Во многих случаях это очень удобно, поскольку часто программист представляет себе фигуру, координаты которой выражаются естественными единицами измерения (такими как дюймы или миллиметры), aуж затем переводит их в пиксели. В библиотеке Java 2D в ходе внутренних вычислений применяется одинарная точность чисел с пла-
вающей точкой. Этого вполне достаточно — в конце концов, основной целью геометрических вычислений является вывод пикселей на экран или принтер. Поскольку все округления в ходе вычислений не выходят за пределы одного пикселя, это совершенно не влияет на внешний вид фигуры. Более того, вычисления над числами с плавающей точкой с одинарной точностью на некоторых платформах выполняются быстрее, к тому же числа типа floatзанимают вдвое меньше памяти, чем числа типа double.
И все же манипуляции с числами типа float иногда бывают неудобными, поскольку язык Java в вопросах приведения типов непреклонен и требует преобразовывать числа double в числа floatявно. Рассмотрим, к примеру, следующий оператор. float f = 1.2; // Ошибка.
Этот оператор не будет скомпилирован, поскольку константа 1.2имеет тип double, и компилятор беспокоит потеря точности. Придется приписать к константе с плавающей точкой суффикс F:
float f = 1.2F; // Правильно.
Теперь рассмотрим следующий оператор.
rectangle2D Г = ...
float f = r.getWidthf); // Ошибка.
Этот оператор также не скомпилируется по тем же причинам. Метод getWidth возвращает число, имеющее тип double. На этот раз нужно ВЫПОЛНИТЬ приведение типов: float f = (float)r.getWidth(); // Правильно.
Поскольку суффиксы и приведение типов доставляют довольно много хлопот, разработчики библиотекиJava 2D решили предусмотреть две версии каждого класса для рисования фигур: в первой версии все координаты выражаются числами, имеющими тип float (для дисциплинированных программистов), а во второй— числами типа double (для ленивых). (В рамках этой книги мы относим себя ко второй группе и везде используем тип double.)
Разработчики библиотеки выбрали необычный и на первый взгляд запутанный способ создания пакетов, соответствующих этим двум версиям. Рассмотрим класс Rectangle2D. Это абстрактный класс, имеющий два конкретных подкласса, каждый из которых является внутренним и статическим.
Rectangle2D.Float
Rectangle2D.Double
На рис. 7.8 показана диаграмма наследования этих классов.
Лучше всего совсем проигнорировать тот факт, что эти два конкретных класса являются внутренними и статическими — это просто хитроумная уловка, чтобы избежать применения таких имен, как FloatRectangle2D и DoubleRectangle2D. (Статические внутренние классы детально рассмотрены в главе 6.)
При создании объекта класса Rectangle2d.Float используются координаты, имеющие тип float, а в объектах класса Rectangle2d. Double они выражаются через числа типа double.
Rectangle2D.Float floatRect = new Rectangle2d.Float(10. OF,
25.OF, 22.5F, 20.OF);
Rectangle2D.Double doubleRect = new Rectangle2d.Double(10.0,
25.0, 22.5, 20.0);
Рис. 7.8. Классы для рисования прямоугольникаРис. 7.8. Классы для рисования прямоугольника
Рис. 7.8. Классы для рисования прямоугольника
Рис. 7.8. Классы для рисования прямоугольника
Фактически, поскольку и класс Rectangle2d. Float, и класс Rectangle2D. Double расширяют один и тот же класс Rectangle2D, а методы в этих подклассах просто замещают собой методы суперкласса, нет нужды помнить точный тип координат, используемых при создании фигуры. Достаточно применять переменные типа Rectangle2D в качестве ссылок на прямоугольники.
Rectangle2D floatRect = new Rectangle2d.Float (10.OF,
25.OF, 22.5F, 20.0F);
Rectangle2D doubleRect = new Rectangle2d.Double(10.0,
25.0, 22.5, 20.0);
Таким образом, при рисовании фигур программисту нужны лишь внутренние классы.
Параметры конструктора задают координаты верхнего левого угла, ширину и высоту прямогольника.
DDВ действительности класс Rectangle2D. Float имеет один дополнительный метод, который не наследуется от класса Rectangle2d, — метод setRect (float x, float h, float w). Если вместо ссылок типа Rectangle2D. Float использоватьссылкитипаRectangle2D, этотметодстанетнедоступным. Однако это не большая потеря — класс Rectangle2D имеет свой метод setRect с параметрами, имеющими тип double.
D
D
И параметры, и значения, возвращаемые методами класса Rectangle2D, имеют тип double. Например, метод getwidth возвращает значение типа double, даже если ширина хранится в переменной, имеющей тип float,в объекте класса Rectangle2D. Float.
Просто используйте классы, в которых координаты выражаются числами типа double, чтобы избежать значений типа float. Однако, если вам нужно создать тысячи объектов, следует подумать о классах, в которых координаты задаются числами типа Float, чтобы сэкономить память.
Все, что было сказано нами о классах Rectangle2D, относится и к любым другим класам, предназначенным для рисования плоских фигур. Кроме того, существует еще и класс Point2D с подклассами Point2D. Float и Point2D. Double. Вот как создается его объект.
Point2D p = new Point2D.Double(10, 20);
Класс point2D очень полезен. Работа с объектами класса Point2D больше соответствует объектно-ориентированной парадигме, чем работа с отдельными координатами х и у. Многие конструкторы и методы получают на вход параметры, имеющие тип Point2D. Мы полагаем, что при малейшей возможности программист должен использовать именно эти объекты — они обычно делают геометрические вычисления более понятными.
Рис. 7.9. Прямоугольник, ограничивающий эллипсРис. 7.9. Прямоугольник, ограничивающий эллипсИ класс Rectangle2D, и класс Ellipse2D являются производными классами одного и того же суперкласса RectangularShape. Всем известно, что эллипс не является прямоугольной фигурой, однако он ограничен прямоугольником (рис. 7.9).
Рис. 7.9. Прямоугольник, ограничивающий эллипс
Рис. 7.9. Прямоугольник, ограничивающий эллипс
В классе RectangularShape определено более 20 методов, общих для всех фигур, среди них находятся и такие полезные методы, как getWidth, getHeight, getCenterX и getCenterY (однако, к сожалению, во время создания этой книги в нем не было метода getCenter, возвращающего центр фигуры в виде объекта класса Point2D).
В заключение отметим, что в новую библиотеку вошли и старые классы из библиотеки пакета JDK 1.0, заняв свое место в иерархии наследования классов. Так, например, классы Rectangle и Point, которые хранят координаты прямоугольника и его центра в виде целых чисел, расширяют классы Rectangle2D и Point2D.
На рис. 7.10 показаны отношения между классами для рисования фигур. Однако подклассы, в которых координаты выражаются с помощью чисел типа float и double, на рисунке не приведены. Классы, унаследованные от предыдущей версии, на рисунке закрашены.
Объекты классов Rectangle2D и Ellipse2D создаются довольно легко. Нужно только указать
координатыхи улевого верхнего угла;
ширину и высоту.
Для эллипсов эти параметры относятся к ограничивающим их прямоугольникам.
Например, оператор
Ellipse2D e = new Ellipse2D.Double(150, 200, 100, 50);
создает эллипс, ограниченный прямоугольником, левый верхний угол которого находится в точке (150, 200), а сам он имеет ширину и высоту, равные 100 и 50 пикселям соответственно.
Однако иногда левый верхний угол фигуры определить нелегко. Довольно часто прямоугольник задается своим центром (т.е. точкой пересечения диагоналей), а не вершинами. В этом случае невозможно построить прямоугольник с помощью оператора. Rectangle2D rect = new Rectangle2D.Double(px, py,
qx - px, qy - py); // Ошибка.
Рис. 7.10. Классы для рисования прямоугольника
Если параметр р не соответствует координатам верхнего левого угла, одна или обе разности между координатами будут отрицательными и прямоугольник окажется пустым. В этом случае сначала нужно создать пустой прямоугольник, а затем использовать метод setFrameFromDiagonal.
Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(px, py, qx, qy) ;
Еще лучше, если координаты левого верхнего угла известны и представлены в виде объектов р и q класса point2D. воспользоваться следующим оператором.
rect.setFrameFromDiagonal(p, q) ;
При создании эллипса обычно известны его центр и размеры полуосей, а не координаты верхнего левого угла ограничивающего прямоугольника. Существует метод setFrameFromCenter, использующий центр, но ему нужно задать остальные четыре угловые точки. Следовательно, эллипс можно нарисовать таким образом.
Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);
Для того чтобы нарисовать линию, нужно задать начальную и конечную точки, либо как объекты класса Point2D, либо как пару чисел:
Line2D line = new Line2D.Double(start, end);
ИЛИ
Line2D line = new Line2D.Double(startX, startY, endX, endY);
Программа, приведенная в листинге 7.4, рисует следующие фигуры.
•
Эллипс, вписанный в прямоугольник.
Диагональ прямоугольника.
Окружность, центр которой совпадает с центром прямоугольника.
Результат работы программы показан на рис.
Прямоугольники и эллипсы
import java.awt.*;
import
import
public class DrawTest
{
public static voidargs)
{
=
/**
Фрейм, содержащий п'анель для рисования.
*/
class DrawFrame extends JFrame
public
setTitle("DrawTest");
setsize(DEFAUT-WIDTH, DEFAUT-HEIGHT);
// Добавить панель к фрейму.
DrawPanel panel = new DrawPanel();
Container contentPane = getContentPane();
contentPane.add(panel) ;
}
publicstaticfinalintDEFAUT-WIDTH = 400;
publicstaticfinalintDEFAUT-HEIGHT = 400;
}
/**
Панель, на которой изображаются прямоугольники и эллипсы.
*/
class DrawPanel extends JPanel
{
public void paintcomponent(Graphics g)
{
super.paintcomponent(g) ;
Graphics2D g2 = (Graphics2D)g;
// Рисуем прямоугольник.
double leftx = 100;
double topY = 100;
double width = 200;
double height - 150;
Rectangle2D rect = new Rectangle2D.Double(leftX, topY,
width, height);
g2.draw(rect);
// Рисуем вписанный эллипс.
Ellipse2D ellipse = new Ellipse2D.Double();
ellipse.setFrame(rect);
g2.draw(ellipse);
// Рисуем диагональ.
g2.draw(new Line2D.Double(leftx,topY,
leftX + width, topY + height));
// Рисуем окружность с тем же центром.
double centerX = rect.getCenterX();
double centerY = rect.getCenterY();
double radius = 150;
74 .Ellipse2D circle = new Ellipse2D.Double();
circle.setFrameFromCenter(centerX, centerY,
centerX + radius, centerY + radius);
g2.draw(circle);
}'
}
A 1 java.awt.geom.RectangularShape 1.2
* —' • double getCenterX ()
double getCenterY()
double getMinX()
double getMinY(J
double getMaxX(;
double getMaxY(]
Возвращает центр, минимум или максисмум координат x и у описанного прямоугольника.
double getWidth()
double getHeightO
Возвращает ширину и высоту описанного прямоугольника.
B.B.double getX() double getY()
B.
B.
Возвращает координаты х и у описанного прямоугольника.
java.awt.geom.Rectangle2D.Double 1.2
Rectangle2D.Double(double x, double y, double w, double h) Создает прямоугольник с заданным верхним левым углом, шириной и высотой.
java.awt.geom.Rectangle2D.Float 1.2
Rectangle2D.Float(float x, float y, float w, float h)
Создает прямоугольник с заданным верхним левым углом, шириной и высотой.
java.awt.geom.Ellipse2D.Double 1.2
Ellipse2D.Double(double x, double y, double w, double h)
Создает эллипс, у которого описанный прямоугольник имеет заданные ко-
ординаты верхнего левого угла, ширину и высоту.
java.awt.geom.Point2D.Double 1.2 Point2D.Double(double x, double y) Рисует точку с заданными координатами.
java.awt.geom.Line2D.Double1.2
Line2D.Double(Point2D start, Point2D end)
Line2D.Double(Point2D startX, Point2D startY,
Point2D endX, Point2D startX)
Рисует линию с заданными началом и концом.
Цвета
Метод setPaint класса Graphics позволяет выбрать цвет, который будет применяться при всех дальнейших операциях, связанных с рисованием на графическом контексте или компоненте. Для того чтобы иметь возможность создавать многоцветные рисунки, следует выбрать некоторый цвет, нарисовать фигуру, затем выбрать новый цвет и нарисовать другую фигуру.
Цвет определяется с помощью класса Color. Класс j ava . awt. Color предлагает использовать встроенные константы, приписанные к 13 стандартным цветам, перечисленным в табл. 7.1.
Например,
g2.setPaint(Color.red;
g2.drawstring("Предупреждение", 100, 100);
До появления пакета SDK 1.4 имена констант, задающих цвета, задавались строчными буквами, например Color.red. Это довольно странно, поскольку имена константы принято задавать прописными буквами. Начиная с версии SDK 1.4, стандартные названия цветов указываются как прописными буквами, так и строчными (для сохранения обратной совместимости). В наших программах мы используем прописные буквы. Если вы работаете со старой версией пакета SDK, указывайте названия цветов строчными буквами.
Пользователь может указать свой собственный цвет, создав объект класса Color на основе его красного, зеленого и синего компонентов. Для этого нужно вызвать конструктор класса Color, используя шкалу от 0 до 255 (т.е. один байт) для определения оттенка красного, синего и зеленого цветов.
Color(int redness, int greenness, int blueness)
Вот как можно задать свой собственный цвет.
g2.setPaint(new Color(0, 128, 128); // Бледный сине-зеленый цвет.
g2.drawstring("Добро пожаловать!", 75, 125);
Кроме сплошного цвета, можно выбирать более сложные установки для рисования, в которых варьируются оттенки. Подробности можно найти в главе, посвященной усовершенствованной библиотеке AWT во втором томе. Если вместо объекта класса Graphics2D используется объект класса Graphics, для установки цвета нужно использовать метод setcolor.
BLACK
GREEN
RED
BLUE
LIGHTGRAY
WHITE
CYAN
MAGENTA
YELLOW
DARKGRAY
.ORANGE
GRAY
PINK
Для того чтобы установить цвет фона, следует применять метод setBackground класса Component, являющийся предком класса JPanel. Фактически цвет фона нужно задавать до первого появления фрейма на экране.
MyPanel р = new MyPanel ();
p. setBackground(Color.white] contentPane.add(p);
Существует также метод set Foreground. Он задает цвет, который используется для рисования компонента по умолчанию.
Методы brighter () и darker О класса Color создают (в полном соответствии со своими именами) более светлые или темные варианты текущего цвета. Используя метод brighter, можно легко выделить заголовок или пункт меню. В действительности одного вызова метода brighter () для увеличения яркости экрана не достаточно. Для того чтобы достичь нужного эффекта, требуется применить его три раза:
c.brighter)).brighter!).brighter(]
Для большинства известных цветов в классе SystemColor предусмотрены соответствующие имена. Константы в этом классе инкапсулируют в себе цвета, которые используются для разнообразных элементов пользовательского интерфейса. Например, вызов frame.setBackground(SyetemColor.window)
выбирает в качестве фона фрейма цвет, заданный про умолчанию для всех окон пользовательского интерфейса. (Фон заполняет окно при каждой перерисовке.) Использовать цвета, заданные в классе SystemColor, особенно полезно, если вы хотите, чтобы пользовательский интерфейс был выдержан в той же цветовой гамме, что и рабочая панель. В табл. 7.2 приведены названия системных цветов и их смысл.
desktopdesktopЦвет фона для рабочей панелиЦвет фона для рабочей панели
desktop
desktop
Цвет фона для рабочей панели
Цвет фона для рабочей панели
activeCaption activeCaptionText activeCaptionBorder inactiveCaption inactiveCaptionText inactiveCaptionBorder windowwindowBorderwindowTextmenumenuTextactiveCaption activeCaptionText activeCaptionBorder inactiveCaption inactiveCaptionText inactiveCaptionBorder windowwindowBorderwindowTextmenumenuTextЦвет фона для заголовков
activeCaption activeCaptionText activeCaptionBorder inactiveCaption inactiveCaptionText inactiveCaptionBorder window
windowBorder
windowText
menu
menuText
activeCaption activeCaptionText activeCaptionBorder inactiveCaption inactiveCaptionText inactiveCaptionBorder window
windowBorder
windowText
menu
menuText
Цвет текста для заголовков
Цвет каймы для неактивных заголовков
Цвет фона для неактивных заголовков
Цвет текста для неактивных заголовков
Цвет каймы для неактивных заголовков
Цвет фона для окна
Цвет каймы для окна
Цвет текста внутри окна
Фон для меню
Цвет текста в меню
Окончание табл. 7.2
text
Цвет фона для текста
textText
Цвет текста для текста
textlnactiveText
Цвет текста для неактивных элементов управления
textHighlight
Цвет фона для выделенного текста
textHighlightText
Цвет выделенного текста
control
Цвет фона для элементов управления
controltext
Цвет текста для элементов управления
controlLtHighlight
Бледный цвет выделения для элементов управления
controlHighlight
Цвет выделения для элементов управления
controlShadow
Цвет тени для элементов управления
controlDkShadow
Темный цвет тени для элементов управления
scrollbar
Цвет фона для полосы прокрутки
info
Цвет фона для информационных сообщений
infoText
Цвет текста для информационных сообщений
java.awt.Color1.0
Color (int r, int g, int b) Создает объект класса Color.
Параметры: ГОттенок красного (0-255).
gОттенок зеленого (0-255).
bОттенок синий (0-255).
java.awt.Graphics 1.0
void setColor(Color c)
Изменяет текущие установки цвета. Все последующие графические операции будут использовать новый цвет.
Параметры: cНовый цвет.
java.awt.Graphics2D 1.0
void setPaints(Paint p)
Устанавливает атрибуты рисования для данного графического контекста. Класс Color реализует интерфейс Paint. Следовательно, этот метод можно использовать для установки цвета линий (solid color).
java.awt.Component " 1.0
Параметры: сПараметры: сНовый цвет фона.Новый цвет фона.void setBackground(Color c) Устанавливает цвет фона.
Параметры: с
Параметры: с
Новый цвет фона.
Новый цвет фона.
• void setForeground(Color с)
Устанавливает цвет переднего плана.
Параметры: сПараметры: сНовый цвет переднего плана.
Параметры: с
Параметры: с
Заполнение фигур
Внутренность замкнутых фигур (например, прямоугольников или эллипсов) можно заполнять цветом (соответствующим заданным установкам рисования). Просто вызовите метод fillвместо метода draw.
Rectangle2D rect = ...;
g2.setPaint(Color.red);
g2.fill(rect); // Заполняет объект rect красным цветом.
Программа, приведенная в листинге 7.5, заполняет прямоугольник красным цветом, а эллипс, вписанный в этот прямоугольник, — бледно-зеленым (рис. 7.12).
Рис. 7.12. Заполненные прямоугольники и эллипсыРис. 7.12. Заполненные прямоугольники и эллипсы
Рис. 7.12. Заполненные прямоугольники и эллипсы
Рис. 7.12. Заполненные прямоугольники и эллипсы
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class FillTest
{
11.11.12.12.13.13.14.14.15.15.16.17.16.17.18.19.20.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.65.public static void main(String[' args)
11.
11.
12.
12.
13.
13.
14.
14.
15.
15.
16.
17.
16.
17.
18.
19.
20.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
65.
FillFrame frame = new FillFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ; frame.showO ;
}
}
/**
Фрейм, содержащийпанель для рисования.
/ class FillFrame extends JFrame {
public FillFrame()
{
setTitle("FillTest");
setSize(DEFAUT-WIDTH, DEFAUT-HEIGHT);
// Добавляем панель к фрейму.
FillPanel panel = new FillPanel();
Container contentPane = getContentPane(); contentPane.add(panel] ;
}
public static final int DEFAUT-WIDTH = 400;
public static final int DEFAUT-HEIGHT = 400; }
/**
Панель, на которой изображаются прямоугольники и эллипсы.
*/ class FillPanel extends JPanel {
public void paintcomponent(Graphics g) {
super.paintcomponent(g) ;
Graphics2D g2 = (Graphics2D)g;
// draw a rectangle
double leftX = 100;
double topY = 100;
double width = 200;
double height = 150;
Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
g2.setPaint(Color.RED);
g2.fill(rect);
// draw the enclosed ellipse
Ellipse2D ellipse = new Ellipse2D.Double();
ellipse.setFrame(rect);
g2.setPaint(newColor(0,128, 128)); //Бледно-зеленый.
g2.fill(ellipse);
}
}
Текст и шрифты
Программа "Not a Hello, World", приведенная в начале этой главы, выводила на экран строку текста, шрифт которого задавался по умолчанию. Часто требуется, чтобы текст был набран разными шрифтами. Шрифты задаются названием начертания литер (font face name). Это имя состоит из названия семейства шрифтов (например "Helvetika") и возможного суффикса (например "Bold"). Так, скажем, названия "Helvetika" и "Helvetika Bold" считаются частью одного и того же семейства "Helvetika".
Для того чтобы выяснить, какие шрифты доступны на конкретном компьютере, вызовите метод getAvai1able Fami1yName s класса GraphicsEnvironment. Этот метод возвращает массив строк, состоящих из названий всех доступных шрифтов. Чтобы создать экземпляр класса GraphicsEnvironment, описывающего графическую среду пользовательской системы, используется статический метод getLocal- GraphicsEnvironment. Таким образом, приведенная ниже программа выводит на печать названия всех шрифтов, доступных в вашей системе.
import java.awt.* '
public class ListFonts
{
public static void main(String[] args) {
String[] fontNames = GraphicsEnvironment
.getLocalGraphicsEnvironment(1
.getAvailableFontFamilyNames(I;
for (int i = 0; i fontNames.length; i++) System.out.printin(fontNames[i]);
)
В одной из систем список начинается такими строками.
Abadi MT Condenced light
Arial
Arial Black
Arial Narrow
Arioso
Baskerville
Binner Gothic
И так далее для следующих семидесяти шрифтов.
В документации по пакету SDK утверждается, что такие суффиксы, как "heavy", "medium", "oblique" или "gothic", определяют разновидности шрифтов внутри одного семейства. Как показывает наш опыт, это не так. Суффиксы "Bold", "Italic" и "Bold Italic" распознаются как вариации шрифтов внутри одного и того же семейства, а остальные — нет.
К сожалению, нет абсолютно надежного способа узнать, есть ли в компьютере шрифт, имеющий заданный вид. Имена шрифтов представляют собой торговые марки, а сами шрифты могут быть защищены авторскими правами. Итак, распространение шрифтов часто сопряжено с оплатой лицензии. Разумеется, аналогично дешевым имитациям дорогих духов существуют подделки фирменных шрифтов. Например,
имитации шрифтов Helvetica и Times Roman распространяются вместе с операционной системой Windows под именами Arial и Times New Roman.
Для того чтобы установить какой-то порядок, в библиотеке AWT определены пять логических названий шрифтов.
SansSerif
Serif
Monospaced
Dialog
Dialoglnput
Эти имена всегда соответствуют шрифтам, фактически существующим в данном клиентском компьютере. Например, в системе Windows шрифт SanSerif соответствует шрифту Arial.
Для того чтобы изобразить литеру с помощью заданного шрифта, сначала необходимо создать объект класса Font, а затем указать название шрифта, его стиль и размер литер. Ниже показано, как можно создать объект класса Font.
Font helvbl4 = new Font"Helvetika", Font.BOLD, 14);
Третий аргумент задает размер шрифта.
В конструкторе класса Font логическое имя шрифта можно использовать вместо фактического названия начертания. Затем нужно указать стиль (plain, bold, italic или bold italic), задав второй аргумент конструктора равным одному из следующих значений. Font.PLAIN Font.BOLD Font.ITALIC Font.BOLD + Font.ITALIC
Рассмотрим пример.
Font sansbold!4 = new Font("SansSerif", Font.Bold, 14)
В предыдущих версиях языка Java в качестве логических имен шрифтов использовались имена Helvetica, TimesRoman, Courier и ZapfDingbats. Для обратной совместимости эти имена все еще считаются логическими, несмотря на то, что имя Helvetica относится к реально существующему шрифту, а имена TimesRoman и ZapfDingbats и вовсе не являются названиями шрифтов — фактические шрифты называются "Times Roman" и "Zapf Dingbats" соответственно.
Начиная с версии J2SE 1.3, можно использовать шрифты TrueType. Для этого нужно создать отдельный поток ввода для конкретного шрифта — обычно с файла на диске или с URL-адреса. Затем необходимо вызвать статический метод FontCreateFont.
URL url = new URL("http://www.fonts.com/Wingbats.ttf"); Inputstream in = url.openstream();
Font f = Font.createFont(Font.TRUETYPE_FONT, in);
Этот шрифт является ровным, а его размер равен одному пункту. Чтобы получить шрифт заданного размера, используйте метод deriveFont: Font df = f.deriveFont(14F);
Существуют две перегруженные версии метода deriveFont. Одна из них (с параметром типа float)задает размер шрифта, другая (с параметром типа int) — стиль шрифта. Таким образом, вызов f.deriveFont(14) задает стиль, а не размер! (В результате будет установлен курсив, поскольку двоичное разложение числа 14содержит единицу в разряде, соответствующем курсиву.)
В языке Java шрифты состоят из обычных ASCII-литер, а также символов. Например, если вывести на печать символ ' \u2297 ', набранный шрифтом Dialog, на экране появится символ ®. Доступными являются только символы, указанные в множестве символов Unicode. (Подробная информация о доступных символах и способах добавления новых шрифтов приведена во врезке в конце этой главы.)
Ниже приводится код, который выводит на экран строку "Hello, World" с помощью стандартного шрифта SanSerif полужирного начертания, имеющего размер 14 пунктов.
Font sansboldl4 = new Font("SansSerif ", Font.BOLD, 14);
g2 .setFont(sansboldl41 ;
String message = "Hello, World!";
g2.drawstring(message, 75, 100);
Теперь выровняем строку no центру панели. Для этого нужно знать ширину и высоту панели в пикселях. Эти размеры зависят от трех факторов:
используемого шрифта (в данном случае — sansserif, полужирный, 14 пунктов);
строки (в данном случае — "Hello, World!");
устройства, на котором будет отображаться строка (в данном случае — экран).
Чтобы создать объект, который выводил бы строку с заданными характеристиками на экран, нужно вызвать метод getFontRenderContext класса Graphics2D. Он возвращает объект класса FontRenderContext. Нужно просто передать этот объект методу getStringBounds класса Font.
FontRenderContext context = g2.getFontRenderContext();
Rectangle2D bound = f.getStringBounds(message, context);
Метод getStringBounds возвращает прямоугольник, который окаймляет строку.
базовая пиния ~высотабазовая 'линиябазовая пиния ~высотабазовая 'линиямаксимальная глубина неакцентированных букв
базовая пиния ~
высота
базовая 'линия
базовая пиния ~
высота
базовая 'линия
k p g 1
■— максимальная глубина посадки
" неакцентированных букв
\ ЛИДИН г
Рис. 7.13. Иллюстрация основных терминов, применяемых при наборе текстов
Чтобы проинтерпретировать размеры этого прямоугольника, полезно рассмотреть некоторые основные термины, применяемые при наборе текстов (см. рис. 7.12). Базовая линия (baseline) — это воображаемая линия, которая, например, касается снизу буквы "е". Максимальная высота литер (ascent) — максимальное расстояние от базовой линии до вершин выступающих на базовой линией литер (asceders), например, верхних оконечностей букв “b” или "к". Максимальная глубина посадки литер (descent) — это расстояние от базовой линии до нижних оконечностей литер (descenders), таких как буквы "р" ИЛИ "g".
Интерлиньяж (leading) — это разность между максимальной глубиной посадки литер предыдущей строки и максимальной высотой литер следующей строки. (Название этого термина происходит от свинцовых (lead) полос, которыми наборщики отделяли одну строку от другой.) Высота (height) шрифта — это расстояние между соседними базовыми линиями, т.е. максимальная высота литер + максимальная глубина посадки литер + интервал.
Ширина прямоугольника, возвращаемого методом getStringBounds, задает горизонтальный размер строки. Высота прямоугольника равна сумме максимальной высоты и максимальной глубины посадки литер плюс интерлиньяж. Этот прямоугольник опирается на базовую линию строки. Верхняя абсцисса прямоугольника (координата у) является отрицательной величиной. Таким образом, ширина, высота и максимальная высота посадки литер задаются следующими операторами. double stringwidth = double stringHeight = double ascent = -bounds.getY();
Если нужно узнать, чему равен интерлиньяж или максимальная глубина посадки литер, следует вызвать метод getLineMetries класса Font. Этот метод возвращает объект класса LineMetrics, в котором есть методы для определения интерлиньяжа и максимальной глубины посадки литер.
LineMetrics metrics = f.getLineMetrics(message, context); float descent = metrics.getDescent();
float leading = metrics.getLeading();
В приведенном ниже фрагменте эта информация используется для выравнивания строки по центру окружающей ее панели.
FontRenderContext context = g2.getFontRenderContext();
Rectangle2D bounds = f.getStringBounds(message, context);
// (x,y) — координаты верхнего левого угла текста
double x = (getWidth(; - bounds.getWidth()] / 2;
double у = (getHeight(] - bounds.getHeight(); / 2;
// Прибавить к координате у максимальную высоту литер, // чтобы определить базовую линию.
double ascent = -bounds.getY();
double baseY = у + ascent;
g2.drawstring(message, (int)x, (int)(baseY));
Для того чтобы понять, как происходит выравнивание текста по центру, отметим, что метод getWidth () возвращает ширину панели. Часть этой ширины занимает строка сообщения. Ее горизонтальный размер возвращает метод bounds . getwidth (). Оставшееся пространство нужно равномерно распределить по обеим сторонам строки. Следовательно, размер незаполненного пространства с каждой стороны должен рав-
няться половине разности между шириной панели и длиной строки. Те же рассуждения справедливы и для выравнивания строки по высоте.
Итак, полученная программа рисует базовую линию и ограничивающий прямоугольник. На рис. 7.14 показано, как это выглядит на экране, а сама программа приведена в листинге 7.6.
Рис. 7.14. Внешний вид программы в среде WindowsРис. 7.14. Внешний вид программы в среде Windows
Рис. 7.14. Внешний вид программы в среде Windows
Рис. 7.14. Внешний вид программы в среде Windows
12.13.22.23.24.27.34.35.12.13.22.23.24.27.34.35.
12.
13.
22.
23.
24.
27.
34.
35.
12.
13.
22.
23.
24.
27.
34.
35.
import java.awt.*;
import j ava.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;
public class FontTest
(
public static void main(String[1 args) {
FontFrame frame = new FontFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) frame.show();
)
/*•
Фрейм с панелью для вывода текста.
*/
class FontFrame extends JFrame (
public FontFrame(]
setTitle("FontTest");
setSize(DEFAUT-WIDTH, DEFAUT-HEIGHT);
// Добавляем панель к фрейму.
FontPanel panel = new FontPanel();
Container contentPane = getContentPane J; contentPane.add(panel);
)
public static final int DEFAUT-WIDTH = 300;
public static final int DEFAUT-HEIGHT200;
)
/**
Панель, в центре которой выводится сообщение в рамке.
*/
class FontPanel extends JPanel
{
public voidg)
{
;
Graphics2D g2 =
String message"Hello, World!";
Font f = new36);
// Измеряем длину сообщения.
context =
Rectangle2D bounds =ntext);
// Устанавливаем (х,у) = левый верхний угол текста.
double x =-/ 2;
double у =-/ 2;
//Добавляя кпеременнойу расстояние довершин букв,
//определяемкоординатыбазовой линии.
double ascent =
double baseY = у + ascent;
// Выводим на экран сообщение.
(int)x,
// Рисуем базовую линию.
g2.draw(newbaseY,
x +baseY));
77.
// Рисуем описанный прямоугольник.
Rectangle2D rect = newy,
}
}
1.0
Font (string name, int style, int size) Создает новый объекта класса Font.
Параметры: nameПараметры: nameНазвание шрифта, представляющее собой либо название начертания литер (например "Helvetica Bold"), либо логическое название шрифта (например, "Serif или “SansSerif”).
Параметры: name
Параметры: name
styleСтиль(Font.PLAIN, Font.BOLD, Font.ITALIC
или Font.BOLD+Font.ITALIC)
sizeРазмер пункта (например 12).
String getFontName()
Возвращает название начертания литер (например "Helvetica Bold").
String getFamily()
Возвращает названия семейства шрифтов (например "Helvetica").
String getName()
Возвращает логическое название (например "SansSerif), если оно было присвоено шрифту при его создании; в противном случае возвращает названия начертания литер.
Rectangle2D getStringBounds(String s, FontRenderContext)1.2
Возвращает прямоугольник, в который вписана данная строка. Прямоугольник опирается на базовую линию. Верхняя координата у равна максимальной высоте неакцентированных букв, взятой с отрицательным знаком. Высота прямоугольника равна сумме максимальной высоты и максимальной глубины посадки неакцентированных букв. Ширина прямоугольника равна ширине строки.
LineMetrics getLineMetrics(String s, FontRenderContext context)1.2
Возвращает объект класса LineMetrics, позволяющий определить длину строки.
Font deriveFont(int style)1.2
Font deriveFont(float size)1.2
Font deriveFont(int style, float size)1.2
Возвращает новый объект класса Font, совпадающий с прежним, за исключением размера и стиля, заданных как параметры.
java.awt.font.LineMetrics 1.2
float getAscent((
Определяет максимальную высоту литер среди расстояний от базовой линии до вершин прописных литер.
float getDescent((
Определяет максимальную глубину посадки литер среди расстояний от базовой линии до нижних оконечностей литер.
float getLeading()
Определяет интервал шрифта — пространство между низом предыдущей строки и верхом следующей.
float getHeight()
Определяет общую высоту шрифта — расстояние между двумя базовыми линиями текста (максимальная высота литер + интерлиньяж + максимальная глубина посадки литер).
java.awt.Graphics 1.0
void setFont(Font font)
Устанавливает шрифт для графического контекста. Этот шрифт будет использоваться во всех дальнейших графических операциях.
void drawstring(String str, int x, int y)
Выводит на экран строку, шрифт и цвет которой определяются текушими установками графического контекста.
Параметры:strПараметры:strСтрока, подлежащая выводу.
Параметры:str
Параметры:str
xКоордината х начала строки.
УКоордината у начала строки.
java.awt.Graphics2D 1.2
FontRenderContext getFontRenderContext()
Определяет контекст шрифта, который задает характеристики шрифта в данном графическом контексте.
void drawstring(String str, float x, float y)
Параметры:strПараметры:strххУУСтрока, подлежащая выводу. Координата xначала строки. Координата)) начала строки.Строка, подлежащая выводу. Координата xначала строки. Координата)) начала строки.Выводит на экран строку, шрифт и цвет которой определяются текущими установками графического контекста.
Параметры:str
Параметры:str
х
х
У
У
Строка, подлежащая выводу. Координата xначала строки. Координата)) начала строки.
Строка, подлежащая выводу. Координата xначала строки. Координата)) начала строки.
Шрифты и файл font, properties
Для того чтобы определить, какие логические шрифты доступны и какие символы в них входят, система поддержки выполнения программ на языке Java, созданная компанией Sun, ищет файл font.properties в каталоге jre/lib. Если нужны дополнительные шрифты, кроме предусмотренных по умолчанию, этот файл необходимо модифицировать. Рассмотрим типичную запись, содержащуюся в этом файле.
serif.0=Times New Roman,ANSI_CHARSET
serif.l=WingDings,SYMBOLCHARSET,NEED_CONVERTED
serif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTEE
exclusion.serif.0=0100-ffff
fontcharset.serif.l=sun.awt.windows.CharToByteWingDingfont- charset.serif.2=sun.awt.CharToByteSymbolfontchar-
set .serif.2=sun.awt.CharToByteSymbol
Это означает следующее. Чтобы представить на экране символ, содержащийся в шрифте serif, нужно сначала проверить, имеет ли он ранг "0", т.е. относится ли данный символ
к исключительной области 0100-ffff. Если не относится, для представления используется шрифт Times New Roman. Затем проверяем, принадлежит ли данный символ классу sun.awt.windows.CharToByteWingoing. Этот (недокументированный) класс расширяет (также недокументированный) класс sun.io.CharToByteConverter. С этими классами можно работать двумя основными способами. Проверить, допускается ли преобразование данного символа, можно, вызвав метод
boolean canConvert(char!
Для того чтобы действительно выполнить преобразование, нужно предусмотреть два массива: один для исходных символов, а второй — для преобразованных байтов. Затем следует выполнить такой вызов.
int convert(char[ ], input, int inStart, int inPastEnd, byte[] output, int outStart, int outPastEnd)
Этот метод преобразовывает символы, количество которых определяется числом inPastEnd - inStart, начиная с номера inStart. Он размещает их в массиве байтов output, начиная с индекса outStart, и заполняет по крайней мере outPastEnd - outStart позиций. (Может возникнуть вопрос, почему метод convert использует массивы? Почему бы просто не преобразовать один символ в один байт? Причина заключается в том, что в некоторых схемах кодирования, например, в схеме Japanese JIS, некоторые символы кодируются одним байтом, некоторые — несколькими, причем существуют контрольные коды, осуществляющие переключение между разными множествами символов.)
Если метод canConvert возвращает значение true, символ будет представлен с помощью шрифта WingDings. В противном случае система попытается применить шрифт Symbol. Если и эта попытка завершится неудачей, символ будет заменен знаком вопроса “?”.
Заметим, что дескрипторы шрифтов зависят от операционной системы. В нашем примере описываются шрифты для системы Windows. В других операционных системах дескрипторы шрифтов будут иными. Например, на платформе Solaris дескрипторы шрифта Times выглядят следующим образом.
serif.plain 0=
-linotype-times-medium-r-normal-*-%d-*-*rp-*-iso8859-l
В файл font .properties можно добавить и свои собственные шрифты. Например, если система выполнения программ на языке Java работает в среде Windows, следует добавить такие строки.
oldstyle.0=Bookman Old Style,ANSI_CHARSET exclusion.oldstyle.0=0100-ffff
Затем можно создать шрифт:
new Font("OldStyle", Font.PLAIN, 12)
Можно даже добавить свою раскладку клавиатуры для шрифта. Рассмотрим наиболее общий случай, когда каждый символ в шрифте задается одним байтом. Чтобы добавить свою собственную раскладку клавиатуры, нужно выполнить следующие действия.
Расширить(недокументированныйинеудачноназванный)класс
sun.io.CharToByte8859_1. (Обозначение ISO 8859-1 относится к 8-битовому множеству символов Latin-1, одному из десяти 8-битовых множеств символов в
стандарте ISO 8859. Этот класс можно использовать в качестве базового для преобразования любых символов в кодировке Unicode в 8-битовое представление, не только символов множества
Заместить два метода — canConvert, возвращающий значение true, если данные символы Unicode являются частью вашего собственного шрифта, и convert, преобразовывающий массив символов в эквивалентный массив байтов.
Рассмотрим практический пример. Допустим, у нас есть русский шрифт в формате ISO 8859-5. Если проигнорировать массу технических деталей, можно сказать, что преобразование символов в кодировке Unicode в стандарт ISO 8859-5 выглядит просто.
->
ch -> ch->0x0360
Вот как выглядит класс, осуществляющий это преобразование.
public classextends
{
public booleanch)
{
return 0x0021 <= chch0x007E
0x0401 <= chch <= 0x45F;
)
public intinput, int inStart, int inPastEnd,
output, intint outPastEnd)
throws
{
int= outStart;
for (int i = inStart; i < inPastEnd; i++) {
char ch =
byte b0;
if (0x0021 <= ch && ch <= 0x077E) b = (byte)ch;
if (0x0401 <= chch <= 0x45F)
b = (byte)(ch-0x0360);
if (b!=0)
<
if (outlndex >= outPastEnd) throw new outlndex++;
}
) return outlndex - outStart';
)
)
Для того чтобы подгрузить русский шрифт во время выполнения программы, следует разместить класс CharToByteRussian в каком-нибудь каталоге, путь к которому уже задан. Затем в файлproperties нужно записать следующие строки.
russian.О-Cyrillic,SYMBOL_CHARSET,NEED_CONVERTED
fontcharset.russian.O-CharToByteRussian/
Рисунки
Мы уже видели, как с помощью рисования линий и фигур создаются простые изображения. Сложные изображения, такие как фотографии, обычно создаются отдельно, например, в результате сканирования или применения графических редакторов. (В томе 2 показано, как рисовать изображения, а затем помещать результат в массив. Эта процедура широко применяется при генерировании фрактальных изображений.)
Если изображения хранятся в локальных файлах или где-нибудь в сети, их можно прочитать, а затем отобразить на экране с помощью объектов класса Graphics. Пакет SDK 1.4 позволяет очень легко считывать изображения. Если рисунок хранится в локальном файле, достаточно вызвать метод
String filename = "...";
Image image = ImagelO.read(new File(filename));
В противном случае можно воспользоваться URL-адресом:
String filenameString filename;;
String filename
String filename
;
;
Image image = ImagelO.read(new URL(urlname));
Если рисунок не доступен, метод read возбуждает исключительную ситуацию IOException. Обработка исключительных ситуация изложена в главе 11. Пока будем считать, что наша простая программа просто перехватывает исключительную ситуацию и выводит на экран содержимое стека.
Теперь переменная image содержит ссылку на объект, инкапсулирующий графическое изображение в формате GIF. Этот объект можно отобразить на экране с помощью метода drawlmage класса Graphics.
public void paintComponent(Graphics g)
g.drawlmage(image, x, y, null);
}
Программа, приведенная в листинге 7.7, делает немного больше и создает мозаику (tiles), состоящую из указанного графического изображения. Результат показан на рис. 7.15. Покрытие окна мозаикой выполняется с помощью метода paintComponent. Сначала одна копия рисунка помещается в левый верхний угол окна, а затем вызывается метод copyArea, который копирует его на все окно.
for (int i = 0; i * imagewidth , = getWidthO; i++)
for (int j = 0; j * imageHeight <= qetHeight(); j++)
if ( i + j > 0)
g.copyArea(0, 0, imageWidth, imageHeight,
i * imageWidth, j * imageHeight);
Рис. 7.15. Окно, покрытое мозаикой
При программировании с помощью библиотеки AWT для того, чтобы избежать мерцания экрана, нужно выполнить намного больше работы, используя буферизацию. При работе с библиотекой Swing буферизация выполняется автоматически. (Ее можно отключить, но делать это нет никаких причин.) Если нужно загрузить изображение для какого-нибудь компонента библиотеки AWT, сначала необходимо создать рисунок в буфере, а затем вывести его на экран.
Imagebufferedimage =height);
Graphics bg = buffered_image.getGraphics() ;
// Все операции рисования используют объект bg
// для заполнения объектаbufferedimage.
// В заключение на экран выводится содержимое буфера.
g.drawlmage(buf fered_image, 0, 0, null);
bg.diapose();
bufferedimage.flush();
Ранее в этой главе для чтения рисунка из файла мы использовали метод get Image класса Toolkit. Однако при создании этого метода предполагалось, что изображение может медленно передаваться по сетевым соединениям. Вместо того, чтобы загрузить файл и вернуть управление вызывающему модулю после того. как графическое изображение действительно будет полностью загружено, система запускает новый поток выполнения программы, загружает изображение, а затем возвращает управление вызывающему модулю, когда загрузка еще не закончилась. (См. главу, посвященную многопоточности, в томе 2.)
Код нашей программы будет выполняется параллельно с загрузкой изображения. В конце концов графический файл когда-нибудь загрузится полностью и станет доступным. Тем временем программа будет заполнять буфер пустыми рисунками.
Существует механизм для отслеживания процесса загрузки изображения. Используя его, можно получать уведомления о том, что размер рисунка стал известным, поступила очередная порция изображения, и, в заключение, загрузка изображения полностью завершена. Используя броузер и зайдя на любую Web-страницу, содержащую рисунок, можно увидеть, как это происходит в действительности.
Определив размер рисунков на Web-странице, броузер создает ее схему. Затем по мере получения более детальной информации он постепенно заполняет экран изображениями. Четвертый параметр метода drawlmage. который мы установили равным null, может указывать на объек! класса Imageobserver, получающий упомянутые уведомления.
Однако нас не устраивает постепенное заполнение экрана. Мы просто желаем, чтобы графический файл в формате GIF сначала был полностью загружен, а уж потом покрыл своими изображениями весь буфер. Запрограммировать эту задержку легко, для этого предназначен специальный класс MediaTracker. Объекты этого класса могут отслеживать процесс загрузки одного или нескольких изображений. (Имя "media" предполагает, что с помощью объектов этого класса можно также отслеживать загрузку аудио- и других медиафайлов. Однако это — дело будущего, пока объекты класса MediaTracker могут работать только с изображениями.)
Передать изображение объекту класса MediaTracker можно с помощью следующих операторов.
MediaTracker tracker" = new MediaTracker();
Image img =
int id1; // Идентификационный номер для отслеживания
// загрузки изображения.
tracker.addimage(img, id);
Одному объекту можно передавать сколько угодно изображений. Каждое изображение получает свой идентификационный номер, при этом можно использовать любую систему нумерации. Для того чтобы приостановить выполнение программы до окончательного завершения загрузки изображения, используются такие операторы
try (tracker.waitForlD(id); }
catch (InterruptedException e) ; {}
Если нужно загрузить несколько изображений, их все нужно передать объекту класса MediaTracker, а затем ожидать завершения загрузки. Достичь этого можно с помощью таких операторов.
try { tracker.waitForAll(); }
catch (InterruptedException e;{}
В листинге 7.7 приведен полный исходный текст программы для работы с изображениями. Эта программа завершает введение в программирование графики на языке Java. Более сложные приемы программирования будут изложены при обсуждении двухмерной графики и обработки изображений во втором томе.В листинге 7.7 приведен полный исходный текст программы для работы с изображениями. Эта программа завершает введение в программирование графики на языке Java. Более сложные приемы программирования будут изложены при обсуждении двухмерной графики и обработки изображений во втором томе.1. import java.awt.*;2. import java.awt.event.*;3. import javax.imageio.*4 . import java.io.*;5. import javax.swing.*;6.7. public class ImageTest8.(1. import java.awt.*;2. import java.awt.event.*;3. import javax.imageio.*4 . import java.io.*;5. import javax.swing.*;6.7. public class ImageTest8.(
В листинге 7.7 приведен полный исходный текст программы для работы с изображениями. Эта программа завершает введение в программирование графики на языке Java. Более сложные приемы программирования будут изложены при обсуждении двухмерной графики и обработки изображений во втором томе.
В листинге 7.7 приведен полный исходный текст программы для работы с изображениями. Эта программа завершает введение в программирование графики на языке Java. Более сложные приемы программирования будут изложены при обсуждении двухмерной графики и обработки изображений во втором томе.
1. import java.awt.*;
2. import java.awt.event.*;
3. import javax.imageio.*
4 . import java.io.*;
5. import javax.swing.*;
6.
7. public class ImageTest
8.(
1. import java.awt.*;
2. import java.awt.event.*;
3. import javax.imageio.*
4 . import java.io.*;
5. import javax.swing.*;
6.
7. public class ImageTest
8.(
12.13.14.12.13.14.
12.
13.
14.
12.
13.
14.
public static void main(String!] args) {
ImageFrame frame = new ImageFrame ();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)< frame.show();
}
/
Фрейм с панелью, на которую выводится изображение.
*/
class ImageFrame extends JFrame
{
public ImageFrame(]
(
24.
setTitle("ImageTest");
25.
setSize(DEFAULT WIDTH, DEFAULT_HEIGHT);
26.
27.
28.
// Добавляем панель к фрейму.
29.
ImagePanel panel = new ImagePanel();
30.
Container contentPane = getContentPane();
31.
contentpane.add(panel);
32.
33.
}
34.
public static final int DEFAULT_WIDTH = 300;
35.
public static final int DEFAULT_HEIGHT = 200;
36.
37.
}
38.
/**
39.
Панель, на которую выводится тиражированное изображение
40.
*/
41.
class ImagePanel extends JPanel
42.
{
43.
public ImagePanel(]
44.
{
45.
46.
// Загрузка изображения.
47.
/*
48.
image = Toolkit.getDefaultToolkit().getlmage
49.
("blue-ball.gif");
50.
MediaTracker tracker = new MediaTracker(this) ;
51.
tracker.addimage(image, 0);
52.
try { tracker.waitForlD(O); }
53.
catch (InterruptedException exception) {}
54.
*/
55.
try
56.
{
57.
image = ImagelO.read(new File("blue-ball.gif"))
58.
}
59.
catch (IOException exception)
60.
{
61.
exception.printStackTrace();
62.
}
63.
64.
}
65.
public void paintcomponent(Graphics g)
66.
{
67.
68.
super.paintcomponent(g);
69.
70.
if (image == null) return
71.
int imagewidth =
72.
73.
int imageHeight = image.getHeight(this);
// Отображение рисунка в левом верхнем углу.
g.drawlmage(image, 0, 0, null);
77.
78.// Тиражирование рисунка на поверхности панели.
79.
80.
for
(int
i = 0;
i
imagewidth <= getWidth(J ;
i++)
81.
for
(int
j =0;
j *
imageHeight <= getHeight()
; j+*)
82.
if
(i + j
> 0)
83.g.copyArea(0, 0, imageWidth, imageHeight,
84.i * imageWidth, j * imageHeight);
}
private Image image;
}
javax.swing.ImagelO 1.4
static Bufferedlmage read(File f)
static Bufferedlmage read(URL u)
Эти методы считывают изображение из указанного файла или URL-адреса.
java.awt.Image 1.0
boolean drawlmage(Image img, int x, int y,
ImageObserver observer)
УУРисует масштабированное изображение. Примечание: этот вызов может вернуть управление до того, как изображение будет полностью нарисовано.
У
У
Параметры: img
Изображение.
X
Координата х левого верхнего угла.
У
Координата у левого верхнего угла.
observer
Объект, обеспечивающий уведомление о ходе рендеринга (может быть нулевой ссылкой null).
width
Требуемая ширина изображения.
height
Требуемая высота изображения.
observer
Объект, обеспечивающий уведомление о ходе рендеринга (может быть нулевой ссылкойnull).
boolean copyArea(int int dy)
x, int у int width, int height, int dx,
Копирует область на весь экран.
Параметры: x
Координата х левого верхнего угла копируемой области.
У
Координата у левого верхнего угла копируемой области.
width
Требуемая ширина изображения.
height
Требуемая высота изображения.
dx
Горизонтальное расстояние от копируемой области до области, в которую выполняется копирование.
dy
Вертикальное расстояние от копируемой области до области, в которую выполняется копирование.
phics getGraphics(I
Определяет графический контекст для рисования в буфере.
• void flush(]
Освобождает все ресурсы, выделенные объект)'.
java.awt.Graphics1.0
void dispose()
Уничтожает графический контекст и освобождает ресурсы операционной системы. Все графические контексты, полученные при вызовах метода Image. getGraphics, должны быть уничтожены, однако это не касается графических контекстов, обрабатываемых методом paintComponent.
java.awt.Component 1.0
Image createlmage(int width, int height) Создает буфер, используемый для двойной буферизации.
Параметры:widthПараметры:widthШирина изображения.Ширина изображения.
Параметры:width
Параметры:width
Ширина изображения.
Ширина изображения.
heightВысота изображения.
IA Р ГIA Р Гjava.awt.MediaTracker 1.0
IA Р Г
IA Р Г
a void addimage(Image image, int id)
Добавляет изображение в список контролируемых. После этого начинается процесс его загрузки.
Параметры: imageПараметры: imageididКонтролируемое изображение.
Параметры: image
Параметры: image
id
id
Идентификатор, используемый для ссылки на данное изображение.
void waitForID(int id)
Ожидает, пока все изображения с указанным идентификационным номером не будут загружены.
void waitForAll()
Ожидает, пока все контролируемые изображения не будут загружены.
Обработка событийОбработка событий
Обработка событий
Обработка событий
в этой главе...
Т Основы обработки событий
Иерархия событий библиотеки AWT
Семантические и низкоуровневые события в библиотеке AWT
Типы низкоуровневых событий
Т События, связанные с фокусом
Многоадресная передача событий
Т Очередь событий
о« работка событий имеет огромное значение для программ с графическим пользовательским интерфейсом. Для того чтобы его реализовать, необходимо очень хорошо разбираться в этом механизме. В этой главе описана модель событий Java AWT. Вы узнаете, как перехватывать события мыши и клавиатуры, а также использовать простейшие элементы графического пользовательского интерфейса, например кнопки. В частности, здесь обсуждается обработка событий, генерируемых этими компонентами. В следующей главе будет показано, как объединить в одно целое компоненты библиотеки Swing и весь спектр событий, которые ими генерируются.
Современная модель событий отличается от использованной в версии языкаJava 1.0. Если вы уже работали с этой версией, достаточно научиться новым правилам программирования, которые имеют весьма отдаленное отношение к применяемому способу обработки событий. Однако компания Sun заявила, что программы, написанные с использованием старой модели событий, должны быть совместимы с новыми версиями языка Java. Таким образом, переход к новой модели событий позволит не только улучшить
эффективность программы, но и сделать ее более гибкой. Старую модель событий в этой главе мы опишем кратко. Можно сделать так, чтобы компилятор отметил все строки, в которых используются методы старой модели обработки событий, переключателем -deprecation. (При этом могут возникнуть проблемы, связанные с несовместимостью. Компания Sun не брала на себя обязательства во что бы то ни стало добиться полной совместимости со старой моделью обработки событий. Разумеется, если вы используете компоненты пакета Swing, следует применять новую модель событий.)
Если вам нужно создать аплет, который будет выполняться старыми броузерами, поддерживающими версию Java 1.0 (например, броузерами Netscape 2 или 3 или Internet Explorer до версии 4), используйте старую модель событий.
Основы обработки событий
Любые операционные системы, поддерживающие графический пользовательский интерфейс, непрерывно отслеживают такие события, как нажатие клавиш или щелчок мыши, а затем сообщают о них выполняемой программе. Каждая программа решает, как реагировать на эти события (если это предусмотрено ее кодом). В языках, подобных языку Visual Basic, соответствие между событиями и кодом очевидно: среда программирования создает код, реагирующий на определенное событие, и помещает его в так называемую процедуру события (event procedure). Например, при щелчке на кнопке с именем "HelpButton" будет вызвана процедура HelpButton_Click. Каждому компоненту пользовательского интерфейса в языке Visual Basic соответствует фиксированное множество событий, состав которого изменить невозможно.
С другой стороны, если для событийно-ориентированного программирования используется язык, подобный языку С, программист должен создать код, непрерывно проверяющий очередь событий, которая передается ему операционной системой. (Обычно для этого код обработки событий помещается внутрь гигантского цикла с большим количеством переключателей!) Этот способ программирования крайне непривлекателен, и, кроме того, создавать такие программы чрезвычайно трудно. Его преимущество заключается в том, что множество событий ничем не ограничено, в отличие от языка Visual Basic, в котором очередь событий скрыта от программиста.
Подход, принятый в языке Java, по мощности представляет собой нечто среднее между подходами, применяемыми в языках Visual Basic и С. Следовательно, он довольно сложен. При обработке событий, предусмотренных в библиотеке AWT, программист полностью контролирует передачу событий от источников событий (event sources) (например, кнопок или полос прокруток) к офшлмыю событий (event listener). Любой объект можно считать слушателем некоего события — на практике все объекты так или иначе реагируют на события. Эта модель делегирования событий (event delegation model) позволяет достичь большей гибкости, чем язык Visual Basic, в котором слушатель предопределен. Однако для этого нужно написать намного больше кода, в котором сложнее разобраться (по крайней мере, до того момента, когда вы его примените).
Источники событий содержат методы, позволяющие связывать их со слушателями. Когда происходит соответствующее событие, источник посылает извещение всем объектам слушателя, зарегистрированным для этого.
Поскольку язык Java является объектно-ориентированным, информация о событии инкапсулируется в объекте события (event object). В языке Java все объекты событий представляют собой объекты классов, производных от класса j ava.util.EventOb j ect. Разумеется, для каждого типа событий существует свой подкласс, например подклассы ActionEvent и WindowEvent.
Различные источники могут порождать разные виды событий. Например, кнопка может посылать объекты класса ActionEvent, а окно — объекты класса WindowEvent.
Итак, перед вами краткое описание механизма обработки событий в библиотеке AWT.
Объект слушателя представляет собой экземпляр класса, реализующего специаль- ныйинтерфейс,естественно,называемый интерфейсом слушателя (listenerinterface).
Источник события — это объект, который может регистрировать объекты слушателя и посылать им объекты событий.
При наступлении события источник посылает объекты события всем зарегистрированным слушателям.
Затем объекты слушателя используют информацию, инкапсулированную в объекте события, для того, чтобы решить, как на него реагировать.
Для регистрации объекта слушателя источником события применяется следующий оператор:
объектИсточникаСобытия. addCoOwrneListener (объектСлушателяСобытия,
Например, рассмотрим следующий фрагмент программы.
ActionListener listener = ...;
Jbutton button = new JButton("Ok") ; button.addActionListener(listener) ;
Теперь объект слушателя извещается о "наступлении события", связанного с кнопкой. Это может быть, например, простой щелчок.
Код, подобный приведенному выше, требует, чтобы класс, которому принадлежит объект слушателя, реализовывал соответствующий интерфейс (в данном случае — ActionListener). Как и для остальных интерфейсов, реализация предполагает наличие методов, имеющих соответствующую сигнатуру. Для того чтобы реализовать интерфейс ActionListener, класс слушателя должен иметь метод с именем actionPerformed, получающий объект класса ActionEvent в качестве параметра.
class MyListener
implements ActionListener
{
public void actionPerformed(ActionEvent event)
(
// Реакция на щелчок кнопки.
}
}
Когда пользователь, щелкает на кнопке, объект класса JButton создает объект класса ActionEvent и вызывает метод listener.actionPerformed(event), передавая ему этот объект события. Слушателями источника событий (например, щелчка на кнопке) можно назначать и несколько объектов одновременно. В этом случае при щелчке на кнопке объект класса JButton вызовет методы actionPerformedвсех слушателей.
На рис. 8.1 показано взаимодействие между источником, слушателем и объектом события.
Рис. 8.1. Регистрация событияРис. 8.1. Регистрация события
Рис. 8.1. Регистрация события
Рис. 8.1. Регистрация события
В этой главе особое внимание уделено обработке событий, связанных с пользовательским интерфейсом, таких как щелчок на кнопке или перемещение курсора мыши. Однако основная архитектура обработки событий не предназначена исключительно для поддержки пользовательского интерфейса. Например, в главе о компонентах JavaBeans во втором томе вы увидите, как компонент может порождать события при изменении своих свойств.
Пример: обработка щелчка на кнопке
Для того чтобы освоить модель делегирования событий, рассмотрим подробно, как обрабатывается щелчок на кнопке. Для этого нам понадобятся три кнопки, расположенные на панели, и три объекта слушателя, обеспечивающие ответную реакцию на события, происходящие с кнопками.
В рамках этого сценария каждый раз, когда пользователь щелкает мышью на какой-нибудь кнопке, расположенной на панели, соответствующий объект слушателя получает объект класса ActionEvent. В нашей программе объект слушателя в ответ на щелчок будет изменять цвет панели.
Прежде чем перейти к изучению программы, реагирующей на щелчок кнопки, нужно объяснить, как создаются кнопки и как их размещают на панели. (Более подробно элементы графического пользовательского интерфейса рассматриваются в главе 9.)
Для того чтобы создать кнопку, в ее конструкторе нужно задать строку метки, пиктограмму или оба параметра одновременно. Рассмотрим два примера.
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new Imageicon("blue-ball.gif"));
Кнопки помещаются на панель с помощью вызова метода с досточно выразительным именем add. Этот метод получает в качестве параметра указанный компонент и добавляет его в контейнер. Вот характерный пример.
class ButtonPanel extends JPanel {
public ButtonPanel(1
{
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButon) ;
add(blueButton);
add(redButton);
}
)
Результат работы этого фрагмента программы показан на рис. 8.2.
Рис. 8.2. Панель с кнопками
Теперь, поместив кнопки на панель, нужно добавить код, позволяющий ей отслеживать события, происходящие с этими кнопками. Для этого нужен класс, реализующий интерфейс ActionListener, который, как мы уже говорили, имеет один метод — actionPerformed. Сигнатура этого метода выглядит следующим образом.
public void actionListener(ActionEvent event)
Интерфейс ActionListener, использованный в нашем примере, не ограничивается отслеживанием щелчков на кнопках. Он используется во многих ситуациях:
при двойном щелчке на пункте списка;
при выборе пункта меню;
при нажатии клавиши <ENTER> в поле ввода текста;
по истечении заданного отрезка времени, отслеживаемого компонентом Timer.
Во всех ситуациях интерфейс ActionListener используется совершенно одинаково: метод actionPerformed (единственный метод в интерфейсе ActionListener) получает объект типа ActionEvent в качестве параметра. Этот объект несет в себе информацию о случившемся событии.
Допустим, что мы хотим в ответ на щелчок кнопки изменить цвет панели. Новый цвет нужно указать в классе слушателя.
class ColorAction implementsListener {
public ColorAction(Color c)
{
backgroundColor = c;
}
public void actionPerformed(ActionEvent event)
{
// Устанавливаем цвет панели.
*-
}
private Color backgroundColor;
}
Затем мы создаем no одному объекту для каждого цвета и устанавливаем их в качестве объектов слушателей кнопки.
ColorAction yellowAction = new ColorACtion(Color.yellow);
ColorAction blueAction = new ColorACtion(Color.blue);
ColorAction redaction = new ColorAction(Color.red);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction) ; redButton.addActionListener(redAction);
Например, если пользователь щелкает на кнопке, помеченной словом "Yellow", вызывается метод actionPerf ormed объекта yellowAction. Его поле backgroundColor хранит значение Color.yellow, поэтому теперь он может перейти к изменению цвета панели.
Остался один небольшой вопрос. Объект ColorAction не имеет доступа к переменной panel. Эту проблему можно решить двумя способами. Переменную panel можно поместить в объект класса ColorAction и установить ее значение в конструкторе этого класса. Удобнее все же сделать класс ColorAction внутренним по отношению к классу ButtonPanel. В этом случае его методы получат доступ к внешним переменным автоматически. (Более подробно внутренние классы рассматриваются в главе 6.)
Мы будет применять второй способ. Вот как можно поместить класс ColorAction в класс ButtonPanel.
class ButtonPanel extends JPanel {
private class ColorAction implements ActionListener {
public void actionPerformed(ActionEvent event)
•{
setBackground(backgroundcolor);
//Т.е. outer.setBackground(...).
repaint();
// Т.е. outer.repaint() .
}
private Color backgroundColor;
}
)
Рассмотрим метод более подробно. Класс ColorAction не имеет методов setBackground и repaint. Однако во внешнем классе
эти методы есть. Они вызываются объектом класса ButtonPanel, создающим объекты внутреннего класса. (Напомним, что слово outer не является ключевым словом языка Java. Мы применяем его в качестве интуитивно понятного символа для обозначения невидимой ссылки на внешний класс в объекте внутреннего класса.)
Это очень широко распространенная ситуация. Объекты слушателя событий обычно должны выполнять какие-то действия, влияющие на другие объекты. Часто бывает очень удобным заранее поместить класс слушателя внутрь другого класса, который данный слушатель должен модифицировать.
Листинг 8.1 содержит полную программу, выполняющую описанные выше операции. Как только пользователь щелкает на какой-нибудь кнопке, соответствующий объект слушателя изменяет цвет панели.
ЛистингПрограммаЛистингПрограмма
ЛистингПрограмма
ЛистингПрограмма
import java.awt.*;
import
import
public class ButtonTest
{
public static voidargs)
{
frame = new
frame.setDefaultCloseOperation(JFrame.EXIT_0N_CL0SEl;
frame.show();
}
}
/**
Фрейм, содержащий панель с кнопками.
*/
class ButtonFrame extends JFrame
{
public
{
DEFAULT_HEIGHT);
// Добавить панель к фрейму.
ButtonPanel panel = new
Container contentPane =
;
}
public static final int= 300;
publicfinal int= 200;
}
42.43.44.45.46.47.53.57.64.6575.85.42.43.44.45.46.47.53.57.64.6575.85.Панель с тремя кнопками.
42.
43.
44.
45.
46.
47.
53.
57.
64.
65
75.
85.
42.
43.
44.
45.
46.
47.
53.
57.
64.
65
75.
85.
*/ class ButtonPanel extends JPanel {
public ButtonPanel(;
{
// Создаем кнопки.
JButton yellowButtonnew JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
// Добавляем кнопки на панель.
add(yellowButton) ;
add(blueButton);
add(redButton);
// Указываем действия, связанные с кнопками.
ColorAction yellowAction = new ColorAction(Color.yellow);
ColorAction blueAction = new ColorAction(Color.blue); ColorAction redAction = new ColorAction(Color.red);
// Связываем действия с кнопками.
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction); redButton.addActionListener(redAction);
}
/**
Слушатель, задающий цвет панели.
*/
private class ColorAction implements ActionListener {
public ColorAction(Color c) {
backgroundColor = c; }
public void actionPerformed(ActionEvent event) {
setBackground(backgroundcolor); }
private Color backgroundColor; }
}
I
Если вам доводилось программировать графический пользовательский интерфейс на языке Java 1 .о, то вы, должно быть, ужасно расстроены. В языке Java 1.0 все было просто: не нужно было беспокоиться о слушателях. Вместо этого в методы, например action или handleEvent, классов, содержащих элементы пользовательского интерфейса, добавлялся соответствующий код. Так, проверка щелчка мыши выглядела следующим образом.
class ButtonPanel ’ {
public boolean action(Event event, Object arg) {
if(arg.equals("Yellow")) setBackground(Color.YELLOW); else if (arg.equals("Blue"); setBackground(Color.BLUE); else if (arg.equals("Red")] setBackground(Color.RED);
return true;
}
Между новой моделью событий и старой существуют два важных различия.
В языке Java 1.0 щелчок на кнопке всегда обнаруживался объектом, содержащим эту кнопку (например панелью). Теперь информация о щелчке на кнопке пересылается только объектам класса actionListener, связанным с данной кнопкой.
В языке Java 1.0информация о всех событиях попадала в методы action и handleEvent. Теперь существует много отдельных методов (например actionPerformed и windowclosing), реагирующих на событие.
Для простых программ старая модель событий удобнее (хотя проще ли она концептуально, другой вопрос). Однако для сложных программ такая модель накладывает слишком строгие ограничения. Новая модель более гибкая и потенциально эффективнее, поскольку события передаются только слушателям, специально предназначенным для этого.
pSti^SS/
API javax.swing.JButton 1.2
^■le JButton (String label)
Создает кнопку. Строка метки может представлять собой текст или гиперссылку (начиная с версии J2SE 1.3). Например, строка метки может выглядеть так: "<HTML><B>0k</BX/HTML>"
Параметры: labelТекст на поверхности кнопки.
JButton(Icon icon)
Создает кнопку.
Параметры: iconПиктограмма на поверхности кнопки.
JButton(String label, Icon icon)
Создает кнопку.
Параметры:labelТекст на поверхности кнопки.
iconПиктограмма на поверхности кнопки.
java. awt.Container 1.0 Component add(Component c) Добавляет компонент с в контейнер.
javax.swing.Imageicon 1.2
Imageicon(String filename)
Создает пиктограмму, изображение которой хранится в файле. Это изображение автоматически загружается в объект класса MediaTracker (см. главу 7).
Выбор слушателей событий
Отдельные программисты недолюбливают внутренние классы, потому что считают, будто они замедляют выполнение программы. Попробуем разобраться в этом. Для каждого компонента пользовательского интерфейса совсем не нужен новый класс. В нашем примере все три кнопки делят между собой один класс-слушатель. Разумеется, каждая из них имеет свой собственный объект этого класса. Однако эти объекты невелики. Каждый из них содержит число, обозначающее цвет, и ссылку на панель. Традиционно с помощью операторов if. . .else можно проверить, какое значение записано в поле объекта слушателя, работая с этим полем как с обычной локальной переменной.
Полагая, что время пользоваться внутренними классами уже наступило, мы настоятельно рекомендуем применять именно их в качестве обработчиков событий и не превращать обычные классы в слушателей. Думаем, что для этого подойдут даже безымянные внутренние классы.
Приведем яркий пример, иллюстрирующий полезность безымянных внутренних классов, позволяющих действительно упростить исходный код программы. Вернемся к листингу 8.1. Обратите внимание на то, что обработка событий для всех кнопок совершенно одинакова. Она состоит из следующих операций.
Создается кнопка с соответствующей меткой.
Кнопка помещается на панель.
Создается слушатель, изменяющий цвет панели.
Слушатель добавляется к панели.
Чтобы упростить эту задачу, создадим вспомогательный метод.
void makeButton(String name, Color backgroundcolor: {
JButton button = new JButton(name);
add(button);
ColorAction action = new ColorAction(backgroundcolor);
button.addActionListener(action);
}
Тогда конструктор классаButtonPanelупрощается.
public ButtonPanel(!
{
makeButton("yellow", Color.YELLOW);
makeButton("blue". Color.BLUE);
makeButton("red". Color.RED);
Теперь можно приступить к дальнейшим упрощениям. Отметим, что класс ColorAction нужен лишь один раз: в методе makeButton. Следовательно, его можно преобразовать в безымянный внутренний класс.
void makeButton(String name, final Color backgroundcolor I
{
JButton button = new JButton(name);
add(button);
button.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event)
{
setBackgroiund(backgroundcolor);
}
));
Код слушателя заметно упростился. Метод actionPerf ormed теперь просто ссылается на параметр backgroundColor. (Как и все локальные переменные, доступные во внутреннем классе, этот параметр должен быть терминальным.)
Явный конструктор теперь не нужен. Как мы уже видели в главе 6, механизм внутреннего класса автоматически генерирует конструктор, размещающий в памяти все локальные терминальные переменные, используемые в одном из методов этого класса.
Безымянные внутренние классы могут выглядеть пугающе. Однако, если вы научитесь выделять в программе главное, абстрагируясь на время от остального кода, вы легко расшифруете синтаксис внутренних классов. Рассмотрим пример.
button.addActionListener(new
ActionListener(I
{ public voidev.ent)
{
setBackground(backgroundcolor);
)
});
Таким образом, действия, связанные с кнопкой, сводятся к установке цвета панели и ее перерисовке. Поскольку обработчик события состоит всего лишь из нескольких операторов, мы полагаем, что в этом коде вполне можно разобраться, особенно, если программист не боится применять механизм внутренних классов.
В пакете SDK 1.4 реализован механизм, позволяющий создавать слушатели событий без программирования внутренних классов. Допустим, например, что приложение имеет кнопку "Load", обработчик событий которой содержит всего один вызов метода:
frame.loadData();
Разумеется, можно было бы воспользоваться безымянным внутренним классом.
loadButton.addActionListener(new
ActionListener(;>
(
public void actionPerformed(ActionEvent event)
{
frame.loadData();
}
};
Однако класс EventHandler может создать такой слушатель автоматически, вызвав метод create.
EventHandler.create(ActionListener.class, frame, "loadData") Разумеется, обработчик по-прежнему необходимо инсталлировать.
loadButton.addActionListener(ActionLestener) •
EventHandler.create(ActionListener.class, frame, "loadData"));
Приведение типа необходимо, поскольку метод create возвращает объект глобального класса Object.
Очень удобно, что обработчик событий вызывает метод без параметров.
Если обработчик событий вызывает метод с одним параметром, производным от обработчика событий, можно воспользоваться другой формой метода create. Например, вызов
EventHandler.create(ActionListener.class, frame,
"loadData", "source.text”]
эквивалентен конструкции
new ActionListener () <
public void actionPerformed(ActionEvent event)
{
frame.loadData!
}((JTextField)event.getSource()).getText());
Обратите внимание на то, что обработчик событий превращает имена сущностей source и text в вызовы методов getSource и getText, используя соглашения, принятые для компонентов JavaBeans. (Более полная информация о свойствах компонентов JavaBeans содержится во втором томе.)
Однако на практике эта ситуация встречается редко, и механизма поддержки параметров, не являющихся объектами классов, производных от классов событий, не существует.
Превращение компонентов в слушателей событий
Программист может совершенно свободно назначать слушателем кнопки любой объект класса, реализующего интерфейс ActionListener. В нашем примере используются объекты нового класса, созданного специально для выполнения требуемых действий в ответ на щелчок кнопки. Многие программисты поступают иначе и добавляют метод actionPerformed в существующий класс. Разумеется, этот класс также должен реализовывать интерфейс ActionListener. Например, можно превратить класс ButtonPanel в слушатель.
class ButtonPanel extends JPanel
implements ActionLi stener {
public void actionPerformed(ActionEventa event)
{
// Устанавливает цвет фона.
} ' }
В этом случае панель сама себя назначит слушателем всех трех кнопок. yellowButton.addActionListener(this) ;
blueButton.addActionListener(this); redButton.addActionListener(this);
Отметим, что теперь три кнопки больше не имеют индивидуальных слушателей. У них есть общий слушатель— панель, на которой они расположены. Следовательно, метод actionPerformedдолжен как-то распознавать, на какой кнопке произведен щелчок.
Источник любого события распознается с помощью метода getSource класса EventObject, суперкласса для всех других событий. Источник события (event source) — это объект, генерирующий событие и уведомляющий об этом слушателя. Object source = event.getSource ();
Затем метод actionPerformed проверяет, какая из кнопок была источником события. if (source == yellowButton) ...
else if (source == blueButton) ...
else if (source == redButton) ...
Разумеется, при этом нужно, чтобы все ссылки на кнопки хранились в полях содержащей их панели.
Некоторые программисты для того, чтобы распознать источник событий в слушателе, общем для нескольких источников, пользуются другим способом. В классе ActionEvent есть метод getActionCommand, возвращающий командную строку (command string), связанную с данным действием. Для кнопки он по умолчанию возвращает ее метку. Если вы предпочитаете такой способ, то метод actionPerformed должен содержать следующий фрагмент кода (или подобный ему).
String command = event.getActionCommand();
if (command.equals("Yellow");1 ...;
else if (command.equals("Blue")] ...;
else if...;
Разумеется, полагаться на метки кнопок небезопасно. Легко сделать ошибку и назвать кнопку "Gray", а затем попытаться проверить источник события с помощью оператора if (command.equals("Grey"))...
Кроме того, при интернационализации вашей программы для каждого отдельного языка понадобится называть кнопку снова. Например, в немецком языке кнопки будут носить названия "Gelb”, "Blau" и "Rot". В каждом из этих случаев программист будет вынужден изменять как метку кнопки, так и параметр метода actionPerf ormed.
Как видим, превратить панель, содержащую кнопки, в слушатель на самом деле не проще, чем определить внутренний класс. Ситуация становится еще более запутанной, если панель, кроме кнопок, содержит друтие элементы графического пользовательского интерфейса. Несмотря на это, такой подход довольно широко распространен. Очевидно, это связано с тем, что многие программисты (включая авторов) неуютно чувствуют себя, работая с внутренними классами, в основном из-за их невразумительного синтаксиса.
java.util.Eventobject 1.1
Obj ect getSource ()
Возвращает ссылку на объект, в котором произошло событие.
java.awt.event. ActionEvent 1.1
String getActionCommand()
Возвращает командную строку, связанную с событием. Если событие про- зощло с кнопкой, командной строке присваивается метка строки, если она не изменяется методом setActionCommand.
java.beans.EventHandler 1.4
static Object create(Class listenerinterface, Object target, String action)
static Object create(Class listenerlnterface, Object target, String action, String eventProperty)
static Object create(Class listenerlnterface, Object target, String action, String eventProperty, String listenerMethod) Эти методы создают объект класса-заместителя, реализующего заданный интерфейс. В результате либо указанный метод, либо все методы интерфейса выполняют заданное действие с требуемым объектом. Параметр action может быть именем метода или свойством объекта. Если это свойство, выполняется заданный метод. Например, если параметр action имеет значение "text", оно превращается в вызов метода setText. Свойство события состоит из одного или нескольких имен свойств, разделенных точками. Первое свойство задает считывание параметра метода из класса-слушателя, второе свойство — считывание результирующего объекта и т.д. Последний результат становится параметром метода, выполняющего заданное действие. Например, свойство "source. text" превращается в вызовы методов getSources и getText.
Пример: изменение стиля
По умолчанию программы, созданные с помощью пакета Swing, используют стиль "Metal". Изменить стиль можно двумя способами. В каталоге jdk/jre/lib можно предусмотреть файл swing.properties, в котором с помощью свойства swing.defaultlaf можно задать имя класса, определяющего нужный стиль, например:
swing.default laf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
Отметим, что стиль Metal описан в пакете javax . swing. Другие стили описаны в пакете com. sun . j ava. Они не являются обязательным атрибутом каждой реализации языка Java. В данный момент по соображениям, связанным с авторским правом, пакеты стилей для операционных систем Windows и Mac продаются отдельно.
Вотхороший советпо поводупроверки. Посколькустроки, начинающиеся символом #, в файлах, описывающих свойства стилей, игнорируются, в файлеswing.properties можно предусмотреть несколько стилей, "закомментировав" ненужные.
#swing.defaulflaf=javax.swing.plaf.metal.MetalLookAndFeel swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel #swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
Чтобы изменить стиль программы, ее нужно запустить заново. Программа прочитывает файл swing.properties лишь один раз, при запуске.
Для того чтобы изменять стиль динамически, вызовите метод UIManager. setLookAndFeel и задайте ему имя требуемого стиля. Затем вызовите статический метод Swingutilities.updateComponentTreeUI и обновите с его помощью весь набор компонентов. Этому методу достаточно передать один компонент, остальные он найдет автоматически. Если нужный стиль не будет обнаружен или произойдет ошибка при его загрузке, метод UIManager.setLookAndFeel может возбудить большое количество исключительных ситуаций. Как обычно, можете проигнорировать фрагменты кода, посвященные обработке исключительных ситуаций, поскольку эта тема будет полностью освещена в главе 11.
Рассмотрим пример, в котором задается стиль графического интерфейса программы под названием Motif.
String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; try {
UIManager.setLookAndFeel(plaf) ;
Swingutilities.updateComponentTreeUI(panel);
}
catch(Exceptione); { e.printStackTrace(); }
В листинге 8.2 приведена законченная программа, демонстрирующая, каким образом переключаются стили (рис. 8.3). Эта программа очень похожа на листинг 8.1. Следуя советам, изложенным выше, для того, чтобы задать действия, происходящие с кнопками, т.е. переключение стилей, мы применили вспомогательный метод makeButton и безымянный внутренний класс.
У этой программы есть одно преимущество. Метод actionPerformed во внутреннем классе слушателя должен передать методу updateComponentTreeUI ссылку this на объект внешнего класса Plaf. Напомним, что ссылка this на объект внешнего класса должна сопровождаться префиксом — именем внешнего класса: Swingutilities.updateComponentTreeUI(PlafPanel.thisI
Обратите внимание на то, что кнопка "Windows" будет работать, только если программа запускается на платформе Windows, поскольку компания Sun не предусматривает стиля Windows для других платформ.
Рис. 8.3. Переключение стилей
5к ПрограммаF
1.
import import import
public
java.awt.*;
java.awt.event.*; javax.swing.*;
class PlafTest
public static void main(String[' args)
9.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
}
PlafFrame frame = new PiafFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show();
}
/**
Фрейм с панелью, содержащей кнопки.
*/
class PlafFrame extends JFrame
public PiafFrame()
}
setTitle("PlafTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Добавляем панель к фрейму.
PlafPanel panel = new PlafPanel ();
Container contentPane = getContentPane (1; contentPane.add(panel);
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
Панель с кнопками
*/
class PlafPanel extends JPanel {
public PlafPanel(1 {
}
makeButton("Metal",
"javax.swing.plaf.metal.MetalLookAndFeel");
makeButton("Motif",
"com.sun.java.swing.plaf.motif.MotifLookAndFeel"); makeButton("Windows",
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
Создает кнопки, стиль которых будет изменяться.
@param name имя кнопки
@param plafName имя класс, который описывает стиль /
61.74.61.74.}}
61.
74.
61.
74.
}
}
v{oid makeButton(String name, final String plafName)
// Помещаем кнопку на панель.
JButton button = new JButton(name);
add(button);
// Задаем действия над кнопкой.
button.addActionListener(new
ActionListener(I
{
public void actionPerformed(ActionEvent event)
{
// Действие: переключение на новый стиль. try
{
UIManager.setLookAndFeel(plafName);
Swingutilities.updateComponentTreeUI (PlafPanel.this) ;
}
catch(Exception e) { e.printStackTrace(); }
}
});
}
Пример: перехват событий, происходящих с окнами
Не все события обрабатываются так же легко, как события, связанные с кнопками. Рассмотрим более сложный случай, который мы уже упоминали в главе 7. До того как в пакете Java SDK 1.3 появилась опция EXIT_ON_CLOSE, программисты должны были вручную программировать выход из программы при закрытом главном фрейме. Если программа представляет собой профессиональное приложение, выход из нее обязательно должен быть корректным, поскольку перед ним нужно гарантировать, что пользователь не потеряет результаты выполненной работы. Например, если пользователь закрыл фрейм, можно вывести на экран диалоговое окно и предупредить его о необходимости сохранить результаты работы и только после его согласия завершить выполнение программы.
Если пользователь пытается закрыть фрейм окна, объект JFraime становится источником события WindowEvent. Для того чтобы перехватить это событие, нужен подходящий объект слушателя, который следует добавить в список слушателей окна. WindowListener listener = . ..;
frame.addWindowListener(listener);
Слушатель окна должен быть объектом класса, реализующего интерфейс WindowListener. В интерфейсе WindowsListener есть семь методов. Фрейм вызывает их в ответ на семь разных событий, которые могут произойти с окном. Их имена самодостаточны, за исключением слова “iconified”, что в системе Windows означает "свернутое" окно. Вот как выглядит полный интерфейс WindowsListener.
public interface WindowListener
{
void windowOpened(WindowEvent e);
void;
void windowClosed(WindowEvent e) ;
void windowlconified(WindowEvent e) void windowDeiconified(WindowEvent e);
void windowActivated(WindowEvent e);
void windowDeactivated(WindowEvent e);
Если нужно проверить, было ли окно увеличено до максимальных размеров, необходимо инсталлировать слушатель WindowStateListener. Детали изложены в замечаниях об интерфейсеAPI.
Как обычно, любой класс, реализующий какой-либо интерфейс, должен реализовывать все его методы. В данном случае это означает реализацию семи методов. Напомним, что нас интересует только один из них — методwindowClos ing.
Разумеется, можно определить класс, реализующий интерфейс WindowListener, добавить в метод windowClosing вызов System. exit (0) и написать пустые функции для остальных шести методов.
class Terminator implements WindowListener {
public void windowclosing(WindowEvent e)
{
System.exit(0);
}
public void windowOpened(WindowEvent e) {}
public void windowclosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) (}
public void windowlconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
Классы-адаптеры
Создавать код для шести методов, которые ничего не делают, — неблагодарное занятие. Чтобы упростить нашу задачу, каждый из интерфейсов слушателя в пакете AWT, в котором содержится несколько методов, сопровождается классом-адаптером, реализующим все эти методы, но ничего с ними не делающим. Например, класс WindowAdapter содержит семь методов, не выполняющих никаких операций. Следовательно, класс-адаптер автоматически удовлетворит технические требования, предъявляемые языком Java к реализации соответствующего интерфейса слушателя. Программист может расширить класс- адаптер и уточнить нужные реакции на некоторые, но не на все, типы событий, происходящих в интерфейсе. (Например, интерфейс ActionListener содержит только один метод, поэтому ему не нужен класс-адаптер.)
Рассмотрим применение адаптера для окна. Расширим класс WindowAdapter, унаследовав шесть "пустых" методов и заменив метод windowClosing.
class Terminator extends WindowAdapter
public void windowclosing(WindowEvent e)
{
System.exit(0);
} i
Некоторые программисты предпочитают превращать компоненты в слушатели событий. Здесь этот трюк не пройдет. Преобразовать класс MyFrame в подкласс класса WindowAdapter невозможно, поскольку класс MyFrame уже имеет суперкласс JFrame. Следовательно, нельзя просто присвоить слушателю окна ссылку this. Придется создавать новый класс.
Теперь объект класса Terminator можно зарегистрировать как слушатель событий. WindowListener listener = new terminator();
frame.addWindowListener(listener);
С этого момента, как только фрейм породит событие, связанное с окном, он передаст его объекту слушателя, вызвав один из его семи методов (см. рис. 8.4). Шесть из этих методов ничего не делают, а метод windowClosing выполняет вызов System. exit (0) , прекращая работу приложения.
Рис. 8.4. Слушатель окна
Создание класса слушателя, расширяющего класс WindowAdapter, — несомненный шаг вперед, но можно достичь еще большего. Объекту слушателя не обязательно давать имя. Достаточно просто написать следующий оператор.
frame.addWindowListener(new Terminator());
Но и это еще не все! Можно создать класс слушателя внутри безымянного внутреннего класса фрейма.
frame.addWindowListener(new
WindowAdapter(]
{
public voide)
{
System.exit(0);
>
});
В этом фрагменте кода выполняются следующие действия.
Определяется безымянный класс, расширяющий класс WindowAdapter.
В этот безымянный класс добавляется метод windowClosing. (Как и прежде, этот метод выполняет выход из программы.)
Производится наследование шести "пустых" методов из класса WindowAdapter.
Создается объект этого класса. Этот объект также не имеет имени.
Безымянный объект передается методу addWindowListener.
Как уже отмечалось ранее, синтаксис, сопровождающий использование безымянных внутренних классов для слушателей событий, порождает некоторые трудности, зато полученный в результате код будет максимально коротким.
Такой код можно увидеть во многих программах, поскольку до тех пор, пока не появилась опциявсе графические приложения должны были преду
сматривать его.
PI
voide)
Этот метод вызывается после открытия окна.
voide)
Этот метод вызывается, когда пользователь отдает диспетчеру окон команду закрыть окно. Обратите внимание на то, что окно закрывается, только если вызван метод hide или dispose.
voide)
Этот метод вызывается после закрытия окна.
voide)
Этот метод вызывается после свертывания окна.
void windowDeiconified(WindowEvent e)<
Этот метод вызывается после развертывания окна.
voide)
Этот метод вызывается после активизации окна. Активным может быть только фрейм или диалоговое окно. Обычно диспетчер окон украшает активное окно, например, подсвечивает его заголовок.
voide)
Этот метод вызывается после того, как окно перестало быть активным.
j ava. awt. event. WindowStateLiatener 1.4
void windowStateChanged(WindowEvent event)1.4
Этот метод вызывается после того, как окно было максимально увеличено, свернуто или восстановлено до нормальных размеров.
java.awt.event.WindowEvent 1.1
int getNewState()1.4
int getOldState()1.4
Эти методы возвращают новое или старое состояние окна при изменении его состояния. Возвращаемое целое число может принимать одно из следующих значений:
Frame.NORMAL,
Frame.ICONIFIED
Frame.MAXIMIZED_HORIZ
Frame. MAXIMIZED_VERT
Frame.MAXIMIZED BOTH
Иерархия событий библиотеки AWT
Обсудив механизм обработки событий, перейдем к более общим вопросам. Как уже указывалось ранее, в языке Java производится объектно-ориентированная обработка событий, причем все события являются потомками класса Eventobject из пакета java.util. (Общий суперкласс не назвали именем Event, так как это имя означало класс событий в старой модели. Хотя старая модель в настоящее время считается нежелательной, ее классы все еще являются частью текущей версии пакета AWT.)
Класс Eventobject имеет подкласс AWTEvent, являющийся родительским по отношению ко всем классам событий из пакета AWT. На рис. 8.5 показана диаграмма наследования событий из пакета AWT.
Некоторые компоненты пакета Swing генерируют объекты намного более широкого спектра типов событий; они непосредственно расширяют класс Eventobject, a не класс AWTEvent.
Можно даже добавить свои собственные события в виде подклассов класса EventOb j ect или одного из его подклассов, как будет показано в конце этой главы.
ActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent WindowEventActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent WindowEventItemEventKeyEventMouseEventMouseWheelEventTextEventItemEventKeyEventMouseEventMouseWheelEventTextEventОбъекты событий инкапсулируют информацию, которую источник события передает его слушателю. При необходимости объекты событий, переданные объекту слушателя, можно проанализировать с помощью методов getSource и getActionSource (см. пример с кнопками на панели). Некоторые из классов событий в пакете AWT не имеют практической ценности. Например, пакет AWT вставляет объекты PaintEvent в очередь событий, однако эти объекты слушателю не передаются. Программисты должны сами замещать метод paintComponent, чтобы управлять перерисовкой. Ниже приводится список типов событий из пакета AWT, которые действительно передаются слушателям.
ActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent WindowEvent
ActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent WindowEvent
ItemEvent
KeyEvent
MouseEvent
MouseWheelEvent
TextEvent
ItemEvent
KeyEvent
MouseEvent
MouseWheelEvent
TextEvent
Рис. 8.5. Диаграмма наследования классов событий из пакета AWTРис. 8.5. Диаграмма наследования классов событий из пакета AWT
Рис. 8.5. Диаграмма наследования классов событий из пакета AWT
Рис. 8.5. Диаграмма наследования классов событий из пакета AWT
В этой и последующих главах будут приведены примеры, иллюстрирующие все указанные типы событий.
Пакет j avax . swing.event содержит дополнительные события, характерные для компонентов пакета Swing. Некоторые из них будут описаны в следующей главе.
Вот список, состоящий из четырнадцати интерфейсов слушателя в пакете
java.awt.event.ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListenerjava.awt.event.ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListenerMouseListener MouseMotionListener MouseWheelListenerTextListenerWindowListenerWindowFocusListener WindowStateListenerMouseListener MouseMotionListener MouseWheelListenerTextListenerWindowListenerWindowFocusListener WindowStateListener
java.awt.event.
ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListener
java.awt.event.
ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListener
MouseListener MouseMotionListener MouseWheelListener
TextListener
WindowListener
WindowFocusListener WindowStateListener
MouseListener MouseMotionListener MouseWheelListener
TextListener
WindowListener
WindowFocusListener WindowStateListener
Интерфейсы ActionListener и WindowListener мы уже видели в предыдущих примерах.
Несмотря на то что пакет j avax. swing. event содержит очень много интерфейсов слушателя, специфичных для компонентов из пакета Swing, в нем все еще исполь-
зуются основные интерфейсы слушателя из библиотеки AWT, в частности, для обработки событий общего характера.
Семь интерфейсов слушателя из пакета AWT, имеющих несколько методов, сопровождаются классами-адаптерами, реализующими все "пустые" методы интерфейса. (Оставшиеся интерфейсы содержат только по одному методу, поэтому для них классы- адаптеры не нужны.) Например, в классе WindowAdapter есть семь "пустых" методов.
Ниже перечислены имена классов-адаптеров.
ComponentAdapterMouseAdapter
ContainerAdapterMouseMotionAdapter
FocusAdapterWindowAdapter
KeyAdapter
Очевидно, есть много классов и интерфейсов, заслуживающих внимания. К счастью, принцип их отбора весьма прост. Класс, предназначенный для получения события, должен реализовывать интерфейс слушателя. Он регистрирует себя вместе с источником события. Затем объекту этого класса передается требуемое событие, которое обрабатывается с помощью методов интерфейса слушателя.
Программисты, работавшие на языках C/C++, могут поинтересоваться: зачем для обработки событий нужны все эти хитросплетения объектов, методов и интерфейсов? Ведь для создания графического пользовательского интерфейса на языке C++ можно использовать обратные вызовы с обобщенными указателями или обработчиками. Однако в языке Java этот механизм не работает. Модель обработки событий в языке Java строго типизирована: компилятор следит, чтобы события отправлялись только объектам, предназначенным для их обработки.
Семантические и низкоуровневые события в библиотеке AWT
В библиотеке AWT проведено полезное разделение событий на низкоуровневые (low- level events) и семантические (semantic events). Семантические события описывают действия пользователя, например, "щелчок на кнопке"; следовательно, событие ActionEvent является семантическим. Низкоуровневые события на деле обеспечивают возможность таких действий. Если пользователь щелкнул на кнопке, значит, он нажал кнопку мыши, переместил курсор по экрану и в момент, когда он находился на поверхности кнопки, отпустил кнопку мыши. К таким событиям относится и нажатие клавиш, например, для перемещения по кнопкам с помощью клавиши <ТАВ>. Аналогично перемещение бегунка на панели прокрутки относится к семантическим событиям, в то время как буксировка объекта c помощью мыши относится к низкоуровневым.
В пакете j ava.awt.event предусмотрено четыре класса семантических событий.
ActionEvent (щелчок на кнопке, выбор пункта меню, выбор пункта в списке, а также нажатие клавиши <ENTER> в поле ввода текста).
AdjustmentEvent (перемещение бегунка на панели прокрутки).
ItemEvent (установка флажка или выбор пункта в списке).
TextEvent (изменение содержания текстового поля).
Кроме того, существуют семь классов низкоуровневых событий.
ComponentEvent (изменение размера компонента, его перемещение, показ или сокрытие; этот класс является базовым для всех низкоуровневых событий).
KeyEvent (нажатие или высвобождение клавиши).
MouseEvent (нажатие, высвобождение клавиши мыши, а также перемещение курсора и буксировка объекта).
MouseWheelEvent (вращение шарика мыши).
FocusEvent (перемещение фокуса внимания на компонент и снятие фокуса с компонента).
WindowEvent (активирование, деактивирование, сворачивание, разворачивание и закрытие окна).
ContainerEvent (добавление и удаление компонента).
Событие MouseWheelEvent былодобавлено в пакет SDK 1.4.
D
Обработка событий — резюме
В табл. 8.1 показаны все интерфейсы слушателя, события и источники событий в пакете AWT. Отметим, что в таблице приведено большое количество событий, отслеживающих фокус компонентов и активацию окон, — эти понятия рассматриваются здесь немного ниже.
3.1.
Интерфейс
Методы
Параметры/аксессоры Источник события
ActionListener
асtionPerformed
ActionEvent
AbstractButton
Adjustment-
adjustment-
getActionCommand getModifiers
AdjustmentEvent
JComboBox
JTextField
Timer
JScrollBar
Listener
ItemListener
ValueChanged
itemStateChanged
getAdjustable getAdjustmentType getValue
ItemEvent
AbstractButton
TextListener
textValueChanged
getltem getltemSelectable getStateChange
TextEvent
JComboBox
Продолжение табл.8.1
Интерфейс
Методы
Параметры/аксессоры Источник события
ComponentListener
componentMoved
componentHidden componentResized componentShown
ComponentEvent
getComponent
Component
ContainerListener
componentAdded
componentRemoved
ContainerEvent
getChild getContainer
Container
FocusListener
focusGained
focusLost
FocusEvent isTemporary
Component
KeyListener
keyPressed keyReleased keyTyped
KeyEvent getKeyChar getKeyCode getKeyModifiers- Text
getKeyText isActionKey
Component
MouseListener
rnousePressed mouseReleased mouseEntered mouseExited mouseClicked
MouseEvent getClickCount getX getY getPoint translatePoint isPopupTrigger
Component
MouseMotionListener
mouseDragged
mouseMoved
MouseEvent
Component
MouseWheel
Listener
mouseWheelMoved
MouseWheelEvent
getWheelRotation getScrollAmount
Component
WindowListener
windowclosing windowOpened windowlconi fied windowDeiconified windowClosed
WindowEvent getWindow
Window
Окончание табл. 8.1
Интерфейс
Методы
Параметры/аксессоры
Источник события
WindowFocus-
windowActivated
windowDeactivated
windowGainedFocus
WindowEvent
Window
Listener
Windowstate-
windowLostFocusgetOppositeWindow
windowStateChanged WindowEvent
Window,
Listener
getOldState getNewState
Рассмотрим еще раз механизм делегирования событий, чтобы убедиться, что вы правильно понимаете отношения между классами событий, слушателем интерфейсов и классами-адаптерами.
Источники событий — это компоненты пользовательского интерфейса, окна и меню. Операционная система извещает источник события о поведении интересующих его объектов, например, перемещении мыши или нажатии клавиши. Источник события описывает природу события в объекте события. Кроме того, он хранит набор слушателей— объектов, которые следует вызвать при наступлении определенного события (рис. 8.6). Затем источник события вызывает соответствующий метод интерфейса слушателя для того, чтобы доставить информацию о событии разным слушателям. С этой целью источник передает соответствующий объект события методу из класса слушателя. Слушатель анализирует объект события, извлекая из него информацию о происшедшем действии. Например, чтобы обнаружить источник события, можно вызвать метод getSource, а для того, чтобы определить местонахождение курсора мыши, — методы getX и getY.
Рис. 8.6. Отношения между источниками событий и слушателями
Обратите внимание на то, что интерфейсы MouseListener и MouseMotionListener отличаются друг от друга. Это сделано для повышения эффективности — есть много событий, связанных с мышью, которые не требуют реакции, например, бесцельное перемещение курсора мыши по экрану. Следовательно, слушатель должен заботиться лишь об ответе на нажатие кнопки мыши и не обращать внимания на ее произвольные перемещения.
Кроме того, в языке Java для каждого интерфейса слушателя, имеющего несколько методов, предусмотрены классы-адаптеры. Такой класс определяет все методы интерфейса как "пустые". Применение классов-адаптеров позволяет сэкономить время: их следует использовать, если нужно заместить лишь небольшое количество методов интерфейса слушателя.
В механизме событий, предусмотренном в версии пакета AWT 1.0, события порождались отдельными компонентами, а затем передавались во все контейнеры, содержащие данный компонент. Начиная с версии AWT 1.1, события передаются только тем слушателям, которые зарегистрированы для их получения.
Семантические события высокого уровня вполне естественны для программирования графического пользовательского интерфейса— они соответствуют вводу информации. Для того чтобы лучше разобраться в событиях низкого уровня, указанных в табл. 8.1, сделаем краткий обзор терминологии.
Компонент— это элемент пользовательского интерфейса, например, кнопка, поле ввода текста или полоса прокрутки.
Контейнер— это область экрана или компонент, который моясет содержать другие компоненты, например, окно или панель.
Все события низкого уровня происходят от базового класса ComponentEvent. Этот класс содержит метод getComponent, сообщающий о компоненте, породившем данное событие. Этот метод можно использовать вместо метода getSource. Он возвращает то же значение, что и метод getSource, но при этом возвращаемое значение приводится К типу Component. Например, если при вводе текста произойдет событие, связанное с нажатием клавиши, метод getComponent вернет ссылку на соответствующее поле ввода.
Событие ContainerEvent происходит всякий раз, когда компонент добавляется или удаляется из контейнера. Это событие довольно сильно отличается от остальных. Щелчки на кнопках и нажатия клавиш производятся пользователем случайным образом, в то время как добавление или удаление компонента является запрограммированным. Следовательно, для этого события механизм уведомления о его наступлении не нужен — такое извещение может предусмотреть сам программист. Это событие было предназначено для того, чтобы упростить автоматическое генерирование программ, создающих интерфейс пользователя. Если вы не собираетесь изменять интерфейс пользователя в ходе выполнения программы, можете не обращать на это событие внимание.
APIjava.awt.event.ComponentEvent 1.0
■^■■i • Component getComponent()
Возвращает ссылку на компонент, являющийся источником события. Точно такая же ссылка возвращается при вызове (Component; getSources {).
Типы низкоуровневых событий
В этом разделе мы детально обсудим такие понятия, как фокус и события, связанные с окнами. После этого мы рассмотрим события, не связанные с конкретными компонентами пользовательского интерфейса, в частности, события, связанные с клавиатурой и мышью. Подробный анализ семантических событий, порождаемых компонентами графического пользовательского интерфейса, проводится в следующей главе.
События, связанные с клавиатурой
Нажимая клавишу, пользователь порождает событие KEY_PRESSED, а отпуская — KEY_RELEASED. Оба эти события принадлежат классу KeyEvent. Их можно перехватить с помощью методов keyPressed и keyReleased любого класса, реализующего интерфейс KeyListener. Эти методы используются для отслеживания ошибочно нажатых клавиш. Третий метод, keyTyped, представляет собой комбинацию двух первых: он сообщает, какой символ соответствует нажатой клавише.
Лучше всего рассмотреть все это на конкретном примере. Однако сначала уточним терминологию. В языке Java существует различие между символами (symbols) и виртуальными кодами клавиш (virtual key codes). Виртуальные коды клавиш обозначаются с помощью префикса VK_, например, VK_A или VK_SHIFT. Они соответствуют клавишам клавиатуры, так, код VK_A означает клавишу <А>. Виртуальный код клавиши не различает прописные и строчные литеры.
Виртуальный код клавиш напоминает скан-код клавиатуры (и тесно связан с ним). D
Итак, допустим, что пользователь напечатал прописную букву "А", нажав одновременно клавишу <SHIFT> и клавишу <А>. При этом просходит пять событий.
Нажата клавиша <SHIFT> (вызывается метод keyPressed с параметром VK_SHIFT).
Нажата клавиша <А> (вызывается метод keyPressed с параметром VK_A).
Напечатана буква "А" (вызывается метод keyTyped с параметром "А").
Клавиша <А> отпущена (вызывается метод keyReleased с параметром VK_A).
Клавиша <SHIFT> отпущена (вызывается метод keyReleased с параметром VK_SHIFT).
С другой стороны, если пользователь набрал строчную букву "а", просто нажав на клавишу <А>, произойдут только три события.
Нажата клавиша <А> (вызывается метод keyPressed с параметром VK_A).
Напечатана буква "а" (вызывается метод keyTyped с параметром "А").
Клавиша <А> отпущена (вызывается метод keyReleased с параметром VK_A).
Итак, процедура keyTyped сообщает, какой символ был напечатан ("А" или "а"), в то время как методы keyPressed и keyReleased сообщают, какие клавиши были нажаты.
При работе с методами keyPressed и keyReleased нужно сначала проверить код клавиши (key code).
public void keyPressed(KeyEvent event) {
int keyCode = event.getKeyCode();
)
Код клавиши равен одной из следующих констант (их имена вполне очевидны). Эти константы определены в классе KeyEvent.
VK_A ... VK_Z ...
VK_SLASH,
VK_OPEN_BRACKET, VK_BACK_SLASH, VK_CLOSE_BRACKET
VK_BACK_QUOTE, VK_QUOTE
VK_GREATER, VK_LESS,
VK_AMPERSAND,VK_AT, VK_BRACELEFT, VK_BRACERIGTH
VK_LEFT_PARENTHESIS, VK_RIGHT~PARENTHESIS
VK_CIRCUMFLEX, VK_COLON, VK_NUMBER_SIGN, VK_QUOTEDBL*
VK_EXCLAMATION MARK, VK_INVERTE D_EXCLAMATION_MARK VK_DEAD_BREVE
VK_DEAD_CEDILLA,
VK_DEAD_DIAERESIS
VK_DEAD_DOUBLEACUTE, VK_DEAD_GRAVE, VK_DEAD_IOTA, VK_DEAD_MACRON
VK_DEAD_TILDE, D
VK_DOLLAR, VK_EURO_SIGN
VK_ENTER, VK_BACK_CPACE, VK_TAB, VK_ESCAPE , VKNUMLOCK, VK_SCROLL_LOCK, VK_CAPS_LOCK VK_PAUSE, VK_PAGE_UP,VK_END,VK_UP,
VK_RIGHT, VH_DOWN VK_F1 ... VK_F24 ... VK_KP_LEFT, VK_KP_RIGHT, VK_KP_UP VK_MULTIPLY, VK_ADD, VK_SEPARATER [sic], VK_SUBTRACT, VK_DIVIDE VK_DELETE, VK_INSERT VK_CANCEL,VK_FINAL
VK_ACCEPT, VK_AGAIN, VK_STOP VK_ALL_CANDIDATES, VK_PREVIOUS_CANDIDATE VK_CUT, VK_PASTE, VK_HALF_WIDTH VK_HIRAGANA, VK_KATAKANA, VK_KANA, VK_KANJI VK_JAPANESE_HIRAGANA, VK_UNDEFINED
Для того чтобы определить текущее состояние клавиш <SHIFT>, <ALT> иможно, конечно, отслеживать кодыи
VK МЕТА, но это очень утомительно. Вместо этого можно просто использовать методыи
Например, следующий фрагмент кода проверяет, нажата ли комбинация клавиш <SHIFT>+<RIGHT public voidevent)
{ int keyCode == if (keyCode == keyEvent.VK_RIGTH s& event.isShiftDown()1 ( } }
В методе keyTyped вызывается метод getKeyChar, позволяющий определить, какой символ был напечатан.
DDМетод keyTyped можно применять не для всех клавиш. Он предназначен для отслеживания лишь таких нажатий клавиш, которые генерируют символ в кодировке Unicode. Его следует применять также для отслеживания нажатий навигационных клавиш и других управляющих комбинаций.
D
D
Листинг 8.3 иллюстрирует обработку нажатия клавиш. Программа (см. рис. 8.7) представляет собой простую реализацию игрушки Etch-A-Sketch™ и позволяет создавать простые эскизы.
Перо можно перемещать вверх, вниз, налево и направо, пользуясь навигационными клавишами (стрелками). Если при этом нажать клавишу <SHIFT>, перо переместится на большее расстояние. Если вы — опытный редактор визуальных изображений, можете переназначить клавиши и для перемещения пера по экрану использовать клавиши "h", "j", "k" и "1" (в нижнем регистре). Верхний регистр этих клавиш ("Н", "J", "К" и "L") перемещает перо на большее растояние. Отслеживание состояния навигационных клавиш осуществляется с помощью метода keyPressed, а символов— метода keyTyped.
Здесь есть одна техническая тонкость: обычно панель не может находиться в фокусе, т.е. она не может получать никаких событий, связанных с нажатием клавиш. Для того чтобы позволить панели оказаться в фокусе, мы заместили метод isFocusTraversable в классе SketchPanelтак, чтобы он возвращал значение true.
Рис. 8.7. Программа для рисованияРис. 8.7. Программа для рисования
Рис. 8.7. Программа для рисования
Рис. 8.7. Программа для рисования
import java.awt.* ;
import java.awt.geom.*;
import java.util.*;
import j ava.awt.event.*;
import javax.swing. *;
public class Sketch
{
public static void main(String[ args)
■{
SketchFrame frame = new SketchFrame();
frame.setDefaultCloseOperation(JFrame.EXIT ON CLOSE!;
frame.showO;
К.)
)
/**
Фрейм с панелью для рисования фигур.
*/
class SketchFrame extends JFrame
{
public SketchFrame(;
{
setTitle("Sketch");
setSize(DEFAULTWIDTH, DEFAULTHEIGHT);
// Добавляет панель к фрейму.
SketchPanel panel = new SketchPanel();
Container contentPane = getContentPane();
cententPane.add(panel) ;
}
publicstaticfinalintDEFAULT_WIDTH = 300;
publicstaticfinalintDEFAULT_HEIGHT = 200;
}
/**
Панель для рисования с помощью клавиатуры.
*/
class SketchPanel extends JPanel
{
public SketchPanelC
{
last = new Point2D.Double(100, 100);
lines = new ArrayList ();
KeyHandler listener = new KeyHandler ();
addKeyListener(listener; ;
setFocesable(true) ;
}
/**
Добавляет к рисунку новый отрезок.
@paramdxперемещениев направлениих
.@paramdyперемещениев направленииу
*/
public void add(int dx, int dy)
{
// Вычисляем новый конец отрезка.
Point2D end = new Point2D.Double(last.getXt; + dx,
last.getYC + dy) ;
// Добавляем новый отрезок.
Line2D line = new Line2D.Double(last, end);
lines.adddine) ;
repaint();
// Запоминаем новый конец отрезка.
last = end;
}
public void paintcomponent(Graphics g)
{
super.paintcomponent(g);
Graphics2D g2 = (Graphics2D)g;
76.77.83.84.85.86.87.97.100101102103104 105 106.107108.109110. 111. 112.113114115 116 117 118 119 120 121 122.123124125126127128129130.76.77.83.84.85.86.87.97.100101102103104 105 106.107108.109110. 111. 112.113114115 116 117 118 119 120 121 122.123124125126127128129130.// Рисуем все линии.
76.
77.
83.
84.
85.
86.
87.
97.
100
101
102
103
104 105 106.
107
108.
109
110. 111. 112.
113
114
115 116 117 118 119 120 121 122.
123
124
125
126
127
128
129
130.
76.
77.
83.
84.
85.
86.
87.
97.
100
101
102
103
104 105 106.
107
108.
109
110. 111. 112.
113
114
115 116 117 118 119 120 121 122.
123
124
125
126
127
128
129
130.
for (int i = 0; i < lines.size(); i++)
g2.draw((Line2D)lines.get(i));
}
private Point2D last;
private ArrayList lines;
private static final int SMALL_INCREMENT = 1;
private static final int LARGE_INCREMENT - 5;
private class KeyHandler implements KeyListener {
public void keyPressed(KeyEvent event) {
int keyCode = event.getKeyCode();
// Устанавливаем расстояние.
iifnt d;
d= LARGEINCREMENT;
else
d = SMALL_INCREMENT;
// Добавляем новый отрезок.
if (keyCode == KeyEvent.VK_LEFT: add(-d, 0) ;
else if (keyCode == KeyEvent.vk_right; add(d, 0),
else if (keyCode ==KeyEvent.VKUP) add(0, -d) ;
else if (keyCode == KeyEvent.VK_DOWN) add(0, d) ; }
public void keyReleased(KeyEvent event) {}
public void keyTyped(KeyEvent event) {
char keyChar = event.getKeyChar();
// Устанавливаем расстояние.
inft d;
{
d = LARGE_INCREMENT;
keyChar = Character.toLowerCase(keyChar);
} else
d = SMALL_INCREMENT;
// Добавляем отрезок.
if (keyChar — •h' ] add(-d, 0) ;
else if(keyChar ==Ч1)add(d,0) ;
else if(keyChar =='k']add(0,-d);
else if(keyChar ==1 j1:add(0,d) ;
}
}
}
java.awt.event.KeyEvent 1.1
char getKeyChar ()
Возвращает символ, напечатанный пользователем.
int getKeyCode()
Возвращает виртуальный код клавиши, соответствующий данному событию.
boolean isActionKey()
Возвращает значение true, если данное событие относится к "действиям", выполняемым с клавишей, т.е. при нажатии клавиш <HOME>, <END>, <PAGE UP>, <PAGE DOWN>, <UP>, <DOWN>, <LEFT>, <RIGHT>, <F1 . .. F24>, <PRINT SCREEN>, <SCROLL LOCK>, <CAPS LOCK>, <NUM LOCK>, <PAUSE>, <INSERT>, <DELETE>, <ENTER>, <BACKSPACE>, <DELETE> и <ТАВ>.
static String getKeyText(int keyCode)
Возвращает строку, описывающую код клавиши, например, вызов getKetText (KeyEvent.VK_END) возвращает строку "END".
e static String getKeyModifiersText(int modifiers)
Возвращает строку, описывающую модификатор клавиши, например <SHIFT> или <CTRL>+<SHIFT>.
Параметры: modifiers Состояние модификатора.
java.awt.event.InputEvent 1.1
int getModifiers()
Возвращает целое число, описывающее состояние модификаторов <SHIFT>, <ALT> и <МЕТА>. Этот метод применяется как для клавиатуры, так и для мыши. Для проверки, принадлежит ли возвращаемое значение заданному множеству чисел, оно сравнивается с одной из следующих битовых масок (SHIFT_MASK, CTRL_MASK, META_MASK), или применяется один из нижеперечисленных методов.
boolean isShiftDown(1
boolean isControlDown()
boolean isAltDown(I
boolean isAltGraphDown()1.2
boolean isMetaDown()
Эти методы возвращают значение true, если клафиша-модификатор в момент генерации данного события была нажата.
События, связанные с мышью
Если пользователь должен только щелкать на кнопках или выбирать пункты меню, обрабатывать явным образом события, связанные с мышью, не нужно. Эти операции автоматически обрабатываются разными компонентами пользовательского интерфейса, а затем преобразовываются в соответствующие семантические события. Однако, если пользователь должен иметь возможность рисовать с помощью мыши, необходимо отслеживать ее перемещения, щелчки и события, связанные с буксировкой объектов.
В этом разделе мы рассмотрим простой графический редактор, позволяющий создавать, перемещать и стирать квадраты на канве (рис. 8.8).
Рис. 8.8. Программа для тестирования мыши
Когда пользователь щелкает мышью, вызываются три метода слушателя: mousePressed, если была нажата кнопка мыши, mouseReleased, если кнопка мыши была отпущена, и, наконец, mouseClicked. Чтобы отследить только сам щелчок, первые два метода можно проигнорировать. Пользуясь методами getX и getY с параметром типа MouseEvent, можно определить координаты указателя мыши в момент щелчка. Если нужно различать обычный, двойной и тройной (!) щелчки, используется метод getClickCount.
Некоторые разработчики программного обеспечения навязывают пользователям использование комбинаций, состоящих из одновременного нажатия клавиш и щелчка мышью, например, <CONTROL + SHIFT> + CLICK. В исходной библиотеке API маски, соответствующие двум кнопкам мыши, совпадали с масками клавиш: BUTTON2_MASK -= ALT_MASK BUTTON3_MASK == META_MASK
Вероятно, это было сделано для того, чтобы пользователи однокнопочных мышей могли имитировать нажатие второй кнопки с помощью нажатия клавиши на клавиатуре. Однако в пакете SDK 1.4 использован другой подход. Теперь в нем предусмотрено несколько масок.
BUTTON1_DOWN_MASК
BUTTON2_DOWN_MASК
BUTTON3_DOWN_MASK
SHIFT_DOWN_MASК
CTRL_DOWN_MASK
ALT_DOWN_MASK
ALT_GRAPH_DOWN_MASK
META_DOWN_MAS К
Метод getModifiersEx позволяет точно различать события, связанные с нажатием кнопок мыши и нажатием клавиш на клавиатуре.
Обратите внимание на то, что маска BUTTON3_DOWN_MASK позволяет проверять нажатие правой (вспомогательной) кнопки мыши в среде Windows. Например, для определения, нажата ли правая клавиша, можно воспользоваться следующим кодом. if ((event.getModifiersEx(1 & InpoutEvent.BUTTON3_DOWN_MASK]
!= 0) ... // Код, связанный с нажатием правой кнопки
В программе, которую мы рассмотрим, предусмотрены оба метода— mousePressed и mouseClicked. Если щелнуть кнопкой мыши на пикселе, не принадлежащем ни одному нарисованному квадрату, на экране появится новый квадрат. Эта процедура реали-
зована в методе mousePressed, поэтому компьютер отреагирует на щелчок мыши немедленно, не дожидаясь освобождения клавиши мыши. Двойной щелчок внутри какого- нибудь квадрата приведет к тому, что он будет стерт. Эта процедура реализована в методе mouseClicked, поскольку щелчки мыши нужно подсчитывать.
public void mousePressed(MouseEvent event) {
current = find(event.getPoint());
if (current == null) // Вне квадрата.
add(event.getPoint());
}
public void mouseClicked(MouseEvent event)
(
current = find(event.getPoint());
if (current != null && event.getClickCount') >= 2) remove(current);
}
При перемещении мыши no окну оно получает постоянный поток событий, связанных с движением мыши. В большинстве приложений эти события игнорируются. Однако в нашем примере программа отслеживает события, чтобы изменить форму курсора (на крестообразную), как только он выйдет за пределы квадрата. Эта процедура реализована в методе getPredefinedCursor в классе Cursor. В табл. 8.2 перечислены константы, используемые вместе в этом методе, и показаны формы курсоров, предусмотренные в среде Windows.
Пиктограмма
Константа
DEFAULT_CURSOR
CROSSHAIR_CURSOR
HAND_CURSOR
MOVE_CURSOR
I
TEXT_CURSOR
1
WAIT_CURSOR
:
N_RESIZE_CURSOR
NE_RESIZE_CURSOR■
E_RESIZE_CURSOR
\
SE_RESIZE_CURSOR
S_RESIZE_CURSOR
SW_RESIZE_CURSOR
W_RESIZE_CURSOR
NW RESIZE CURSOR
(Обратите внимание на то, что некоторые из курсоров выглядят одинаково. Их точный внешний вид зависит от конкретной платформы.)
Изображения курсоров можно найти в каталоге jre/1ib/images/cursors.Pro- perties. В файле cursor.propertiesопределены точки, на которые указывают курсоры. Например, если курсоримеет формууказывающей руки,то местом, нако- торое он указывает, считается кончик указательного пальца. Если курсор имеет вид лупы, томестом, на котороеон указывает, считается центр этой лупы.
Рассмотрим метод mouseMoved из класса MouseMotionListener. public void mouseMoved(MouseEvent event) {
if (find(event.getPoint()] == null) setcursor(Cursor.getDefaultCursor());
else
setcursor(Cursor.getPredefinedCursor
(Cursor.CROSSHAIR_CURSOR));
}
Используя метод createCustomCursor из класса Toolkit, можно определить свою собственную форму курсора.
Toolkit tk = Toolkit.getDefaultToolkit();
Image img • tk.getImageCdynamite.gif");
Cursor dynamitecursor =" tk.createCustomCursor(img,
new Point(10, 10), "кусок динамита");
Первый параметр метода createCustomCursor указывает на изображение курсора. Второй параметр задает координаты точки, на которую указывает курсор. Третий параметр — строка, описывающая курсор. Эту строку можно использовать для вспомогательных целей, например, чтобы подсказать пользователю, как выглядит курсор, не отображая его на экране.
Если пользователь щелкнет мышью во время ее перемещения, произойдет вызов метода mouseDragged, а не mouseClicked. В нашем примере квадраты можно буксировать по экрану вслед за курсором. При этом просто происходит перерисовка буксируемого квадрата в координате, на которую указывает мышь. Затем канва перерисовывается заново для того, чтобы отобразить новое положение мыши.
public void mouseDragged(MouseEvent event) {
if (cursor >= 0)
{
int x = event.getX();
int у = event. getY();
current.setFrameI
X - SIDELENGTH / 2,
у - SIDELENGTH / 2, SIDELENGTH, • SIDELENGTH);
repaint();
)
}
DDМетод вызывается, только если указатель мыши находится внутри компонента. Однако метод вызывается даже тогда, когда указатель мыши пребывает вне компонента.
D
D
Есть еще два метода, обрабатывающих события, связанные с мышью: mouse- Entered и mouseExited. Эти методы вызываются, когда указатель мыши входит в пределы компонента и, соответственно, выходит из него.
В заключение нужно объяснить, как прослушиваются события, связанные с мышью. Щелчки мыши опознаются процедуройявляющейся частью интерфейсаПоскольку во многих приложениях отслеживаются только
щелчки мышью, а также потому, что перемещения мыши происходят слишком часто, события, связанные с перемещением мыши и буксировкой, определяются в отдельном интерфейсе
В нашей программе нас интересуют оба типа событий, связанных с мышью. В ней определены два внутренних класса:иКласс
расширяет класспоскольку он определяет только два
из пяти методов класса MouseListener. Класс MouseMotionHandler реализует интерфейс MouseMotionListener и определяет оба его метода. Текст программы приведен в листинге 8.4.
12.13.14.17.22.23.•24.27.12.13.14.17.22.23.•24.27.
12.
13.
14.
17.
22.
23.
•24.
27.
12.
13.
14.
17.
22.
23.
•24.
27.
import java.awt.*;
import
import java.util.*;
import
import
public class MouseTest
public static voidargs)
(
MouseFrame= new
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show();
}
}
/♦*
Фрейм, содержащий панель для тестирования мыши.
class MouseFrame extends
{
public
(
setTitle("MouseTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Добавляем панель к фрейму.
MousePanel panel = new MousePanel ();
Container contentPane =
42.43.44.54.57.72.80.42.43.44.54.57.72.80.contentPane.add(panel);
42.
43.
44.
54.
57.
72.
80.
42.
43.
44.
54.
57.
72.
80.
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
/**
Панель для работы с квадратами
*/.
class MousePanel extends JPanel
{
public MousePanel()
{ .
squares = new ArrayList();
current = null;
addMouseListener(new MouseHandler());
addMouseMotionListener (new MouseMotionHandler());
}
public void paintcomponent(Graphics g)
{
super.paintcomponent(g);
Graphics2D g2 = (Graphics2D)g;
// Рисуем все квадраты.
for (int i = 0; i < squares.size(); i++)
g2.draw((Rectangle2D)squares.get(i));
}
/**
Находит первый квадрат, содержащий заданную точку. @param p точка @return первый квадрат, содержащий точку р
*/
public Rectangle2D find(Point2D p)
{
for (int i = 0; i < squares.size(); i++) {
Rectangle2D r = (Rectangle2D)squares.get(i);
if (r.contains(p)) return r;
} return null;
}
/**
Добавляет квадрат в набор.
@param p центр квадрата
*/
public void add(Point2D p)
{
double x = p.getx();
double у = p.getYO;
current = new Rectangle2D.Double(
x - SIDELENGTH / 2,
у - SIDELENGTH / 2,
104 .104 .SIDELENGTH,
104 .
104 .
SIDELENGTH);
squares.add(current) ;
repaint();
}
/**
Удаляет квадрат из набора.
Sparam s удаляемый квадрат
*/
public void remove(Rectangle2C s) {
if (s == null) return;
if (s == current) current = null;
squares.remove(s);
repaint();
}
private static final int SIDELENGTH = 10;
private ArrayList squares;
private Rectangle2D current;
// Квадрат, содержащий курсор мыши.
private class MouseHandler extends MouseAdapter {
public void mousePressed(MouseEvent event) {
// Добавляет новый квадрат,
// курсор находится вне всех квадратов.
current = find(event.getPoint());
if (current == null)
" add(event.getPoint());
}
public void mouseClicked(MouseEvent event) {
// Удаляет текущий квадрат при двойном щелчке мыши. current = find(event.getPoint());
if (current != null ss event.getClickCountC >= 2) remove(current);
} }
private class MouseMotionHandler implements MouseMotionListener
{
public void mouseMoved(MouseEvent event) {
// Устанавливает крестообразную форму курсора,
// если он находится внутри квадрата.
if (findfevent.getPoint(); == null)
setcursor(Cursor.getDefaultCursor());
else
setcursor(Cursor.getPredefinedCursor (Cursor.CROSSHAIR_CURSOR));'
}
147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.})147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.})public voidevent)
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
}
)
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
}
)
{
if (current != null)
{
int x =
int у =
// Буксирует центр текущего квадрата в точку (х, у) current.setFrameI
х - SIDELENGTH / 2,
у - SIDELENGTH / 2,
SIDELENGTH,
SIDELENGTH);
repaint();
}
1.1
int getX (1 int getY() Point
Используя систему координат источника, возвращает горизонтальную (х) и вертикальную (у) координаты или точку, в которой произошло событие.
voidx, int у)
Изменяет координаты события, перемещая точку на х единиц горизонтально и у единиц вертикально.
int
Возвращает количество последовательных щелчков мыши, связанных с данным событием. (Интервал времени, для которого подсчитываются последовательные щелчки, зависит от операционной системы.)
1.1
int1.4
Возвращает расширенный модификатор или модификатор "нажатия кнопки". Для проверки возвращаемого значения используются маски BUTTON1_DOWN_MASK
BUTTON2_DOWN_MASK
BUTTON3_DOWN_MASK
SHIFT_DOWN_MASK
CTRL_DOWN_MASK
ALT_DOWN_MASK
ALT_GRAPH_DOWN_MASK
DOWN MASK
• static int getModifiersExText(int modifiers)1.4Возвращает строку, например “Shift+Buttonl”, описывающую расширенный модификатор или модификатор "нажатия кнопки", соответствующий• static int getModifiersExText(int modifiers)1.4Возвращает строку, например “Shift+Buttonl”, описывающую расширенный модификатор или модификатор "нажатия кнопки", соответствующийуказанному значению признака.java.awt.Toolkit 1.0publicCursor createCustomCursor(ImagehotSpot,String name)1.2Создает новый объект курсора.указанному значению признака.java.awt.Toolkit 1.0publicCursor createCustomCursor(ImagehotSpot,String name)1.2Создает новый объект курсора.image,image,PointPointПараметры: imageПараметры: imageИзображение активного курсора.Изображение активного курсора.gotSpotgotSpotМесто, на которое указывает курсор (конец стрелки или центр креста).Место, на которое указывает курсор (конец стрелки или центр креста).namenameОписание курсора.Описание курсора.java.awt.Component 1.0
• static int getModifiersExText(int modifiers)1.4
Возвращает строку, например “Shift+Buttonl”, описывающую расширенный модификатор или модификатор "нажатия кнопки", соответствующий
• static int getModifiersExText(int modifiers)1.4
Возвращает строку, например “Shift+Buttonl”, описывающую расширенный модификатор или модификатор "нажатия кнопки", соответствующий
указанному значению признака.
java.awt.Toolkit 1.0
publicCursor createCustomCursor(Image
hotSpot,
String name)1.2
Создает новый объект курсора.
указанному значению признака.
java.awt.Toolkit 1.0
publicCursor createCustomCursor(Image
hotSpot,
String name)1.2
Создает новый объект курсора.
image,
image,
Point
Point
Параметры: image
Параметры: image
Изображение активного курсора.
Изображение активного курсора.
gotSpot
gotSpot
Место, на которое указывает курсор (конец стрелки или центр креста).
Место, на которое указывает курсор (конец стрелки или центр креста).
name
name
Описание курсора.
Описание курсора.
public void setcursor(Cursor cursor)1.1
Задает изображение для одного из заранее определенных курсоров, ука-
занных параметром cursor.
События, связанные с фокусом
Пользуясь мышью, можно указать любой объект на экране. Однако при наборе текста нажатие клавиши должно передаваться конкретному объекту на экране. Диспетчер окон (window manager) (например, в операционных системах Windows и X Window) направляет все нажатия клавиш активному окну (active window). Обычно активное окно отличается от остальных подсвеченной строкой заголовка. Активным может быть лишь одно окно.
Допустим теперь, что активным окном управляет программа, написанная на языке Java. Это окно получает нажатия клавиш и направляет их конкретному компоненту. В таком случае говорят, что этот компонент находится в фокусе. Аналогично тому, как активное окно выделяется среди остальных подсвеченной строкой заголовка, компонент, имеющий фокус, визуально отличается от других. В поле ввода текста мерцает маркер, активная кнопка выделяется прямоугольником, обрамляющим ее заголовок, и т.д. Если поле ввода текста находится в фокусе, в него можно вводить текст. Если кнопка находится в фокусе, на ней можно "щелкнуть" не мышью, а клавишей пробела.
В окне всегда есть по крайней мере один компонент, находящийся в фокусе. Компонент может выйти из фокуса (lose focus), если пользователь выбрал другой компонент, который при этом оказывается в фокусе (gain focus). Компонент оказывается в фокусе, если пользователь щелкнул на нем мышью. Для этой цели можно воспользоваться также клавишей <TAB>, которая перемещает фокус с одного компонента на другой. Таким образом, можно пройтись по всем компонентам, которые потенциально могут находиться в фокусе. По умолчанию перемещение по компонентам библиотеки Swing осуществляется слева направо, а затем сверху вниз в соответствии с их расположением в окружающем контейнере. Порядок обхода компонентов можно изменить (детали изложены в следующей главе).
К сожалению, обработка событий, связанных с фокусом, в предыдущих версиях пакета SDK была реализована не слишком удачно. Пользователи обнаружили более сотни разных ошибок. Это явление объясняется двумя причинами. Во-первых, фокус компонента взаимодействует с фокусом окна, который находится в компетенции диспечера окон. Следовательно, поведение фокуса зависит от платформы, на которой выполняется приложение. Во-вторых, реализация кода, очевидно, быстро вышла из-под контроля, особенно в сочетании с неудовлетворительной реализацией механизма отслеживания фокуса в пакете SDK 1.3.
Уинстон Черчилль (Winston Churchill) однажды сказал: "Американцы всегда поступают правильно..., после того как исчерпают все остальные возможности". Очевидно, то же самое относится и к группе разработчиков языка Java. Они все же реализовали правильный механизм отслеживания фокусов в пакете SDK 1.4. В этом пакете полностью переработан код обработки фокусов и исчерпывающим образом описано его ожидаемое поведение, включая неизбежную зависимость от платформы.
Спецификацию механизма обработки фокусов можно найти на Web-странице http://java.sun.com/j2se/~l.4/docs/api/java/awt/docfiles/FocusSpec.html .
К счастью, большинство прикладных программистов не слишком беспокоятся об обработке событий, связанных с фокусом. До появления пакета SDK 1.4 этот механизм обычно использовался для проверки возможных ошибок или верификации данных. Допустим, что в текстовое поле вводится номер кредитной карточки, и после завершения редактирования пользователь переходит в новое поле. В этот момент можно перехватить событие, связанное с выходом из фокуса. Если номер кредитной карточки не соответствует ожидаемому формату, можно вывести сообщение об ошибке и вернуть в фокус предыдущее поле ввода. Однако в пакете SDK 1.4 есть более простые и надежные механизмы верификации. Мы обсудим их в главе 9.
Некоторые компоненты, например, метки и панели, не находятся в фокусе по умолчанию, поскольку предполагается, что они просто используются для украшения или объединения нескольких компонентов в одно целое. Если приложение содержит панель, реагирующую на нажатие клавиш, это предположение следует аннулировать. Используя пакет SDK 1.4, достаточно просто вызвать метод setFocusable: panel.setFocusable(true);
В старых версиях пакета SDK замещение метода isFocusTraversable, принадлежащего компоненту, приводило к такому же результату. Однако в старых пакетах использовалась другая коцепция фокуса. Это различие могло вводить пользователей в заблуждение, поэтому метод isFocusTraversable был объявлен нежелательным.
В оставшейся части раздела мы обсудим детали обработки событий, связанных с фокусом. Этот текст читатели могут спокойно пропустить, пока не почувствуют необходимость использовать описанный механизм в своих приложениях.
В пакете SDK 1.4 используются следующие понятия:
владелец фокуса (focus owner), т.е. компонент, находящийся в фокусе;
фокусированное окно (focused windows), т.е окно, содержащее владельца фокуса;
активное окно (active window), т.е фрейм или диалоговое окно, содержащее владельца фокуса.
Фокусированное окно обычно является активным. Разница между ними проявляется лишь тогда, когда владелец фокуса содержится в окне верхнего уровня, не обладающем украшениями фрейма, например, во всплывающем меню.
Чтобы получить эту информацию, сначала создадим диспетчер фокуса клавиатуры. KeyboardFocusManager manager =
Он выполняет следующие вызовы.
Component owner = manager.getFocusOwner();
Window focused = manager.getFocusedWindow();
Window active = manager.getActiveWindow(); // Фрейм или диалоговое окно
Для регистрации перемещений фокуса необходимо инсталлировать слушателей компонентов или окон. Слушатель фокуса должен реализовывать два метода: focusGained и focusLost. Эти методы вызываются, когда источник события попадает в фокус (gained focus) и выходит из фокуса (lost focus). Каждый из этих методов имеет параметр FocusEvent. В этом классе есть также два полезных метода. Метод getComponent сообщает, находится компонент в фокусе или нет, а метод isTempo- raryвозвращает значение true, если фокус переместился временно. Временное перемещение фокуса происходит тогда, когда компонент временно теряет управление, но впоследствии автоматически получает его обратно. Это происходит, например, когда пользователь выбирает активное окно, перемещаясь из одного окна в другое. Как только пользователь остановит свой выбор на каком-либо окне, исходный компонент снова окажется в фокусе.
В пакете SDK 1.4 реализован механизм доставки событий, связанных с фокусом окна. Для этого с окном связывается класс WindowFocusListener и реализуются методы windowGainedFocus и windowLostFocus.
Кроме того, в пакете SDK 1.4 можно обнаружить "противоположный" компонент или окно, которое оказывается в фокусе следующим. Когда компонент или окно выходят из фокуса, "противоположный" компонент или окно оказываются в фокусе. И, наоборот, когда исходный компонент или окно вновь оказываются в фокусе, их противоположности выходят из него. Метод getOppositeComponent класса FocusEvent возвращает противоположность компонента, вышедшего из фокуса, а метод getOppo- sitewindow класса WindowEvent возвращает окно, которое является противоположностью окну, вышедшему из фокуса.
Не следует считать, что ваш компонент находится в фокусе, если метод requestFocus или requestFocusInWindow вернул значение true. Подождите, пока не произойдет событие FOCUS GAINED.
Некоторые программисты неверно понимают суть события FOCUS_GAINED И пытаются предотвратить получение фокуса другим компонентом, вызывая обработчик focusLost. Однако в это время компонент уже вышел из фокуса. Если вам необходимо отслеживать перехват фокуса конкретным компонентом, необходимо инсталлировать "слушатель, обладающий правом вето на изменения" в классе KeyBoardFocusManager и задать право вето в свойстве focusOwner. Детали описаны в главе 8 тома 2.
APIAPIПеремещение фокуса можно запрограммировать с помощью метода requestComponent класса Component. Однако поведение фокуса сильно зависит от платформы, на которой выполняется приложение, если компонент не находится внутри фокусированного окна. Для создания платформно-независимых программ в пакете SDK 1.4 предусмотрен метод requestFocusInWindow класса Component. Этот метод работает, только если компонент содержится внутри фокусированного окна.
API
API
java.awt.Component 1.0
® • void requestFocus()
Перемещает фокус на данный компонент.
, Boolean requestFocusInWindow()1.4
Перемещает фокус на данный компонент. Возвращает значение false, если данный компонент не содержится в фокусированном окне или запрос невозможно выполнить по другой причине. Возвращает значение true, если запрос можно выполнить.
Void setFocusable(boolean b)1.4
boolean isFocusable()1.4
Устанавливает или определяет состояние компонента, способного находиться в фокусе, если параметр b имеет значение true, значит компонент вышел из фокуса.
Boolean isFocusOwner()1.4
Возвращает значение true, если компонент в данный момент находится в фокусе.
j
static KeyboardFocusManager getCurrentKeyboardFocusManager() Определяет текущий диспетчер фокуса.
Component getFocusowner()
Определяет компонент, находящийся в фокусе, или возвращает ссылку null, если диспетчер фокуса не управляет компонентом, находящимся в фокусе.
Window getFocusWindow()
Определяет окно, находящееся в фокусе, или возвращает ссылку null, если диспетчер фокуса не управляет окном, находящимся в фокусе.
Window getActiveWindow(]
Определяет диалоговое окно или фрейм, содержащийся в фокусированном окне, или возвращает ссылку null, если диспетчер фокуса не управляет фокусированным окном.
java.awt.Window 1.0
boolean isFocused()1.4
Возвращает значение true, если окно в данный момент находится в фокусе.
boolean isActive() 1.4
Возвращает значение true, если фрейм или диалоговое окно в данный момент находится в фокусе. Заголовок активного фрейма или диалогового окна обычно выделяется диспетчером окон.
java.awt.event.FocusEvent1.1
Component getOppositeComponent1.4
Возвращает компонент, вышедший из фокуса в обработчике focusGained или оказавшийся в фокусе в обработчике focusLost.
java.awt.event.WindowEvent 1.4
Component ge tOppositeWindow1.4
Возвращает окно, вышедшее из фокуса в обработчике windowGainedFocus, или окно, оказавшееся в фокусе в обработчике windowLostFocus, или окно, активированное в обработчике windowActivated, или окно, деактивированное в обработчике windowDeactivated.
java.awt.event.WindowFocusListener 1.4
void windowGainedFocus(WindowEvent event)
Вызывается, когда окно, являющееся источником событий, оказывается в фокусе.
void windowLostFocus(WindowEvent event)
Вызывается, когда окно, являющееся источником событий, выходит из фокуса.
Действия
Одну и ту же команду можно выполнить разными способами. Пользователь может выбрать пункт меню, нажать соответствующую клавишу или щелкнуть на кнопке, расположенной на панели инструментов. Это позволяет просто применить модель событий AWT: достаточно связать все эти события с одним и тем же слушателем. Допустим, что blueAction— это объект некоего класса (скажем, ColorAction), реализующего интерфейс ActionListener и изменяющего цвет фона на синий. Можно связать один и тот же объект слушателя с разными источниками событий, перечисленными ниже.
Кнопка "Blue" на панели инструментов.
Пункт меню "Blue".
Нажатие клавиш <CTRL+B>.
В этом случае команда, изменяющая цвет, обрабатывается совершенно одинаково, независимо от того, что привело к ее выполнению — щелчок на кнопке, выбор пункта меню или нажатие клавиш.
В пакете Swing предусмотрен очень полезный механизм, позволяющий инкапсулировать команды и связывать их с несколькими источниками событий. Этот механизм представляет собой интерфейс Action. Действие (action) — это объект, инкапсулирующий описание команды (в виде текстовой строки или пиктограммы) и параметры, необходимые для выполнения программы (например, нужный цвет).
Интерфейс Action содержит следующие методы.
Void actionPerformed(ActionEvent event) void setEnabled(boolean b) boolean isEnabledO
void pitvalue(String key, Object value)
Object getValue(String key)
void addPropertyChangeListener(PropertyChangeListener listener) void removePropertyChangeListener(PropertyChangeListener listener)
Первый метод похож на метод интерфейса ActionListener: действительно, интерфейс Action расширяет интерфейс ActionListener. Следовательно, вместо объекта класса, реализующего интерфейс ActionListener, можно использовать объект класса, реализующего интерфейс Action.
Следующие два метода позволяют блокировать (disable) и разблокировать (enable) действие, а также проверить, является ли указанное действие разблокированным в данный момент. Если пункт меню или кнопка на панели инструментов связаны с заблокированным действием, они выделяются светло-серым цветом.
Методы putValue и getValue позволяют записывать и извлекать из памяти произвольные пары, состоящие из имен и значений объектов класса, реализующего интерфейс Action. Существует большое количество важных строк, определенных заранее, например, Action.NAME и Action. SMALL_ICON, предназначенных для хранения в объектах классов, реализующих интерфейс Action, имен и пиктограмм действий.
Action.putValue(Action.NAME, "Blue");
action.putValue(Action.SMALLICON,
new Imageicon("blue-ball.gif"));
В табл. 8.3 показаны все заранее определенные имена действий.
Имя
Значение
NAME
Имя действия. Отображается на кнопке и в названии пункта меню
SMALL_ICON
Место для хранения небольшой пиктограммы, которая изображается на кнопке, в пункте меню или на панели инструментов
SHORT_DESCRIPTION
Короткое описание пиктограммы, отображается в строке подсказки
LONG_DESCRIPTION
Длинное описание пиктограммы. Может использоваться для интерактивной помощи. Не используется ни одним компонентом из библиотеки Swing
MNEMONIC_KEY
Мнемоническое сокращение. Отображается в пункте меню (см. главу 9)
ACCELERATOR_KEY
Место для хранения "горячей" клавиши. Не используется ни одним компонентом из библиотеки Swing
ACTION_COMMAND_KEY
Ранее использовалось в теперь уже устаревшем методе registeredKeyBoardAction
DEFAULT
Может быть полезным для хранения разнообразных объектов. Не используется ни одним компонентом из биб-
лиотеки Swing
Если в меню или к панели инструментов добавляется какое-то действие, его имя и пиктограмма автоматически извлекаются из памяти и отображаются в меню и на панели. Значение SHORT_DESCRIPTION отображается в строке подсказки.
Последние два метода интерфейса Action позволяют извещать другие объекты, в частности, меню и панели инструментов, об изменении свойств действий. Например, если меню создано как слушатель изменения свойств действия, и это действие впоследствии было заблокировано, то при отображении меню на экране соответствующее имя действия может быть выделено серым цветом. Слушатели изменения свойств представляют собой часть модели компонентов JavaBeans.
Обратите внимание на то, что Action является интерфейсом, а не классом. Любой класс, реализующий этот интерфейс, должен реализовать семь методов, которые мы только что рассмотрели. К счастью, это сделать достаточно легко, поскольку они все, кроме первого метода в классе AbstractAction, написаны доступно. Класс AbstractAction предназначен для хранения пар, состоящих из имен и значений, а также для управления слушателями изменения свойств. Все, что нужно для этого сделать, — добавить метод actionPerformed.
Попробуем создать действие, изменяющее цвет фона. Поместим в память имя команды, соответствующую пиктограмму и желаемый цвет. Код цвета запишем в таблицу, состоящую из пар имя/значение, предусмотренных классом AbstractAction. Ниже приводится класс ColorAction. Конструктор задает пары, состоящие из имен и значений, а метод ActionPerformed изменяет цвет фона.
public class ColorAction extends AbstractsAction {
public ColorAction(String name, Icon icon, Color c)
{
putValue(Action.NAME, name);
putValue(Action.SMALL_ICON, icon);
putValue("color" , c) ;
}
public void actionPerformed(ActionEvent event)
{
Color с = (Color)getValue("color");
setBackground(c);
} }
В тестовой программе создаются три объекта этого класса.
Action blueAction = new ColorAction("Blue",
new Imafelcon("blue-ball.gif"), Color.blue);
Теперь свяжем это действие с кнопкой. Сделать это можно с помощью конструктора JButton, получающего переменную интерфейса Action в качестве параметра. JButton blueButton = new JButton(blueActiori);
Этот конструктор считывает имя и пиктограмму действия, помещает короткое описание в строку подсказки и делает интерфейс Action интерфейсом слушателя. Пиктограмма и строка подсказки показаны на рис. 8.9.
Как мы увидим в следующей главе, точно так же легко можно добавлять действия
в меню.
Рис. 8.9. Кнопки с пиктограммами из объекта класса ColorAction, реализующего интерфейс ActionРис. 8.9. Кнопки с пиктограммами из объекта класса ColorAction, реализующего интерфейс Action
Рис. 8.9. Кнопки с пиктограммами из объекта класса ColorAction, реализующего интерфейс Action
Рис. 8.9. Кнопки с пиктограммами из объекта класса ColorAction, реализующего интерфейс Action
В заключение нужно связать действия с нажатием клавиш. Это заставит нас на время погрузиться в технические детали. Нажатия кнопок передаются компонентам, находящимся в фокусе. В нашем примере предусмотрено три компонента — три кнопки на панели. Следовательно, в любой момент одна из этих трех кнопок может находиться в фокусе. Каждая из этих кнопок должна обрабатывать события, связанные с нажатием клавиш, и реагировать на комбинации клавиш <CTRL+Y>, <CTRL+B> и <CTRL+R>.
Эта проблема имеет универсальный характер, и разработчики пакета Swing нашли ее удачное решение.
На самом деле в версии 1.2 пакета J2SE было предложено два решения, позволяющих связать клавиши с действиями: метод registerKeyboardActionкласса JComponent и понятие KeyMapдля команд JTextComponent. В версии 1.3 пакета SDK эти решения были объединены. В данном разделе описывается именно этот унифицированный подход.
Для того чтобы связать действия с нажатием клавиш, сначала нужно создать объект класса Keystroke. Это удобный класс, инкапсулирующий описание строк. Чтобы создать объект этого класса, не нужно вызывать конструктор. Вместо него следует использовать статический метод getKeyStroke класса Keystroke. Теперь необходимо указать виртуальный код клавиш и флаги (например, для комбинаций клавиш <SHIFT> или <CONTROL>).
Keystroke ctrlBKey = Keystroke.getKeyStroke(KeEvent.VK_B,
Event.CTRL_MASK) ;.
Это удобный метод, позволяющий описывать нажатие клавиши в виде строки.
Keystroke ctrlBKey = Keystroke.getKeyStroke("Ctrl B");
Каждый объект класса JComponent содержит три входных соответствия (input maps), связывающих объекты класса Keystroke с действиями. Эти три входных соответствия формулируются в виде условий (табл. 8.4).
Обработка нажатия клавиш проверяет эти соответствия в следующем порядке.
Проверяет условие WHEN_FOCUSED компонента, находящегося в фокусе, ввода. Если нажатие клавиш существует, выполняется соответствующее действие, если действие не заблокировано, процесс прекращается.
Начиная с компонента, находящегося в фокусе ввода, выполняется проверка условия WHEN_ANCESTOR_OF_FOCUSED_COMPONENT его родительского компонента. Как только условие будет выполнено, производится соответствующее действие. Если действие не заблокировано, процесс прекращается.
Проверяются все видимые и не заблокированные компоненты в окне, находящемся в фокусе ввода, соответствующие нажатиям клавиш, зарегистрированным в условии WHEN_IN_FOCUSED_WINDOW. Каждый из этих компонентов (в порядке регистрации их нажатий клвиш) получает возможность выполнить соответствующее действие. Когда будет выполнено первое незаблокированное действие, процесс прекратится. Если нажатие строк зарегистрировано в нескольких условиях WHEN_IN_FOCUSED_WINDOW, эта часть процесса становится довольно ненадежной.
Флаг
Условие
WHEN FOCUSED
Когда данный компонент находится в фокусе клавиатуры
WHEN ANCESTOR OF FOCUSED COMPONENT
Когда данный компонент содержит другой компонент, находящийся в фокусе клавиатуры
WHEN IN FOCUSED WINDOW
Когда данный компонент содержится в том же окне, что и компонент, находящийся в фокусе клавиатуры
Входное соответствие извлекается из компонента с помощью метода getlnputMap.
InputMap imap =
Условие WHEN_FOCUSED означает, что данное соответствие проверяется, если данный компонент попадает в фокус. В нашем случае это условие не проверяется, поскольку в фокусе находится одна кнопка, а не панель в целом. Каждое из оставшихся двух соответствий также позволяет очень легко изменить цвет фона в ответ на нажатие клавиш. В программе мы будем использовать условие WHEN_ANCESTOR_OF_FOCUSED_COMPONENT.
Класс InputMap не связывает напрямую объекты класса Keystroke с объектами класса ColorAction, реализующего интерфейс Action. Вместо этого объектам класса Keystroke он ставит в соответствие произвольные объекты, а с помощью второго соответствия, реализованного классом ActionMap, связывает их с действиями. Это облегчает связывание одного и того же действия с разными нажатиями клавиш, которые зарегистрированы в разных входных соответствиях.
Итак, каждый компонент имеет три входных соответствия и одно соответствие, связывающее нажатие клавиш с действиями. Чтобы связать их вместе, нужно каждому действию присвоить имя. Например, так.
imap.put(Keystroke.getKeyStroke("Ctrl Y"), "panel.yellow");
ActionMap amap =
amap.put("panel.yellow", yellowAction);
Если нужно задать "пустое" действие, можно использовать строку "none". Это позволяет легко заблокировать клавишу.
imap.put(Keystroke.getKeyStroke("Ctrl C", "none");
В документации пакета SDK предполагается, что название комбинации клавиш и соответствующего действия совпадают. Имя действия отображается на кнопке и в пункте меню. Оно может изменяться по прихоти разработчика пользовательского интерфейса и переводиться на разные языки. Такая изменчивость создает большие неудобства при поиске соответствующей комбинации клавиш. Мы рекомендуем делать так, чтобы имена действий, которые выводятся на экран, и их настоящие имена не зависели друг от друга.
Итак, вот что нужно сделать в ответ на щелчок на кнопке, выбор пункта меню или нажатие строки.
Создать класс, расширяющий класс AbstractAction. Один класс можно будет использовать для программирования разных действий.
Создать объект класса, реализующего интерфейс Action.
Создать кнопку или пункт меню на основе объекта класса, реализующего интерфейс Action.
Для действий, которые выполняются в ответ на нажатие клавиш, нужно сделать один дополнительный шаг. Сначала следует идентифицировать компонент окна верхнего уровня, например, панель, содержащую все остальные компоненты.
Проверить условие WHEN_ANCESTOR_OF_FOCUSED_COMPONENT компонента верхнего уровня. Создать объект класса Keystroke для нужного нажатия клавиш. Создать объект, соответствующий нажатию клавиш, например, строку, описывающую нужное действие. Добавить пару (нажатие строки, действие) во входное соответствие.
Получить соответствие действий и клавиш для компонента верхнего уровня. Добавить пару ("горячая" клавиша, действие) в это соответствие.
В листинге 8.5 приведен полный текст программы, задающей соответствие между кнопками, нажатием клавиш и действиями. Действия выполняются в ответ на кнопки или нажатие клавиш <CTRL+Y>, <CTRL+B> или <CTRL+R>.
import java.awt.*;
import java.awt.event.*;.
import javax.swing.*;
public class ActionTest
{
public static void main(String[] args)
{
ActionFrame frame = new ActionFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
16. 17. 18. 19. 20. 25. 26. 27. 28.29. 30. 31. 32. 33. 6'2. 63. 64. 65.16. 17. 18. 19. 20. 25. 26. 27. 28.29. 30. 31. 32. 33. 6'2. 63. 64. 65.Фрейм с панелью, демонстрирующий изменение цвета. */ class ActionFrame extends JFrame {
16. 17. 18. 19. 20.
25. 26. 27. 28.
29. 30. 31. 32. 33.
6'2. 63. 64. 65.
16. 17. 18. 19. 20.
25. 26. 27. 28.
29. 30. 31. 32. 33.
6'2. 63. 64. 65.
public ActionFrame()
{
setTitle("ActionTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Добавляет панель к фрейму.
ActionPanel panel = new ActionPanel();
Container contentPane = getContentPane() ;
contentPane.add(panel);
public static final int DEFAULT_WIDTH =• 300;
public static final int DEFAULT_HEIGHT = 200;
Панель, цвет которой можно изменять щелчком на кнопке или нажатием клавиш.
Ч
class ActionPanel extends JPanel
{
p{ublicActionPanel(;
// Определяем действия.
Action yellowAction = new ColorAction("Yellow”, new Imageicon("yellow-ball.gif") , Color.YELLOW);
Action blueAction = new ColorAction("Blue”, new Imageicon ("blue-ball, gif), Color.BLUE);
Action redAction = new ColorAction("Red”, new ImageIconCred-ball.gif"), Color.RED);
// Все кнопки, предназначенные для данных действий.
add(new JButton(yellowAction)); // Начиная с версии 1.3. add(new JButton(blueAction));
add(new JButton (redLAction));
// Связывает клавиши <Y>, <B> и <R> с именами.
InputMap imap =// Начиная с версии 1.3.
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(Keystroke.getKeyStroke("Ctrl Y") , "panel.yellow");
imap.put(Keystroke.getKeyStroke("CtrlB"), "panel.blue"); imap.put(Keystroke.getKeyStroke("CtrlR"), "panel.red");
// Связываем имена с действиями.
ActionMap amap = getActionMap();
amap. put (" pa ne 1.yellow", yellowAction) ;
amap.put("panel.blue", blueAction);
75.75.amap.put("panel.red", redAction);
75.
75.
public class ColorAction extends AbstractAction
/**
Создает действия для изменения цвета.
Sparam name имя на кнопке
Sparam icon пиктограмма на кнопке
Sparam с цвет фона
*/
public ColorAction(String name, Icon icon, Color c) <
putValue(Action.NAME, name);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION,
"Set panel color to " + name.toLowerCase());
putValue("color", c);
public void actionPerformed(ActionEvent event) {
Color с = (Color)getValue("color”);
setBackground(c);
javax.swing.Action 1.2
void setEnabled(boolean b)
Блокирует и разблокирует действия. Элементы пользовательского интерфейса могут определять статус действия и отключаться, если соответствующее действие заблокировано.
boolean isEnabledO
Возвращает значение true, если действие заблокировано.
void putValue(String key, Object value) Помещает пару (имя, значение) в объект действия.
Параметры:key
Название свойства, которое должно сохраняться в объекте действия. Оно может быть строкой, но четыре имени имеют заранее определенный смысл.
Name
Значение
Action.NAME
Имя действия, отображаемое на компоненте пользовательского интерфейса.
Action.SMALL ICON
Пиктограмма действия, отображаемая на компоненте пользовательского интерфейса.
Action.SHORT_DESCRIPTION Короткое описание, например, для под-
сказки.
Action.LONG_DESCRIPTION Длинное описание для интерактивной помощи.
valueОбъект, связанный с данным именем.
Object getValue(String key)
Возвращает значение из сохраненной пары (имя, значение).
javax.swing.JMenu 1.2
JMenuItem add(Action a)
Добавляет в меню пункт, вызывающий данное действие. Возвращает добавленный пункт меню.
javax.swing.Keystroke 1.2
static Keystroke getKeyStroke(char keyChar)
Создает объект класса Keystroke, инкапсулирующий нажатие клавиши, соответствующее событию KEY_TYPED.
static Keystroke getKeyStroke(int keyCode, int modifiers)
static Keystroke getKeyStroke(int keyCode, int modifiers, boolean onRelease)
Создает объект класса Keystroke, инкапсулирующий нажатие клавиши, соответствующее событиям KEY_PRESSED или KEY_RELEASED.
Параметры: keyCode
Виртуальный код клавиши.
modifiers
Одна из комбинаций InputEvent.SHIFT_MASK, InputEvent.CONTROL_MASK, InputEvent.ALT_MASK, InputEvent.META_MASK.
onRelease Значение true, если клавиша отпущена.
static Keystroke getKeyStroke(String description)
Создает объект класса Keystroke no описанию, представленному в виде строки, состоящей из лексем, разделенных пробелами, в следующем формате.
Лексемы shift control Ctrl meta alt buttonl button2 buttons преобразовываются в соответствующую битовую маску.
Лексема typed должна сопровождаться строкой, состоящей из одного символа, например "typed a".
Лексема pressed или released означает, что клавиша нажата или отпущена. (По умолчанию предполагается, что клавиша нажата.)
В противном случае лексема с префиксом VK_ соответствует константе KeyEvent, например KeyEvent.VK_INSERT .
Так, строка "released Ctrl Y" соответствует команде getKeyStroke
KeyEvent.VK_Y, Event.CTRL_MASK, true).
javax.swing.JComponent 1.2
ActionMap getActionMap(I 1.3
Возвращает соответствие между действиями и клавишами.
• InputMap getlnputMap(int flag)1.3
Получает входное соответствие между клавишами и действиями.
Параметры:flag
Одно из следующих условий переключения фокуса клавиатуры:
Name
Значение
WHEN_FOCUSED
Внутри данного компонента.
WHEN_IN_FOCUSED_WINDOW
В окне, содержащем данный компонент.
WHEN_ANCESTOR_OF_FOCUSED В подкомпоненте, содержащемся в дан-
COMPONENT
HO компоненте.
Многоадресная передача событий
В предыдущем разделе мы связали несколько источников событий с одним слушателем. Теперь попробуем сделать противоположное. Все источники событий в библиотеке AWT поддерживают модельмногоадресной передачи событий (multicastmodel) слушателям. Это означает, что одно и то же событие может передаваться нескольким объектам слушателей. Многоадресная модель передачи событий полезна, когда событие потенциально представляет интерес для нескольких объектов. Нужно просто добавить несколько слушателей для одного и того же источника событий, и все зарегистрированные пользователи смогут реагировать на эти события.
В соответствии с документацией пакета SDK "интерфейс прикладных программ не гарантирует определенного порядка, в котором события передаются слушателям, зарегистрированным для данного события из данного источника событий". В частности, логику программы, в которой перехватываются события, передаваемые по нескольким адресам, реализовать невозможно. Перехват события, которое передается нескольким слушателям в случайном порядке, приводит к непредсказуемым последствиям (см. раздел "Перехват событий").
Рассмотрим простое приложение, в котором используется многоадресная передача сообщений. Предположим, что в некоем фрейме открывается несколько окон с кнопкой "New". Все окна закрываются кнопкой "Close all" (рис. 8.10).
Слушатель кнопки "New" класса MultleastPanel— объект newListener, созданный конструктором класса MulticastPanel, — создает новый фрейм в ответ на щелчок мыши на кнопке.
Однако кнопка "Close all" класса MulticastPanel имеет несколько слушателей. Каждый раз, когда выполняется метод makeNewFrame, он добавляет к кнопке "Close all" еще один слушатель. Когда пользователь щелкает мышью на кнопке "Close all", активируются все ее слушатели, и каждый из них закрывает свой фрейм.
Рис. 8.10. Все фреймы прослушивают команду "Close all"Рис. 8.10. Все фреймы прослушивают команду "Close all"
Рис. 8.10. Все фреймы прослушивают команду "Close all"
Рис. 8.10. Все фреймы прослушивают команду "Close all"
Более того, метод actionPerformed удаляет слушатель из всех кнопок "Close" поскольку в нем больше нет необходимости, так как фрейм закрыт.
Полный текст этой программы приведен в листинге 8.6.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MulticastTest {
public static void main(String[; args)
{
MulticastFrame frame = new MulticastFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE;' frame.show();
} }
Фрейм с кнопками для создания и закрытия вторичных фреймов. V class MulticastFrame extends JFrame {
public MulticastFrame(I
57.70.57.70.{
57.
70.
57.
70.
;
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT) ;
// Добавляем панель к фрейму• х'
panel = new
Container contentPane = contentpane.add(panel); }
public static final int DEFAULT_WIDTH =300;
public static final int DEFAULT_HEIGHT - 200;
}..•.
/**
Панель с кнопками для создания и закрытия простых фреймов.
*/ class MulticastPanel extends JPanel. ,
{
public
{
// add "New" button
JButton newButton = new
add (newButton) ;•••, .. .
final JButton closeAllButton - newall");
add (closeAHButton);
ActionListener newListener = new
ActionListener(’ {
public voidevent)
{
frame
= newframe.show();
}
>;
newButton.addActionListener(newListener); }
)
/**
Пустой фрейм, который можно закрыть с помощью щелчка кнопки.
*/ class BlankFrame extends
Создает пустой фрейм@param closeButtonдля закрытияСоздает пустой фрейм@param closeButtonдля закрытия*/
Создает пустой фрейм
@param closeButtonдля закрытия
Создает пустой фрейм
@param closeButtonдля закрытия
publiccloseButton)
. .,. -л • • ;
counter++;
.77..77.87.}87.}setTitle("Frame " + counter);
.77.
.77.
87.
}
87.
}
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setLocation(SPACING * counter, SPACING * counter);
closeListener = hew
ActionListener(]
{
public void actionPerformed(ActionEvent event)
{
closeButton.removeActionListener(closeListener); dispose ();
}
};
closeButton.addActionListener(closeListener);
}
private ActionListener closeListener;
public static final int DEFAULT_WIDTH = 200;
public static final int DEFAULT_HEIGHT = 150;
public static final int SPACING = 40;
private static int counter0;
Очередь событий
Когда в ответ на действие пользователя, например, щелчок мыши, операционная система генерирует некое событие, часть пакета AWT, обменивающаяся с операционной системой информацией, получает извещение и превращает его в событие пакета AWT. Затем это событие помещается в очередь событий (event queue). Часть пакетаAWT, осуществляющая передачу событий слушателям, выполняет следующие действия.
Извлекает событие из очереди.
Идентифицирует объект слушателя, соответствующий данному событию.
Вызывает соответствующую процедуру слушателя, предусмотренную для дан
ного события.
Очередь событий весьма важна для эффективности программы. События, которые возникают достаточно часто (например, перемещения мыши) или выполняются медленно (например, рисование), в очереди могут объединяться. Если программа не извлекает из очереди событие, связанное с перемещением мыши или рисованием, и в это время в очередь вставляется новый объект, пакет AWT может объединять их с существующим событием, создавая отдельное новое событие. Например, можно в области, заданной новыми координатами мыши, повторить предыдущий рисунок.
Иногда бывает полезно непосредственно манипулировать очередью событий. Так, события из очереди можно удалять, вмешиваясь в нормальный порядок доставки событий слушателям. И, наоборот, можно вставлять в очередь новые, более сложные события, чем предусмотренные базовой моделью событий языкаJava.
Объект, представляющий очередь событий, можно получить с помощью следующего оператора.
EventQueue queue =
=;
Вставка события в очередь выполняется с помощью метода postEvent.
queue.postEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Blue"));
Удаление события из очереди осуществляется методом getNextEvent. Метод peekEvent возвращает следующее событие в очереди, не удаляя его.
Вставка и удаление событий представляют собой сложные процессы. Если выполнить их неверно, они могут разрушить приложение. По этой причине аплеты — приложения, написанные на языке Java, которые загружаются с посторонних компьютеров и выполняются вашим броузером, — неимеют доступа к системной очереди событий.
java.awt.EventQueue 1.1
AWTEvent peekEvent(I
Возвращает ссылку на объект класса AWTEvent, описывающий данное событие.
AWTEvent getNextEvent()
Возвращает ссылку на объект класса AWTEvent, описывающий данное событие, и удаляет его из очереди.
void postEvent(AWTEvent anEvent)
Помещает событие в очередь.
Параметры: anEventСобытие, вставляемое в очередь.
Добавление собственных событий
В последнем разделе этой главы мы рассмотрим довольно сложную задачу. Мы покажем, как создать событие пользовательского типа (custom event type), которое можно поместить в очередь событий, предусмотренных пакетом AWT, а затем передать слушателю, как обычное событие. В качестве примера реализуем свой собственный таймер. Этот таймер будет посылать события слушателю через заданный промежуток времени. Для этого события мы создадим новый тип и назовем его TimerEvent. Соответствующий слушатель будет содержать один метод под названием timeElapsed.
Пользоваться нашим таймером очень просто. Создадим объект класса Timer и зададим в его конструкторе интервал времени (в миллисекундах). Затем добавим слушатель, который будет зарегистрирован по истечении заданного промежутка времени. Timer t = new Timer(100);
// Создает таймер, отсчитывающий каждые 100 миллисекунд.
TimerListener listener = ...;
t.addTimerListener(listener);
// Регистрирует метод timeElapsed этого класса.
Теперьопределимкласс, реализующийинтерфейсTimerListener.
class TimerAction implements TimerListener
{
public void timeElapsed(TimerEvent event)
// Этот код будет выполняться каждые 100 миллисекунд.
}
Как указывалось в главе 9, в пакете Swing есть свой собственный класс Timer, мало отличающийся от нашего. Стандартный таймер не создает новый тип событий. Вместо этого он пересылает слушателю события, связанные с таймером. Еще более важно то, что стандартный таймер не вторгается в очередь событий, предусмотренных пакетом AWT, сохраняя их в отдельной очереди. Мы реализуем свой собственный класс для того, чтобы показать, как можно добавить в пакет AWT новый тип событий, а вовсе не для того, чтобы создать более совершенный таймер. При необходимости можно воспользоваться таймером, предусмотренным в пакете Наш таймер вам не понадобится.
Теперь посмотрим,реализуется событие, определенное пользователем. Для
этого нам необходимы три составляющие.
Тип события (event type). Мы определим класс
Интерфейс слушателя события (event listener interface). Для этого мы используем следующий интерфейс.
public interface TimerListener extends EventListener {
voidevent);
}
Источник события (event source). Источником события является класс Timer, к которому добавляется соответствующий слушатель событий.
Класс TimerEvent весьма прост.
Он расширяет суперкласс AWTEvent, поскольку все события в очереди событий
пакета AWT имеют тип
Конструктор для события, связанного с таймером, получает объект, представляющий собой источник события (т.е. объект класса Timer).
Теперь нам нужно присвоить суперклассу идентификационный номер события (event ID number). имеет значения, какое целое число мы выберем для этой цели, поскольку мы все равно остаемся вне сферы применения пакета AWT.
Как определить свободный идентификационный номер? Процитируем документацию пакета SDK: "Программа должна выбрать значение идентификационного номера, которое превышает целую константу j class TimerEvent extends AWTEvent {
publict) {}
publicfinal int TIMER_EVENT =
+ 5555; }
Вмы должны реализовать сам класс Timer. Механизм событий, преду
смотренный библиотекой AWT, требует, чтобы источник события расширял класс Component. Обычно компонентами являются элементы пользовательского интер-
фейса, размещенные внутри окна. Мы просто учтем, что таймер является невидимым компонентом и что он расширяет класс JComponent.
Для того чтобы написать код, задающий интервал времени, отсчитываемый таймером, следует использовать потоки. (Они обсуждаются во втором томе, а пока нам нужен лишь код обработки потока.) По истечении заданного интервала времени происходит новое событие, связанное с таймером, которое вставляется в очередь событий. Ниже приведен соответствующий фрагмент кода, в котором полужирным шрифтом выделены команды, необходимые для вставки события в очередь.
class Timer extends JComponent implements Runnable {
public Timer(int i)
{
interval = i;
Thread t = new Thread(this);
t.start();
public void run()
{
while (true) {
try { Thread.sleep(interval); } catch(InterruptedException e) (}
EventQueue queue =
TimerEvent event = new TimerEvent(this);
queue.postEvent(event);
}
}
private int interval;
После выполнения этого фрагмента событие нашего нового типа, связанное с таймером, окажется поставленным в очередь. Доставка этого события слушателям не является автоматической, поэтому оно никуда не передается, если не предусмотреть для этого отдельный код.
Как убедиться, что наше событие передается именно тому компоненту, который в нем заинтересован? Ответственность за это возлагается на источник события. Он должен управлять зарегистрированными для этого слушателями и передавать им события.
Задачи управления носят универсальный характер. Для их решения в библиотеке Swing предусмотрен класс EventListenerList, позволяющий добавлять и удалять слушателей. Этот класс берет на себя всю сложную работу, связанную с обработкой нескольких потоков, пытающихся одновременно добавлять, удалять или передавать события.
Поскольку некоторые источники событий имеют доступ к слушателям нескольких типов, каждый слушатель в списке слушателей событий связан с конкретным классом. Методы add и remove предназначены для реализации методов addXxxlistener.
public void addTimerListener(TimerListener listener)
listener.add(TimetListener.class, listener);
},
public void removeTimerListener(TimerListener listener)
listener.remove(TimerListener.class, listener);
)
• Может возникнуть вопрос, почему класс EventListenerList просто не проверяет, какой интерфейс реализуется объектом слушателя. Однако объект может реализовывать несколько интерфейсов. Например, может оказаться, что класс listener одновременно реализует интерфейсы TimerListener и ActionListener, а программист захочет добавить только интерфейс TimerListener, вызвав метод addTimerListener. Класс EventListenerList должен предусмотреть такую возможность.
Чтобы удалить событие из очереди, пакет AWT вызывает метод processEvent. Для таймеров этот метод вызывает метод timeElapsed из отдельного класса слушателя. Для получения всех слушателей таймера используется метод getListener класса EventListenerList. Затем из каждого слушателя вызывается метод timeElapsed.
public void processEvent(AWTEvent event)
{
if (event instanceof TimerEvent)
{
EventListener[] listeners = listenerList.getListenersI TimerListener.class);
for (int i = 0; i < listener.length; i++)
((TimerListener)listener[i]).timeElapsed(
(TimerEvent)event);
}
else super.processEvent(event); }
Метод getListeners характерен только для пакета J2SE 1.3. В версии пакета J2SE 1.2 следует применять метод getListenerList.
Наш класс расширяет класс JComponent. Если в качестве суперкласса используется класс Component, может возникнуть проблема. Код пакета AWT, удаляющий события из очереди и передающий их источникам событий, будет доставлять их, только если убедится, что компонент поддерживает новую модель событий. Один из способов убедиться в этом — вызвать метод enableEvents класса Component. Этот метод получает параметры, задающие маску для событий пакета AWT, которые для данного компонента должны быть разблокированными. Если не думать о событиях пакета AWT вообще (как это бывает при работе с событиями собственного типа), можно передать маску нуля. Следовательно, если источник события расширяет класс Component, в конструктор нужно поместить вызов enableEvents(0).
Легко видеть, что добавить события собственного типа в механизм пакета AWT можно с помощью относительно небольшого кода.
В листинге 8.7 приведен полный текст программы, использующей таймер. Забавы ради на экране генерируются случайные окружности, которые смещаются вниз на один пиксель через каждые 100 миллисекунд. В результате получается имитация дождя (рис. 8.11).
Рис. 8.11. Применение событий собственного таймера для имитации дождяРис. 8.11. Применение событий собственного таймера для имитации дождя
Рис. 8.11. Применение событий собственного таймера для имитации дождя
Рис. 8.11. Применение событий собственного таймера для имитации дождя
Этимпримероммызаканчиваем обсуждениеобработкисобытий. В следующей главе подробнееописываютсякомпонентыпользовательскогоинтерфейса. Разумеется,что- бы запрограммировать пользовательский интерфейс, знание механизма обработки событий необходимо, поскольку работа таких программ основана именно на обработке перехваченных событий, генерируемых компонентами пользовательского интерфейса.
import j ava.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class CustomEventTest
{
public static void main(String[1 args)
{
CustomEventFrame frame = new CustomEventFrame(); frame.setDefaultCloseOperation(JFrame,EXIT_ON_CLOSE); frame.show();
)
/**
Фрейм с панелью, изображающей дождь.
*/
class CustomEventFrame extends JFrame
(
public CustomEventFrame()
(
setTitle("CustomEventTest");
setSize(DEFAULTWIDTH,DEFAULT_HEIGHT) ;
// Помещаем фрейм на панель.
CustomEventPanel panelnew CustomEventPanel ();
Container contentPane = getContentPane(); contentPane.add(panel) ;
)
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
37.37.38.38.39.40.39.40.41.42.50.51.52.53.54.55.56.57.58.62.63.64.65.66.67.68.69.75.87.88.89.90.91.92.93.94, 95.41.42.50.51.52.53.54.55.56.57.58.62.63.64.65.66.67.68.69.75.87.88.89.90.91.92.93.94, 95./** '
37.
37.
38.
38.
39.
40.
39.
40.
41.
42.
50.
51.
52.
53.
54.
55.
56.
57.
58.
62.
63.
64.
65.
66.
67.
68.
69.
75.
87.
88.
89.
90.
91.
92.
93.
94, 95.
41.
42.
50.
51.
52.
53.
54.
55.
56.
57.
58.
62.
63.
64.
65.
66.
67.
68.
69.
75.
87.
88.
89.
90.
91.
92.
93.
94, 95.
Панель, изображающая дождь.
*/
class CustomEventPanel extends JPanel {
public CustomEventPanel()
{
у = 0;
circles = new ArrayList();
Timer t = new Timer(100);
TimerAction listener = new TimerAction();
t.addTimerListener(listener);
}
public void paintcomponent(Graphics g) {
super.paintcomponent(g);
Graphics2D g2 = (Graphics2D)g;
// Перерисовываем изображение, имитируя дождь.
g2.translate(0, у);
// Рисуем все окружности.
for (int i = 0; i < circles.size(); i++) g2.draw((Ellipse2D)circles.get(i));
}
private ArrayList circles;
private int y;
private class TimerAction implements TimerListener {
public void timeElapsed(TimerEvent event) {
if (getWidthO == 0) return; // Панель невидима.
// Добавим новую окружность.
int x = generator.nextlnt(getwidth());
Ellipse2D circle = new Ellipse2D.Double(x, -y, SIZE, SIZE);
circles.add(circle);
// Смещаем окружность. y++;
repaint(); }
private Random generator = new Random();
private static final int SIZE = 9;
}
}
/** Собственный класс событий.
*/
class TimerEvent extends AWTEvent {
112. 138? 139. 140.151.112. 138? 139. 140.151.public TimerEvent(Timer t) { super(t, TIMER_EVENT) ; } public static final int TIMER_EVENT
112.
138? 139. 140.
151.
112.
138? 139. 140.
151.
= AWTEvent.RESERVED_ID_MAX + 5555;
}
/**
Интерфейс слушателя собственных событий.
*/
interface TimerListener extends EventListener {
public void timeElapsed(TimerEvent event); }
/**
Собственный класс для таймера, являющегося источником событий.
*/
class Timer extends JComponent implements Runnable {
public Timer(int i)
{
listenerList - new EventListenerList();
interval = i;
Thread t = new Thread(this);
t.start();
}
/**
Добавляет слушатель таймера.
@param listener добавляемый слушатель
*/
public void addTimerListener(TimerListener listener)
{
listenerList.add(TimerListener.class, listener);
}
/**
Удаляет слушатель таймера.
@param listener удаляемый слушатель
*/
public void removeTimerListener(TimerListener listener) {
listenerList.remove(TimerListener.class, listener);
}
/**
Передает новое событие таймера каждые <code>interval</code> миллисекунд.
*/
public void run()
{
while (true)
{
try { Thread.sleep(interval); }catch(InterruptedException e) {}
•iv• .
TimerEvent event = new TimerEvent(this);
queue =,
queue.postEvent(event);
}
)
159. public voidevent)
{ if (event instanceof { listeners =
TimerListener.class);
for (int i = 0; i <i++)
((TimerListener)listeners[i]).timeElapsed(
}
else
;
)
private int interval;
private EventListenerList listeners;
javax.swing,event.EventListenerList 1.2
voidt, EventListener 1)
Добавляет слушатель событий и его класс в список. Класс хранится таким образом, чтобы методы, возбуждающие события, могли их выбирать. Обычно для этого используется метод
public void1)
1);
Параметры: t Тип слушателя.
s Слушатель.
voidt, EventListener 1)
Удаляет слушатель событий и его класс из списка. Обычно для этого используется метод removeXxxListener.
public void1)
{
1);
)
Параметры: tТип слушателя.
sСлушатель.
t)1.3
Возвращает массив, содержащий всех слушателей заданного типа. Гарантируется, что массив не
• Object [] getListenerList(I
не пуст.не пуст.Возвращает массив, в котором элементами с четными индексами являются классы, а с нечетными — объекты слушателей. Гарантируется, что массив
не пуст.
не пуст.
java.awt.Component1.0
void enableEvents(long maskForEvents! 1.1
Дает возможность компоненту вставить события в очередь, даже если для конкретного типа событий нет слушателя.
Параметры: maskForEvents Маска допустимого типа события, задаваемая константой, например
ACTION_EVENT_MASK, определенной в классе AWTEvent.
cTvVA^^r 9
Компоненты
пользовательского интерфейса из пакета Swing
В этой главе...
Создание интерфейса с помощью схемы "модель-представление-контроллер"
Введение в управление компоновкой
Т Ввод текста
Выбор вариантов
Меню
Изощренное управление компоновкой
Диалоговые окна
сновное предназначение этом главы — продемонстрировать, как используется модель события языкаJava для создания графического пользовательского интерфейса.
Сначала мы сделаем обзор архитектурных основ пакета Swing. Если вы хотите эффективно использовать современные компоненты пользовательского интерфейса, необходимо разбираться в том, что происходит "под капотом". Затем мы покажем вам, как применять такие обычные компоненты пользовательского интерфейса из пакета Swing, как текстовые поля, переключатели и меню. Вы узнаете, как использо-
вать оригинальные особенности менеджера компоновки макетов в языке Java для того, чтобы разместить компоненты в окне, независимо от конкретного стиля интерфейса. В заключение мы покажем, как создаются диалоговые окна из пакета Swing.
В этой главе описываются основные составляющие пакета Swing, например, текстовые компоненты, кнопки и полосы прокрутки. Более сложные компоненты будут описаны во втором томе.
Созданиеинтерфейсаспомощьюсхемы "модель-представление-контроллер"
Как мы пообещали, начнем эту главу с раздела, в котором опишем архитектуру компонентов пакета Swing. Прежде чем разъяснить смысл заголовка, выбранного для этого раздела, давайте остановимся на минутку и задумаемся о том, из чего состоят компоненты графического пользовательского интерфейса, например, кнопка, окошко метки, текстовое поле или управление каталогами. Каждый компонент имеет три характеристики.
Содержание (contents), например, состояние кнопки (нажата или нет) или текст в текстовом поле.
Внешний вид (visual appearance) — цвет, размер и т.д.
Поведение (behaviour) — реакция на события.
Даже такие относительно простые компоненты, как кнопки, демонстрируют довольно сложные взаимосвязи между этими характеристиками. Очевидно, внешний вид кнопки зависит от стиля интерфейса в целом. Кнопка в стиле "Metal" отличается от кнопки в стиле "Windows" или "Motif. Кроме того, внешний вид зависит от состояния кнопки: нажатая кнопка должна выглядеть иначе, чем обычная. Состояние, в свою очередь, зависит от событий, которые получает кнопка. Если пользователь щелкнул на кнопке, она считается нажатой.
Разумеется, пользователи могут не вдаваться в подробности внутреннего устройства и характеристик кнопки. В конце концов, технические детали — удел программиста, использовавшего эту кнопку в своей программе. Именно он должен хорошо разбираться в ее особенностях. Программисты обязаны разрабатывать элементы пользовательского интерфейса так, чтобы они правильно работали независимо от выбранного стиля.
Для этого разработчики пакета Swing обратились к хорошо известной схеме проектирования (designpattern) “модель-представление-контроллер" (model-view-controller). Эта схема, как и многие другие, основана на объектно-ориентированных принципах, описанных в главе 5: не перегружать один объект слишком большим количеством задач. Класс, предусмотренный для кнопки, не обязан делать сразу все. Вместо этого, стиль компонента связывается с одним объектом, а его содержание — с другим. Схема "модель-представление-контроллер" позволяет достичь этой цели. Следуя ей, нужно реализовать три отдельных класса.
Класс Модель, в котором хранится содержание.
Класс Представление, который отображает содержание.
Класс Контроллер, управляющий вводом данных.
Между этими тремя объектами задается точное взаимодействие. Модель хранит содержание и не имеет пользовательского интерфейса. Содержание кнопки тривиально — это небольшой набор флажков, означающих, нажата кнопка или нет, активна или нет и т.д. Текстовое поле имеет более интересное содержание — это строка, в которой хранится текущий текст. Она не совпадает с представлением содержания — если содержание превышает длину текстового поля, пользователь видит лишь часть отображаемого текста (рис. 9.1).
модель "The quick brown fox jumped over the lazy dog"
представлениепредставлениеЬгошп fox jumpЬгошп fox jump
представление
представление
Ьгошп fox jump
Ьгошп fox jump
Рис. 9.1.Модель и представление текстового поля
Модель должна реализовывать методы, изменяющие содержание и раскрывающие его смысл. Например, модель текстового поля имеет методы для добавления и удаления символов из текущего текста и возвращения этого текста в качестве строки. Учтите, что модель полностью невидима. Изображать на экране данные, хранящиеся в модели,—задачапредставления.
Термин "модель", видимо, неудачен, поскольку его часто ассоциируют со способом представления абстрактного понятия. Например, авиаконструкторы строят модели для того, чтобы имитировать реальные самолеты. Однако эта аналогия не применима к схеме "модель-представление-контроллер". В этом случае модель хранит полное содержание, а представление отвечает за ее полное или неполное визуальное изображение. Гораздо более точной аналогией является натурщица, позирующая художнику. Художникдолжен смотреть на модель и создавать ее изображение. В зависимости от стиля художника, представление может оказаться классическим портретом, картиной в духе импрессионизма или набором фигур в стиле кубизма.
Одно из преимуществ этой схемы состоит в том, что модель может иметь несколько представлений, каждое из которых отражает отдельный аспект ее полного содержания. Например, редактор текстов на языке HTML часто предлагает одновременно два представления одного и того же содержания: WYSIWYG (What You See Is What You Get — "что видишь, то и получишь") и "набор дескрипторов" (raw tag) (рис. 9.2). Когда контроллер обновляет одно из представлений модели, изменения вносятся в оба. Получив извещение об изменениях, представление обновляется автоматически. Разумеется, для таких простых компонентов пользовательского интерфейса, как кнопки, нет никакой необходимости предусматривать несколько представлений одной и той же модели.
Контроллер обрабатывает события, связанные с вводом информации, например, щелчки мыши и нажатия клавиш, а затем решает, преобразовывать ли эти события в изменения модели или представления. Например, если пользователь щелкнул на кнопке в текстовом окне, контроллер вызовет из модели команду "вставить символ". Затем модель заставит представление обновить изображение. Представление никогда не узнает, почему изменился текст. Однако, если пользователь нажал клавишу, контроллер может отдать представлению команду "прокрутить текст на экране". Прокрутка текста не влияет на его содержание, поэтому модель никогда не узнает об этом событии.
модельмодельвидWYSIWYGвидWYSIWYG...I II 11 l■l■■lll■ ■ II ■■■ lie I• lllllIl■■■lll■■l I.II 111 lll ll- • ||||>Ы I •llllllll■■lllll■• II •...I II 11 l■l■■lll■ ■ II ■■■ lie I• lllllIl■■■lll■■l I.II 111 lll ll- • ||||>Ы I •llllllll■■lllll■• II •ВИД"набордескрипторов'ВИД"набордескрипторов'<P>IIlllll ■■llll■■ll</P>|<OL><LI>|||||||.B|lll </U><U>llllllleelll</U><U>llllllleellll»</U></OL><P>IIlllll ■■llll■■ll</P>|<OL><LI>|||||||.B|lll </U><U>llllllleelll</U><U>llllllleellll»</U></OL>
модель
модель
вид
WYSIWYG
вид
WYSIWYG
...I II 11 l■l■■lll■ ■ II ■■■ lie I
• lllllIl■■■lll■■l I.I
I 111 lll ll- • ||||>Ы I •
llllllll■■lllll■• II •
...I II 11 l■l■■lll■ ■ II ■■■ lie I
• lllllIl■■■lll■■l I.I
I 111 lll ll- • ||||>Ы I •
llllllll■■lllll■• II •
ВИД
"набордескрипторов'
ВИД
"набордескрипторов'
<P>IIlllll ■■llll■■ll</P>|
<OL>
<LI>|||||||.B|lll </U>
<U>llllllleelll</U>
<U>llllllleellll»</U>
</OL>
<P>IIlllll ■■llll■■ll</P>|
<OL>
<LI>|||||||.B|lll </U>
<U>llllllleelll</U>
<U>llllllleellll»</U>
</OL>
Рис. 9.2. Два разных представления одной и той же модели
Схемы проектирования
Решая задачу, люди редко используют первичные принципы. Чаще всего они руководствуются своим опытом или спрашивают совета у других. Схемы проектирования — это способ представления опыта в структурированном виде.
Последние годы разработчики программного обеспечения стали собирать каталоги таких схем. Пионеров в этой области вдохновили схемы проектирования, созданные архитектором Кристофером Александером (Christopher Alexander). Он составил каталог схем для проектирования общественных и частных жилых помещений. Вот типичный пример.
Окна
Каждый человек любит посидеть в удобном кресле на балконе, у эркера или большого окна с низким подоконником... В комнате, где нет такого места, редко чувствуешь себя удобно и спокойно...
В такой комнате человек разрывается между двумя желаниями:
1) сидеть удобно;
2) сидеть лицом к свету.
Разумеется, если удобные места — т.е. места, где вам больше всего хотелось бы сесть, — находятся далеко от окна, то разрешить этот конфликт невозможно.
Следовательно: в каждой комнате, где человек проводит продолжительное время, должно быть по крайней мере одно окно в подходящем для этого месте.
Каждая схема в каталоге Александера, как и в каталоге схем программного обеспечения, следует определенному формату. Сначала описывается контекст, т.е. ситуация, которая порождает проблему. Затем ставится задача, обычно в виде конфликтующих между собой ограничений. В заключение предлагается ее решение, в котором достигается компромисс между противоречивыми требованиями.
В приведенном выше примере контекстом является комната, в которой человек проводит значительную часть дня. Конфликтующие ограничения заключаются в его желании сидеть удобно и при этом лицом к свету. Решение заключается в создании "места у окна".
В схеме "модель-представление-контроллер" контекстом является система пользовательского интерфейса, которая отображает информацию и получает данные. В ней существует несколько противоречий. К ним могут относиться разные визуальные представления одних и тех же данных, которые должны одновременно обновляться. Визуальное представление может изменяться в зависимости от стиля интерфейса. Механизмы взаимодействия также могут изменяться, например, для того, чтобы поддерживать выполнение команд, которые вводятся голосом. Решение этой задачи заключается в распределении ответственности между тремя разными взаимодействующими компонентами: моделью, представлением и контроллером.
Разумеется, схема "модель-представление-контроллер" сложнее, чем описанное выше архитектурное решение, касающееся окон, поэтому нужно хорошо изучить механизм взаимодействия между ее элементами.
Схема "модель-представление-контроллер" — не единственная схема разработки в языке Java. Например, механизм обработки событий из пакета AWT создан на основе схемы "наблюдателя".
Схемы разработки стали частью общей культуры. Программисты в любой части света знают, что такое схема "модель-представление-контроллер" и схема "наблюдателя". Таким образом, схемы стали эффективным способом описания проблем разработки программного обеспечения.
Взаимодействие между моделью, представлением и контроллером проиллюстрировано на рис. 9.4.
Используя компоненты Swing, совершенно необязательно постоянно думать об архитектуре "модель-представление-контроллер". Каждый пользовательский интерфейс имеет так называемый интерфейсный класс (например, JButton или JTextField), в котором хранятся модель и ее представление. Если вас интересует содержание (например, текст в соответствующем поле), интерфейсный класс запросит модель и вернет ответ. Если вас интересует изменение внешнего вида (например, положение маркера в текстовом поле), интерфейсный класс пошлет запрос представ-
лению. Однако иногда интерфейсный класс работает недостаточно хорошо. Тогда нужно попросить его извлечь модель и работать непосредственно с ней. (Работать непосредственно с представлением необязательно — это задача кода, задающего стиль интерфейса.)
Рис. 9.4. Взаимодействие между моделью, представлением и контроллером
Кроме того, что схема "модель-представление-контроллер" хорошо работает, она оказалась очень удобной для реализации переменных стилей интерфейса. Модель кнопки или текстового поля не зависит от стиля интерфейса. В то же время визуальное представление полностью зависит от конкретного стиля. Контроллер также может изменяться. Например, в устройствах, управляемых голосом, контроллер должен справляться с совершенно другими наборами событий, резко отличающимися от стандартных событий, связанных с клавиатурой и мышью. Отделяя модель от пользовательского интерфейса, разработчики, использующие пакет Swing, получили возможность повторно применять код модели и даже переключать стиль интерфейса в ходе выполнения программы.
Разумеется, схемы — это лишь руководство к действию, а не догма. Нет такой схемы, которая идеально подходила бы для любых ситуаций. Например, может оказаться
сложным следовать схеме "место у окна", если вам нужно перестроить больничную палату. Аналогично разработчики пакета Swing обнаружили, что грубая реальность не всегда позволяет точно реализовать схему "модель-представление-контроллер" для переменных стилей интерфейса. Модели легко разделяются, и каждый компонент пользовательского интерфейса имеет свой класс модели. Однако задачи, стоящие перед представлением и контроллером, не всегда четко отделяются одна от другой, будучи распределенными между разными классами. Конечно, пользователю этих классов это совершенно безразлично. Действительно, как мы уже говорили, о моделях вообще часто можно не думать, а использовать компоненты интерфейсных классов.
Анализ схемы "модель-представление-
контроллер" при разработке кнопок из пакета Swing
В предыдущей главе мы показали, как работает кнопка, ничего не говоря о ее модели, представлении и контроллере. Поскольку кнопка — это простейший элемент пользовательского интерфейса, ее удобно использовать в качестве наглядного пособия для изучения схемы "модель-представление-контроллер". При разработке более сложных компонентов используются аналогичные классы.
Для большинства компонентов класс модели реализует интерфейс Model. Следовательно, должен существовать интерфейс ButtonModel. Классы, реализующие этот интерфейс, могут определять состояние кнопок разного вида. Фактически кнопки настолько просты, что для них в библиотеке Swing предусмотрен отдельный класс DefaultButtonModel, реализующий этот интерфейс.
Понять, какого рода данные поддерживаются моделью кнопки, можно, взглянув на методы интерфейса ButtonModel (табл. 9.1).
ButtonModel
getActionCommand('
Командная строка, связанная с кнопкой
getMnemonic
Мнемоническое обозначение клавиши или комбинации клавиш, связанных с данной кнопкой
isArmed()
Возвращает значение true, если кнопка была нажата и курсор мыши все еще находится на поверхности кнопки
isEnabled()
Возвращает значение true, если кнопка не заблокирована
isPressed()
Возвращает значение true, если кнопка была нажата и кнопка мыши все еще не отпущена
isRollover(1
Возвращает значение true, если курсор мыши находится на поверхности кнопки
isSelectedd
Возвращает значение true, если кнопка была включена (используется для флажков опций и переключателей)
В каждом объекте класса JButton хранится объект класса для модели кнопки, который можно оттуда извлечь.
JButton button = new JButtonfBlue");
ButtonModel model = button.getModel();
На практике подробности, касающиеся состояния кнопки, интересуют лишь представление, которое рисует ее на экране. Однако из класса JButton можно извлечь и другую полезную информацию — в частности, заблокирована ли кнопка. (Для этого класс JButton запрашивает ее модель.)
Взглянем на интерфейс ButtonModel еще раз и попробуем определить, чего в нем не хватает. Оказывается, модель не хранит название кнопки и ее пиктограмму. По этой причине анализ модели не дает информации о внешнем виде кнопки. (Фактически при реализации групп переключателей эта скрупулезность схемы порождает немало осложнений.)
Стоит также отметить, что одна и та же модель (а именно, DefaultButtonModel) используется для нажатия кнопок, переключателей, флажков и даже выбора пункта меню. Разумеется, каждый из упомянутых типов кнопок имеет свое собственное представление и отдельный контроллер. Если интерфейс имеет стиль "Metal", класс JButton использует для представления класс BasicButtonUI, а для контроллера — класс ButtonUIListener. В общем, каждый компонент библиотеки Swing имеет связанный с ним объект представления, название которого заканчивается на UI. При этом не все компоненты пакета Swing имеют свой собственный объект контроллера.
Итак, прочитав это краткое вступление, посвященное классу JButton, вы можете спросить: а что на самом деле представляет собой класс JButton? Это просто интерфейсный класс, производный от класса JComponent, который содержит объект класса DefaultButtonModel, некоторое представление данных (например, метку кнопки и ее пиктограмму), а также объект класса DefaultButtonUI, предназначенный для представления кнопки.
Введение вуправлениекомпоновкой
Прежде чем перейти к обсуждению таких компонентов пакета Swing, как текстовые поля и переключатели, рассмотрим, каким образом они объединяются внутри фрейма. Поскольку в пакете Java SDK нет диспетчера форм, как в средах программирования Visual Basic или Delphi, программист должен сам писать код и размещать компоненты пользовательского интерфейса внутри фрейма (компоновка).
Разумеется, если вы работаете в интегрированной среде программирования на языке Java, возможно, в ней предусмотрена полная или частичная автоматическая компоновка элементов интерфейса. Как бы там ни было, программист обязан знать абсолютно точно, как выполняется компоновка элементов интерфейса, поскольку интерфейсы, полученные с помощью наилучших систем автоматической генерации, обычно нуждаются в "ручной доводке".
Рассмотрим программу, приведенную в предыдущей главе, в которой кнопка использовалась для изменения цвета фрейма (рис. 9.5).
Напомним, как создавалась эта программа.
Сначаламы определиливнешнийвидкаждойкнопки, задавстрокуметкив
конструкторе:
JButton yellowButton = new JButton("Yellow");
Затем мы добавили на панель отдельные кнопки:
add(yellowButton);
Наконец мы добавили необходимые обработчики событий: yellowButton.addActionListener(listener! ;
Рис. 9.5. Панель с тремя кнопками
Что произойдет, если к панели добавить еще несколько кнопок? На рис. 9.6 показана панель с шестью кнопками. Расположены они симметрично относительно центра строки, и, если кнопки в строке уже не помещаются, они переносятся на новую строку.
Рис. 9.6. Панель с шестью кнопками, управляемая потоковой компоновкой
Более того, симметрия сохраняется, даже если пользователь изменяет размер фрейма (рис. 9.7).
В языке Java реализована очень элегантная концепция динамической компоновки: все компоненты контейнера управляются менеджером компоновки (layout manager). В нашем примере кнопки управляются менеджером потоковой компоновки (flow layout manager), который применяется к панели по умолчанию.
Менеджер потоковой компоновки размещает компоненты горизонтально, пока хватает места, а затем начинает новую строку.
Если пользователь изменил размеры контейнера, менеджер компоновки автоматически перемещает компоненты, заполняя свободное пространство.
Для размещения компонентов в строке можно выбрать разные способы. По умолчанию они размещаются симметрично относительно центра контейнера. Кроме того, их можно выровнять по левому или правому краю. Для этого нужно задать параметры LEFT или RIGHT в конструкторе объекта класса FlowLayout.
setLayout(new FlowLayout(FlowLayout.LEFT));
Рис. 9.7. Изменение размера панели автоматически изменяет положение кнопокРис. 9.7. Изменение размера панели автоматически изменяет положение кнопокОбычно программист предоставляет менеджеру потоковой компоновки самому управлять размещением компонентов внутри фрейма. Однако, используя другой вариант конструктора потоковой компоновки, между компонентами можно задать фиксированное расстояние (по вертикали и горизонтали). (См. замечания об интерфейсе прикладных программ.)
Рис. 9.7. Изменение размера панели автоматически изменяет положение кнопок
Рис. 9.7. Изменение размера панели автоматически изменяет положение кнопок
java.awt.Container1.0
setLayout(LayoutManager m)
Задает менеджер компоновки для данного контейнера.
java.awt.FlowLayout 1.0
FlowLayout(int align)
Создает новый объект класса FlowLayout с указанным видом выравнивания внутри фрейма.
Параметры: align LEFT, CENTER или RIGHT.
• FlowLayout(int align, ing hgap, int vgap)
Создает новый объект класса FlowLayout с указанным видом выравнивания внутри фрейма и фиксированным расстоянием между компонентами.
Параметры:
align
LEFT, CENTER или RIGHT.
hgap
Горизонтальное расстояние между компонентами, заданное в пикселях (при отрицательных значениях компоненты перекрываются).
vgap
Вертикальное расстояние между компонентами, заданное в пикселях (при отрицательных значениях компоненты перекрываются).
Компоновка рамок
В языке Java есть несколько менеджеров компоновки, при этом программист может создать свой собственный менеджер компоновки: как это сделать, будет показано в этой главе ниже. Однако, чтобы рассмотреть более интересный пример, нужно кратко описать менеджер компоновкиралсок (border layout manager). Этот менеджер назначается по умолчанию для каждой панели содержимого в каждом объекте класса
JFrame. В отличие от менеджера потоковой компоновки, который полностью контролирует положение каждого компонента, менеджер компоновки рамок позволяет выбрать место для каждого компонента. Компонент можно разместить внутри панели в центре, наверху, внизу, слева и справа (рис. 9.8).
Верхняя часть
Левая часть
Центральная
часть
Правая часть
Нижняя часть
Рис. 9.8. Компоновка рамок
class MyPanel extends JPanel
{
setLayout(new BordeLayout());
add(yellowButton, BorderLayout.SOUTH);
}
Сначала на панели размещаются рамки компонентов, а оставшееся свободным пространство заполняется симметрично относительно центра. При изменении размера контейнера толщина контуров компонентов остается неизменной, однако размер внутренней части компонента изменяется. При добавлении компонента к панели указываются константы CENTER, NORTH, SOUTH, EATH или WEST, определенные в классе BorderLayout. Занимать все позиции на панели не обязательно. Если не указано никакого значения, по умолчанию задается константа CENTERED.
Константы в классе BorderLayout определены как строки. Например, константа BorderLayout . SOUTH — это строка "South". Многие программисты предпочитают использовать непосредственно строки, поскольку они короче, например contentPane.add (component, "South"). Однако, если вы случайно сделаете в строке опечатку, компилятор не распознает этой ошибки.
Компоновка рамок масштабирует все компоненты, чтобы заполнить свободное пространство. (В отличие от нее, потоковая компоновка оставляет размеры компонента неизменными.) При компоновке рамок можно задать расстояние между областями, занимаемыми компонентами. Для этого нужно определить соответствующие параметры конструктора класса BorderLayout.
Как уже указывалось, панель содержимого класса JFrame использует компоновку рамок. До сих пор мы не пользовались этим преимуществом, просто добавляя компоненты внутрь области, задаваемой по умолчанию (центр панели). Однако компоненты можно добавлять и в другие области.
Container contentPane = getContentPane();
contentpane.add(yellowButton, BorderLayout.SOUTH);
С этим фрагментом кода, который будет рассмотрен в следующем разделе, связаны определенные проблемы.
java.awt.Container 1.0
void add(Component c, Object constraints) 1.1 Добавляет компонент в заданный контейнер.
Параметры: cПараметры: cДобавляемый компонент.Добавляемый компонент.
Параметры: c
Параметры: c
Добавляемый компонент.
Добавляемый компонент.
constraintsconstraintsjava.awt.BorderLayoutBorderLayout(int hgap,java.awt.BorderLayoutBorderLayout(int hgap,Идентификатор, распознаваемый менедже-ром компоновки.
constraints
constraints
java.awt.BorderLayout
BorderLayout(int hgap,
java.awt.BorderLayout
BorderLayout(int hgap,
1.0
int vgap)
Параметры: hgapПараметры: hgapСоздает новый объект класса BorderLayout с фиксированным расстоянием между компонентами.
Параметры: hgap
Параметры: hgap
Горизонтальное расстояние между компонентами, заданное в пикселях (при отрицательных значениях компоненты перекрываются).
vgap
Вертикальное расстояние между компонентами, заданное в пикселях (при отрицательных значениях компоненты перекрываются).
Панели
Класс BorderLayout сам по себе не очень интересен. На рис. 9.9 показано, что произрйдет, если мы попробуем применить фрагмент кода, приведенный выше. Кнопка увеличилась в размерах и заполнила собой всю нижнюю часть фрейма. Если мы попытаемся вставить в нижнюю часть еще одну кнопку, она просто заменит собой предыдущую кнопку.
Один из способов решить эту проблему заключается в использовании дополнительных панелей (additional panels). Панели представляют собой небольшие контейнеры, содержащие элементы интерфейса. Эти панели могут в свою очередь быть частью более крупной панели, управляемой менеджером компоновки. Например, одну панель (с кнопками) можно поместить в нижнюю часть, а другую (с текстом) — в центр. Используя вложенные панели, а также комбинацию потоковой компоновки и компоновки рамок, можно точно управлять размещением компонентов. Этот подход к компоновке вполне достаточен для создания прототипов, и мы будем применять его в наших программах, приведенных в первой части этой главы. Более точный способ размещения компонентов описан в этой главе ниже, в разделе, посвященном классу GridBagLayout.
Рис. 9.9. Отдельная кнопка, управляемая менеджером компоновки рамокРис. 9.9. Отдельная кнопка, управляемая менеджером компоновки рамок
Рис. 9.9. Отдельная кнопка, управляемая менеджером компоновки рамок
Рис. 9.9. Отдельная кнопка, управляемая менеджером компоновки рамок
Посмотрим на рис. 9.9. Три кнопки внизу экрана расположены внутри панели. Эта панель расположена в нижней части панели содержимого. Допустим, что нам нужно добавить панель с тремя кнопками, как показано на рис. 9.9. Как и следовало ожидать, сначала создается новый экземпляр класса JPanel, к которому затем Добавляются отдельные кнопки. Менеджер компоновки, предусмотренный по умолчанию для панели, называется FlowLayout. В данной ситуации он вполне удобен. В заключение на панель с помощью метода add помещаются отдельные кнопки (эту процедуру мы уже анализировали). Поскольку мы добавляем кнопки к панели, не изменяя менеджер компоновки по умолчанию, положением кнопок управляет менеджер FlowLayout. Это значит, что кнопки будут выровнены по центру панели и не будут масштабироваться, чтобы заполнить все свободное пространство. Ниже приведен фрагмент кода, который добавляет в нижнюю часть контейнера панель, содержащую три кнопки.
Рис. 9.10.Панель, помещенная в нижней части фреймаРис. 9.10.Панель, помещенная в нижней части фрейма
Рис. 9.10.Панель, помещенная в нижней части фрейма
Рис. 9.10.Панель, помещенная в нижней части фрейма
Container contentPane = getContentPane();
JPanel panel = new JPanel();
panel.add(yellowButton);
panel.add(blueButton);
panel.add(redButton);
contentPane.add(panel, BorderLayout.SOUTH);
Границы панели невидимы для пользователя. Панели просто представляют собой некий механизм, предназначенный для разработки пользовательского интерфейса.
Как мы уже видели, класс JPanel по умолчанию использует менеджер FlowLayout. В конструкторе класса JPanel можно указать другой менеджер компоновки по умолчанию. Несмотря на то что большинство других контейнеров не имеют такого конструктора, все они содержат метод setLayout, позволяющий установить менеджер компоновки, отличающийся от менеджера, предусмотренного для этого контейнера по умолчанию.
1.2
JPanel(LayoutManager m]
Задает менеджер компоновки для панели.
Сеточная компоновка
Менеджер сеточной компоновки расставляет все компоненты по строкам и столбцам подобно электронной таблице. Однако при этом размер всех ячеек одинаков. Программа, имитирующая работу калькулятора, показанная на рис. использует сеточную компоновку для размещения кнопок. При изменении размера окна кнопки автоматически увеличиваются или уменьшаются, причем размер всех кнопок остается одинаковым.
Калькулятор
Желаемое количество строк и столбцов указывается в конструкторе объекта сеточной
4));
Как и для рамочной и потоковой компоновок, нужно указать расстояние между компонентами по горизонтали и вертикали.
4, 3, 3));
Два последних параметра в конструкторе задают расстояние между компонентами по горизонтали и вертикали (в пикселях).
Компоненты добавляются построчно,— в первую ячейку в первой строке,
затем — во вторую ячейку в первой строке и т.д.
panel, add (new JButtonC'l"));
panel.add(new JButton("2"));
В листинге 9.1 приведен исходный текст программы, имитирующей работу калькулятора. Это обычный калькулятор. Он не использует "обратную польскую запись", получившую необъяснимую популярность в учебниках поJava.
Листинг 9.1 Программа Calculator
import java.awt.*;
import
import
public class Calculator
{
public static void main(String(] args)
{
CalculatorFrame frame = new CalculatorFrame();.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE]'
frame.show() ;
}
}
/**
Фрейм с панелью калькулятора.
Ч
class CalculatorFrame extends JFrame
{
public CalculatorFrame()
{
setTitle("Calculator");
Container contentPane=getContentPane();
CalculatorPanel panel=newCalculatorPanel();
contentPane.add(panel);
pack();
}
}
/**
Панель с кнопками калькулятора и дисплеем результатов.
*/
class CalculatorPanel extends JPanel
{
public CalculatorPanel()
{
setLayout(new BorderLayout());
result = 0;
= "=";
start = true;
// Добавляем дисплей.
display = new JTextFieldfO");
add(display, BorderLayout.NORTH);
ActionListener insert = new InsertAction();
ActionListener command - new CommandAction();
// Добавляем кнопки размером4x4узла сетки.
panelnew JPanel О;
panel.setLayout(new GridLayout(4, 4)) ;
addButton("7",insert);
addButton("8",insert);
addButton("9",insert);
addButtonf/",command);
addButtonf"4",insert);
addButton("5",insert);
addButton("6", insert);
addButton("*", command);
addButton("1",insert);
addButton("2",insert);
addButton("3",insert);
addButton("-",command);
addButton("0",insert);
addButton(".",insert);
addButton("=",command);
addButton("+",command);
add(panel, BorderLayout.CENTER);
}
/**
Добавляет кнопку в центр панели.
@param label метка кнопки
Sparam listener слушатель кнопки
*/
private void addButton(String label,
ActionListener listener)
{
JButton button = new JButton(label);
button.addActionListener(listener);
panel.add(button) ;
}
/**
Действие, вставляющее в конец дисплея строку,
являющуюся результатом нажатия кнопки.
*/
private class InsertAction implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
String input = event.getAct ionCommand();
if (start)
{
display.setText("");
start = false;
}
display.setText(display.getText(] + input);
)
}
/**
Действие, выполняющее команды, которые задаются
строкой, описывающей кнопку.
*/
private class CommandAction implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
String command = evt. get Act IonCommandO ;
if (start)
{
if (command.equals ("-"))
{
display.setText(command);
start = false;
}
else
lastcommand = command;}
else
{
calculate(Double.parseDouble(display.getText())); lastCommand = command;
start = true;
/**
Выполняет вычисление.
Sparam x значение, накапливающее результат
*/
public void calculate(double x)
{
if (lastcommand.equals("+”) ) result += x;
else if (lastcommand.equals(”-")) result -= x; else if (lastCommand.equals("*"); result *= x; else if (lastCommand.equals("/")] result /= x; else if (lastCommand.equals("="): result = x; display.setText("" + result);
private JLabel display;
private JPanel panel;
private double result;
private String lastCommand;
private boolean start;
Разумеется, редко можно встретить приложение, в котором компоненты были бы так же строго упорядочены, как в калькуляторе. На практике небольшие сетки (обычно состоящие лишь из одной строки или столбца) могут оказаться полезными при компоновке отдельных частей окна. Например, если нужно расположить в окне ряд кнопок одинакового размера, лучше их поместить на панель, которой управляет менеджер сеточной компоновки.
java.awt.GridLayout GridLayout(int rows,
1.0
int cols)
Создает новый объект класса GridLayout.
Параметры:rows
Количество строк в сетке.
columnsКоличество столбцов в сетке.
• GridLayout(int rows, int cols, int hgap, int vgap)
Создает новый объект класса GridLayout с заданным расстоянием между объектами по горизонтали и вертикали.
Параметры: rowsПараметры: rowsКоличество строк в сетке.
Параметры: rows
Параметры: rows
columnsКоличество столбцов в сетке.
hgaphgapvgapvgapРасстояние по горизонтали, выраженное в пикселях (отрицательное значение приводит к перекрытию).
hgap
hgap
vgap
vgap
Расстояние по вертикали, выраженное в пикселях (отрицательное значение приводит к перекрытию).
Ввод текста
Теперь мы готовы приступить к изучению компонентов интерфейса, предусмотренных в пакете Swing. Начнем с компонентов, позволяющих пользователю вводить и редактировать текст. Для этого в языке Java предусмотрены два компонента: текстовые поля (text fields) и текстовые области (text areas). Различие между ними заключается в том, что в текстовое поле можно ввести только одну строку, а в текстовую область — несколько. Для ввода отдельной строки предназначен класс JTextField, а для ввода нескольких строк — класс JTextArea.
Оба эти класса расширяют класс JTextComponent. Создать компонент этого класса невозможно, поскольку он является абстрактным. С другой стороны, как это часто бывает при программировании на языке Java, при просмотре документации по интерфейсу прикладных программ оказывается, что существуют методы, принадлежащие именно классу JTextComponent, а не производным от него классам. Например, методы, получающие или помещающие текст в текстовое поле или область, на самом деле принадлежат классу JTextComponent.
javax.swing.text.JTextComponent 1.2
void setText(String t)
Изменяет текст в текстовом компоненте.
Параметры:tНовый текст.
String getText()
Возвращает текст, содержащийся в данном текстовом компоненте.
void setEditable(boolean b)
Определяет, может ли пользователь редактировать содержание компонента.'
Текстовые поля
поместить его на панель или впоместить его на панель или вОбычный способ добавить текстовое поле в окно другой контейнер (точно так же, как и кнопки).
поместить его на панель или в
поместить его на панель или в
JPanel panel = new JPanel();
JTextField textField = new JTextField(" Текстовое поле", 20); panel.add(textField);
Этот фрагмент кода добавляет текстовое поле, инициализируя его строкой "Текстовое поле". Второй параметр конструктора задает длину строки. В данном случае длина строки равна 20 символам. К сожалению, символы — не очень точная единица измерения. Их ширина зависит от выбранного шрифта. Если этот параметр равен числу п, это означает, что пользователь может ввести в поле текста п символов или меньше. На практике эта единица измерения не слишком удобна, и ради безопасности длину строки приходится завышать на один или два символа. Кроме того, следует учесть, что заданное количество символов считается пакетом AWT лишь желательной длиной строки. Решив уменьшить или увеличить текстовое поле, менеджер компоновки изменит длину строки. Длина строки, задаваемая параметром конструктора JTextField, не является максимально допустимым количеством символов, которое может ввести пользователь в данное текстовое поле. Пользователь может набирать и более длинные строки: если текст выйдет за пределы текстового поля, произойдет его прокрутка. Часто прокрутка текста раздражает пользователей, поэтому размер текстового поля следует задавать с запасом. Если во время выполнения программы возникает необходимость изменить этот размер, следует применить метод setColunins.
Изменив размер текстового окна с помощью метода setColumns, следует вызвать метод validate, принадлежащий окружающему контейнеру.
textField.setcolumns(10) ;
panel.validate() ;
Метод validate вычисляет заново размер и взаимное расположение всех компонентов в контейнере. Затем менеджер компоновки перерисовывает контейнер, изменяя размер текстового поля.
Обычно нужно дать пользователю возможность добавлять новую или редактировать существующую строку в текстовом поле. Довольно часто текстовые поля сначала пусты. Для того чтобы создать пустое текстовое поле, просто не указывайте соответствующий параметр в конструкторе класса JTextField.
JTextField textField = new JTextField(20);
Содержание текстового поля можно изменить в любое время, используя метод setText из родительского класса TextComponent: textField.setText("Привет!"] ;
Как указывалось ранее, определить, какой текст ввел пользователь, можно с помощью метода getText. Этот метод возвращает строку, которую пользователь ввел в данном поле текста. Чтобы отбросить незначащие пробелы, стоящие перед текстом и после него, к значению, возвращаемому методом getText, применяется метод trim. String text = textField.getText().trim();
Для того чтобы изменить шрифт, которым набран текст, следует использовать метод setFont.
Попробуем создать несколько текстовых полей. На рис. 9.12 показано выполняющееся приложение. Эта программа демонстрирует циферблат часов и два текстовых поля для ввода часов и минут. Как только содержание текстовых полей изменяется, часы начинают показывать новое время.
Рис. 9.12. Пример текстового поляРис. 9.12. Пример текстового поля
Рис. 9.12. Пример текстового поля
Рис. 9.12. Пример текстового поля
Отслеживать каждое изменение в текстовом поле— довольно трудоемкая задача. Прежде всего отметим, что контролировать нажатие клавиш в данном случае очень неудобно. Некоторые клавиши (например стрелки) не влияют на содержание текста. В зависимости от стиля, выбранного для внешнего вида программы, на текст могут влиять события, связанные с мышью. Как указывалось в начале этой главы, текстовое поле, предусмотренное в пакете Swing, реализовано в самом общем виде: строка, которую вы видите в текстовом поле, это просто внешний вид {представление) соответствующей структуры данных (модели). Разумеется, для небольшого текстового поля различие между его представлением и моделью несущественно. Представление— это строка, показанная на экране, а модель— это объект класса String. Однако эта же архитектура используется и в компонентах, предназначенных для редактирования и форматирования текста с помощью шрифтов, абзацев и других атрибутов, которые представлены гораздо более сложными структурами данных. Модель для всех текстовых компонентов описывается интерфейсом Document, который относится и к обычному, и к форматированному тексту (например, на языке HTML). Дело в том, что программист может поручить документу (не являющемуся текстовым компонентом) сообщить об изменении данных, установив слушатель документа (documentlistener).
textField.getDocument().addDocumentListener(listener);
При изменении текста вызывается один из трех следующих методов.
void insertupdate(DocumentEvent e)
void removetUpdate(DocumentEvent e)
void changedUpdate(DocumentEvent e)
Первые два метода вызываются при вставке или удалении символа. Третий метод вызывается только при форматировании сложных текстовых документов. К сожалению, не существует способа, который позволил бы сообщить об изменении текста с помощью вызова одного метода. Адаптерный класс для этой цели также не предусмотрен. Таким образом, слушатель документа должен реализовывать все три метода. Например, в нашей программе нужен следующий класс.
DocumentListener{ setClock(); }{ setClock(); }{}DocumentListener{ setClock(); }{ setClock(); }{}private class ClockFieldListener implements {
DocumentListener
{ setClock(); }
{ setClock(); }
{}
DocumentListener
{ setClock(); }
{ setClock(); }
{}
public void insertUpdate(DocumentEvent e) public void removeUpdate(DocumentEvent e) public void changeUpdate(DocumentEvent e) }
Для того чтобы получить строку, введенную пользователем в текстовое поле, метод setClock использует метод getText. К сожалению, этот метод возвращает только строки. После этого их нужно преобразовать в целые числа, применяя обычные (и довольно громоздкие) заклинания.
int hours = Integer.parseint(hourField.getText().trim());
int minutes = Integer.parseint(minuteField.getText().trim());
Однако этот фрагмент кода не будет правильно работать, если пользователь введет в текстовое поле не целые числа, а, например, "два" вместо"2",или даже оставит пустое поле. В этом случае мы будем перехватывать исключительную ситуацию NumberFormatException, возбуждаемую методомparseint, ипростоне обновлять циферблат часов, если в текстовом поле введено не число, ачто-то другое. В следующем раз- делемыувидим, какможнопредотвратитьвводнеправильныхданныхвтекстовомполе.
Вместо прослушивания событий, связанных с документом, к текстовому полю можно добавить слушатель действий. Этот слушатель распознает, когда пользователь нажимает клавишу <ENTER>. МЫ не рекомендуем этот подход, поскольку пользователи иногда забывают нажать клавишу <ENTER>, введя данные в текстовое поле. Если вы используйте слушатель действий, следует также установить слушатель фокуса, чтобы обнаружить момент, когда пользователь покинет текстовое поле.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event. *;
public class TextTest
{
public static void main(String [] args)
{
TextTestFrame frame = new TextTestFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
;
}
}
/**
Фрейм с двумя текстовыми полями для установки часов.
*/
class TextTestFrame extends JFrame
{
public TextTestFrame()
{
_ setTitleC'TextTest") ;
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Container contentPane = getContentPane();
DocumentListener listener = new ClockFieldListener(];
// Добавляем панель с текстовыми полями.
JPanel panelnew JPaneld;
hourField = new JTextField("12", 3);
panel.add(hourField);
hourField.getDocument().addDocumentListener(listener);
minuteField= new JTextField("00", 3);
panel.add(minuteField);
minuteField.getDocument().addDocumentListener(listener]
contentPane.addfpanel, BorderLayout.SOUTH);
// Добавляем часы.
clock = new ClockPanel();
) contentPane.add(clock, BorderLayout.CENTER);
/**
Устанавливаем часы, пользуясь значениями, хранящимися в текстовых полях.
*/
public void setClockO
(
try
( int hours
=
int minutes -
clock.setTime(hours, minutes);
}
catch (NumberFormatException e) {}
// Если ввод неверен, часы не устанавливаются. }
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 300;
private JTextField hourField;
private JTextField minuteField;
private ClockPanel clock;
private class ClockFieldListener
implements DocumentListener
{
public void insertUpdate(DocumentEvent e) { setClockO; } public void removeUpdate(DocumentEvent e) { setClockO; }
public void changedUpdate(DocumentEvent e) {}
Панель, на которой изображаются часы.
class ClockPanel extends JPanel
{
public void paintcomponent(Graphics g)
{
// Рисуем окружность.
super.paintComponent(g I ;
Graphics2D g2 = (Graphics2D)g;
Ellipse2D circle
= new Ellipse2D,Double(0, 0, 2 * RADIUS, 2 * RADIUS);
g2.draw(circle; ;
<
// Рисуем часовую стрелку.
double hourAngle
= Math.toRadians(90 - 360 * minutes / (12 * 60));
drawHand(g2, hourAngle, hour_hand_length; ;
// Рисуем минутную стрелку.
double minuteAngle
= Math.toRadians(9C - 360 * minutes / 60);
drawHand(g2, minuteAngle, MINUTE_HAND_LENGTH);
}
public void drawHand(Graphics2D g2,
double angle, double handLength)
{
Point2D end = new Point2D.Double(
RADIUS + handLength * Math.cos(angle),
RADIUS - handLength * Math.sin(angle));
Point2D center = new Point2D.Double(RADIUS, RADIUS);
g2.draw(new Line2D.Double(center, end));
}
/**
Set the time to be displayed on the clock
@param h hours
@param m minutes
*/
public void setTime(int h, int m]
{
minutes = h * 60 + m;
repaint();
}
private double minutes = 0;
private double RADIUS = 100;
private double MINUTE_HAND_LENGTH 0.8 * RADIUS;
private double HOUR_HAND_LENGTH = 0.6 * RADIUS;
}
java.awt.Component 1.0
void validate ()
Вычисляет заново размер компонента или размер и взаимное расположение компонентов в контейнере.
javax.swing.JTextField 1.2
JTextField (int cols) Создает пустое поле указанного размера.
Параметры:colsПараметры:colsКоличество символов в строке.
Параметры:cols
Параметры:cols
JTextField(String text, int cols)
Создает поле указанного размера, инициализированноеуказанной строкой.
Параметры:textПараметры:textСтрока, заполняющая текстовое поле.
Параметры:text
Параметры:text
colsКоличество символов в строке.
void setColumns(int cols)
Сообщает текстовом}* полю количество символов, которое следует использовать.
Параметры:colsКоличество символов в строке.
javax.swing.text.Document1.2
int getlength()
Возвращает количество символов в документе.
String getText(int offset, int length)
Возвращает текст, содержащийся в заданной части документа.
Параметры:offsetПараметры:offsetНачало текста.Начало текста.
Параметры:offset
Параметры:offset
Начало текста.
Начало текста.
lengthДлина нужной строки.
void addDocumentListener(DocumentListener listener) Регистрирует пользователя для уведомления об изменениях документа.
javax.swing.event.DocumentEvent 1.2
Document getDocument()
Возвращает документ, являющийся источником данного события.
javax.swing.event.DocumentListener 1.2 void changedUpdate(DocumentEvent e)
Вызывается при изменении одного или нескольких атрибутов текста.
void insertUpdate(DocumentEvent e)
Вызывается при вставке в документ одного или нескольких символов.
void removedUpdate(DocumentEvent e)
Вызывается при удалении части документа.
Поля для ввода пароля
Поля для ввода пароля представляют собой особый вид текстовых полей. Чтобы скрыть пароль от пронырливых наблюдателей, его символы не отображаются на экране. Вместо этого каждый символ в пароле заменяется символом эха (echo symbol), обычно звездочкой (*). В библиотеке Swing предусмотрен класс JPasswordField, реализующий такое текстовое поле.
Поле для ввода пароля — это еще один пример могущества архитектурной схемы "модель-представление-контроллер". Для хранения данных пароль использует ту же модель, что и обычное текстовое поле, но его представление изменено и заменяет все символы пароля символами эха.
API javax.swing.JPasswordField 1.2
^■ie jPasswordFirld(String text, int columns)
Создает новое поле для ввода пароля.
Параметры: textТекст, который должен отображаться на экра
не. Если текста нет, задается параметр none.
columnsКоличество символов.
void setEchoChar(char echo)
Задает символ эха, который может зависеть от выбранного стиля интерфейса. Если задано значение 0, выбирается символ эха, определенный по умолчанию.
char[] getPassword()
Возвращает текст, содержащийся в поле для ввода пароля. Для обеспечения большей безопасности возвращаемый массив после использования следует перезаписать. Пароль возвращается не в виде объекта класса String, поскольку строка может оставаться в виртуальной машине до тех пор, пока не будет уничтожена механизмом сборки мусора.
Поля для форматированного ввода
Проблема, упомянутая в предыдущем разделе, встречается очень часто — прежде чем начать работать с данными, следует проверить, правильно ли они были введены. В нашем примере нужно гарантировать, что пользователь ввел именно целое число, а не что-нибудь другое. Таким образом, пользователю разрешается вводить только цифры от 0 до 9 и знак переноса (-). Знак переноса, если он необходим, должен быть повьем символом входной строки.
На первый взгляд верификация данных выглядит простой задачей. Можно установить слушатель клавиш для тестового поля и затем перехватывать (consume) все события, связанные с клавишами, не являющимися цифрами или знаком переноса. К сожалению, этот простой подход, часто рекомендуемый для верификации данных, на практике себя не оправдал. Во-первых, не каждая комбинация допустимых символов представляет собой правильное число. Например, группы символов --3 и 3-3 являются недопустимыми, хотя образованы из допустимых символов. Еще важнее, что существует много способов изменить текст, не нажимая клавиш. В зависимости от стиля внешнего вида программы, для удаления, копирования и вставки текста могут применяться разные комбинации клавиш. Например, в стиле "Metal" комбинация клавиш <CTRL+V> используется для вставки содержимого буфера в текстовое поле. Таким образом, возникает необходимость следить, чтобы пользователь не вставил из буфера неверные данные. Очевидно, что попытка фильтровать нажатие клавиш с целью блокирования недопустимых символов окажется безуспешной.
Как ни странно, до появления пакета SDK 1.4 не существовало компонентов для ввода числовых значений. Начиная с первого издания нашей книги, для реализации текстового поля, содержащего надлежащим образом форматированное целое число, мы использовали класс IntTextField. В каждом новом издании мы изменяли эту реализацию, пытаясь извлечь хоть какую-то пользу из недоделанного и постоянно изменяющегося класса JFormattedTextField, который можно использовать не только для ввода чисел, но и для ввода дат и даже таких изощренных форматированных значений, как IP-адреса.
Ввод целых чисел
Начнем с простого случая: текстового поля для ввода целого числа.
JFormattedTextField intField - new JFormattedTextField(
NumberFormat.getlntegerOnstance());
Как и для любого текстового поля, мы можем задать количество позиций.
intField.setcolumns(61 ;
Установим значение по умолчанию с помощью метода setValue. Этот метод получает параметр, являющийся объектом класса Object, поэтому нам необходимо запаковать значение типа int, заданное по умолчанию, в объект класса Integer.
intField.setvalue(new Integer(100));
Обычно пользователи вводят данные в нескольких текстовых полях, а затем щелкают кнопкой мыши, чтобы ввести все значения сразу. После щелчка мыши числа, введенные пользователем, считываются с помощью метода getValue. Этот метод возвращает в качестве результата объект класса Object, поэтому его необходимо привести к соответствующему типу. Если пользователь отредактировал значение, метод JFormattedTextField вернет объект класса Long. Однако, если пользователь не внес никаких изменений, возвращается исходный объект класса Integer. Следовательно, возвращаемое значение нужно преобразовать в объект общего суперкласса Number.
Number value = (Number)intField.getValue(); int v = value.intValue();
Поля для ввода форматированного текста не представляют для нас большого интереса, пока пользователь не введет неверно отформатированные данные. Эту тему мы рассмотрим в следующем разделе.
Поведение поля, вышедшего из фокуса
Посмотрим, что происходит, когда пользователь вводит данные в текстовое поле. Пользователь набирает какой-то текст и вдруг решает выйти из поля, например, щелкнув мышью на другом компоненте. В этом случае текстовое поле выходит из фокуса. Текстовый курсор исчезает из поля, а нажатие клавиш теперь связывается с другим компонентом.
Когда текстовое поле выходит из фокуса, средства форматирования (formatter) анализируют текстовую строку, введенную пользователем. Если средства форматирования знают, как преобразовать текстовую строку в объект, текст считается корректным. В противном случае он рассматривается как неправильный. Для проверки корректности текущего содержания текстового поля можно применять метод isEditValid.
По умолчанию поведение поля, вышедшего из фокуса, определяется принципом "фиксируй или возвращайся в исходное состояние" ("commit or revert"). Если текстовая строка является корректной, она фиксируется. Средства форматирования превра-
щают ее в объект, который становится текущим значением поля (т.е. значением, возвращаемым методом getValue). Затем это значение преобразовывается обратно в строку, которая становится видимой в текстовом поле. Например, средства форматирования целых чисел признают ввод 1234 56 корректным, устанавливают текущее значение с помощью оператора new Long (123456), а затем преобразовывают это значение в строку с десятичной запятой: 123, 456.
И наоборот, если текстовая строка некорректна, текущее значение поля не изменяется. В этом случае текстовое поле возвращается в исходное состояние — в нем содержится строка, представляющая старое значение. Например, если пользователь ввел неверное значение, скажем, х123, в текстовое поле, предназначенное для ввода целых чисел, то будет восстановлено старое значение текстового поля, которое оно содержала до выхода из фокуса.
Средства форматирования целых чисел считают текстовую строку корректной, если она начинается с целого числа. Например, строка 1234х считается корректной. Она преобразовывается в число 1234, а затем форматируется в строку1,234.
Поведение текстового поля можно задать с помощью метода setFocusLost- Behaviour. Фиксация ("commit") в таком случае несколько отличается от принятой по умолчанию. Если текстовая строка оказывается неверной, то и сама строка, и значение, записанное в поле, остаются неизменными — они теперь не связаны друг с другом. Устойчивое ("persist") поведение еще более консервативно. Даже если текстовая строка является правильной, ни сама строка, ни текущее значение поля не изменяются. Для восстановления связи между ними нужно вызывать методы commitEdit, setValue или setText. В заключение опишем "возвращение в исходное состояние", которое теперь становится совершенно излишним. Если поле вышло из фокуса, ввод пользователя игнорируется, а текстовая строка восстанавливает свое старое значение.
Как правило, принцип "фиксируй или возвращайся в исходное состояние" оправдывает себя. Допустим, что пользователь работает с диалоговым окном и вводит в текстовое поле, предназначенное для ввода целых чисел, строку "1234", поставив перед цифрами пробел, а затем щелкает на кнопке "Ok". Пробел, стоящий перед цифрами, делает число некорректным, поэтому текстовое поле возвращается в исходное состояние. Слушатель действий, связанный с кнопкой "Ok", извлекает значение поля и закрывает диалоговое окно. Пользователь никогда не узнает, что новое значение, введенное им, было отвергнуто. В этой ситуации имеет смысл придерживаться принципа фиксации и позволить слушателю кнопки проверять, все ли поля содержат корректные значения перед закрытием диалогового окна.
Фильтры
Основные функциональные возможности текстовых полей совершенно очевидны и удовлетворяют запросы большинства пользователей. Однако их можно усовершенствовать. Допустим, мы хотим предотвратить ввод в текстовое поле символов, не являющихся цифрами. Это поведение реализуется фильтром документа (document filter). Напомним, что в архитектуре "модель-представление-контроллер" контроллер переводит события ввода в команды, модифицирующие документ, который содержится в тексто-
вом поле, т.е. текстовую строку, хранящуюся в объекте классаНапри
мер, при обработке команды вставки текста в документ контроллер вызывает команду "вставить строку". Б качестве вставляемой строки может выступать либо символ, либо содержимое буфера вставки. Фильтр документа может перехватить эту команду и модифицировать строку либо отменить вставку вообще. Ниже приведен код фильтра, анализирующего строк)', подлежащую вставке, и вставляющего только цифры или символ "-". class IntFilter extends {
public voidfb, int offset,
String string, AttributeSet attr) throws
{
StringBuffer buffer = new for (int i =- 1; i >= 0; i--)
{
char=
ifch ! =-
;
}
offset, attr);
}
Следует также заместить метод replace класса DocumentFilter — он вызывается при выделении и замене текста. Реализация метода replace вполне очевидна (см. программу в конце раздела).
Теперь нам необходимо инсталлировать фильтр документа. К сожалению, выполнить это задание нелегко. Нужно заместить методв классе, реали
зующем средства форматирования, и передать объект этого класса методу
TestField. Текстовое поле, предназначенное для ввода целых чисел, использует метод InternationalFormatter, инициализированный объектом. get-
(). Вот как инсталлируются средства форматирования для создания нужного фильтра.
intField = new
InternationalFormatter(
)
{ protected DocumentFilter
:
return filter;
}
private DocumentFilter filter = new
});
В документации пакету SDK указано, что класс DocumentFilter был изобретен для того, чтобы избежать создания подклассов. В версиях, предшествующих пакету SDK фильтрация текстовых полей достигалась путем расширения класса PlainDocument и замещения методов insertString и replace. Теперь класс PlainDocument обладает сменными фильтрами. Это прекрасно. Но было бы еще лучше, если бы фильтры были сменными и в классах, реализующих средства форматирования. Увы, этого сделано не было, поэтому нам приходится создавать подклассы этих классов.
Запустите программу FormatText, приведенную в конце этого раздела. Третье текстовое поле имеет фильтр. В него можно вводить только цифры и знак "минус". Обратите внимание на то, что в это поле по-прежнему можно вводить некорректные строки, например "1-2-3". Как правило, с помощью фильтров нельзя предусмотреть все возможные некорректные строки. Например, строка, состоящая их одного минуса "-", некорректна, однако фильтр на это никак не реагирует, поскольку символ "минус" может оказаться префиксом числа "-1". Хотя фильтры не обеспечивают стопроцентной защиты, их полезно применять для предотвращения очень грубых ошибок.
Фильтры полезно также применять для превращения строчных букв в прописные. Написать такой фильтр довольно легко. Для этого достаточно в методах insertstring и replace, принадлежащих фильтру, преобразовать буквы введенной строки в прописные, а затем вызвать соответствующий метод суперкласса.
Верификаторы
Для предотвращения неправильного ввода существует еще один полезный механизм. С объектом класса JComponent можно связать верификатор (verifier). Если компонент выходит из фокуса, верификатору посылается запрос. Если в ответ верификатор сообщает, что содержимое компонента некорректно, то компонент немедленно возвращается в фокус. Таким образом, пользователь вынужден исправить содержимое компонента, прежде чем продолжить ввод.
Верификатор должен расширять абстрактный класс InputVer ifier и определять метод verify. В частности, довольно легко определить верификатор, проверяющий форматированные текстовые поля. Метод isValid класса JFormattedTextField вызывает метод форматирования и возвращает значение true, если существует способ преобразовать строку в объект. Рассмотрим пример такого верификатора.
class FormattedtextFieldVerifier(JComponent component) {
public boolean verify(JComponent component)
{
JFormattedTextField field = (JFormattedTextField)component;
return field.isEditValid() ;
}
}
Этот верификатор можно связать с любым объектом класса JFormattedField. intField.setinputverifier(new FormatedTextFieldVerifier() );
Однако верификатор еще не гарантирует полной безопасности. Если щелкнуть на кнопке, то это действие будет зарегистрировано слушателями до того, как некорректный компонент вернется в фокус. Следовательно, слушатели могут получить неверный результат от компонента, не прошедшего верификацию. Это объясняется тем, что пользователь может щелкнуть на кнопке "Cancel", не исправив неверный ввод.
Четвертое текстовое поле в программе также связано с верификатором. Попробуйте ввести неверное число (например х123) и нажмите клавишу <ТАВ> или щелкните мышью на другом текстовом поле. Обратите внимание на то, что поле немедленно вернется в фокус. Однако, если щелкнуть на кнопке "Ok", то слушатель действий вызовет метод getValue, который введет последнее правильное значение.
Другие стандартные средства форматирования
Кроме средств форматирования целых чисел, класс JFormatedTextField поддерживает еще несколько методов форматирования. Класс NumberFormat содержит статические методы getNumberlnstance, getCurrencylnstance и getPercentlnstance, обеспечивающие форматирование чисел с плавающей точкой, денежных знаков и процентов. Например, можно создать текстовое поле для ввода значений, задающих суммы денег. Для этого нужно сделать следующий вызов.
JFormattedTextField currencyField = new JFormattedTextField(
NumberFormat.getCurrencylnstance()] ;
Для того чтобы отредактировать даты и моменты времени, можно воспользоваться методами getDatelnstance, getTimelnstance и getDateTimelnstance. Например: JFormattedTextField dateField = new JFormattedTextField(
DateFormat.getDatelnstance() ];
В этом поле можно отредактировать дату в формате, предложенном по умолчанию: Feb 24, 2002
Можно также выбрать "короткий" формат
2/24/02
с помощью метода
DateFormat.getDatelnstance(DateFormat.SHORT]
По умолчанию предлагается "снисходительный" формат даты. Иначе говоря, неверная дата, например Февраль 31, 2002, будет преобразована в ближайшую правильную дату Март 3, 2002. Такая реакция может оказаться неожиданной. В этом случае вызывайте метод setLenient (false), принадлежащий объекту DateFormat.
Метод DefaultFormatter может форматировать объекты любого класса, обладающего конструктором, который имеет строковый параметр, и соответствующим методом toString. Например, класс URL имеет конструктор URL (String), который можно использовать для создания URL-адреса из строки.
URL url = new URL("http://java.sun.com");
Следовательно, метод DefaultFormatter можно применять для форматирования объектов класса URL. Для инициализации текстового поля метод форматирования вызовет метод toString. Когда поле выходит из фокуса, метод форматирования, используя конструктор с параметром класса String, создает новый объект того класса, которому принадлежит текущее значение. Если конструктор возбуждает исключительную ситуацию, редактирование считается некорректным. В этом можно убедиться, попробовав ввести URL-адрес, не начинающийся с префикса “http:”.
DDПо умолчанию метод DefaultFormatterработает в режиме замены. Этим он отличается от остальных методов форматирования. Следует признать, что это не слишком удобно. Для отключения режима замены нужно вызвать метод setOverwriteMode(false).
D
D
В заключение отметим, что метод MaskFormatter оказывается полезным для создания шаблонов фиксированного размера, состоящих из постоянных и переменных символов. Например, номер карточки социального страхования (скажем, 078-05-1120) можно форматировать следующим образом: new MaskFormatter("И##-##-####")
Символ # означает отдельную цифру. Символы, которые можно использовать для создания маски, приведены в табл. 9.2.
1
#
Цифра
?
Буква
U
Буква, преобразованная в прописную
L
Буква, преобразованная в строчную
A
Буква или цифра
Н
Шестнадцатеричная цифра [0-9A-Fa-f]
*
Произвольный СИМВОЛ
'
Управляющий символ
Виды символов для ввода в текстовое поле можно ограничить, вызвав методы setValidCharacters и setInvalidcharacters. Например, для ввода баллов успеваемости (от А+ до F) нужно использовать следующий вызов.
MaskFormatter formatter - new MaskFormatter("U*");
formatter.setValidCharacters("ABCDF+-");
Однако в языке нет способа, чтобы указать, что второй символ не может быть буквой.
Обратите внимание на то, что строка, форматированная с помощью маски, имеет точно такую же длину, что и сама маска. Если пользователь стирает символы во время редактирования, они заменяются символом-заполнителем (place-holder character). По умолчанию символом-заполнителем является пробел, однако в качестве заполнителя можно задать другой символ, вызвав метод setPlaceholderCharacter.
formatter.setPlaceholderCharacterCO');
По умолчанию метод форматирования с помощью маски работает в режиме вставки, который достаточно удобен — убедитесь в этом, запустив программу. Отметим также, что курсор перепрыгивает через позиции, занятые фиксированными символами маски.
Метод форматирования с помощью маски очень эффективен, если шаблон имеет точно заданную форму, например, номер карточки социального страхования или номер телефона. Однако следует заметить, что эти шаблоны не поддаются модификации. Например, невозможно задать маску для международного номера телефона, имеющего переменное количество цифр.
Методы форматирования, определяемые пользователем
Если вам не подходит ни один из стандартных методов форматирования, вы можете легко создать свой собственный. Рассмотрим 4-байтовый IP-адрес
130.65.86.66
К нему невозможно применить методпоскольку каждый байт может
представлять одну, две или три цифры. Кроме того, мы хотели бы проверять значения каждого байта, чтобы они не превышали число 255.
Для того чтобы определить свой собственный метод форматирования, следует расширить класси заместить методы String valueToString (Object
value) и Object stringToValue (String text). Первый метод превращает значение поля в строку, которая отображается в текстовом поле. Второй метод анализирует текст, введенный пользователем, и превращает его обратно в объект. Если один из методов обнаруживает ошибку, возбуждается исключительная ситуация ParseException.
В нашей программе IP-адрес хранится в массиве byte [ ], имеющем длину 4. Метод valueToString формирует строку, в которой байты разделены точками. Обратите внимание на то, что значения элементов массива byte имеют знак и лежат в диапазоне от -128 до 127. Чтобы превратить отрицательные байтовые значения в целые числа без знака, следует добавить к ним число
public Stringvalue) throws ParseException
{
if (!(value instanceof byte[]))
throw newbyte[], 0);
a =
if!= 4)
throw new!= 4", 0) ;
StringBuffer buffer = new
for (int i = 0; i < 4; i + + )
{
int b = a [i] ;
if (b < 0) b += 256;
);
if (i < 3)' .);
}
return }
И, наоборот, метод stringToValue анализирует строку и создает объект byte [], если строка является корректной. В противном случае он возбуждает исключительную ситуацию ParseException.
public Objecttext) throws ParseException
{
StringTokenizer tokenizer = new".") ;
byte[] a = new
for i=0; i<4; i++)
{
intb 0;
try
{
b =
}
catche)
{
throw newцелое число", 0) ;
}
if (b<0 b>=256)
throw newзначение", 0);
a[i] = (byte)b;
)
return a;
Попробуйте ввести IP-адрес, работая с демонстрационной программой. Если вы введете неправильный адрес, поле восстановит предыдущее правильное значение.
Программа, приведенная в листинге 9.3, демонстрирует различные поля для ввода форматированного текста в действии (рис. 9.13). Чтобы задать текущие значения, введенные в полях, щелкните на кнопке "Ok".
DDВ интерактивной газете "Swing Connection" была опубликована короткая заметка, описывающая метод форматирования, соответствующий любому регулярному выражению (см. Web-страницу
D
D
http://java.sun.eom/products/j fc/tsc/articles/reftf).
Программа FormatTest
import
import
import
import java.net.*;
import
import
import
import
/ * *
Программа тестирования форматированных текстовых полей */
public class FormatTest
public static voidargs)
{
FormatTestFrame frame = new
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE];
;
)
Фрейм, содержащий форматированные текстовые поля
и кнопку, предназначенную для просмотра их содержимого.
27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.8.4.85.86.87.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.8.4.85.86.87.class FormatTestFrame extends JFrame {
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
8.4.
85.
86.
87.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
8.4.
85.
86.
87.
''public FormatTestFrame(] {
'
'
setTitle("FormatTest");
setsize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Container contentPane = getContentPane();
JPanel buttonPanel = new JPanel();
okButton = new JButtonCOk");
buttonPanel.add(okButton);.
contentPane.add(buttonPanel, BorderLayout.SOUTH);
mainPanel = new JPanel();
mainPanel.setLayout(new GridLayout(0, 3)); contentPane.add(mainPanel, BorderLayout.CENTER);
JFormattedTextField intField = new JFormattedTextField( NumberFormat.getlntegerlnstance());
intField.setValue(new Integer(100));
addRow("Number:", intField);
JFormattedTextField intField2 = new JFormattedTextField(
NumberFormat.getlntegerlnstanceO);
intField2.setValue(new Integer(100));
intField2.setFocusLostBehavior(JFormattedTextField.COMMIT] ; addRow("Number (Commit behavior):", intField2);
JFormattedTextField intField3
= new JFormattedTextField(new
InternationalFormatter(
NumberFormat.getIntegerinstance())
{
protected DocumentFilter getDocumentFilter(] {
return filter;
}
private DocumentFilter filter
= new IntFilterO;
}) ;
intField3.setValue(new Integer(100));
addRow("Filtered Number", intField3);
JFormattedTextField intField4 = new JFormattedTextFieldi
NumberFormat.getlntegerlnstance());
intField4.setValue(new Integer(100));
intField4.setlnputVerifier(
new FormattedTextFieldVerifier());
addRow("Verified Number:", intField4);
JFormattedTextField currencyField = new
JFormattedTextField(NumberFormat.getCurrencyInstance()); currencyField.setValue(new Double(10));
addRow("Currency:", currencyField);
JFormattedTextField dateField = new JFormattedTextField(
DateFormat.getDatelnstance());
dateField.setValue(new Dated);
97.101.110.117.137.14 6.147.148.97.101.110.117.137.14 6.147.148.addRow("Date (default):", dateField);
97.
101.
110.
117.
137.
14 6.
147.
148.
97.
101.
110.
117.
137.
14 6.
147.
148.
DateFormat format =
DateFormat.getDatelnstance(DateFormat,. SHORT);
format.setLenient(false);
JFormattedTextField dateField2 = new JFormattedTextField( format);
dateField2.setValue(new Date ());
addRow("Date (short, not lenient):", dateField2);
try
{
DefaultFormatter formatter = new DefaultFormatter();
formatter.setOverwriteMode(false); JFormattedTextField urlField = new JFormattedTextField(
formatter);
urlField.setvalue(new URL("http://java.sun.com")); addRow("URL:", urlField);
}
catch (MalformedURLException exception) {
exception.printStackTrace();
}
try
{
MaskFormatter formatter
= new MaskFormatter("###-##-####");
formatter.setPlaceholderCharacterCO'); JFormattedTextField ssnField
= new JFormattedTextField(formatter);
ssnField.setvalue ("078-05-1120");
addRow("SSN Mask:", ssnField); }
catch (ParseException exception) {
exception.printStackTrace();
}
JFormattedTextField ipField
= new JFormattedTextField(new IPAddressFormatter()); ipField. setvalue (new byte (] { (byte)130, 65, 86, 66 }); addRowflP Address:", ipField);
}
/**
Помещает строку на главную панель.
@param labelText метка поля
@param field пример поля
*/
public void addRow(String labelText, final JFormattedTextField field)
{
mainPanel.add(new JLabel(labelText));
mainPanel.add(field);
final JLabel valueLabel = new JLabel ();
mainPanel.add(valueLabel) ;
okButton.addActionListener(new
ActionListener() {
149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164 .165.166.167 .168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204.205.206.207.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164 .165.166.167 .168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204.205.206.207.public void actionPerformed(ActionEvent event) {
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164 .
165.
166.
167 .
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164 .
165.
166.
167 .
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
Object value = field.getValue();
if (value.getClass().isArray() 1
{
StringBuffer buffer = new StringBuffer();
buffer.append(*(');
for (int i = 0; i < Array.getLength(value); i++)
{
if (i > 0) buffer.append!',');
buffer.append(Array.get(value, i).toString());
}
buffer.append(•)•);
valueLabel.setText(buffer.toString());
)
else
valueLabel.setText(value.toString());
} });
}
public static final int DEFAULT_WIDTH = 500;
public static final int DEFAULT_HEIGHT = 250;
private JButton okButton;
private JPanel mainPanel;
}
/**
Фильтр, допускающий только цифры и знак "-".
*/
class IntFilter extends DocumentFilter {
public void insertstring(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException
{
StringBuffer buffer = new StringBuffer (string);
for (int i = buffer.length() - 1; i >= 0; i--J {
char ch =
if ((Character.isDigit(ch) && ch != '-') buffer.deleteCharAt(i);
}
super.insertstring(fb, offset, buffer.toString(), attr) }
public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException
{
if (string != null)
{
StringBuffer buffer = new StringBuffer(string);
for (int i = buffer.length() - 1; i >= 0; i—1 {
char ch = buffer.charAt(i);
if (!Character.isDigit(ch) &s ch != '-') buffer.deleteCharAt(i);
}
string =;
}
offset, length, string, attr);
}
}
/**
Верификатор, проверяющий корректность содержимого
форматированных текстовых полей.
*/
classextends
{
public booleancomponent)
{
JFormattedTextField field =
return
}
}
/**
форматирования 4-байтовых IP-адресов в виде a.b.c.d.
*/
class IPAddressFormatter extends DefaultFormatter
{
public Stringvalue)
throws ParseException
{
if (!(value instanceof byte[]))
throw newa0);
byte[] a
if4)
throw new!= 4", 0);
buffer = new
24 5. for (int i = 0; i < 4; i++)
{
4 7.int b = a[i] ;
if(b<0)b+=256;
if (i< 3)
}
return
253.}
public Objecttext) throws
{
tokenizer = new StringTokenizer (text, ".") ;
byte[] a = new
2 60.for (int i = 0; i < 4; i + +)
{
intb=0;
try
{
b =
}
catche)
{
throw newan integer", 0);
}
if (b<0b>=256)
272.273.274 .275.276.277. )272.273.274 .275.276.277. )throw new ParseException("Byte out of range", 0); a[i] = (byte)b;
272.
273.
274 .
275.
276.
277. )
272.
273.
274 .
275.
276.
277. )
}
return a;
javax.swing.JFormattedTextField 1.4
JFormattedTextField(Format fmt]
Создает текстовое поле, использующее указанный формат.
JFormattedTextField(JFormattedTextField.AbstractFormatter formatter)
Создает текстовое поле, использующее указанный формат. Обратите внимание на то, что классы DefaultFormatter и InternationalFormatterяв- ляются подклассами класса JFormattedTextField. AbstractFormatter.
Object getValue()
Возвращает текущее корректное значение поля. Обратите внимание на то, что оно может не совпадать с редактируемой строкой.
void setvalue(Object value)
Пытается присвоить значение указанному объекту. Если попытка оказалась безуспешной, преобразовать объект в строку невозможно.
void commitEdit()
Пытается присвоить полю корректное значение, определенное редактируемой строкой. Если преобразовать объект в строку невозможно, попытка может оказаться безуспешной.
boolean isEditValid()
Проверяет, корректное ли значение задает редактируемая строка.
void setFicusLostBehaviour(int behaviour) int getFocusLostBehaviour()
Задает или определяет поведение поля при выходе из фокуса. Вид поведения задается константами COMMIT_OR_REVERT, REVERT, COMMIT и PERSIST, определенными в классе JFormattedTextField.
javax.text.DateFormat 1.1
static DateFormat getDatelnstance()
static DateFormat getDatelnstance(int dateStyle)
static DateFormat getTimelnstance()
static DateFormat getTimelnstance(int timeStyle)
static DateFormat getDateTimelnstance()
static DateFormat getDateTimelnstance(int dateStyle, int timeStyle)
Эти методы возвращают объекты класса DateFormat, генерирующие дату, время или дату и время одновременно. Значения параметров dateStyle и timeStyle задаются константами SHORT, MEDIUM, LONG, FULL и DEFAULT, определенными в классе DateFormat.
j avax.swing.JFormattedTextField.AbstractFormatter 1.4 abstract String valueToString(Object value)
Преобразовывает значение в редактируемую строку. Если для параметра value не существует соответствующего метода форматирования, возбуждается исключительная ситуация ParseException.
abstract Object stringToValue(String s)
Преобразовывает строку в значение. Если строка s имеет неправильный формат, возбуждается исключительная ситуация ParseException.
DocumentFilter getDocumentFilter()
Замещает метод для создания фильтра документа, ограничивающего ввод в текстовое поле. Если фильтр не нужен, возвращает значение null.
j avax.swing.text.DefaultFormatter 1.3
void setOverwriteMode(boolean mode)
boolean getOverwriteMode()
Включает или отменяет режим замены. Если параметр mode имеет значение true, то новые символы замещают символы редактируемой строки.
javax.swing,text.DocumentFiIter 1.4
void insertstring(DocumentFilter.FilterByPass bypass,
int offset, String text, AttributeSet attrib)
Параметры: bypass Объект, позволяющий выполнять команды редактирования, проходящие через фильтр.
offset Смещение, с которого начинается вставка текста.
textВставляемые символы.
attrib Атрибуты форматирования вставляемого текста.
void replace(DocumentFilter.FilterBypass bypass, int offset, int length, String text, AttributeSet attrib)
Этот метод вызывается перед заменой части документа новой строкой. Метод можно заместить, а строку— модифицировать. Режим замены блокируется, если не вызван метод super . replace или вызван метод bypass для модификации документа без фильтра.
Параметры: bypass
Объект, позволяющий выполнять команды редактирования, проходящие через фильтр.
offset
Смещение, с которого начинается вставка текста.
length
Длина заменяемой части текста.
text
Вставляемые символы.
attrib
Атрибуты форматирования вставляемого текста.
void remove(DocumentFilter.FilterBypass bypass, int offset, int length)
Этот метод вызывается перед удалением части документа. Для проверки результата можно вызвать метод bypass . getDocument ().
Параметры: bypassПараметры: bypassoffsetoffsetlengthlengthОбъект, позволяющий выполнять команды редактирования, проходящие через фильтр.Смещение, с которого начинается удаление текста.Длина удаляемой части текста.Объект, позволяющий выполнять команды редактирования, проходящие через фильтр.Смещение, с которого начинается удаление текста.Длина удаляемой части текста.
Параметры: bypass
Параметры: bypass
offset
offset
length
length
Объект, позволяющий выполнять команды редактирования, проходящие через фильтр.
Смещение, с которого начинается удаление текста.
Длина удаляемой части текста.
Объект, позволяющий выполнять команды редактирования, проходящие через фильтр.
Смещение, с которого начинается удаление текста.
Длина удаляемой части текста.
javax.swing.text.MaskFormatter 1.4
MaskFormatter(String mask)
Создает метод форматирования с помощью заданной маски. Символы маски указаны в табл. 9.2.
void setValidCharacter(String charactyers)
String getValidCharacters()
Эти методы задают и определяют корректные символы. В качестве переменной части маски допускаются только символы заданной строки.
void setlnvalidCharacters(String characters)
String getlnvalidCharacters(I
Эти методы задают и определяют некорректные символы. Ни один из символов заданной строки при вводе не принимается.
void setPlaceholderCharacter (char ch)
char getPlaceholderCharacter (]
Эти методы задают и определяют символ, используемый для указания позиции маски, которую пользователь еще не ввел. По умолчанию символом- заполнителем является пробел.
void setPlaceholder(String s)
String getPlaceholder()
Эти методы задают и определяют строку, которая используется для заполнения той части маски, которую пользователь еще не ввел. Если строка равна константе null или короче маски, то для заполнения оставшейся части используется символ-заполнитель.
void setValueContainsLiteralCharacters(boolean b)
boolean getValueContainsLiteralCharacters ()
Эти методы задают и определяют "значение, содержащее литеральные символы". Если булевский параметр равен true, то значение поля содержит литеральную (постоянную) часть маски. В противном случае литеральные символы удаляются. По умолчанию булевский параметр имеет значение true.
Текстовые области
Иногда в программу нужно ввести несколько строк. Как указывалось ранее, для этого применяется компонент JTextArea. Поместив этот компонент в свою программу, пользователь может вводить любое количество текста, разделяя строки нажатием клавиши <ENTER>. Каждая строка заканчивается символами ' \п', как это преду-
смотрено в языке Java. Если возникает необходимость разбить текст, введенный пользователем, на несколько строк, можно использовать класс StringTokenizer (см. главу 12). Пример текстового поля показан на рис. 9.14.
Г«*Л«мТ»м
Thequickbrpwr foxjumpsoverthelazydog.
kbrown jumps theThequickbrown
foxjumps ver thelazydogThequickbrownfoxjum
theThequickbrownfoxjumpsover
helazydogThequickbrownfoxjumpsover the dog The quickfoxjumps w the
equick brown foxjumps wer thedog. Thequick
umpsumpsbrownfoxjumpstheThequickbrown
umps
umps
xjumpsoverthelazydog. Thequickbrownfoxjumps I over the i az* dog.
insert No wrap
Рис. 9.14.Текстовое поле
В конструкторе компонента JTextArea указывается количество строк и их длина. textArea = new JTextArea(8, 40); // 8 строкПС 40 символов.
contentPane.add(textArea);
Параметр, задающий количество символов в строке, имеет прежний смысл— для безопасной работы его значение нужно немного завысить. Кроме того, как и прежде, пользователь не ограничен количеством строк и их длиной. Если текст выйдет за пределы заданных параметров, он будет просто прокручен в окне. Для изменения длины строк можно применять метод setColumns, а для изменения их количества — метод setRows. Эти параметры задают лишь желательные размеры — менеджер компоновки может самостоятельно увеличивать или уменьшать размеры текстового поля.
Если пользователь ввел больше текста, чем помещается в текстовой области, то оставшийся текст будет просто отсечен. Этого можно избежать, установив автоматический перенос строки (line-wrapping).
textArea.setLineWrap(true); // В длинных строках выполняется перенос.
Автоматический перенос является лишь визуальным эффектом. Текст, хранящийся в документе, изменен не будет — в текст не вставляются символы ' \п '.
В пакете Swing текстовая область не имеет полосы прокрутки. Если эта полоса необходима, ее следует вставить в текстовую область внутри панели прокрутки (scroll pane). textArea = new JtextArea(8, 40);
JScrollPane scrollPane = new;
contentPane.add(scrollPane, BorderLayout.CENTER);
Теперь панель прокрутки управляет представлением текстовой области. Полоса прокрутки автоматически появляется, когда текст выходит за рамки отведенной для него области, и исчезает, когда часть текста удаляется и оставшаяся часть помещается внутри текстовой области. Прокрутка обрабатывается внутри панели прокрутки — сама программа не обязана обрабатывать события, связанные с ней.
Чтобы вставить полосу прокрутки в компонент, поместите ее на панель прокрутки. Этот механизм носит универсальный характер и встречается при работе с пакетом Swing очень часто.
Листинг 9.4 содержит полный текст программы, демонстрирующей работу с текстовой областью. Эта программа позволяет пользователю редактировать текст в текстовой области. Чтобы вставить предложение в конец текста, щелкните на кнопке "Insert". Для того чтобы установить или отключить автоматический перенос, щелкните на второй кнопке. (Ее название переключается с "Wrap" на "No wrap", и обратно.) Разумеется, для редактирования текста можно использовать только клавиатуру, не прибегая к помощи мыши. Обратите внимание на то, как можно выделить часть текста, вырезать его, скопировать и вставить с помощью комбинаций клавиш <CTRL+X>, <CTRL+C> и <CTRL+V>. (Комбинации клавиш должны соответствовать выбранному стилю интерфейса. В частности, указанные комбинации характерны для стилей "Metal", "Windows" и "Mac".)
Компонент JTextArea отображает только простой текст, не содержащий специальных шрифтов или особого форматирования. Чтобы отобразить на экране форматированный текст (например, в формате HTML или RTF), можно использовать классы JEditPaneи JTextPane.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TextAreaTest
{
public static void main(String[ ] args)
{
TextAreaFrame frame = new TextAreaFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
frame.show ();
}
}
/**
Фрейм, содержащий текстовую область и кнопки
для редактирования текста.
*/
class TextAreaFrame extends JFrame
{
public TextAreaFrame(1
{
setTitle("TextAreaTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Container content Pane = getContentPane();
buttonPanel = new JPanelO ;
// Кнопки для ввода текста в текстовую область.
JButton insertButton = new JButton("Insert");
;
35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.insertButton.addActionListener(new
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
ActionListener()
{
public void actionPerformed(ActionEvent event)
{
textArea.append("The quick brown fox "
+ "jumps over the lazy dog. ") ;
}
});
// Добавляем кнопку для управления режимом переноса.
wrapButtonnew JButtonfWrap") ;
buttonPanel.add(wrapButton);
wrapButton.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent evt)
{
boolean wrap = !textArea.getLineWrap();
textArea.setLineWrap(wrap);
scrollPane.validate ();
wrapButton.setText(wrap ? "No Wrap" : "Wrap" ); }
});
contentPane.add(buttonPanel, BorderLayout.SOUTH] ;
// Добавляем текстовую область с полосой прокрутки.
textArea = new JTextArea(8, 40);
scrollPane = new JScrollPane(textArea);
contentPane.add(scrollPane, BorderLayout.CENTER);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 300;
private JTextArea textArea;
private JScrollPane scrollPane;
private JPanel buttonPanel;
private JButton wrapButton;
javax.swing.JTextArea 1.2
JTextArea(int rows, int cols) Создает новую текстовую область.
Параметры: rows Количество строк.
cols Количество символов в строке.
• JTextArea(String text, int rows, int cols)
Создает новое текстовое поле, содержащее заданный текст.
Параметры: text Заданный текст.
rows Количество строк.
colsКоличество символов в строке.
void setcolumns(int cols)
Задает нужную длину строки.
Параметры: cols Количество символов в строке.
void setRows(J
Определяет нужное количество строк.
Параметры: rows Количество строк.
void append(String newText)
Добавляет заданный текст в конец текста, уже введенного в текстовой области.
Параметры: text Текст, который нужно добавить.
void setLineWrap(boolean wrap)
Включает и отключает режим автоматического переноса строк.
Параметры: wrap Равен значению true, если должен выполняться автоматический перенос строк.
void setWrapStyleForward(boolean word)
Если параметр word равен значению true, то перенос в длинных строках выполняется по границе слова, в противном случае границы слов во внимание не принимаются.
void setTabSize(int с)
Устанавливает табуляцию через каждые с символов. Обратите внимание на то, что табуляция не превращается в пробелы: текст просто выравнивается по следующей позиции табуляции.
Параметры: c Количество символов, определяющее шаг табуляции.
javax.swing.JScrollPane1.2
JScrollPane(Component c)
Создает панель прокрутки, которая отображает содержание указанного компонента. Полоса прокрутки появляется, только если текст превышает допустимые размеры.
Параметры: с Компонент, подлежащий прокрутке.
Метки и маркирующие компоненты
Метки — это компоненты, хранящие текст. Они не имеют украшений (например границ). Кроме того, они не реагируют на ввод данных пользователем. Метки можно использовать для идентификации компонентов. Например, в отличие от кнопок, текстовые компоненты не имеют меток, которые позволили бы их различать. Чтобы пометить компонент, не имеющий идентификатора, нужно выполнить следующие действия.
Создать компонент JLabel, содержащий заданный текст.
Поместить его достаточно близко к компоненту, который нужно идентифицировать, чтобы пользователь мог видеть, что эта метка относится именно к данному компоненту.
Конструктор класса JLabel позволяет задать текст или пиктограмму, а также выровнять содержимое компонента (при необходимости), используя константы интерфейса SwingConstants. В этом интерфейсе определено большое количество полезных констант, например, LEFT, RIGHT, CENTER, NORTH, EAST и т.д. Класс JLabel является одним из нескольких классов библиотеки Swing, реализующих этот интерфейс. Таким образом, можно, например, задать метку, текст в которой будет выровнен по левому краю.
JLabel label = new JLabel("Текст", SwingConstsnts.LEFT);
или
JLabel label = new JLabel("Текст" , JLabel.LEFT);
Методы setText и seticon позволяют задать текст и пиктограмму метки в ходе выполнения программы.
Начиная с версии J2SE 1.3, на кнопки, метки и пункты меню можно помещать как обычный текст, так и текст в формате HTML. Мы не рекомендуем помещать на кнопки текст в формате HTML— он нарушает стиль интерфейса. Однако в метках такой текст очень эффективен. Нужно просто окружить строку метки символами <HTML>. . . </HTML> .
label = new JLabel (<HTMLXB>TeKCT</B> на кнопке:</HTML>");
Учтите, что первый компонент с меткой, содержащей текст в формате HTML, отображается на экране с запаздыванием, поскольку для этого нужно загрузить сложный код рендеринга.
Метки можно помещать внутрь контейнера, как и любой другой компонент. Это означает, что для них применяются обычные описанные выше способы программирования. Например, на рис. 9.15 показано текстовое поле с меткой "with".
Рис. 9.15.Редактирование текстаРис. 9.15.Редактирование текста
Рис. 9.15.Редактирование текста
Рис. 9.15.Редактирование текста
Параметры: textПараметры: textТекст метки.Текст метки.
Параметры: text
Параметры: text
Текст метки.
Текст метки.
j
JLabel(String text)
Создает метку с текстом, выровненным по левому краю.
JLabel(Icon icon)
Создает метку с пиктограммой, выровненной по левому краю.
Параметры: iconПиктограмма метки.
JLabel(String text, int align)
Параметры: textТекст метки.
align Одна из констант SwingConstants . LEFT, SwingConstants.CENTERили SwingConstants.RIGHT.
JLabel(String text, Icon icon, int align)
Создает метку с текстом и пиктограммой. Пиктограмма располагается ле- веетекста.
Параметры: textТекст метки.
iconПиктограмма метки.
align Одна из констант SwingConstants . LEFT, SwingConstants.CENTERили SwingConstants.RIGHT.
void setText(String text)
Параметры: textТекст метки.
void seticon(Icon icon)
Параметры: iconПиктограмма метки.
Выделение и редактирование текста
Классы для текстового поля и текстовой области наследуют методы суперкласса JTextComponent, позволяющие выделять (подсвечивать) текст, содержащийся в компоненте. Кроме того, они могут проверять, выделен ли указанный текст в данный момент.
Во-первых, существует метод selec All, выделяющий весь текст в текстовом поле. Этот метод можно применять для выделения текста, который пользователь должен либо принять целиком, либо целиком отвергнуть. В последнем варианте при первом же нажатии клавиши выделенный текст исчезнет.
Метод select выделяет часть текста. Аргументы метода select те же, что и у метода substring: первый индекс задает начало подстроки, а последний на единицу превышает индекс конца строки. Например, при вызове t. select (10, 15) выбираются символы, начиная с десятого и заканчивая четырнадцатым. Символ конца строки считается одним символом.
Методы getSelectStart и getSelectEnd возвращают начало и конец выделенного фрагмента, а метод getSelectedText возвращает выделенный текст. Способ выделения текста зависит от операционной системы. Если применяется стиль "Metal", можно использовать мышь или стандартные комбинации клавиши <SHIFT> и стрелок.
Класс JTextArea содержит большое количество методов, предназначенных для редактирования текста в текстовой области. Они позволяют добавлять текст в конец уже
набранного текста, вставлять его в середину, а также заменять текст. Чтобы удалить текст, просто замените его пустой строкой. В листинге 9.5 показано, как реализовать простое редактирование текста. В программе, результаты работы которой показаны на рис. 9.13, каждый раз, когда пользователь щелкает на кнопке "Replace", первое вхождение текста, указанного в первом поле, заменяется текстом, содержащимся во втором поле. Несмотря на то что этот способ не очень практичен, его можно применять для вылавливания грамматических ошибок или опечаток в URL-адресах.
import java.awt.*;
import java.awt.event.*;.
import javax.swing.*;
public class TextEditTest
{
public static void main(String[] args)
<
TextEditFrame frame = new TextEditFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
frame.show(] ;
}
}
/**
Фрейм с текстовой областью и компонентами
для поиска и замены.
*/
class TextEditFrame extends JFrame
(
public TextEditFrame()
{
setTitle("TextEditTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT) ;
Container contentPane = getContentPane();
JPanel panel = new JPanel();
// Добавляем кнопку, текстовые поля и метки.
JButton replaceButton = new JButton("Replace"] ;
panel.add(replaceButton);
replaceButton.addActionListener(new ReplaceAction());
from = new JTextField("brown", 8);
panel.add(from);
panel.add(new JLabel("with"));
to = new JTextField("purple", 8);
panel.add(to);
contentPane.add(panel, BorderLayout.SOUTH);
// Добавляем текстовую область с полосой прокрутки.
textArea = new JTextArea(8, 40);
textArea.setText
("The quick brown fox jumps over the lazy dog.");
JScrollPane scrollPane = new JScrollPane(textArea);
contentPane.add(scrollPane, BorderLayout.CENTER);
}
publicstaticfinalintDEFAULT_WIDTH = 400;
publicstaticfinalintDEFAULT_HEIGHT = 200;
private JTextArea textArea;
private JTextField from;
private JTextField to;
/**
Слушатель действий, связанных с кнопкой "Replace".
*/
private class ReplaceAction implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
String f = from.getText();
int n = textArea.getText().indexOf(f);
if (n >= 0 &s f.lengthC > 0)
textArea.replaceRange(to.getText(), n, n + f. length (]) ;
}
}
}.
О javax.swing.text.JTextComponent 1.2
void selectAll()
Выделяет весь текст, содержащийся в компоненте.
void select(int selStart, int selEnd)
Выделяет фрагмент текста, содержащегося в компоненте.
Параметры: selStart Начало фрагмента.
seiEndКонец фрагмента.
int getSelectionStart()
Возвращает начало выделенного фрагмента.
int getSelectionEnd()
Возвращает конец выделенного фрагмента.
String getSelectedText()
Возвращает выделенный текст.
void insert(String str, int pos)
Вставляет строку в текстовую область.
Параметры: strВставляемый текст.
posПозиция вставки (0 первая позиция, символ пере-
ходанановуюстрокусчитаетсяоднимсимволом).
void replaceRange(String str, int start, int end) Заменяет фрагмент текста другой строкой.
Параметры: str
Новый текст.
start
Начало заменяемого фрагмента.
end
Позиция, на единицу превышающая конец выделяемого фрагмента.
Выбор вариантов
Теперь вы знаете, как вводить данные. Однако во многих случаях предпочтительнее предоставить пользователю самому сделать выбор среди конечного набора возможностей. Используя набор кнопок или список пунктов, можно обозначить имеющиеся возможности. (Это освободит вас от необходимости отслеживать ошибки.) В этом разделе мы изучим флажки, опции, переключатели, списки и ползунковые регуляторы.
Флажки опций
Если в программу нужно ввести просто "да" или "нет", используется флажок опции. Эти флажки автоматически сопровождаются метками, позволяющими их идентифицировать. Чтобы установить флажок, нужно щелкнуть мышью внутри компонента, а для того, чтобы снять флажок— щелкнуть повторно. Установить флажок можно также с помощью клавиши "пробел", нажав ее в момент, когда флажок находится в фокусе.
На рис. 9.16 показана простая программа с двумя флажками опций, один из которых включает и отключает курсив, а другой — полужирный шрифт. Обратите внимание на то, что первый флажок находится в фокусе. Об этом говорит прямоугольная рамка вокруг его метки. Каждый раз, когда пользователь щелкает на флажке, экран обновляется с учетом нового атрибута шрифта.
Флажки опций должны сопровождаться метками, чтобы указать их предназначение. Текст метки задается в конструкторе.
bold = new JCheckBox("Bold"l ;
Рис. 9.16. Флажки опций
Для установки и снятия флажка используется метод setSelected. bold.setSelected(true! ;
Затем метод isSelected определяет текущее состояние каждого флажка. Если значение равнозначит, он не установлен.
Щелкая на флажке, пользователь выбирает события, связанные с действиями. Как всегда, подключается слушатель действий. В нашей программе для двух кнопок предусмотрен один и тот же слушатель действий.
ActionListener listener = ... bold.addActionListener(listener); italic.addActionListener(listener);
Его методзапрашивает текущее состояние флажков bold и
italic, а затем устанавливает шрифт, которым должен отображаться обычный текст: полужирным, курсивом или полужирным курсивом.
public voidevent)
{ int mode = 0; ifmode +=
ifmode +=
mode, FONTSIZE));
}
В листинге 9.6 приведена полная программа, демонстрирующая работу с флажками
В библиотеке AWT компонент, эквивалентный компоненту называется Checkbox (со строчной буквой "Ь"). Он генерирует события, связанные с пунктом, а несдействиями.
import java.awt.*;
import
import
public class CheckBoxTest
{
public static voidargs)
{
CheckBoxFrame frame = new
frame.show();
}
}
/**
Фрейм с текстовой меткой и флажками для
установки атрибутов шрифта.
*/
class CheckBoxFrame extends
{
public
{
DEFAULT_HEIGHT) ;
Container contentPane =
42.46.54.55.72.42.46.54.55.72.// Добавляем текстовую метку.
42.
46.
54.
55.
72.
42.
46.
54.
55.
72.
label = new JLabelI
"The quick brown fox jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.PLAIN, FONTSIZE)); contentPane.add(label, BorderLayout.CENTER);
// Этот слушатель задает атрибуты шрифта
// для меток флажков опций.
ActionListener listener = new
ActionListener () {
public void actionPerformed(ActionEvent event)
{
int mode = 0;
if (bold.isSelected(); mode += Font.BOLD;
if (italic.isSelectedO ) mode += Font.ITALIC;
label.setFont(new Font("Serif", mode, FONTSIZE)); }
};
// Добавляем флажки опций.
JPanel buttonPanel = new JPanel();
bold = new JCheckBox("Bold");
bold.addActionListener(listener);
buttonPanel.add(bold);
italic = new JCheckBox("Italic");
italic.addActionListener(listener);
buttonPanel.add(italic);.
contentPane.add(buttonPanel, BorderLayout.SOUTH);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private JLabel label;
private JCheckBox bold;
private JCheckBox italic;
73. }73. }private static final int FONTSIZE = 12;
73. }
73. }
javax.swing.JCheckBox 1.2
JCheckBox (String label)
Параметры: label Метка флажка опций.
JCheckBox(String label, bodean state)
Параметры: label Метка флажка.
state Первоначальное состояние флажка.
JCheckBox(String label, Icon icon)
Создает флажок, имеющий первоначальное состояние "снят".
Параметры: label Метка флажка.
iconПиктограмма флажка.
boolean isSelected()
Возвращает состояние флажка.
void setSelected(boolean state)
Устанавливает новое состояние флажка.
Переключатели
В предыдущем примере пользователь мог установить как оба флажка, так и ни одного. Во многих ситуациях пользователь должен принять единственное решение, выбрав только один из предложенных флажков. Если пользователь установит другой флажок, то предыдущий флажок будет снят. Такую группу флажков часто называют группой переключателей (radio button group). Эти кнопки напоминают переключатели диапазонов на радиоприемниках — когда нажимаешь на одну из таких кнопок, ранее нажатая кнопка освобождается. На рис. 9.17 показан типичный пример. Пользователь может выбрать размер шрифта— Small (маленький), Medium (средний), Large (большой) и Extra large (очень большой). Разумеется, выбрать можно лишь один из указанных размеров.
Пакет Swing позволяет легко реализовывать группы переключателей. Для этого нужно создать по одному объекту, имеющему тип ButtonGroup, для каждой группы переключателей. Затем в группу переключателей нужно добавить объекты типа JRadioButtons. Объект класса ButtonGroup предназначен для того, чтобы отключать ранее выбранный переключатель, если пользователь щелкнул на новой кнопке. ButtonGroup group = new ButtonGroup();
JRadioButton smallButton
= new JRad i oBut t on("Sma11", false); group.add(smallButton);
JRadioButton smallButton
= new JRadioButton("Medium", true) group.add(mediumButton);
Рис. 9.17. Группа переключателей
Второй аргумент в конструкторе принимает значение true, если изначально переключатель должен быть включен, и false— если выключен. Отметим, что объект типа ButtonGroup управляет лишь поведением переключателей. Если нужно объединить несколько групп переключателей в одно целое, их следует поместить в контейнер, например, в объект класса JPanel.
Вернемся к рис. 9.16 и 9.17. Обратите внимание на то, что группы переключателей отличаются по внешнему виду от флажков опций. Флажки опций изображаются в виде квадратиков, причем для установленных флажков такие квадратики содержат галочку, в то время как переключатели изображаются кружочками: включенные — с точкой внутри, а выключенные — пустыми.
Механизм извещения о наступлении событий, связанных с переключателями, точно такой же, как и для любых других кнопок. Если пользователь включил переключатель, соответствующий объект генерирует действие. В нашем примере установлен слушатель, задающий конкретный размер шрифта.
ActionListener listener = new
ActionListener(]
{
public void actionPerformed(ActionListener evt)
{
// Размер шрифта задается последним параметром
// метода addRadioButton.
label.setFont(new Font ("Serif", Font.PLAIN, size)); }
Сравните этот слушатель со слушателем для флажка опций. Каждый переключатель имеет свой объект слушателя. Каждый объект слушателя точно знает, что нужно делать — установить конкретный размер шрифта. Для флажков ситуация иная. Оба флажка в нашем примере имели один и тот же объект слушателя. Он вызывал метод, просматривающий текущее состояние обоих флажков.
В группе переключателей может быть выбран только один переключатель. Хорошо бы сразу знать, какой именно, не проверяя каждый переключатель в группе. Поскольку объект класса ButtonGroup управляет всеми переключателями, было бы удобно, если бы он давал нам ссылку на выбранный переключатель. Действительно, в классе ButtonGroup есть метод getSelection, но этот метод не возвращает ссылку на выбранный переключатель. Вместо этого он возвращает ссылку на объект класса ButtonModel — модель, связанную с этим переключателем. К сожалению, все методы класса ButtonModel не представляют собой ничего ценного. Интерфейс ButtonModel наследует метод getSelectedObjects из интерфейса ItemSelectable, возвращающий совершенно бесполезную ссылку null. Метод getActionCommand выглядит предпочтительнее, поскольку команда, связанная с переключателем, задается текстовой строкой. Однако команда в модели этого переключателя снова представляет собой нулевую ссылку null. Только если программист явно задаст команды для каждого переключателя с помощью метода setActionCommand, установив значение, соответствующее каждой команде, впоследствии можно будет определить команду включенного переключателя с помощью метода buttonGroup.getSelection() getActionCommand ().
Можно ли реализовать такой подход для переключателей? Зададим один слушатель, устанавливающий конкретный размер шрифта.
if (smallButton.isSelected()) size = 8;
else is (mediumButton.isSelected()) size = 12;
Однако предпочтительнее использовать отдельные объекты слушателя, поскольку они теснее связывают размер шрифта с конкретным переключателем.
В листинге 9.7 представлена полная программа, позволяющая установить размер шрифта с помощью переключателей.
import java.awt.*;
import java.awt.event. *;
import javax.swing.*;
public class RadioButtonTest
(
public static void main(String[] args)
{
RadioButtonFrame frame new RadioButtonFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
)
/**
Фрейм с текстовой меткой и переключателями
для выбора размера шрифтов.
*/
class RadioButtonFrame extends JFrame
{
public RadioButtonFrame()
{
setTitle("RadioButtonTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Container contentPane = getContentPane();
// Добавляем текстовую метку.
label = new JLabel(
"The quick brown fox jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.PLAIN,
DEFAULT_SIZE));
contentPane.add(label, BorderLayout.CENTER);
// Добавляем переключатели.
buttonPanel = new JPaneK);
group = new ButtonGroup();
addRadioButton("Small", 8);
addRadioButton("Medium", 12);
addRadioButton("Large", 18);
addRadioButton("Extra large", 36);
45.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.contentPane.add(buttonPanel, BorderLayout.SOUTH);
45.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
}
/* *
Добавляет переключатели, устанавливающие размер шрифта для текстовой метки.
Sparam name строка на поверхности кнопки
Sparam size размер шрифта для кнопки
*/
public void addRadioButton(String name, final int size)
{
boolean selected = sizeDEFAULT_SIZE;
JRadioButton button = new JRadioButton(name, selected);
group.add(button);
buttonPanel.add(button);
// Этот слушатель устанавливает размер шрифта для метки.
ActionListener listener = new
ActionListener(]
{
.public void actionPerformed(ActionEvent evt)
{
// Размер относится к последнему параметру
// метода addRadioButton.
label.setFont(new Font("Serif", Font.PLAIN, size));
}
};
button.addActionListener(listener);
}
public static final int DEFAULT_WIDTH = 400;
public static final int DEFAULT_HEIGHT = 200;
private JPanel buttonPanel;
private ButtonGroup group;
private JLabel label;
private static final int DEFAULT_SIZE = 12;
}
javax.swing.JRadioButton 1.2
JRadioButton(String label, boolean state)
Параметры: label Метка переключателя.
state Первоначальное состояние переключателя.
• JRadioButton(String label, boolean state)
Параметры: label Меткапереключателя.
iconiconПиктограмма переключателя.
icon
icon
javax.swing.ButtonGroup 1.2 void add(AbstractButton b) Добавляет переключатель в заданную группу.
ButtonModel getSelection()
Возвращает переключатель из заданной группы.
javax.swing.ButtonModel 1.2 String getActionCommand(String s) Возвращает команду из модели данной кнопки.
javax.swing.AbstractButton 1.2 void setActionCommand(String s) Задаеткомандудляданнойкнопкииеемодели.
Рамки
Если в одном окне расположено несколько групп переключателей, нужно иметь возможность различать их между собой. Для этого в библиотеке Swing предусмотрен набор полезных рамок (borders). Рамку можно задать для каждого компонента, расширяющего класс JComponent. Обычно рамка окружает панель и заполняет ее другими элементами пользовательского интерфейса, например, группами переключателей.
Выбор рамок довольно невелик и все они задаются с помощью одинаковых действий.
Вызывается статический метод класса BorderFactory, создающий рамку, для которой предусмотрены следующие стили (рис. 9.18).
Lowered bevel (утопленная фаска).
Raised bevel (приподнятая фаска).
Etched (гравировка).
Line (линия).
Matte (матовая).
Empty (пустая — создается пустое пространство, окружающее компонент).
При желании с помощью метода BorderFactory.createBorder добавляется заголовок.
При необходимости все рамки можно объединить в одну, воспользовавшись методом BorderFactory.createCompoundBorder.
Получившаяся рамка добавляется в компонент с помощью вызова setBorder . класса JComponent.
Например, вот как создается пустая рамка с заголовком панели.
Border etched = BorderFactory.createEtchedBorder()
Border titled = BorderFactory.createTitleBorder(etched, "Заголовок"); panel.setBorder(titled);
Запустите программу, приведенную в листинге 9.8, и вы увидите, как выглядят рамки, выполненные в разных стилях.
Разные рамки имеют разные возможности для задания ширины и цвета. Детали описаны в документации по интерфейсу прикладных программ. Истинные энтузиасты использования рамок обрадуются, узнав, что существует класс SoftBevelBorder для создания рамок с закругленными углами, а также класс LineBorder, позволяющий сглаживать углы. Такие рамки можно создать только с помощью одного из конструкторов этих классов — для них не существует метода BorderFactory.
9.18. Тестированиерамок9.18. Тестированиерамок
9.18. Тестированиерамок
9.18. Тестированиерамок
1.4.101112131415161718192021222324252627282530311.4.10111213141516171819202122232425262728253031
1.
4.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
25
30
31
1.
4.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
25
30
31
import
import*;
import
import
public class BorderTest
{
public static voidargs)
{
BorderFrame= new
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE] j frame.show();
}
/*•
Фрейм с переключателями для изменения стиля рамки.
*/
class BorderFrame extends JFrame
{
public {
setTitle("BorderTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
= new
buttonPanel = new
group = new;
bevel",
BorderFactory.createLoweredBevelBorder());
37.38.39.42.52.66.67.68.69.37.38.39.42.52.66.67.68.69.addRadioButton("Raised bevel",
37.
38.
39.
42.
52.
66.
67.
68.
69.
37.
38.
39.
42.
52.
66.
67.
68.
69.
;
addRadioButton("Etched",
BorderFactory.createEtchedBorder());
addRadioButton("Line",
BorderFactory.createLineBorder(Color.blue));
addRadioButton("Matte",
BorderFactory.createMatteBorderI
10, 10, 10, 10, Color.blue));
addRadioButton("Empty",
BorderFactory.createEmptyBorder());
Border etched = BorderFactory.createEtchedBorder();
Border titled = BorderFactory.createTitledBorder
(etched, "Border types");
;
Container contentPane = getContentPane();
contentPane.setLayout(new GridLayout(2, 1));
contentPane.add(buttonPanel);
contentPane.add(demoPanel);
}
public void addRadioButton(String buttonName, final Border b) (
JRadioButton button = new JRadioButton(buttonName);
button.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event)
demoPanel.setBorder(b);
validate();
}
});
group.add(button);
buttonpanel.add(button);
}
public static final int DEFAULT_WIDTH - 600;
public static final int DEFAULT_HEIGHT = 200;
private JPanel demoPanel;
private JPanel buttonPanel;
private ButtonGroup group;
)
javax.swing.BorderFactory 1.2
static Border createLineBorder(Color color)
static Border createLineBorder(Color color, int thickness) Создает простую рамку в виде линии.
static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color)
static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon icon)
Создает широкую рамку, заполненную однотонным фоном или мозаикой, состоящей из повторяющихся пиктограмм.
static Border createEmptyBorder(]
static Border createEmptyBorder(int top, int left, int bottom, int right)
Создает пустую рамку.
static Border createEtchedBorder()
static Border createEtchedBorder(Color highlight, Color shadow)
static Border createEtchedBorder(int type)
static Border createEtchedBorder(int type, Color highlight, Color shadow)
Создает рамку с эффектом трехмерного изображения.
Параметры: highlight, Цвета для создания эффекта трехмерного изо- shadowбражения.
typeОдна из констант EtchedBorder.RAISED или
Etched. Border. LOWERED.
static Border createBevelBorder(int type)
static Border createBevelBorder(int type, Color highlight, Color shadow)
static Border createLoveredBevelBorder(]
static Border createRaisedBevelBorder()
Создает рамку, имитирующую утопленную или приподнятую поверхность.
Параметры: typeОдна из констант BevelBorder.RAISED или
BevelBorder.LOWERED.
highlight, Цвета для создания эффекта трехмерного изо- shadowбражения.
static TitledBorder createTitledBorder(String title)
static TitledBorder createTitledBorder(Border border) static TitleBorder createTitleBorder(Border Border, String title)
static TitleBorder createTitleBorder(Border Border, String title, int justification, int position)
static TitleBorder createTitleBorder(Border Border, String title, int justification, int position, Font font)
static TitleBorder createTitleBorder(Border Border, String title, int justification, int position, Font font, Color color)
Параметры: titleСтрока заголовка.
borderРамка, украшенная заголовком.
justificationОдна из констант TitleBorder. LEFT, TitleBorder.CENTERили BevelBorder.RIGHT.
position
Одна из констант класса TitleBorder: ABOVE_TOP, TOP, BEL0W_T0P, ABOVE_BOTTOM, BOTTOM или BELOW_BOTTOM.
font
Шрифт заголовка.
color
Цвет заголовка.
static CompoundBorder createCompoundBorder (Border outsideBorder, Border inside Border) Объединяет две рамки в одно целое.
javax.swing.border.SoftBevelBorder 1.2
SoftBevelBorder(int type)
Sof tBevelBorder ('int type, Color highlight, color shadow) Создает фаску со сглаженными углами.
Параметры: typeПараметры: typeОдна из констант BevelBorder. RAISED или BevelBorder.LOWERED
Параметры: type
Параметры: type
highlight,Цвета для создания эффекта трехмерного
shadowизображения
APIjavax.swing.border.LineBorder1.2
‘rTdr'i • public LineBorder(Color color, int thickness,
boolean roundedCorners)
Создает рамку в виде линии заданной ширины и цвета. Если значение параметра roundedCorners равно true, рамка имеетзакругленные углы.
javax.swing.JComponent 1.2
• void setBorder(Border border)
Задает рамку заданного компонента.
Комбинированные списки
Если пользователю нужно предоставить несколько альтернативных вариантов, переключатели не подойдут, поскольку они займут на экране слишком много места. В этом случае следует использовать комбинированные списки. Если пользователь щелкнет на этом компоненте, откроется список альтернативных вариантов. Выбрать можно только один из них (рис. 9.19).
Если открывшийся список является редактируемым (editable), то любую строку в нем можно редактировать как обычное текстовое поле. По этой причине такой компонент называется комбинированным списком (combo box): он объединяет в себе гибкость текстового поля и возможность выбора из фиксированного набора возможностей, предусмотренных заранее. Комбинированные списки реализуются с помощью класса JComboBox.
Рис.9.19. Комбинированный список
Для того чтобы сделать комбинированный список редактируемым, следует вызвать метод setEditable. Обратите внимание на то, что редактирование распространяется только на текущий пункт списка. Остальная часть списка остается неизменной.
Выбранную опцию или редактируемый текст можно получить с помощью метода getSelectedltem.
В демонстрационной программе пользователь может выбрать стиль шрифта из заданного заранее списка стилей (Serif, SunsSerif, Monospaced и т.д.). Кроме того, пользователь может внести в список новый шрифт.
Добавить опцию в список можно с помощью метода additem. В нашей программе метод additemвызывается только в конструкторе, но, в принципе, его можно вызывать где угодно.
faceCombo = new JComboBox();
faceCombo.setEditable(true);
faceCombo.additem("Serif");
faceCombo.additem("SansSerif");
Этот метод добавляет строку в конец списка. Если строку нужно вставить в произвольное место списка, следует вызвать метод insertItemAdd.
faceCombo.insertltemAt("Monospaced", 0); //Вставка в начало.
В список можно вставлять элементы любого типа— для отображения их на экране компонент вызовет метод toString.
Если во время выполнения программы возникает необходимость удалить опцию из списка, используются методы removeItem или removeItemAdd, в зависимости от того, хотите вы удалить конкретный элемент или то, что находится на указанной позиции в списке.
faceCombo.removeitem("Monospaced");
faceCombo.removeltemAt(0); // Удаляется первый элемент.
Для удаления всех элементов списка сразу предусмотрен метод removeAllIterns.
Когда пользователь выбирает пункт меню из комбинированного списка, компонент генерирует действие. Чтобы определить, какой пункт списка был выбран, вызывается метод getSource, параметром которого является данное событие. Он возвращает ссылку на комбинированный список, породивший это событие. Затем вызывается метод getSelectedltem, возвращающий выбранный пункт списка. Значение, возвращенное этим методом, нужно привести к соответствующему типу, обычно к типу String.
public voidevent);
{
label. setFont (new Font (
(String)faceCombo.getSelectedtem(),
Font.Plain, DEFAULT_SIZE));
}
Полная программа показана в листинге 9.9.
11
14.
15. 16
17. 18.
19
20
21
22 23.
24. 25 26
27.
28. 29.
30
31.
32. 33.
34.
35
36
37
38
39
40
41
42 43.
import import import
public
}
java.awt.event.*; javax.swing.*;
class ComboBoxTest
public static voidargs)
}
ComboBoxFrame frame = new
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE ;' frame.show();
Фрейм с текстовой меткой и комбинированным списком для выбора начертаний.
*/
class ComboBoxFrame extends JFrame
I
public I
setTitle("ComboBoxTest");
Container contentPane
DEFAULT_HEIGHT);
// Добавляем текстовую метку.
label = new
"The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN,
DEFAULT_SIZE));
contentPane.add(label, BorderLayout.CENTER);
// Создаем комбинированный список
// и названия = new faceCombo.setEditable(true); faceCombo.additem("Serif"); faceCombo.additem("SansSerif"); faceCombo.additem("Monospaced"); faceCombo.addItem("Dialog");
faceCombo.additem("Dialoginput");
// Слушатель комбинированного списка изменяет шрифт
54.54.
54.
54.
// метки на шрифт с выбранным начертанием.
faceCombo.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event) {
label.setFont(new Font I
(String)faceCombo.getSelectedltem(),
Font.PLAIN, DEFAULT SIZE)); )~
));
// Добавляем в нижнюю часть панели комбинированный // СПИСОК.
JPanel comboPanel = new JPaneK);
comboPanel.add(faceCombo);
contentPane.add(comboPanel, BorderLayout.SOUTH);
)
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private JComboBox faceCombo;
private JLabel label;
private static final int DEFAULT_SIZE = 12;
javax.swing.JComboBox 1.2
void setEditable(boolean b)
Параметры: ЬЗначение true, если комбинированный список мож
но редактировать, и false— если нет.
void additem(Object item)
Добавляет новый элемент в список.
void additem(Object item)
Добавляет элемент в заданную позицию списка.
void removeItern(Obj ect item)
Удаляет заданный элемент из списка.
void removeitem(int index)
Удаляет элемент из списка по заданному индексу.
void removeAllItems()
Удаляет все элементы из списка.
Object getSelectedItem()
Возвращает выбранный элемент списка.
Ползунковые регуляторы
Комбинированные списки позволяют пользователю делать выбор из дискретного набора вариантов. Ползунковые регуляторы дают возможность выбрать конкретное значение из континуума возможных значений, например, любое число из интервала от 1 до 100.
Наиболее часто ползунковые регуляторы создаются следующим образом.
JSlider slider = new JSlider(min, max, initialValue);
Если не указывать минимум, максимум и начальное значение, они инициализируются значениями 0, 100 и 50 соответственно.
Если ползунковый регулятор должен располагаться вертикально, используется такой вызов конструктора.
JSlider slider = new JSlider(Swingconstants.VERTICAL,
min, max, initialValue) ;
Эти конструкторы создают простой регулятор, такой, например, как изображенный на рис. 9.20. В дальнейшем мы покажем более сложные варианты ползунковых регуляторов.
Рис. 9.20. Ползунковые регуляторы
Когда пользователь перемещает ползунковый регулятор, значение (value) регулятора изменяется между минимальным и максимальным. Все слушатели изменения ползункового регулятора получают событие ChangeEvent. Для того чтобы получить извещение об изменении значения регулятора, нужно вызвать метод addChangeListener и создать объект, реализующий интерфейс ChangeListener. В этом интерфейсе существует лишь один метод stateChanged. В нем нужно получить значение регулятора.
public void stateChanged(ChangeEvent event) {
JSlider slider = (JSlider)event.getSource();
int value = slider.getvalue();
Ползунковый регулятор можно украсить отметками делений (tics). Например, в программе, приведенной ниже, второй регулятор использует следующие настройки. slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5) ;
Регулятор снабжен большими отметками делений через каждые 20 единиц измерения и маленькими — через каждые 5 единиц.
Эти операторы касаются исключительно единиц делений регулятора. Чтобы вывести эти отметки на экран, нужно вызвать следующий метод: slider.setpaintTicks(true); ,
Основные и вспомогательные отметки не зависят друг от друга. Например, можно задать расстановку основных отметок через каждые 20 единиц, а вспомогательных — через 7 единиц, хотя такая шкала будет довольно неудобной.
Можно заставить регулятор делать снос на деления (snap to ticks). Как только пользователь отпустит регулятор, работающий в таком режиме, он тут же переместится на ближайшее деление. Чтобы задать такой режим работы, нужно вызвать следующий метод. slider.setSnapToTicks(true);
"Снос на деления" работает не так, как можно было бы ожидать. Пока регулятор не установится точно на деление, слушатель изменений получает значения, не соответствующие отметкам шкалы, и, если пользователь повторно щелкает на регуляторе — обычное действие, приводящее к небольшому перемещению ползунка, — регулятор все равно не перемещается на ближайшую отметку.
С помощью вызова
slider.setPaintLabeIs(true);
можно задать метки основных делений (tick mark labels). Например, если значение регулятора изменяется в пределах от 0 до 100, а шаг основных делений равен 20, на экране появятся отметки 0, 20, 40, 60, 80 и 100.
Можно задать и другие отметки делений, например, строки или пиктограммы (см. рис. 9.20). Этот процесс достаточно сложен. Сначала нужно заполнить таблицу хэширования с ключами new Integer (tickvalue)и значениями типа Component. Затем вызывается метод setLabelTable. Компоненты размещаются под отметками делений. Обычно используются объекты класса JObject. Вот как можно задать метки делений А, В, С, D, Е и F.
Hashtable labelTable = new Hashtable();
labelTabel.put(new Integer(0), new JLabel("A"));
labelTabel.put(new Integer(lO), new JLabel("B"));
labelTabel.put(new Integer (100), new JLabel("F")); slider.setLabelTable(labelTable);
Более подробно таблицы хэширования описаны в главе 2 тома 2.
В листинге 9.10 показан регулятор, у которого метками деления являются пиктограммы.
Если отметки делений или их метки не выводятся на экран, проверьте, вызваны ли
методы setPaintTicks(true) и setpaintLabels(true).
В заключение отметим, что, если для интерфейса используется стиль "Metal", внешний вид ползункового регулятора можно улучшить и "заполнить" диапазон между минимальным и текущим значением. Четвертый и пятый регуляторы, показанные на рис. 9.20, заполнены. Заполнитель регулятора задается следующим образом. slider.putClientPropertyfJSlider .isFilled", Boolean.TRUE);
В пятом регуляторе ползунок передвигается в противоположном направлении. Для этого был вызван следующий метод.
slider.setlnverted(true);
В демонстрационной программе показаны все упомянутые визуальные эффекты и виды ползунковых регуляторов. У каждого регулятора есть свой слушатель, отслеживающий местоположение ползунка и отображающий его в текстовом поле, расположенном в нижней части фрейма.
import java.awt.*;
import
import
import
import
public class SliderTest
{
public static voidargs)
{
frame = new
}
}
/**
Фрейм с несколькимирегуляторами
и текстовым полем для отображения значений регулятора.
*/
class SliderTestFrame extends
{
public
{
sliderPanel = new
// Общий слушатель для всех ползунковых регуляторов.
listener = new
{
public voidevent)
{
// Обновляет текстовое поле при изменении
// значения регулятора.
JSlider source =
42.42.43.43.47.53.54 .62.63.64 .65.66.72.73.83.84.85.47.53.54 .62.63.64 .65.66.72.73.83.84.85.textField.setText("" + source.getValue< ) ); };}
42.
42.
43.
43.
47.
53.
54 .
62.
63.
64 .
65.
66.
72.
73.
83.
84.
85.
47.
53.
54 .
62.
63.
64 .
65.
66.
72.
73.
83.
84.
85.
// Добавляем обычный регулятор.
JSlider slider = new JSlider();
addSlider(slider, "Plain");
// Добавляем регулятор с основными
//и вспомогательными делениями.
slider = new JSlider ();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Ticks");
// Добавляем регулятор со сносом на деления.
slider = new JSlider ();
slider.setPaintTicks(true);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Snap to ticks");
// Добавляем заполняемый регулятор.
slider = new JSlider ();,
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing (5) ;
slider.putClientProperty("JSlider.isFilled", Boolean.TRUE);
addSlider(slider, "Filled");
// Добавляем заполняемый и обратный регулятор.
slider = new JSlider();'
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
slider.putClientProperty("JSlider.isFilled",
Boolean.TRUE);
slider.setInverted(true);
addSlider(slider, "Inverted");
// Добавляем регулятор с числовыми метками.
slider = new JSlider();
slider.setPaintTicks(true);
slider.setPaintLabeIs(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Labels");
// Добавляем регулятор с буквенными метками.
slider = new JSliderO;
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
105.Hashtable labelTable = new Hashtable();
106.
labelTable.put(new
Integer(0),
new JLabel("A"));
107.
labelTable.put(new
Integer(20),
new JLabel(HB"));
108.
labelTable.put(new
Integer(40),
new JLabel ГС") );
109.
labelTable.put(new
Integer(60),
new JLabel("D"));
110.
labelTable.put(new
Integer(80),
new JLabel("E"));
111. labelTable.put(new Integer(100) , new JLabel("F"));
112.
slider.setLabelTable(labelTable);
addSlider(slider, "Custom labels");
// Добавляем регулятор с пиктограммами.
slider = new JSliderO;
slider.setPaintTicks(true) ;
slider.setPaintLabels(true);
slider.setSnapToTicks(true) ;
slider.setMajorTickSpacing(20) ;
slider.setMinorTickSpacing(20);
labelTable = new Hashtablet);
// Добавляем изображения карт.
labelTable.put(new Integer(0),
new JLabel (new Imagelcon("nine.gif")));
labelTable.put(new Integer(20),
new JLabel(new Imageicon("ten.gif")));
labelTable.put(new Integer(40),
new JLabel(new Imageicon("jack.gif")));
labelTable.put(new Integer(60),
new JLabel(new Imageicon("queen.gif")));
labelTable.put(new Integer(80),
new JLabel(new Imageicon("king.gif")));
labelTable.put(new Integer(100),
new JLabel(new Imageicon("ace.gif")));
slider.setLabelTable(labelTable);
addSlider(slider, "Icon labels");
// Добавляем текстовое поле, отображающее значение
// регулятора.
textField = new JTextField!);
Container contentPane = getContentPane();
contentPane.add(sliderPanel, BorderLayout.CENTER);
contentPane.add(textField, BorderLayout.SOUTH);
}
Its
/**
Добавляет регулятор к панели и вызывает слушатель.
@param s регулятор
@param description описание регулятора
160.161.162.163.164".)160.161.162.163.164".)*/
160.
161.
162.
163.
164".
)
160.
161.
162.
163.
164".
)
public void addSlider(JSlider s, String description)
{
s.addChangeListener(listener);
JPanel panel = new JPanel();
panel.add(s);
panel.add(new JLabel(description));
sliderPanel.add (panel);
}
public static final int DEFAULT_WIDTH = 350;
public static final int DEFAULT_HEIGHT = 450;
private JPanel sliderPanel;
private JTextField textField;
private ChangeListener listener;
javax.swing.JSlider 1.3
JSlider(J
JSlider(int direction)
JSlider(int min, int max)
JSlider(int min, int max, ini initialValue)
Параметры: directionПараметры: directionmin, maxmin, maxinitialValueinitialValueОдна из константSwingConstantc.HORIZONTALили swingConstants.VERTICAL.Минимальное и максимальное значения регулятора. По умолчанию равны 0 и 100 соответственно.Начальное значение регулятора. По умолчанию равно 50.Одна из константSwingConstantc.HORIZONTALили swingConstants.VERTICAL.Минимальное и максимальное значения регулятора. По умолчанию равны 0 и 100 соответственно.Начальное значение регулятора. По умолчанию равно 50.JSlider(int direction, int min, int max, ini initialValue) Создает горизонтальный ползунковый регулятор с заданным направлением, минимальным и максимальным значениями.
Параметры: direction
Параметры: direction
min, max
min, max
initialValue
initialValue
Одна из констант
SwingConstantc.HORIZONTALили swingConstants.VERTICAL.
Минимальное и максимальное значения регулятора. По умолчанию равны 0 и 100 соответственно.
Начальное значение регулятора. По умолчанию равно 50.
Одна из констант
SwingConstantc.HORIZONTALили swingConstants.VERTICAL.
Минимальное и максимальное значения регулятора. По умолчанию равны 0 и 100 соответственно.
Начальное значение регулятора. По умолчанию равно 50.
void setPaintTicks(boolean b)
Если значение параметра b равно true, отметки деления отображаются на экране.
void setMajorTickSpacing(int units)
void setMinorTickSpacing(int units) Устанавливают основную и вспомогательную шкалы делений соответственно по заданной единице измерения.
void setPaintLabels(boolean b)
Если значение параметра b равно true, отметки деления отображаются на экране.
slider.setlabelTable(Dictionary table)
Устанавливает компоненты, которые используются в качестве меток делений. Каждая пара ключ/значение в таблице образует new Integer (value) /component.
void setSnapToTicks(boolean b)
Если значение параметра b равно true, ползунок регулятора всегда перемещается на отметку деления, ближайшую к установленному значению регулятора.
Компонент JSpinner
Компонент JSpinner представляет собой текстовое поле с двумя маленькими кнопками, размещенными сбоку и предназначенными для увеличения или уменьшения значения, хранящегося в поле (рис. 9.21).
Рис. 9.21. Разновидности компонента JSpinner
Значениями этого компонента могут быть числа, даты, значения, хранящиеся в списке, т.е. любая последовательность, в которой можно определить предшественника и преемника текущего элемента. Для чисел, дат и списка в классе JSpinner определены стандартные способы представления данных. Для описания произвольной последовательности значений пользователь может определить свою собственную модель.
По умолчанию компонент JSpinner управляет целыми числами, а кнопки увеличивают или уменьшают эти числа на единицу. Для получения текущего значения следует вызвать метод setValue. Этот метод возвращает объект класса Object. Его следует привести к типу Integer, а затем извлечь запакованное значение.
JSpinner defaultSpinner = new JSpinner(0;
int value = ((Integer)defaultSpinner.getvalue()).intValue();
Приращение значения можно изменить. Кроме того, можно задать нижнюю и верхнюю границы изменения значений. Рассмотрим компонент JSpinner, у которого стартовое число равно 5, нижняя и верхняя границы равны 0 и 10 соответственно, а приращение равно 0.5.
JSpinner boundedSpinner = new JSpinner(
new SpinnerNumberModel(5, 0, 10, 0.5));
Существуют два конструктора SpinnerNumberModel: у одного из них все параметры имеют тип int, а у другого— double. Если какой-либо из параметров пред-
ставляет собой число с плавающей точкой, вызывается второй конструктор. Он задает в качестве значения компонента JSpinner объект класса Double.
Компоненты JSpinner не ограничены лишь числами. Они могут просматривать любую коллекцию значений. Для этого достаточно передать объект класса SpinnerListModelконструкторуклассаSpinner.Объекткласса
SpinnerListModel можно создать из массива или класса, реализующего интерфейс List (например, класса ArrayList). В нашей демонстрационной программе компонент JSpinner управляет списком шрифтов.
String!’ fonts = GraphicsEnvironment
. getLocalGraphicsEnvironment(1
.getAvailableFontFamilyNames();
JSpinner listSpinner = new JSpinner(new SpinnerListModel(fonts));
Оказалось, что компонент прокручивает значения в неожиданном направлении, противоположном направлению, принятом в комбинированных списках. Как вы помните, в комбинированных списках большее значение размещается под меньшим, поэтому для поиска более высоких значений пользователь интуитивно выбирает стрелку вниз. Однако с компоненте JSpinner увеличивается индекс массива, в котором хранятся значения, поэтому для выбора более высоких значений нужно использовать стрелку вверх. В классе SpinnerListModel не предусмотрена возможность для установки обратного направления прокрутки, однако этого можно добиться с помощью безымянного подкласса.
JSpinner reverseListSpinner = new JSpinner(
new SpinnerListModel(fonts)
{
public Object getNextValue()
{
return super.getPreviousValue();
}
public Object getPreviousValue()
{
return super.getNextValue();
}
});
Испытайте обе версии и определите, какая из них более естественна.
Еще одно полезное применение компонентов JSpinner заключается в том, что с их помощью можно увеличивать или уменьшать даты. Например, можно создать объект класса JSpinner, инициализированный сегодняшней датой.
JSpinner dateSpinner = new JSpinner(new SpinnerDateModel());
Однако, если внимательно присмотреться к рис. 9.21, можно увидеть, что в текстовом поле отображаются не только дата, но и время.
3/12/02 7:23 PM
Однако время нас в данном случае не интересует! Оказывается, что создать объект класса JSpinner, который просто показывает дату, довольно сложно. И все же нам это удалось!
JSpinner betterDateSpinner = new JSpinnerI
new SpinnerDateModel());
String patter = ((SimpleDateFormat)
DateFormat.getDatelnstance()).toPattern;
betterDateSpinner.setEditor(new JSpinner.DateEditor(
betterDateSpinner, pattern));
Используя тот же самый подход, можно создать объект, управляющий только временем. Для этого нужно применить конструктор SpinnerDateMode1, позволяющий задавать объект класса Date, нижнюю и верхнюю границы (или ссылку null, если границ нет) и модифицируемое поле класса Calendar (например Calendar . HOUR).
JSpinner timeSpinner = JSpinner(new SpinnerDateModel(
new GregoriaCalendar(2000, Calendar.Jabuary, 1,
12, 0, 0).getTime(), null, null, Calendar.HOUR));
Однако, если вам захочется задать 15-минутное приращение, стандартных возможностей класса SpinnerDateModel окажется недостаточно.
Для отображения произвольной последовательности значений в компоненте класса JSpinner необходимо определить свою собственную модель прокрутки. В нашей демонстрационной программе мы заставили объект класса JSpinner прокручивать все возможные перестановки букв в слове "meat": "mate", “meta", "team" и еще двадцать перестановок можно вывести на экран, щелкая на кнопках этого компонента.
Определяя свою собственную модель, необходимо расширить класс AbstractSpinnerModel и определить четыре метода:
Object getValue()
void Setvalue(Object value)
Object getNextValue()
Object getPreviousValue(]
Метод getValue возвращает значение, хранящееся в модели. Метод setValue задает новое значение. Если значение является неприемлемым, метод setValue должен возбуждать исключительную ситуацию IllegalArgumentException.
После установки нового значения метод setValue должен вызывать метод fireStateChanged. В противном случае поле компонента JSpinner останется неизменным.
Методы getNextValue и getPreviousValue возвращают преемника и предшественника текущего значения или ссылку null, если достигнут конец коллекции.
Методы getNextValue и getPreviousValue не должны изменять текущее значение. Когда пользователь щелкает на стрелке, направленной вверх, вызывается метод getNextValue. Если он вернул ссылку null, следует задать значение с помощью метода setValue.
В демонстрационной программе использован стандартный алгоритм определения следующей и предыдущей перестановки. Детали этого алгоритма не представляют для нас интереса.
В листинге 9.11 показан способ генерации различных объектов класса JSpinner. Для просмотра их значений щелкните на кнопке "Ok".
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
12.13.14.17.22.23.24.32.33.34.37.42.43.44.47.52.53.54.55.56.57.12.13.14.17.22.23.24.32.33.34.37.42.43.44.47.52.53.54.55.56.57./**
12.
13.
14.
17.
22.
23.
24.
32.
33.
34.
37.
42.
43.
44.
47.
52.
53.
54.
55.
56.
57.
12.
13.
14.
17.
22.
23.
24.
32.
33.
34.
37.
42.
43.
44.
47.
52.
53.
54.
55.
56.
57.
Программа для тестирования компонентов JSpinner.
*/ public class SpinnerTest {
public static void main(String( args) {
SpinnerFrame frame = new SpinnerFrame();
frame.setDefaultCloseOperation(JFrame. EXIT_ON_CLOSE); frame.show();
}
}
/**
Фрейм с панелью, содержащей несколько компонентов JSpinner и кнопку для просмотра их значений.
*/
class SpinnerFrame extends JFrame {
public SpinnerFrame(I {
setTitle("SpinnerTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Container contentPane = getContentPane();
JPanel buttonPanel = new JPanel();
okButton = new JButton("Ok");
buttonPanel.add(okButton] ;
contentPane.add(buttonPanel, BorderLayout.SOUTH);
mainPanel = new JPanel();
mainPanel.setLayout(new GridLayout(0, 3)); contentpane.add(mainPanel, BorderLayout.CENTER);
JSpinner defaultSpinner = new JSpinner(); addRow("Default", defaultSpinner);
JSpinner boundedSpinner = new JSpinner( new SpinnerNumberModel(5, 0, 10, 0.5)); addRow("Bounded", boundedSpinner);
String(] fonts = GraphicsEnvironment .getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames() ;
JSpinner listSpinner = new JSpinner( new SpinnerListModel(fonts));
addRow("List", listSpinner);
JSpinner reverseListSpinner = new JSpinnerI new
SpinnerListModel (fonts) {
public Object getNextValue () {
return super.getPreviousValue();
}
public Object getPreviousValue(] {
65.66.67 .97.105.65.66.67 .97.105.return super.getNextValue();
65.
66.
67 .
97.
105.
65.
66.
67 .
97.
105.
} }) ;
addRow("Reverse List", reverseListSpinner);
JSpinner dateSpinner = new JSpinner( new SpinnerDateModel());
addRow("Date", dateSpinner);
JSpinner betterDateSpinner = new JSpinner( new SpinnerDateModel());
String pattern = ((SimpleDateFormat)
DateFormat.getDatelnstance()).toPatternO ;
betterDateSpinner.setEditor(new JSpinner.DateEditor( betterDateSpinner, pattern));
addRow("Better Date", betterDateSpinner);
JSpinner timeSpinner = new JSpinner( new SpinnerDateModel(
new GregorianCalendar(2000, Calendar.JANUARY, 1,
12, 0, 0).getTime(), null, null,
Calendar.HOUR)) ;
addRow("Time", timeSpinner);
JSpinner permSpinner = new JSpinnerI new PermutationSpinnerModel("meat"));
addRow("Word permutations", permSpinner);
}
/**
Добавляет строку на главную панель.
@param labelText метка компонента JSpinner
@param spinner пример компонента JSpinner
*/
public void addRow(String labelText, final JSpinner spinner) {
mainPanel.add(new JLabel(labelText));
mainPanel.add(spinner) ;
final JLabel valueLabel = new JLabel();
mainPanel.add(valueLabel);
okButton.addActionListener(new
ActionListener(] {
public void actionPerformed(ActionEvent event) {
Object value = spinner.getValue(); valueLabel.setText(value.toString());
} });
}
public static final int DEFAULT_WIDTH = 400;
public static final int DEFAULT_HEIGHT = 250;
private JPanel mainPanel;
private JButton okButton;
}
/**
Модель, динамически генерирующая перестановки букв в слове
*/
126.126.127.127.128.128.134.135.136.134.135.136.137.138.139.140.141.142.152.153.154.155.156.157.158.159.160.161.137.138.139.140.141.142.152.153.154.155.156.157.158.159.160.161.162.163.164.182.183.184.185.186.162.163.164.182.183.184.185.186.class PermutationSpinnerModel extends AbstractSpinnerModel
126.
126.
127.
127.
128.
128.
134.
135.
136.
134.
135.
136.
137.
138.
139.
140.
141.
142.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
137.
138.
139.
140.
141.
142.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
182.
183.
184.
185.
186.
162.
163.
164.
182.
183.
184.
185.
186.
{ /**
Создает модель.
@param w слово, в котором переставляются буквы
*/
public PermutationSpinnerModel(String w) {
word = w;
}
public Object getValue()
{
return word;
}
public void setvalue(Object value)
{
if (!(value instanceof String))
throw new IllegalArgumentException();
word = (String)value;
fireStateChanged();
}
public Object getNextValue ()
{
StringBuffer buffer = new StringBuffer(word);
for (int i = buffer.length(J - 1; i > 0; i—) ■ {
if (buffer.charAt(i - 1) < buffer.charAt(i)’
{
int j = buffer.length() - 1;
while (buffer.charAt(i - 1) > buffer.charAt(j ) ) j— ;
swap(buffer, i - 1, j) ;
reverse(buffer, i, buffer.length(] - 1);
return buffer.toString();
}
reverse(buffer, 0, buffer.length() - 1);
return buffer.toString();
}
public Object getPreviousValue(]
{
StringBuffer buffer = new StringBuffer(word);
for (int i = buffer.length(] - 1; i > 0; i—1 {
if (buffer.charAt(i - 1) > buffer.charAt(i))
{
int j = buffer.length(] - 1;
while (buffer.charAt(i - 1) < buffer.charAt(j)]
j--;
swap(buffer, i - 1, j);
reverse(buffer, i, buffer.length(I - 1);
return buffer.toString();
}
}
reverse(buffer, 0, buffer.length() - 1);
return buffer.toStringO ;
}
private static void swap(StringBuffer buffer, int i, int j;
{
char temp = buffer.charAt(i);
buffer.setCharAt(i, buffer.charAt(j));
buffer.setCharAt(j, temp);
}
193.
private static void reverse(StringBuffer buffer, int i,
int j)
{
while (i < j) { swap(buffer, i, j!i++; j--; }
199.
private String word;
}
javax.swing.JSpinner 1.4
JSpinner()
Создает объект класса JSpinner для редактирования неограниченных целых чисел, начиная с 0 и с приращением 1.
• JSpinner(SpinnerModel model)
Создает объект класса JSpinner, использующий заданную модель данных.
Object getValue()
Определяет текущее значение объекта класса JSpinner.
void setValue(Object value)
Пытается установить текущее значение объекта класса JSpinner. Если значение неприемлемо, возбуждает исключительную ситуацию IllegalArgumentException.
void setEditor(JComponent editor)
Задает компонент, использующийся для редактирования значения объекта класса JSpinner.
javax.swing.SpinnerNumberModel 1.4
SpinnerNumberModel(int initval, int minimum, int maximum, int stepSize)
SpinnerNumberModel(double initval, double minimum, double maximum,double stepSize)
Эти конструкторы создают числовые модели, управляющие объектами классов Integer или Double. Для неограниченных величин используются константы MIN_VALUEиMAX_VALUE,определенныевклассах Integer и Double.
Параметры: initvalminimumПараметры: initvalminimumНачальное значение.Начальное значение.Минимальное допустимое число.Минимальное допустимое число.
Параметры: initval
minimum
Параметры: initval
minimum
Начальное значение.
Начальное значение.
Минимальное допустимое число.
Минимальное допустимое число.
maximum Максимальное допустимое число.
stepSize Приращение.
javax.swing.SpinnerListMode 1.4 SpinnerListModel(Object[ values) SpinnerListModel(List values)
Эти конструкторы создают модели выбора значения из заданной коллекции.
javax.swing.SApinnerDateModel1.4
SpinnerDateModel()
Создает модель данных, инициализированную текущей датой и не имеющую ограничений.
SpinnerDateModel(Date initval,Comparableminimum,
Comparable maximum,int step)
Параметры: initval Начальное значение.
minimum Минимальное допустимое значение, если не указана нижняя граница.
maximum Максимальное допустимое значение, если не ука
зана верхняя граница.
stepПриращение даты. Может задаваться одной
из следующий констант: ERA, YEAR, MONTH, WEEK_OF_YEAR, WEEK_OF_MONTH, DAY_OF_MONTH, DAY_OF_YEAR, DAY_OF_WEEK, DAY_OF_WEEK_IN_MONTH,AM_PM, HOUR, HOUR_OF_DAY^ MINUTE, SECOND или MILLISECOND, определенных в классе Calendar.
javax.text.SimpleDateFormat1.1
String toPatternO1.2
Определяет шаблон редактирования, используемый в данном методе форматирования. Типичный шаблон выглядит так: "yyyy-MM-dd". Более детальное описание шаблона приведено в документации по пакету SDK.
javax.swing.JSpinner.DateEditor 1.4
DateEditor(JSpinner spinner, String pattern)
Параметры: spinner Объект класса JSpinner, которому принадлежит редактор.
pattern Формальный шаблон для ассоциированного метода SimpleDateFormat.
1.41.4javax.swing.AbstractSpinnerModel
1.4
1.4
Object getValue()
Определяет текущее значение модели.
void setvalue(Object value)
Пытается задать новое значение модели. Если это значение неприемлемо, возбуждает исключительную ситуацию IllegalArgumentException. При замещении этого метода после задания нового значения следует вызывать метод fireStateChanged.
Object getNextValue(]
Object getPreviousValue()
Эти методы вычисляют (но не устанавливают) преемника и предшественника текущего значения в последовательности, определенной в данной модели.
Меню
В начале этой главы мы описали наиболее распространенные компоненты, которые можно разместить в окне, например, разнообразные кнопки, текстовые поля и комбинированные списки. В пакете Swing предусмотрены также выпадающие меню, хорошо известные элементы графического пользовательского интерфейса.
Линейка меню (menu bar) в верхней части окна содержит названия выпадающих меню. Щелкая на имени, пользователь открывает меню, состоящее из пунктов (menu items) и подменю (submenu). Если пользователь щелкнет на пункте меню, все меню закроются и в программу будет передано сообщение. На рис. 9.22 показано типичное меню, содержащее пункты и подменю.
Рис. 9.22. Меню и подменю
Создание меню
Создание меню — довольно простой процесс. Сначала создается линейка меню.
JMenuBar menuBar = new JMenuBar();
Линейка меню — это компонент, который можно добавить куда угодно. Обычно его помещают в верхнюю часть фрейма с помощью метода set JMenuBar.
frame.setJMenuBar(menuBar 1 ;
Для каждого меню создается свой объект.
Jmenu editMenu = new Jmenu("Edit");
Меню верхнего уровня помещаются на линейке меню.
menuBar.add(editMenu; ;
Затем в объект меню добавляются пункты меню, разделители и подменю.
JMenuItem pasteitem = new JMenuItem("Paste");
editMenu.add(pasteitem);
editMenu.addSeparator() ;
Jmenu optionsMenu = ... ; // Подменю. editMenu.add(optionsMenu);
Когда пользователь выбирает меню, происходят действия. Следовательно, нужно определить слушатели действий для каждого пункта меню.
ActionListener listener = ...;
pasteitem.addActionListener(listener);
Существует удобный метод JMenu. add (String), позволяющий добавлять новый пункт в конец меню, например, следующим образом.
editMenu.add("Paste"1 ;
Метод add возвращает созданный пункт меню, который можно перехватить и задать его слушатель.
JMenuItem pasteltem = editMenu.add("Paste"); pasteitem.addActionListener(listener);
Довольно часто пункты меню связываются с операторами, которые могут активироваться, и другими элементами пользовательского интерфейса, например кнопками. В главе 8 мы уже видели, как такие операторы задаются с помощью объектов Action. Следует определить класс, реализующий интерфейс Action. Обычно для этого расширяют класс AbstractAction. Затем в конструкторе объекта класса AbstractAction указывается метка пункта меню и замещается метод actionPerformed, что позволяет реализовать обработку действия, связанного с данным пунктом.
Action exitAction = new
AbstractActionCExit") // Пункт меню "Exit".
{
public void actionPerformed(ActionEvent event)
{
// Фрагмент программы, реализующий действие.
System.exit(0) ;
)
Затем действие добавляется в меню.
JMenuItem exititem =
Этот оператор добавляет в меню новый пункт, используя имя действия. Объект действия становится его слушателем. Такой способ представляет собой удобное сокращение двух операторов.'
JMenuItem exitltem = new JMenuItem(exitAction) ,• fileMenu.add(exititem: ;
В программахдля операционной системы Windows меню обычно определяются во внешних файлах ресурсов и связываются с приложениями с помощью идентификаторов ресурсов. Меню можно создавать автоматически, однако, как правило, этого не делают. В языке Java меню в программе создаются обычным образом, поскольку его механизм взаимодействия с внешними ресурсами еще более ограничен, чем в среде Windows.
javax.swing.JMenu 1.2 JMenu(String label)javax.swing.JMenu 1.2 JMenu(String label)
javax.swing.JMenu 1.2 JMenu(String label)
javax.swing.JMenu 1.2 JMenu(String label)
Параметры: label Метка меню на линейке или в родительском меню.
JMenuItem add(JMenuItem item)
Добавляет пункт (или целое меню).
Параметры: itemДобавляется пункт или отдельное меню.
JMenuItem add(String label)
Добавляет пункт в указанное меню.
Параметры: label Метка пункта меню.
JMenuAction add(Action a)
Добавляет пункт и связанное с ним действие.
Параметры: а Действие, инкапсулирующее имя, пиктограмму (по желанию) и слушатель (см. главу 8).
void addSeparator(I
Добавляет в меню разделительную линию.
JMenuItem insert(JMenuItem menu, int index)
Добавляет в указанную позицию меню новый пункт (или подменю).
Параметры: menuИзменяемое меню.
index Позиция, в которую вставляется пункт.
JMenuItem insert(Action a, int index)
Добавляет в указанную позицию меню новый пункт и связанное с ним действие.
Параметры: аДействие, инкапсулирующее имя, пиктограмму (по
желанию) и слушатель.
index Позиция, в которую вставляется пункт.
void insertseparator(int index)
Добавляет в меню разделительную линию.
Параметры: index Позиция, в которую вставляется разделитель.
void remove(int index)
Удаляет указанный пункт из меню.
Параметры: index Позиция меню, подлежащая удалению.
void remove(JMenuItem item)
Удаляет указанный пункт из меню.
Параметры: itemПункт меню, подлежащий удалению.
JMenuItem(String label)JMenuItem(String label)javax.swing. JMenuItem 1.2
JMenuItem(String label)
JMenuItem(String label)
Параметры: label Метка данного пункта меню.
JMenuItem(Action a)
Параметры: аДействие, инкапсулирующее имя, пиктограмму (по
желанию) и слушатель.
API javax.swing.AbstractButton 1.2
void setAction(Action a)1.3
Параметры: аДействие, инкапсулирующее имя, пиктограмму (no
желанию) и слушатель.
El javax.swing.JFrame 1.2
void setJMenuBar(JMenuBar menubar)
Задает линейку меню в указанном фрейме.
Пиктограммы в пунктах меню
Пункты меню очень похожи на кнопки. Фактически класс JMenuItem расширяет класс AbstractButton. Как и кнопки, меню могут иметь текстовую метку, пиктограмму или и то, и другое одновременно. Пиктограмму можно указать в конструкторе JMenuItem (String, Icon) или задать с помощью метода seticon, унаследованного классом JMenuItem от класса AbstractButton.
JMenuItem cutltem = new JMenuItem("Cut", new Imageicon("cut.gif"));
На рис. 9.23 показано меню с пиктограммами. Как видим, по умолчанию названия пунктов меню располагаются справа от пиктограмм. Если вам хочется, чтобы справа находились пиктограммы, вызывайте метод setHorizontalTextPosition, унаследованный классом JMenuItemот класса AbstractButton. Например, вызов cutItem.setHorizontalTextPosition(Swingconstants.LEFT);
перемещает текст пункта меню влево от пиктограммы.
Пиктограмму можно связать с действием.
cutAction.putValue(Action.SMALL_ICON, new Imageicon("Cut.gif));
Если пункт меню не связан ни с одним действием, то значением поля Action. NAME становится название пункта меню, а значением поля Action.SMALL_ICON— его пиктограмма.
Кроме того, пиктограмму можно задать в конструкторе класса AbstractAction. cutAction = new
AbstractAction("Cut", new Imageicon("cut.gif")) public void actionPerformed(ActionEvent event) у // Фрагмент программы, реализующий действие.
};
Рис. 9.23. Пиктограммы в пунктах менюРис. 9.23. Пиктограммы в пунктах менюjavax.swing.JMenuItem 1.2
Рис. 9.23. Пиктограммы в пунктах меню
Рис. 9.23. Пиктограммы в пунктах меню
JMenuItem(String label, Icon icon)
Параметры: labelМетка пункта меню.
iconПиктограмма пункта меню.
^PJ^ javax.swing.AbstractButton 1.2
void setHorizontalTextPosition(int pos)
Задает взаимное расположение текста от пиктограммы.
Параметры: posКонстанта Swingconstants.RIGHT (текст
располагается справа от пиктограммы) или
Swingconstants.LEFT.
API javax.swing.AbstractAction 1.2
Параметры: nameПараметры: nameМетка действия.Метка действия.""I • AbstractAction (String name, Icon smallicon)
Параметры: name
Параметры: name
Метка действия.
Метка действия.
smalllcon Пиктограмма действия.
Комбинированные списки и группы переключателей в меню
Пункты меню, представляющие собой комбинированные списки (combo boxes) или переключатели (radio buttons), изображены на рис. 9.24. Когда пользователь щелкает на пункте меню, флажки расставляются и снимаются автоматически.
За исключением внешнего вида, такие пункты ничем не отличаются от других. Рассмотрим, например, как создается пункт меню с комбинированным списком.
JCheckBoxMenuItem readonlyltem
= new JCheckBoxMenuItem("Read-only");
optionsMenu.add(readonlyltem);
Пункты меню с переключателями работают точно так же, как и обычные переключатели. В меню нужно добавить группу переключателей. Когда выбирается один из них, все остальные автоматически выключаются.
ButtonGroup group = new ButtonGroup();
JRadioButtonMenuItem insertitem =
insertitem.setSelected(true! ;
JRadioButtonMenuItem overtypeitem
=newJRadioButtonMenuItem("Overtype");
group.add(insertitem);
group.add(overtypeltem);
optionsMenu.add(insertitem);
optionsMenu.add(overtypeltem);
В этих пунктах меню не обязательно точно определять, когда именно пользователь сделал выбор. Вместо этого для проверки текущего состояния пункта меню достаточно применять метод isSelected. (Разумеется, это означает, что в какой-то переменной придется хранить ссылку на данный пункт меню.) Кроме того, задать состояние пункта меню можно с помощью метода setSelected.
Рис. 9.24. Пункты меню с комбинированными списками и переключателямиРис. 9.24. Пункты меню с комбинированными списками и переключателями
Рис. 9.24. Пункты меню с комбинированными списками и переключателями
Рис. 9.24. Пункты меню с комбинированными списками и переключателями
javax.swing.JCheckBoxMenuItem 1.2
JCheckBoxMenuItem(String label)
Создает пункт меню, представляющий собой комбинированный список с заданной меткой.
JCheckBoxMenuItem(String label, boolean state)
Создает пункт меню, представляющий собой комбинированный список с заданными меткой и состоянием (если пункт выбран, переменная state принимает значение true).
javax.swing.JRadioButtonMenuItem 1.2
JRadioButtonMenuItem(String label)
Создает пункт меню, представляющий собой переключатель с заданной меткой.
JRadioButtonItem(String label, boolean state)
Создает пункт меню, представляющий собой переключатель с заданными меткой и состоянием (если пункт выбран, переменная state принимает значение true).
javax.swing.AbstractButton 1.2
boolean isSelected()
Возвращает состояние пункта (значение true, если пункт выбран).
void setSelected(bboolean state)boolean isSelectedO Задает состояние указанного пункта.
Всплывающие меню
Всплывающие меню (pop-up menu) — это меню, не связанные с линейкой, а появляющееся в произвольно выбранном месте экрана (рис. 9.25).
Рис.9.25. Всплывающее менюРис.9.25. Всплывающее меню
Рис.9.25. Всплывающее меню
Рис.9.25. Всплывающее меню
Всплывающее меню создается точно так же, как и обычное, за одним исключением — оно не имеет заголовка.
JpopupMenu popup = new JPopupMenu ();.
Пункты меню добавляются как обычно.
Jmenultem item = new Jmenultem("Cut");
item.addActionListener(listener);
popup.add(item);
В отличие от линейки обычного меню, которое всегда находится в верхней части фрейма, всплывающее меню нужно принудительно выводить на экран с помощью метода show. Для этого необходимо задать родительский компонент и местоположение всплывающего меню в его системе координат.
popup.show(panel, x, у);
Обычно всплывающее меню показывают на экране, когда пользователь щелкает кнопкой запуска всплывающего меню (pop-up trigger). Как правило, это правая кнопка мыши. При этом происходят следующие действия.
Устанавливается слушатель мыши.
В программу добавляется следующий код.
public void mouseReleased(MouseEvent event)
{
if (event.isPopuptrigger())
popup.show(event.getComponent() , event.getX(), event.getY());
}
Этот код покажет всплывающее меню в месте, где располагался курсор мыши в момент щелчка кнопки запуска.
Некоторые системы открывают всплывающие меню, когда кнопка мыши нажата, а некоторые — когда она отпущена. Следовательно, необходимо проверить обе возможности, проанализировав методы mousePressedи mouseReleased.
javax.swing.JPopupMenu 1.2 void show(Component c, int x,
int y)
Показывает всплывающее меню.
Параметры: с
Компонент, с помощью которого показывается всплывающее меню.
х,у Координаты (в системе координат компонента с) лево-
го верхнего угла всплывающего меню.
boolean isPopupTrigger(MouseEvent event)1.3
Возвращает значение true, если пользователь щелкнул кнопкой запуска
всплывающего меню.
java.awt.event.MouseEvent
boolean isPopupTrigger() Возвращает значение true, если
1.1
пользователь щелкнул кнопкой запуска
всплывающего меню.
Назначенные и ускоряющие клавиши
Опытному пользователю удобно выбирать пункты меню с помощью назначенных клавиш (keyboar mnemonics). В языке Java связать пункт меню с назначенной клавишей можно, задав ее в конструкторе этого пункта.
JMenuItem cutitem = new JmenuItemfCut", "T");
Назначенная клавиша автоматически отображается в меню, выделяя соответствующую букву в названии пункта (рис. 9.26). Например, в пункте, определенном в последнем примере, метка будет отображена как "Cut", где подчеркнута буква "t". Теперь для выбора этого пункта меню пользователю достаточно нажать клавишу <Т>. (Если буква, соответствующая назначенной клавише, не входит в название пункта меню, она не отображается на экране, но при ее нажатии этот пункт все равно будет выбран. Естественно, польза от таких "невидимых" клавиш весьма сомнительна.)
Имея объект класса Action, клавишу можно назначить с помощью значения поля Action.MNEMONIC_KEY.
cutAct i on.putValue(Act i о n.MNEMONIC_KEY, new Integer('T*));
Рис. 9.26. Назначенные клавиши
Назначенную букву следует задавать только в конструкторе пункта меню (но не в конструкторе меню в целом). Чтобы связать какую-либо клавишу с меню в целом, нужно вызвать метод setMnemonic.
JMenu helpMenu = new JMenu("Help");
helpMenu.setMnemonic(1H*);
Для того чтобы выбрать меню на линейке, нужно нажать клавишу <ALT> вместе с назначенной буквой. Например, чтобы выбрать меню "Help", показанное на рис. 9.26, нужно нажать клавиши <ALT+H>.
Назначенные клавиши позволяют выбрать пункт в уже открытом меню. В противоположность этому, ускоряющие клавиши (accelerators) позволяют выбрать пункт, не открывая меню. Например, во многих программах предусмотрены ускоряющие комбинации клавиш <CTRL+O> и <CTRL+S> для пунктов "Open" и "Save" в меню "File". Для связывания ускоряющей клавиши с пунктом меню используется метод setAccelerator. В качестве параметра он получает объект класса Keystroke. Например, приведенный ниже вызов назначает ускоряющую комбинацию клавиш <CTRL+O> для пункта меню open Item.
openitem.setAccelerator(Keystroke.getKeyStroke(KeyEvent.VK_O,
InputEvent,CTRL_MASK)) ;
Когда пользователь нажимает ускоряющую комбинацию клавиш, он автоматически выбирает соответствующий пункт меню и запускает действие, как если бы он выбирал пункт меню обычным способом. Ускоряющие клавиши можно связывать только с пунктами меню, но не с меню в целом. На самом деле они не открывают меню, а непосредственно запускают действие, связанное с указанным пунктом.
В принципе, ускоряющие клавиши для пунктов меню добавляются так же, как и для остальных компонентов пакета Swing. (Эти способы описывались в главе 8.) Однако, если ускоряющая клавиша назначена, она автоматически отображается в меню (рис. 9.27).
Рис. 9.27. Ускоряющие клавишиРис. 9.27. Ускоряющие клавиши
Рис. 9.27. Ускоряющие клавиши
Рис. 9.27. Ускоряющие клавиши
Объекты класса Action могут связываться с клавишами, которые задаются параметром Action .ACCELERATOR-KEY. Однако конструктор JMenuItem (Action) игнорирует такие клавиши.
В операционной системе Windows комбинация клавиш <ALT+F4> закрывает окна. Однако в языке Java эта комбинация не является ускоряющей. Это просто сокращение, определенное операционной системой, которое всегда вызывает появление события Windowclosing для активных окон, независимо от того, есть ли в меню пункт “Close”.
javax.swing.JMenuItem 1.2
JMenuItem(String label, int mnemonic)
Параметры: labelПараметры: labelМетка указанного пункта меню.
Параметры: label
Параметры: label
mnemonic Мнемоническое обозначение пункта; в метке этот символ подчеркивается.
void setAccelerator(Keystroke k)
Задает ускоряющую комбинацию клавиш k для указанного пункта меню. Ускоряющая комбинация отображается на экране следом за меткой.
javax.swing.AbstractButton1.2
void setMnemonic(char mnemonic)
Назначает клавишу для кнопки. В метке кнопки этот символ подчеркивается.
Параметры: mnemonic Клавиша, назначенная для кнопки.
Блокирование и разблокирование пунктов меню
Иногда некоторые пункты меню должны выбираться лишь в определенных ситуациях. Например, если документ открыт лишь для чтения, то пункт меню "Save" не имеет смысла. Разумеется, этот пункт можно удалить с помощью метода JMenu. remove, но пользователя может удивить постоянно изменяющееся меню. Лучше все же заблокировать пункт меню, временно лишив пользователя возможности выполнять ряд операций. На рис. 9.28 заблокированный пункт меню выделен светло-серым цветом.
Рис. 9.28. Заблокированные пункты менюРис. 9.28. Заблокированные пункты меню
Рис. 9.28. Заблокированные пункты меню
Рис. 9.28. Заблокированные пункты меню
Для того чтобы разблокировать или заблокировать пункт меню, используется метод setEnabled.
saveitem.setEnabled(false);
Существуют две стратегии блокирования и разблокирования пунктов меню. При каждом изменении ситуации можно вызывать метод setEnabled, применяя его к соответствующему пункту меню. Например, открыв документ только для чтения, можно заблокировать пункты "Save" и "Save As". Однако, пользуясь этой стратегией, программист рискует засорить программу фрагментами кода, предназначенными для изменения состояния пунктов меню. Гораздо лучше совсем не беспокоиться о состоянии пунктов ме-
ню в остальной части программы и задавать их параметры непосредственно перед отображением на экране. Для этого нужно зарегистрировать слушатель событий, связанных с выбором пунктов меню. В пакете j avax.swing. event есть такой слушатель, определяющий интерфейс MenuListener, в котором определены три метода.
void menuSelected(MenuEvent evt)
void menuDeselected(MenuEvent evt)
void menuCanceled(MenuEvent evt)
Метод menuSelected вызывается перед отображением меню на экране. Это самый подходящий момент для того, чтобы заблокировать или разблокировать пункты меню. В приведенном ниже фрагменте программы показано, как блокируются и разблокируются пункты "Save" и "Save As", если в соответствующем пункте меню установлен флажок "Read Only".
public void menuSelected(MenuEvent evt) {
saveitem.setEnabled(!readonlyltem.isSelected()) ;
saveAsItem.setEnabled(!readonlyltem.isSelected());
}
Метод menuDeselected вызывается после закрытия меню. Метод menuCanceled вызывается, если процесс выбора пунктов меню был прекращен, например, если пользователь щелкнул мышью где-нибудь за пределами меню. Эти события нас не интересуют, но класса MenuAdapter, который позволил бы заместить отдельный метод, не существует, поэтому два этих метода мы вынуждены будем определить фиктивно.
Блокировка пунктов меню непосредственно перед его отображением на экране— отличная идея, однако она не работает для пунктов меню, связанных с ускоряющими клавишами. Поскольку при нажатии ускоряющей клавиши меню никогда не открывается, пункт меню никогда не будет заблокирован и ускоряющая клавиша будет по-прежнему инициировать его выполнение.
javax.swing.JMenuItem 1.2
void setEnabled(boolean b)
Блокирует и разблокирует пункты меню.
javax.swing.event.MenuListener 1.2
void menuSelected(menuEvent e)
Вызывается, когда меню уже выбрано, но еще не открыто на экране.
void menuDeselected(menuEvent e)
Вызывается, когда меню уже закрыто.
void menuCanceled(menuEvent e)
Вызывается, когда выбор прекращен, например, если пользователь щелкнул мышью за пределами меню.
В листинге 9.12 приведен пример программы, генерирующей набор меню. Продемонстрированы все особенности меню, описанные в этом разделе: вложенные меню, заблокированные пункты, флажки опций и переключатели в пунктах меню, а также назначенные и ускоряющие клавиши.
import java.awt.*;
import
import
import
public class MenuTest
{
public static voidargs)
{
frame = new
;
}
}
/**
Фрейм с линейкой меню.
*/
class MenuFrame extends
{
public
{
DEFAULT_HEIGHT);
JMenu fileMenu = new
JMenuItem=
// Ускоряющие комбинации клавиш.
JMenuItem openltem =
(
();
saveAction = new
JMenuItem saveltem =
saveitem.setAccelerator(Keystroke.getKeyStroke(
saveAsAction = newAs");
JMenuItem=
AbstractAction("Exit")
{
publicvoidevent)
{
.
}
});
55.58.63.74 .95.101.102.103.104 .112.55.58.63.74 .95.101.102.103.104 .112.// Флажки опций и переключатели
55.
58.
63.
74 .
95.
101.
102.
103.
104 .
112.
55.
58.
63.
74 .
95.
101.
102.
103.
104 .
112.
readonlyltem = new JCheckBoxMenuItem("Read-only");
readonlyltem.addActionListener(new
ActionListener(1
{
public void actionPerformed(ActionEvent event)
{
boolean saveOk = !readonlyltem.isSelected(J;
saveAction.setEnabled(saveOk);
saveAsAction.setEnabled(saveOk) ;
}
});
ButtonGroup group = new ButtonGroup();
JRadioButtonMenuItem insertitem
= new JRadioButtonMenuItem("Insert");
insertitem.setSelected(true);
JRadioButtonMenuItem overtypeItem
= new JRadioButtonMenuItem("Overtype");
group.add(insertltem);
group.add(overtypeitem);
// Пиктограммы.
Action cutAction = new TestAction("Cut");
cutAction.putValue(Action.SMALL_ICON,
new ImageIconCcut.gif"));
Action copyAction = new TestActionfCopy");
copyAction.putValue(Action.SMALL_ICON,
new ImagelconCcopy.gif"));
Action pasteAction » new TestAction("Paste") ;
pasteAct ion.putValue(Action.SMALL_ICON, new Imagelcon("paste.gif"));
JMenu editMenu = new JMenu("Edit");
editMenu.add(cutAction);
editMenu.add(copyAction);
editMenu.add(pasteAction);
// Вложенные меню.
JMenu optionMenunew JMenu("Options");
optionMenu.add(readonlyltem);
optionMenu.addSeparator();
optionMenu.add(insertItem);
optionMenu.add(overtypeItern] ;
editMenu.addSeparator(J ;
editMenu.add(optionMenu);
// Мнемонические сокращения.
JMenu helpMenu = new JMenu("Help");
helpMenu.setMnemonic(1H');
из.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162. 163. 164. 165. 166. 167/ 168.
169.
170.
JMenuItem indexitem = new JMenuItem("Index");
indexitem.setMnemonic ( 'I');
helpMenu.add(indexitem);
// Добавляем назначенные клавиши.
Action aboutAction = new TestAction("About");
aboutAct ion.putValue(Action.MNEMONIC_KEY, new Integer('A'));
helpMenu.add(aboutAction);
// Добавляем меню верхнего уровня на панель меню.
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
menuBar.add(fileMenu);
menuBar.add(editMenu);
menuBar.add(helpMenu);
// Всплывающие меню '
popupnew JPopupMenu();
popup.add(cutAction);
popup.add(copyAction);
popup.add(pasteAction);
getContentPane().addMouseListener(new
MouseAdapter(I
{ public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) popup.show(event.getComponent(), tevent.getXO , event . get!());
}
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()] popup.show(event.getComponent(), event.getX(), event.getY());
} });
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private Action saveAction;
private Action saveAsAction;
private JCheckBoxMenuItem readonlyltem;
private JPopupMenu popup;
}
/**
Простое действие, выводящее свое название в поток System.out */
classTestActionextends'AbstractAction
{
public TestAction(String name) { super(name); }
public void actionPerformed(ActionEvent event)
{
System.out.printin(getValue(Action.NAME)
+ " selected.");
}
}
Панель инструментов
Панель инструментов представляет собой набор кнопок, обеспечивающих быстрый доступ к наиболее часто используемым операторам в программе (рис. 9.29).
Рис. 9.29. Панель инструментовРис. 9.29. Панель инструментов
Рис. 9.29. Панель инструментов
Рис. 9.29. Панель инструментов
Что отличает панель инструментов от остальных элементов пользовательского интерфейса? Ее можно перетаскивать на любую из четырех сторон фрейма (рис. 9.30). Затем кнопку мыши можно отпустить, и тогда панель инструментов зафиксируется на новом месте (рис. 9.31).
Перетаскивание панели инструментов допускается, только если она находится внутри контейнера, у которого есть менеджер компоновки рамок или любой другой менеджер, поддерживающий ограничения, задаваемые параметрами North, East,South и West.
Рис. 9.30. Перетаскивание панели инструментовРис. 9.30. Перетаскивание панели инструментов
Рис. 9.30. Перетаскивание панели инструментов
Рис. 9.30. Перетаскивание панели инструментов
Рис. 9.31. Перетаскивание панели инструментов на другую сторонуРис. 9.31. Перетаскивание панели инструментов на другую сторону
Рис. 9.31. Перетаскивание панели инструментов на другую сторону
Рис. 9.31. Перетаскивание панели инструментов на другую сторону
Панель инструментов может быть совершенно независимой от фрейма. Такая панель содержится в своем собственном фрейме (рис. 9.32). Если пользователь закрывает фрейм, содержащий независимую панель инструментов, она перемещается в исходный фрейм.
Рис. 9.32. Независимая панель инструментовРис. 9.32. Независимая панель инструментов
Рис. 9.32. Независимая панель инструментов
Рис. 9.32. Независимая панель инструментов
Панель инструментов легко программировать. Нужно лишь добавить к ней компоненты.
JToolBar bar = new JToolBar() ; bar.add(blueButton] ;
В классе JToolBar также есть методы, предназначенные для добавления объекта класса Action. Нужно лишь заполнить ими панель инструментов.
bar.add(blueAction);
Пиктограмма действия отображается на панели инструментов.
В версии J2SE 1.2 имя действия указывается на панели инструментов, заметно увеличивая размеры кнопки. Следовательно, работая с этой версией пакета, невозможно одновременно использовать объект класса Action и в меню, и на панели инструментов. В версии 1.3 этот недостаток исправлен.
Группы кнопок можно отделить друг от друга с помощью разделителя. bar.addSeparator ();
Например, на панели инструментов, показанной на рис. 9.32, есть разделитель, отделяющий третью кнопку от четвертой.
Затем нужно поместить панель инструментов в контейнер. contentPane.add(bar, BorderLayout.NORTH);
Можно также указать заголовок панели, который появится, когда панель будет опущена на нужное место.
bar = new JToolBar(titlestring);
По умолчанию панель инструментов располагается горизонтально. Чтобы сделать ее вертикальной, нужно выполнить оператор
bar = new JToolBar(SwingCostants.VERTICAL) или
bar = new JToolBar(titlestring, SwingCostants.VERTICAL)
Чаще всего на панель инструментов помещают кнопки. Однако ограничений на вид компонентов, которые можно размещать на панели инструментов, не существует. Например, на нее можно поместить комбинированный список.
г уПрекрасные примеры инструментальных панелей можно найти на Web-странице
http://developer.java.sun.com/developer/techDocs/hi/repository.
Подсказки
Недостатком инструментальных панелей является невразумительность пиктограмм. Чтобы решить эту проблему, были созданы подсказки (tool tips), которые появляются на экране, когда курсор на мгновение застывает над кнопкой. Текст подсказки высвечивается внутри закрашенного прямоугольника (рис. 9.33). Когда пользователь перемещает курсор мыши в другое место, подсказка исчезает.
Рис. 9.33. Подсказка
В пакете Swing подсказки можно добавлять к любому объекту класса JComponent, просто вызывая метод setToolTipText.
exitButton.setTextTiptext("Exit") ;
Кроме того, можно использовать объекты класса Action, связывая подсказку с ключом SHORT_DESCRIPTION.
exitAction.putValue(Action.SHORT_DESCRIPTION), "Exit");
В листинге 9.13 приведена программа, показывающая, как одни и те же объекты класса Action можно добавлять в меню и на панель инструментов. Обратите внимание на то, что имена действий появляются на экране и в виде названий пунктов меню, и в виде подсказок к элементам панели инструментов.
{23.32.33.34.37.42.43.44.52.53.54.{23.32.33.34.37.42.43.44.52.53.54.import java.awt.;
{
23.
32.
33.
34.
37.
42.
43.
44.
52.
53.
54.
{
23.
32.
33.
34.
37.
42.
43.
44.
52.
53.
54.
import java.awt.event. *;
import java.beans.*;
import javax.swing.*;
public class ToolBarTest
{
public static void main(Stringargs)
1
ToolBarFrame frame = new ToolBarFrame();
frame.setDefaultCloseOperation(JFrame,EXIT_ON_CLOSE);
frame.show();
}
}
/**
Фрейм с панелью инструментов и меню для изменения цвета. 18. */
19. class ToolBarFrame extends JFrame
public ToolBarFrame(J {
setTitleC'ToolBarTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Добавляем панель с изменяемым цветом.
Container contentPane = getContentPane();
panel = new JPanel();
contentPane.add(panel, BorderLayout.CENTER); // Задаем действие.
Action blueAction = new ColorAction("Blue”, new Imageicon("blue-ball.gif"), Color.blue);
Action yellowAction = new ColorAction("Yellow", new Imagelcon("yellow-ball.gif"), Color.yellow) ;
Action redAction = new ColorAction("Red”, new Imageicon("red-ball.gif"), Color.red);
Action exitAction = new
AbstractAction("Exit", new Imageicon("exit.gif")) I
public void actionPerformed(ActionEvent event)
System.exit(0) ; } };
exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");
// Пополняем панель инструментов.
JToolBar bar = new JToolBar();
bar.add(blueAction);
bar.add(yellowAction);
bar.add(redAction);
bar.addSeparator();
bar.add(exitAction);
58.59.60.61.62.63.64.65.66.67.68.69.77'.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.58.59.60.61.62.63.64.65.66.67.68.69.77'.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.contentPane.add(bar, BorderLayout.NORTH);
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
77'.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
77'.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
// Пополняем меню.
JMenu menu = new JMenu("Color");
menu.add(yellowAction);
menu.add(blueAction);
menu.add(redAction);
menu.add(exitAction);
JMenuBar menuBar = new JMenuBar();
menuBar.add(menu);
setJMenuBar(menuBar);
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private JPanel panel;
/
Действие, устанавливающее заданный цвет фона.
V
class ColorAction extends AbstractAction (
public ColorAction(String name, Icon icon, Color c) (
putValue(Action.NAME, name);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION, name + " background");
putValue("Color",c);
)
public void actionPerformed(ActionEvent evt) (
Color с = (Color)getValue("Color");
pane1.setBackground(c);
javax.swing.JToolBarJToolBar()javax.swing.JToolBarJToolBar()1.2
javax.swing.JToolBar
JToolBar()
javax.swing.JToolBar
JToolBar()
void addSeparator()
Добавляет разделитель в конец панели инструментов.
javax.swing.JComponent 1.2
void setToolTipText(String text)
Задает текст, подлежащий выводу на экран в качестве подсказки, когда курсор мыши зависает над компонентом.
Изощренное управление компоновкой
До сих пор при разработке интерфейса для наших приложений мы пользовались лишь рамочной или потоковой компоновкой. Для решения более сложных задач этого недостаточно. В этом разделе описываются все менеджеры компоновки, предусмотренные стандартной библиотекой языкаJava.
У программистов, работающих на платформе Windows, может возникнуть вопрос: отчего в языке Java столько суеты вокруг менеджеров компоновки. В конце концов, в системе Windows это не проблема. Во-первых, для того, чтобы перетащить компоненты на поверхность диалогового окна, используется редактор диалога, а затем для их выравнивания применяются средства редактирования. При разработке большого проекта программист может вообще не думать о компоновке — продуманный генератор пользовательского интерфейса все заботы возьмет на себя.
Проблема заключается в том, что полученная в результате компоновка элементов пользовательского интерфейса при изменении их размеров должна обновляться вручную. Отчего компоненты могут изменить свои размеры? Это происходит в двух случаях. Во-первых, пользователь может выбрать более крупный шрифт для кнопочных меток и других текстовых элементов диалога. Если вы попробуете это сделать, работая под управлением операционной системы Windows, то убедитесь сами, что многие приложения крайне неудачно справляются с этой задачей. Кнопки не увеличиваются, и более крупный шрифт просто втискивается в те же самые размеры. Такая же проблема может возникнуть при переводе строк, содержащихся в приложении, на иностранный язык. Например, слово "Cancel" на немецкий язык переводится как "Abbrechen". Если кнопка разработана так, что в ней помещается слово "Cancel", то немецкий перевод на той же кнопке будет выглядеть усеченным, а соответствующая командная строка — обрезанной.
Почему кнопки, созданные на платформе Wndows, не увеличиваются, чтобы поместить метки на своей поверхности? Это происходит потому, что разработчики интерфейса не предусмотрели никаких команд, задающих направление, в котором должны возрастать размеры кнопок. После того как элементы интерфейса будут перемещены в окно и выровнены, редактор диалога просто забывает координаты и размеры всех компонентов. Таким образом, информация, позволяющая судить о том, почему компоненты были расположены именно таким образом, теряется.
Менеджеры компоновки в языке Java справляются с этой задачей намного лучше. Каждая компоновка сопровождается инструкциями, описывающими отношения между компонентами. Это особенно важно при работе с библиотекой AWT, использующей платформнозависимые элементы пользовательского интерфейса. Размер кнопки или списка в программе на платформе Motif, Windows или Macintosh существенно
варьируется, и разработчики приложения или аплета не могут заранее знать, на какой платформе будет реализовываться их пользовательский интерфейс. Если стиль интерфейса, например Metal, заранее задан, то внешний вид приложения будет абсолютно одинаков на всех платформах. Однако, если программист хочет дать пользователю возможность самому задавать стиль интерфейса, придется положиться на гибкость менеджеров компоновки.
Разумеется, для создания сложных интерфейсов нужны более сильные средства управления по сравнению с возможностями менеджеров рамочной или потоковой компоновки. В этом разделе мы обсудим средства компоновки, которые предусмотрены в стандартной библиотеке языка Java. Использование сложных компоновок совместно с несколькими панелями позволяет программисту полностью управлять внешним видом приложения.
Если вы не нашли такой компоновки, которая полностью удовлетворила бы все ваши потребности, разбейте окно на несколько панелей и скомпонуйте их по отдельности. Затем для организации этих панелей примените разные компоновки.
Во-первых, рассмотрим несколько основных принципов. Как известно, в библиотеке AWT компоненты располагаются внутри контейнеров. Кнопки, текстовые поля и другие элементы пользовательского интерфейса являются компонентами и могут быть помещены внутрь контейнера. Следовательно, эти классы расширяют класс Component. Контейнеры, например, панели, сами могут располагаться внутри другого контейнера. Следовательно, класс Container является производным от класса Component. На рис. 9.34 показана иерархия наследования для класса Component.
Обратите внимание на то, что некоторые объекты принадлежат классам, расширяющим класс Component, даже если они не являются элементами пользовательского интерфейса и не могут быть вставлены внутрь контейнеров. Окна верхнего уровня, такие как JFrame и JApplet, не могут находиться внутри другого окна или панели.
Как мы уже видели, чтобы поместить компоненты в контейнер, сначала нужно указать менеджер компоновки. Например, следующий оператор для компоновки панелей использует класс GridLayout.
panel.setLayout(new GridLayout(4, 4));
Итак, менеджер компоновки задан. Теперь нужно добавить компоненты в контейнер. Метод add передает компонент и его координатыменеджеру компоновки.
Менеджер компоновки получает строку, в которой заданы координаты компонента. panel.add(new JTextField(), BorderLayout.SOUTH);
При работе с менеджером сетки, который мы вскоре обсудим, компоненты нужно передавать последовательно.
panel.add(new JCheckBox("italic"));
panel.add(new JCheckBox("bold"));
Рис. 9.34. Иерархия наследования для класса Component
Менеджер сеточной компоновки — полезное средство для расстановки компонентов в узлах сетки, которые иногда выглядят как ячейки электронной таблицы. Однако все строки и столбцы сетки имеют одинаковый размер, что на практике не всегда удобно.
Для того чтобы преодолеть это ограничение, накладываемое менеджером сетки, в библиотеке AWT предусмотрен менеджер компоновки сеточного мультимножества (grid bag layout). Он также позволяет расставлять компоненты по строкам и столбцам, но при этом компоненты могут занимать несколько строк и столбцов. Этот менеджер компоновки очень гибок и сложен. Одно лишь название "компоновка сеточного мультимножества" уже вселяет ужас в сердца программистов на языке Java. На самом деле в большинстве случаев использовать компоновку сеточного мультимножества довольно легко, и мы покажем, как это можно сделать относительно безболезненно.
Предпринимая безуспешную попытку создать менеджер компоновки, не прибегая к тирании сеточных мультимножеств, разработчики библиотеки Swing пришли к понятию блочной компоновки (box layout), которая просто расставляет последовательность компонентов в горизонтальном или вертикальном направлении. Горизонтальная расстановка компонентов напоминает потоковую компоновку; однако компоненты не переносятся в новую строку, если предыдущая оказалась заполненной. Размещая несколько горизонтальных блоков внутри вертикального блока (и в других аналогичных ситуациях), программист может задать определенный порядок размещения компонентов в двухмерной области. Однако, поскольку каждый блок компонуется независимо от других, блочную компоновку нельзя применять для одновременного размещения соседних компонентов в горизонтальном и вертикальном направлениях.
В библиотеке Swing также содержится компоновка перекрытий (overlay layout), позволяющая размещать компоненты один поверх другого. Обычно эта компоновка не приносит много пользы, и мы ее обсуждать не будем.
В заключение упомянем компоновку карт (card layout), которая применялась в библиотеке AWT для создания диалоговых окон с закладками. Поскольку в библиотеке Swing для этой цели разработан гораздо более удачный контейнер (под названием JTabbedPane — см. том 2), такой вид компоновки мы также не рассматриваем.
Мы закончим наше обсуждение, показав, как можно обойтись без менеджеров компоновки вообще и разместить компоненты вручную, а также создать свой собственный менеджер компоновки.
Блочная компоновка
Блочная компоновка позволяет создать более гибкую строку, состоящую из компонентов. Существует даже контейнер — класс Box, имеющий по умолчанию свой собственный менеджер компоновки BoxLayout (в отличие от класса JPanel, использующего менеджер потоковой компоновки FlowLayout). Разумеется, для класса JPanel можно назначить менеджер блочной компоновки, однако проще начать с контейнера Box. Класс Box содержит также большое количество полезных статических методов.
Чтобы создать новый контейнер, нужно просто выполнить один из двух следующих вызовов.
Box b = Box.createHorizontalBox();
Box b = Box.createVerticalBox(; ;
Затем компоненты добавляются как обычно.
b.add(okButton); b.add(cancelButton) ;
В горизонтальном блоке компоненты располагаются слева направо, а в вертикальном — сверху вниз. Рассмотрим подробнее горизонтальную компоновку.
Каждый компонент имеет три размера.
Желательный размер (preferred size) — ширина и высота.
Максимальный размер (maximum size) — наибольшие ширина и высота.
Минимальный размер (minimum size) — наименьшие ширина и высота.
Вот как работает менеджер блочной компоновки.
Вычисляется максимальная (!) высота самого высокого компонента.
Делается попытка увеличить высоту всех компонентов до максимальной высоты самого высокого компонента.
Если какой-либо компонент не увеличился до требуемой высоты, то с помощью метода getAlignmentYзапрашивается его вертикальная координата. Эта величина представляет собой число с плавающей точкой в диапазоне от О (выравнивание по верхней границе) до 1 (выравнивание по нижней границе). По умолчанию в классе Component это число равно 0.5 (центр). Это значение используется для вертикального выравнивания компонента.
Определяется желательная ширина каждого компонента. Все эти величины складываются, образовывая общую желательную ширину.
Если общая желательная ширина не превышает ширину блока, то компоненты увеличиваются в размерах до своих максимально возможных значений ширины. Затем компоненты размещаются слева направо, причем расстояние между ними не увеличивается. Если общая желательная ширина превышает ширину блока, компоненты уменьшаются в размерах, потенциально до минимально возможной ширины, но не более. Если не все компоненты умещаются в своих минимальных размерах, то некоторые из них не будут показаны на экране.
При вертикальной компоновке процесс совершенно аналогичен.
К сожалению, класс BoxLayout пытается увеличить компоненты сверх желательных размеров. В частности, максимальные длина и высота текстовых полей ограничиваются константой Integer. MAX_VALUE. Таким образом, они могут увеличиваться сколько угодно. Если поместить текстовое поле в блок, он может увеличиться до гигантских размеров. Мораль: устанавливайте максимальный размер равным желательному.
textField.setMaximumSize(textField.getPreferredsize());
Заполнители
По умолчанию компоненты в блоке располагаются вплотную друг к другу. (В отличие от потоковой, в блочной компоновке нет понятия "расстояние между компонентами".) Для того чтобы раздвинуть компоненты, добавляются невидимые заполнители (fillers). Есть три вида заполнителей:
распорки (struts);
фиксированные области (rigid areas);
склейки (glue).
Распорка просто добавляет определенное пространство между компонентами. Например, вот как можно добавить десять пикселей между двумя компонентами в горизонтальном блоке.
b.add(label);
b.add(Box.createHorizontalStrut(10));
b.add(textField)
Для того чтобы увеличить расстояние между компонентами в горизонтальных или вертикальных блоках, нужно вставлять распорки. В горизонтальный блок можно вставить вертикальную распорку, но на горизонтальную компоновку это не повлияет, а приведет лишь к установке минимальной высоты блока.
Фиксированная область похожа на пару распорок. Она также раздвигает смежные компоненты, но, кроме того, добавляет минимальное значение высоты или ширины в другом направлении. Например, следующий оператор добавляет невидимую фиксированную область с заданными минимальной, желательной и максимальной шириной, равными 5 пикселям, и высотой, равной 20 пикселям, а также задает выравнивание по центру. b.add(Box.createRigidArea(new Dimension(5, 20));
Если добавить эту область в горизонтальный блок, она сработает как распорка, имеющая ширину 5 пикселей, а также задаст минимальную высоту блока равной 20 пикселям.
Добавляя распорки, вы раздвигаете смежные компоненты на фиксированное расстояние. Добавление склеек приводит к тому, что компоненты раздвигаются на максимально возможное расстояние. Невидимая склейка расширяет блоки, отодвигая компоненты друг от друга, чтобы они заполнили все доступное пространство. (Совершенно непонятно, почему разработчики назвали такой заполнитель "склейкой" — более подходящим названием было бы слово "пружина".)
Вот как, например, можно заполнить блок двумя кнопками с помощью склейки. b.add(buttonl);
b.add(Box.createGlue());.
b.add(button2);
Если блок не содержит других компонентов, кнопка buttonl все время будет перемещаться влево, а кнопка button2 — вправо.
Программа, приведенная в листинге 9.14, размещает метки, текстовые поля и кнопки, используя вертикальную и горизонтальную блочные компоновки. Каждый ряд компонентов помещается в горизонтальный блок. Метки текстовых полей разделяются распорками. Склейки отталкивают две кнопки друг от друга. Три горизонтальных блока помещаются в вертикальный блок, в котором склейка отталкивает кнопку от нижней границы блока (рис. 9.35).
Рис. 9.35. Блочные компоновкиРис. 9.35. Блочные компоновки
Рис. 9.35. Блочные компоновки
Рис. 9.35. Блочные компоновки
{{
{
{
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BoxLayoutTest
public static void main(String[] args)
(' •
BoxLayoutFrame frame = new BoxLayoutFrame();
frame. setDefaultCloseOperation (JFrame. EXIT_ON_CLOSE]; frame.show();
}
/**.
Фрейм, использующий блочную компоновку для организации
разных компонентов
32.33.34.42.43.44.52.53.57.32.33.34.42.43.44.52.53.57.*/
32.
33.
34.
42.
43.
44.
52.
53.
57.
32.
33.
34.
42.
43.
44.
52.
53.
57.
class BoxLayoutFrame extends JFrame
{
public BoxLayoutFrame()
{
setTitle("BoxLayoutTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Создаем верхний горизонтальный блок.
JLabel labell = new JLabel("Name:");
JTextField textFieldl = new JTextField(lO);
textFieldl.setMaximumSize(textFieldl.getPreferredSize(; )
Box hboxl =
hboxl.add(labell);
// Вставляем распорку из 10 писелей.
hboxl.add(Box.createHorizontalStrut(10));
hboxl.add(textFieldl);
// Создаем средний горизонтальный блок.
JLabel label2 = new JLabel("Password:");
JTextField textField2 = new JTextField(lO);
textField2.setMaximumSize(textField2.getPreferredSize() I;
Box hbox2 = Box.createHorizontalBox0;
hbox2.add(label2);
// Вставляем распорку из 10 писелей.
hbox2.add(Box.createHorizontalStrut(10));
hbox2.add(textField2);
// Создаем нижний горизонтальный блок.
JButton buttonl = new JButton("Ok");
JButton button2 = new JButton("Cancel");
Box hbox3 = Box.createHorizontalBox0;
hbox3.add(buttonl);
// Для раздвигания кнопок используем "склейку".
hbox3.add(Box.createGlue());
hbox3.add(button2);
// Добавляем три горизонтальных блока
//в один вертикальный.
Box vbox = Box.createVerticalBox();
vbox.add(hboxl);
vbox.add(hbox2);
vbox.add(Box.createGlue());
vbox.add(hbox3);
Container contentPane = getContentPane(] ;
contentPane.add(vbox, BorderLayout.CENTER);
}
public static final int DEFAULT_WIDTH = 200;
public static final int DEFAULT_HEIGHT = 200;
}
javax.swing.box 1.2
stati c Box createHorizontalBox(I
stati c Box createVerticalBox()
Создает контейнер, содержимое которого упорядочено в горизонтальном
или вертикальном направлении.
static Component createHorizontalGlue()
static Component createVerticalGlue(]
static Component createGlue(;
Создает невидимый компонент, который может неограниченно расширяться в горизонтальном, вертикальном или обоих направлениях.
static Component createHorizontalStrut(int width) stati c Component createVerticalStrut(int height) static Component createRigidArea(Dimension d)
Создает невидимый компонент, ширина и высота которого зафиксированы либо по отдельности, либо одновременно.
java.awt.Component 1.0
float getAlignmentX(]1.1
float getAlignmentY()1.1
Возвращает координаты компонента no оси х или у в виде числа от 0 до 1.
Значение 0 означает выравнивание по верхней границе, а 1 — по нижней.
Компоновка сеточного мультимножества
Эта компоновка— прародитель всех менеджеров компоновки. Ее можно рассматривать как сеточную компоновку без ограничений. В компоновке сеточного мультимножества строки и столбцы могут иметь переменный размер. Для того чтобы разместить более крупный компонент, который не помещается в одну ячейку, несколько смежных ячеек можно объединить в одну. (Многие текстовые редакторы, например HTML, предоставляют такие же возможности при редактировании таблиц: заполнение начинается с обычной сетки, а при необходимости некоторые ее ячейки сливаются в одну.) Компоненты не обязаны заполнять всю ячейку, поэтому можно задать выравнивание внутри ячеек.
Учтите: использование компоновок сеточного мультимножества может оказаться невероятно трудным. Зато они обладают наибольшей гибкостью и работают в самом широком спектре ситуаций. Следует также учесть, что цель менеджеров компоновки — сохранить разумное упорядочение компонентов, независимо от размеров шрифтов и операционных систем, чтобы компоновку можно было выполнить раз и навсегда.
В документации по пакету SDK, касающейся класса BoxLayout, говорится: "Вложение нескольких панелей с различными комбинациями горизонтальной и вертикальной (!) компоновок приводит к тому же эффекту, что и класс GridLayout, но намного быстрее". Однако, как показано на рис. 9.33, этот эффект вряд ли полезен на практике, поскольку для того, чтобы выровнять компоненты, придется много возиться с блоками, распорками и склейками. Для этой цели следует применять класс GridBagLayout.
Рассмотрим диалоговое окно, позволяющее выбрать шрифт, разделенное на ячейки сетки (рис. 9.36). Оно состоит из следующих компонентов.
Два комбинированных списка для определения начертания и размера шрифта.
Метки для указанных комбинированных списков.
Два флажка для выбора полужирного начертания и курсива.
Текстовая область для отображения примерной строки.
Рис. 9.36. Диалоговое окно для выбора шрифта
Рис. 9.37. Сетка диалогового окна, используемая при разработке
Теперь разделим диалоговое окно на ячейки сетки так, как показано на рис. 9.37. (Строки и столбцы не обязаны иметь одинаковый размер.) Как видим, каждый фЛажок занимает два столбца, а текстовое поле — четыре строки.
Чтобы описать эту схему менеджеру компоновки сеточного мультимножества, нужно выполнить следующую замысловатую процедуру.
Создать объект, имеющий тип GridBagLayout. Ему не следует знать, сколько строк и столбцов имеет сетка. Позднее он попытается угадать эти параметры по информации, которую получит от вас.
Установить объект класса GridLayout в качестве менеджера компоновки.
Создать объект, имеющий тип GridBagConstraints. Этот объект будет задавать расположение компонентов в сеточном мультимножестве.
В каждый компонент поместить объект GridBagConstraints. В заключение добавить компонент с ограничениями с помощью вызова
add(Component, constraints);
Ниже приведен соответствующий фрагмент кода. (Многие ограничения будут детально рассмотрены в дальнейшем — пока не следует беспокоиться о том, что они собой представляют.)
GridBagLayout layout = new GridBagLayout();
panel.setLayout(layout);
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 100;
constraints.weighty = 100;
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridweight = 2;
constraints.gridheight = 1;
contentPane.add(style, bold);
Очевидно, что для такого рода повторяющихся фрагментов лучше всего создавать вспомогательные функции (см. листинг 9.15).
Суть этого способа заключается в том, как задать состояние объекта класса GridBagConstrains. Наиболее важные ограничения, накладываемые на использование этого объекта, рассматриваются в следующем подразделе.
Параметры gridx, gridy, gridwidth и gridheight
Эти ограничения определяют место компонента в сетке. Значения gridx и gridy задают столбец и строку, в которых расположен левый верхний угол добавляемого компонента. Значения gridwidth и gridhwight определяют, сколько строк и столбцов занимает данный компонент.
Координаты сетки отсчитываются от нуля. В частности, выражения gridx=0 и gridy=0 означают левый верхний угол.
Например, текстовая область в нашем примере имеет параметры gridx=2 и gridy=01 поскольку она начинается со столбца, имеющего номер 2 (т.е. третьего). Поскольку текстовая область занимает один столбец и четыре строки, gridwidth=l, a gridheight=4.
Весовые поля
Для каждой области в компоновке сеточного мультимножества необходимо задать весовые поля (weight field). В нашем примере им соответствуют поля weightx и weighty. Если вес равен нулю, то область всегда сохраняет свои первоначальные размеры. В компоновке сеточного мультимножества, приведенной на рис. 9.36, поле weightx задано равным 0. Это позволяет сохранять постоянную ширину метки при изменении размера окна. С другой стороны, если положить вес всех областей равным 0, то контейнер сгрудит их все к центру, а не будет растягивать, заполняя все предоставленное пространство.
В принципе, проблема, связанная с весовыми параметрами, заключается в том, что они характеризуют строки и столбцы, а не отдельные ячейки. Однако при этом их нужно задавать в терминах ячеек, поскольку компоновка сеточного мультимножества не различает отдельные строки и столбцы. Вес строк и столбцов вычисляется в качестве весовой характеристики ячеек в каждой строке или столбце. Таким образом, если нужно, чтобы строки и столбцы сохраняли фиксированный размер, необходимо задать вес всех компонентов в этих строках и столбцах равным нулю.
Обратите внимание на то, что вес на самом деле не позволяет определить относительные размеры столбцов. Он лишь указывает, какая часть "свободного" пространства должна быть выделена каждой области, если контейнер превышает желательный размер. Эта процедура не относится к интуитивно ясным. Мы рекомендуем сделать вес равным 100, затем запустить программу на выполнение и посмотреть, как будет выглядеть интерфейс. Для того чтобы определить, как выровнены строки и столбцы, попробуйте изменить размеры диалогового окна. Если окажется, что какая-то строка или столбец не должны увеличиваться, установите вес всех компонентов, находящихся в них, равным нулю. Можно поэкспериментировать и с остальными весовыми параметрами, однако, как правило, это не имеет смысла.
Параметры fill и anchor
Если вы не хотите, чтобы компонент растягивался и заполнял все пространство, нужно установить ограничение fill. Этот параметр принимает четыре возможных значения: GridBagConstraints.NONE, GridBagConstraints.HORIZONTAL, Grid- BagConstraints.VERTICAL и GridBagConstraints.BOTH.
Если компонент не заполняет собой все пространство, можно указать, к какому месту области вы хотели бы его привязать, задав параметр anchor (якорь). Этот параметр может принимать следующие значения: GridBagConstraints .CENTER (по умолчанию), GridBagConstraints.NORTH, GridBagConstraints. NORTHEAST, GridBagConstraints . EAST и т.д.
Набивка
Задав значение поля insets в классе GridBagLayout, компонент можно окружить дополнительным пустым пространством. Для этого нужно задать значения полей left, top, right и bottom в объекте класса Insets. Этот процесс называется внешней набивкой (externalpadding).
Значения полей ipadx и ipady определяют внутреннюю набивку (internal padding). Эти значения добавляются к минимальным ширине и высоте компонента, гарантируя тем самым, что компонент никогда не уменьшится до своих минимальных размеров.
Как иначе задать параметры gridx, gridy, gridwidth и gridheight
В документации по библиотеке AWT рекомендуется не задавать абсолютные значения параметров gridx и gridy, а вместо этого использовать константу GridBagConstraints .RELATIVE. Затем нужно добавить компоненты в сеточное мультимножество, как обычно, слева направо строка за строкой.
Количество занятых строк и столбцов нужно задавать с помощью полей gridheight и gridweight. Исключение составляет компонент, занимающий последнюю строку или столбец. Для него фактический параметр не задается, а вместо него используется константа GridBagConstraints REMAINDER Это позволяет менеджеру компоновки распознавать последние компоненты.
Похоже, что эта схема вполне работоспособна. Однако кажется довольно глупым сначала скрывать от менеджера компоновки информацию о фактическом расположении компонентов, а затем полагаться на то, что он сам угадает правильные параметры.
Все это выглядит довольно сложно и вызывает недоверие. Однако на практике описанный ниже способ компоновки сеточного мультимножества вполне надежен.
Способ компоновки сеточного мультимножества
Шаг 1. Изобразите схему расположения компонентов на бумаге.
Шаг 2. Определите такую сетку, чтобы каждый компонент помещался в ячейке, а большие компоненты могли занимать несколько ячеек.
Шаг 3. Пометьте строки и столбцы вашей сетки числами 0, 1, 2, 3, ... Теперь можно считывать значения параметров gridx,gridy,gridwidth и gridheight.
Шаг 4.Для каждого компонента определите, нужно ли, чтобы он заполнял ячейку в горизонтальном или вертикальном направлении? Если нет, как его выровнять внутри ячейки? На этот вопрос ответят параметры fillи anchor.
Шаг 5. Установите вес равным 100. Однако, если вы хотите, чтобы конкретная строка или столбец всегда сохраняли свой первоначальный размер, установите параметры weightx или weighty всех компонентов, находящихся в этой строке или столбце, равными 0.
Шаг 6. Напишите программу. Тщательно проверьте установки класса GridBagLayout. Одно неверное ограничение может разрушить всю компоновку.
Шаг 7. Скомпилируйте программу, запустите ее на выполнение и расслабьтесь.
Полный текст программы, реализующей диалоговое окно для выбора шрифта, приведен в листинге 9.15.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class FontDialog
{
public static void main(String[j args)
{
FontDialogFrame frame T— new FontDialogFrame();
f rame.s et De fa u11 Clos eOperat i on(JFrame.EXIT_ON_CLOS E)1
frame.show();
1
)
/**
Фрейм, использующий компоновку сеточного мультимножества
при расположении компонентов для выбора шрифта.
*/
class FontDialogFrame extends JFrame
2' ( •
28.37.42.43.44.45.46.47.52.53.54.55.56.57.58.62.67.74.28.37.42.43.44.45.46.47.52.53.54.55.56.57.58.62.67.74.public FontDialogFrame()
28.
37.
42.
43.
44.
45.
46.
47.
52.
53.
54.
55.
56.
57.
58.
62.
67.
74.
28.
37.
42.
43.
44.
45.
46.
47.
52.
53.
54.
55.
56.
57.
58.
62.
67.
74.
{
setTitle("FontDialog");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT) ;
Container contentPane = getContentPane();
GridBagLayout layout = new GridBagLayout(); contentPane.setLayout(layout);
ActionListener listener = new FontAction() ;
// Создаем компоненты.
JLabel faceLabel = new JLabel(”Face: ");
face = new JComboBox(new String[] {
"Serif", "SansSerif", "Monospaced", "Dialog", "Dialoginput"
});
face.addActionListener(listener);
JLabel sizeLabel = new JLabel("Size: ");
size = new JComboBox(new String[] {
"8", "10", "12", "15", "18", "24", "36", "48" });
size.addActionListener(listener);
bold = new JCheckBox("Bold"); bold.addActionListener(listener);
italic = new JCheckBoxf Italic"); italic.addActionListener(listener);
sample = new JTextArea();
sample.setText(
"The quick brown fox jumps over the lazy dog");
sample.setEditable(false);
sample.setLineWrap(true);
sample.setBorder(BorderFactory.createEtchedBorder() ]'
// Добавляем компоненты в сетку.
GridBagConstraints constraints = new GridBagConstraints ();
constraints.fill = constraints.anchor = constraints.weightx = 0; constraints.weighty = 0;
add(faceLabel, constraints, 0, 0, 1, 1) ;
add(sizeLabel, constraints, 0, 1, 1, 1);
constraints.fill = constraints.weightx = 100;
add(face, constraints, 1, 0, 1, 1);
add(size, constraints, 1, 1, 1, 1);
constraints.weighty =100;
constraints.fill -
constraints.anchor =
add(bold, constraints, 0, 2, 2, 1);
add(italic, constraints, 0, 3, 2, 1);
constraints.fill =
add(sample, constraints, 2, 0, 1, 4);
}
/**
Метод для добавления компонента в заданную ячейку.
Sparam с добавляемый компонент
@param constraints ограничения сетки
Sparamxх-координата
Sparamуу-координата
. Sparamwширина сетки
@paramhвысота сетки
*/
public void add(Component с,
GridBagConstraints constraints,
int x, int y, int w, int h)
{
constraints.gridx = x;
constraints.gridy = y;
constraints.gridwidth = w;
constraints.gridheight=h;
getContentPane().add(c, constraints);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private JComboBox face;
private JComboBox size;
private JCheckBox bold;
private JCheckBox italic;
private JTextArea sample;
/**
Слушатель действий, изменяющий шрифт в образце текста.
*/
private class FontAction implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
String fontFace = (String)face.getSelectedltem();
int fontstyle = (bold.isSelectedO ? Font.BOLD : 0)
+ (italic.isSelectedt) ? Font.ITALIC : 0);
int fontsize =
(String)size.getSelectedltem());
Font font = new Font(fontFace, fontStyle, fontsize);
sample.setFont(font) ;
sample.repaint));
}
}
}
AP I Ijava.awt.GridConstrainta 1.0
int gridx, gridy
Задают столбец и строку первой ячейки.
int gridwidth, gridheight
Задают количество столбцов и строк, занимаемых ячейкой.
double weightx, weighty
Задают способность ячейки увеличиваться в размерах.
int anchor
Задает вид выравнивания компонента внутри ячейки. Равен одной из констант CENTER, NORTH, NORTHEAST, EATS, SOUTHEAST, SOUTH, SOUTHWEST, WEST или NORTHWEST.
int fill
Задает вид заполнения компонентом своей ячейки. Принимает одно из следующих значений: NONE, BOTH, HORIZONTALиVERRTICAL.
int ipadx, ipady
Задает вид "внутренней набивки" вокруг компонента.
Insets insets
Задает вид "внешней набивки" вокруг границ ячейки.
GridBagConstraints(int gridx, int gridy, int grigwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady)
Создает объект класса GridBagConstraints, заполняя все его поля указанными аргументами. Компания Sun рекомендует использовать этот конструктор только в программах автоматической генерации интерфейса, поскольку полученный в результате код очень трудно читать.
Эластичная компоновка
С момента появления класса GridBagLayout программисты умоляли разработчиков языка Java создать такой же гибкий, но более естественный менеджер компоновки. В конце концов в пакете SDK 1.4 появилась подходящая кандидатура— класс SpringLayout. В этом разделе мы попробуем определить, соответствует ли он нашим ожиданиям.
При использовании эластичной компоновки с каждым компонентом связываются пружины (springs). Пружина— это средство определения положения компонента на экране. Как показано на рис. 9.38, каждая пружина имеет следующие параметры.
Минимальное значение.
Рекомендуемое значение.
Минимальное значение.
Фактическое значение.
минимумрекомендуемая величинамаксимум
{YXYYXYWnm
фактическая величина
Рис. 9.38. Пружина
В классе пружин определена операция суммирования, получающая две пружины в качестве своих параметров и создающая новую пружину, объединяющую их свойства. При компоновке большого количества компонентов в одной строке к ней присоединяются несколько пружин так, чтобы их сумма заполняла весь контейнер (рис. 9.39). Суммарная пружина сжимается или растягивается так, чтобы ее длина была равна размеру контейнера. Будучи примененной к отдельной пружине, эта операция деформирует ее. Параметры каждой пружины задаются таким образом, чтобы величина деформации каждой пружины была равна величине деформации суммы. Итак, параметры каждой отдельной пружины строго определены, и компоновка зафиксирована. (Если вас заинтересовали детали вычислений, выполняемых над пружинами, обратитесь к документации API, которая сопровождает класс Spring.)
Компс нентыконтейнер
9.39. Суммирование пружин
Запустим нашу демонстрационную программу. Допустим, нам нужно разместить горизонтально три кнопки.
JButton Ы = new
JButton Ь2 = new
JButton b3 = new
Сначала установим класс SpringLayout вменеджера компоновки для
панели содержания и добавим компоненты.
Container contentPane =
SpringLayout layout = new contentPane.setLayout(layout);
; ; ;
Теперь создадим пружину с достаточным коэффициентом сжимаемости. Статический метод Spring . constant создает пружину с заданными минимальной, рекомен-
дуемой и максимальной величиной. (Фактически пружина не может быть постоянной — она может сжиматься и растягиваться.)
Spring s = Spring.constant(0, 10000, 10000);
Затем вставим одну из пружин между левой стороной контейнера и левой стороной кнопки Ы.
layout.putConstant(Springlayout.WEST, Ы, s, SpringLayout.WEST, contentPane);
Метод putConstraint растягивает указанную пружину от первого параметра (левой стороны кнопки Ы) до второго параметра (левой стороны контейнера).
Затем между компонентами вставляются остальные пружины.
layout.putConstraint(SpringLayout.WEST, b2, s,
SpringLayout .EAST,bib-
layout .putConstraint (SpringLayout .WEST, b3, s,
SpringLayout.EAST, b2);
В заключение вставляется пружина, соединяющая кнопку b3 с правой стороной контейнера.
layout.putConstraint(SpringLayout.EAST, b3, s, SpringLayout.EAST, bl);
В результате на панели окажутся четыре пружины, сжатые до одинаковых размеров, а между кнопками будет установлено одинаковое расстояние (рис. 9.40).
Рис. 9.40. Равномерно расположенные кнопки
Расстояние между кнопками может варьироваться. Допустим, что между кнопками должно быть установлено заранее заданное расстояние. Для этого можно использовать распорку (strut) — пружину, которая не способна сжиматься и растягиваться. Такую пружину можно создать с помощью одного из вариантов метода Spring.constant, имеющего один параметр.
Spring strut = Spring.constant(10);
Если между кнопками вставить две распорки, а между сторонами контейнера и кнопками — две пружины, получится группа кнопок, расположенных симметрично относительно центра контейнера (рис. 9.41).
Разумеется, для выполнения такой простой задачи можно было бы обойтись и без пружин. Рассмотрим более сложный случай, а именно: часть диалогового окна для выбора шрифта. В окне расположены два комбинированных списка с метками. Мы хотели бы, чтобы левые стороны обоих списков начинались после более длинной метки (рис.9.42).
Рис. 9.41. Пружины и распоркиРис. 9.41. Пружины и распоркиРис. 9.42. Вертикальное выравниваниеРис. 9.42. Вертикальное выравнивание
Рис. 9.41. Пружины и распорки
Рис. 9.41. Пружины и распорки
Рис. 9.42. Вертикальное выравнивание
Рис. 9.42. Вертикальное выравнивание
Для этого нужна другая операция над пружинами. С помощью метода Spring .max можно создать максимальную из двух пружин. Результатом этой операции является пружина, которая имеет наибольшую длину.
Определим максимальную из двух правых сторон меток.
Spring labelsEast = Spring.max I
layput.getConstraint(SpringLayout.EAST, faceLabel),
layout.getConstraint(SpringLayout.EAST, sizeLabel));
Обратите внимание на то, что метод getConstraint создает пружину, вставленную между левой стороной контейнера и заданной стороной компонента.
Добавим распорку так, чтобы между метками и комбинированными списками оставалось небольшое пространство.
Spring comboWest =strut);
Теперь соединим эти пружины с левыми сторонами обоих комбинированных списков. Каждая пружина начинается на стенке контейнера, поскольку именно с ней связана пружинаlabelsEast.
layout.putConstraint(SpringLayout.WEST, face, comboWest,
SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.WEST, size, comboWest, SpringLayout.WEST, contentPane);
Теперь оба комбинированных списка выровнены, поскольку они связаны с одной и той же пружиной.
Однако остался один недостаток. Мы хотели бы, чтобы метки были выровнены по правому краю. Для этого нужно лучше разобраться в способах соединениях пружин.
Рассмотрим подробнее горизонтальные пружины. Вертикальные пружины устроены точно также. На рис. 9.43 показаны три способа соединения пружин с компонентами.
Соединение левой стороны компонента с левой стороной другого компонента.
Вставка внутрь компонента.
Соединение левой стороны компонента с правой стороной другого компонента.
Эти три пружины можно создать следующим образом.
Spring west = layout.getConstraints(component).getX();
Spring width = layout.getConstraints(component).getWidth();
Spring east = layout.getConstraints(SpringLayoput.EAST, component);
Метод getConstraints создает объект типа SpringLayout . Constraints. Этот объект можно считать прямоугольником, координаты, ширина и высота которого задаются пружинами, а не числами. Метод getConstraints создает отдельную пружину, достигающую одной из четырех сторон компонента. Например, левую пружину можно создать с помощью оператора
Spring west = layout.getConstraints(SpringLayout.WEST, component);
контейнер
леваялеваяширина
левая
левая
пппшшг
I-<— • компонент
tправая
Рис. 9.43. Горизонтальные пружины, соединенные с компонентом
Разумеется, все три пружины связаны между собой: сумма пружин west и width должна быть равна пружине east. Если размеры компонента заданы заранее, пружиной width считается пружина, параметрами которой являются минимальная, рекомендуемая и максимальная ширина компонента. Пружинаwestустанавливается равной 0.
Если не установить значение пружины west (или north), то смещение компонента относительно контейнера будет нулевым.
Если компонент соединен с двумя пружинами, а мы добавляем третью, он становится излишне ограниченным (overcons trained). В табл. 9.3 указаны пружины, параметры которых должны быть вычислены заново.
Добавляемая пружина
Удаляемая пружина
Замена
west
width
east -
west
width
east
west +
width
east
west
east -
width
Разность двух пружин может быть не очевидной, но вполне осмысленной. В языке Java нет метода для вычитания пружин. Если нужно вычислить разность двух пружин, следует использовать метод
Spring.sum(s. Spring.minus(t));
Теперь нам известно о пружинах достаточно, чтобы решить проблему "выравнивания по правому краю". Вычислим максимальную ширину обоих меток. Затем присвоим правым пружинам обоих меток максимальное значение. Как видно из табл. 9.3, ширина метки не изменится, величина левой пружины будет вычислена заново, а метки выровняются по правой границе.
Spring labelEast = SpringEast = Spring.sum(strut,
Spring.max(layout.getCoinstraints(faceLabel).getWidth(),
Spring.max(layout.getConstraints(sizeLabel).getWidth()));
layout putConstraints(SpringLayout.EAST, faceLabel, labelsEast, SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout.WEST, contentPane);
В листинге 9.16 показано, как скомпоновать диалоговое окно для выбора шрифта с пружинами. Если внимательно проанализировать код, вы, возможно, согласитесь, что эластичная компоновка несколько сложнее компоновки сеточного мультимножества. Мы надеемся, что впоследствии все же появятся инструментальные средства, облегчающие работу с эластичной компоновкой. Пока этого не произошло, мы рекомендуем использовать компоновку сеточного мультимножества.
ГоZ-Листинг 9.16ГоZ-Листинг 9.16
ГоZ-
Листинг 9.16
ГоZ-
Листинг 9.16
import java.awt.*;
import java.awt.event. *;
import javax.swing.*;
import javax.swing.event.*;
public class SpringLayoutTest
(
public static void main(String[ args)
{
FontDialogFrame frame = new FontDialogFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE;;
frame.show();
}
}
/“
Фрейм, использующий эластичную компоновку для выравнивания
компонентов выбора шрифта.
*/
class FontDialogFrame extends JFrame
{
public FontDialogFrame(I
{
setTitle("FontDialog") ;
setSize(DEFAULT WIDTH, DEFAULT HEIGHT);
Container contentPane = getContentPane();
SpringLayout layout = new SpringLayout();
contentpane.setLayout(layout);
ActionListener listener = new FontAction();
// Создаем компоненты
38.41.48.49.73.74.75.81.82.83.84.85..38.41.48.49.73.74.75.81.82.83.84.85..JLabel faceLabel = new JLabel("Font Face: ") ;
38.
41.
48.
49.
73.
74.
75.
81.
82.
83.
84.
85.
.
38.
41.
48.
49.
73.
74.
75.
81.
82.
83.
84.
85.
.
face = new JComboBox(new String[' {
"Serif", "SansSerif", "Monospaced", "Dialog", "Dialoginput"
}) ;
;
JLabel sizeLabel = new JLabel("Size: ");
size = new JComboBox(new String[] {
"8", "10", "12", "15", "18", "24", "36", "48" });
size.addActionListener(listener);
bold = new JCheckBox("Bold");
bold.addActionListener(listener);
italic = new JCheckBox("Italic"); italic.addActionListener(listener);
sample = new JTextArea();
sample.setText(
"The quick brown fox jumps over the lazy dog") ;
sample.setEditable(false);
sample.setLineWrap(true);
sample.setBorder(BorderFactory.createEtchedBorder());
contentPane.add(faceLabel) ;
contentPane.add(sizeLabel) ;
content Pane.add(face; ;
contentPane.add(size) ;
contentPane.add(bold) ;
contentPane.add(italic; ;
contentPane.add(sample);
// Добавляем строки в компоновку
Spring strut = Spring.constant(10);
Spring labelsEast = Spring.sum(strut.
Spring.max( layout.getConstraints(faceLabel).getWidthC , layout.getConstraints(sizeLabel).getwidth()));
layout.putConstraint(SpringLayout.EAST, faceLabel, labelsEast, SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.NORTH, faceLabel, strut, SpringLayout.NORTH, contentPane);
layout.putConstraint(SpringLayout.NORTH, face, strut, SpringLayout.NORTH, contentPane);
93.94.98.93.94.98.Spring secondRowNorth =
93.
94.
98.
93.
94.
98.
Spring.max(
layout.getConstraint I SpringLayout.SOUTH, faceLabel),
layout.getConstraint( ;
layout.putConstraint(SpringLayout.NORTH, sizeLabel, secondRowNorth, SpringLayout.NORTH, contentPane);
layout.putConstraint(SpringLayout.NORTH, size, secondRowNorth, SpringLayout.NORTH, contentPane);
layout.putConstraint(SpringLayout.WEST, face, strut, SpringLayout.EAST, faceLabel);
layout.putConstraint(SpringLayout.WEST, size, strut, SpringLayout.EAST, sizeLabel);
layout.putConstraint(SpringLayout.WEST, bold, strut, SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.WEST, italic, strut, SpringLayout.WEST, contentPane);
Spring s = Spring.constant(10, 10000, 10000);
Spring thirdRowNorth = Spring.sum(s.
Spring.max(
layout.getConstraint( SpringLayout.SOUTH, sizeLabel),
layout.getConstraint( SpringLayout.SOUTH, size)));
layout.putConstraint(SpringLayout.NORTH, bold, thirdRowNorth, SpringLayout.NORTH, contentPane);
layout.putConstraint(SpringLayout.NORTH, italic, s, SpringLayout.SOUTH, bold);
layout.putConstraint(SpringLayout.SOUTH, contentPane, s, SpringLayout.SOUTH, italic);
Spring secondColumnWest =
Spring.max I
layout.getConstraint( SpringLayout.EAST, face),
layout.getConstraint( SpringLayout.EAST, size)));
layout.putConstraint(SpringLayout.WEST, sample, secondColumnWest, SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.SOUTH, sample,
Spring.minus(strut), SpringLayout.SOUTH, contentPane)
layout.putConstraint(SpringLayout.NORTH, sample, strut, SpringLayout.NORTH, contentPane);
layout.putConstraint(SpringLayout.EAST, contentPane, strut, SpringLayout.EAST, sample);
}
152.153.154.155.156.157.158.159.160.161.162.163.164 .165.166.167.168.169.170.171.172.173.174 .175.176.177.178. }152.153.154.155.156.157.158.159.160.161.162.163.164 .165.166.167.168.169.170.171.172.173.174 .175.176.177.178. }public static final int DEFAULTWIDTH = 300;
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164 .
165.
166.
167.
168.
169.
170.
171.
172.
173.
174 .
175.
176.
177.
178. }
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164 .
165.
166.
167.
168.
169.
170.
171.
172.
173.
174 .
175.
176.
177.
178. }
public static final int DEFAULT—HEIGHT = 200;
private JComboBox face;
private JComboBox size;
private JCheckBox bold;
private JCheckBox italic-
private JTextArea sample;
Слушатель действий, изменяющий шрифт образца.
*/
private class FontAction implements ActionListener
public void actionPerformed(ActionEvent event) {
String fontFace = (String)face.getSelectedltemO;
int fontStyle = (bold.isSelected(; ? Font.BOLD : 0)
+ (italic.isSelected() ? Font.ITALIC : 0);
int fontSize = Integer.parselnt(
(String)size.getSelectedltemO);
Font font = new Font(fontFace, fontStyle, fontsize);
sample.setFont(font);
sample.repaint();
}
)
javax.swing.SpringLayout 1.4
SpringLayout getConstraints(Component c)
Параметры: cПараметры: cОдин из компонентов или контейнер, управляемый менеджером компоновки.
Параметры: c
Параметры: c
void putConstraints(String endSide, Component end, Spring s, String startSide, Component start)
void putConstraints(String endSide, Component end, int pad, String startSide, Component start)
Связывает указанную сторону компонента end с пружиной, полученной путем сложения строки s или распорки, имеющей размер pad, с пружиной, соединяющей левую сторону контейнера с указанной стороной контейнераstart.
Параметры: endSide, startSide
WEST, EAST, NORTH или SOUTH.
end
Компонент, к которому добавляется пружина.
s
Одна из слагаемых пружин.
pad
Размер распорки.
start
Компонент, с которым соединяется другая слагаемая пружина.
j avax.swing.SpringLayou t.Constrain ts 1.4
Spring getX()
Spring getY()
Эти методы возвращают пружину, соединяющую начало контейнера с правой или верхней стороной ограниченного компонента.
Spring getWidth()
Spring getHeight(1
Эти методы возвращают пружину, вставленную в ограниченный компонент горизонтально или вертикально.
Spring getConstraint(String side)
void setConstarint(String edge, Spring s)
Эти методы определяют или устанавливают пружину, соединяющую нача- локонтейнерасзаданнойстороной ограниченного компонента.
Параметры: side Одна из констант WEST, EAST, NORTH или SOUTH, определенных в классе SpringLayout.
sУстанавливаемая пружина.
javax.swing.Spring 1.4
static Spring constant(int preferred)
Создает распорку с заданным рекомендуемым размером. Минимальный и максимальный размеры задаются равными рекомендуемому.
static Spring constant(int minimum,int preferred, int
maximum)
Создает пружину с заданными минимальным, рекомендуемым и макси- мальнымразмером.
static Spring sum(Spring s, Spring t)
Возвращает сумму пружин s и t.
static Spring max(Spring s, Spring t)
Возвращает максимальную из двух пружин s и t.
static Spring minus(Spring s)
Возвращает пружину, противоположную пружине s.
int getMinimumValue(]
int getPreferred()
int getMaximumValue()
Эти методы возвращают минимальный, рекомендуемый и максимальный размеры пружины.
int getValue(]
void setValue(int newValue)
Эти методы определяют и устанавливают размер пружины. При задании размера составной пружины одновременно задаются размеры компонентов.
Как обойтись без менеджера компоновки
Иногда нужно просто поместить какой-то компонент в нужное место на экране, не прибегая к помощи менеджера компоновки. Такой процесс иногда называют абсолютным позиционированием (absolute positioning). Если приложение является платформ- нонезависимым, эта идея никуда не годится, но если вы хотите быстро создать его прототип, ничего плохого не случится.
Вот что нужно сделать, чтобы поместить компонент в заданное место.
Установить ссылку на менеджер компоновки равной null.
Добавить компонент в контейнер.
Задать координаты и желательный размер.
contentPane.setLayout(null);
JButton ok = new JButtonfOk") ;
cont e nt Pane.add(о к) ;
ok.setBounds(10, 10, 30, 15);
java.awt.Component 1.0
void setBounds(int x, int y, int width, int height) Перемещает компонент и меняет его размеры.
Параметры: x,уКоординаты левого верхнего угла компонента.
width,Новые размеры компонента.
height
Собственный менеджер компоновки
В принципе, можно разработать собственный класс LayoutManager, управляющий компонентами по-своему. Например, можно расставить все компоненты в контейнере по кругу. Разумеется, почти всегда это лишь напрасная трата времени и сил, но результат, как показывает рис. 9.44, может быть впечатляющим.
Рис. 9.44. Круговая компоновкаРис. 9.44. Круговая компоновка
Рис. 9.44. Круговая компоновка
Рис. 9.44. Круговая компоновка
Если вы жить не можете без собственного менеджера компоновки, попробуйте сделать его сами. Ваш класс должен реализовывать интерфейс LayoutManager. Вам придется заместить следующие пять методов.
void addLayoutComponent(String s, Component с)
void removeLayoutComponent(Component c);
Dimension preferredLayoutSize(Container parent);
Dimension minimumLayoutSize(Container parent); void layoutcontainer(Container parent);
Первые две функции вызываются, когда компонент добавляется или удаляется. Если никакой дополнительной информации о компоненте нет, можно оставить эти методы пустыми. Две следующие функции вычисляют объем пространства, необходимого при минимальных и желательных размерах компонентов. Обычно эти величины равны. Пятая функция вызывает метод setBoundsдля всех компонентов и выполняет всю основную работу.
В библиотеке AWT есть второй интерфейс под названием LayoutManager2. В нем содержатся 10 методов, а не 5. Главная особенность этого интерфейса состоит в том, что с его помощью можно использовать метод add без каких-либо ограничений. Например, классы BorderLayout и GridBagLayout реализуют именно интерфейс LayoutManager2.
В листинге 9.17 приведена программа, реализующая менеджер компоновки CircleLayout, который размещает компоненты вдоль окружности внутри контейнера. Это довольно забавно и совершенно бесполезно.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CircleLayoutTest
{
public static void main(String(] args)
{
CircleLayoutFrame frame = new CircleLayoutFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.showd ;<
}
}
/**
Фрейм с кнопками, расположенными по окружности.
*/
class CircleLayoutFrame extends JFrame
{
public CircleLayoutFrame(]
{
setTitle("CircleLayoutTest");
Container contentPane = getContentPane ();
contentPane.setLayout(new CircleLayout());
contentPane.add(new JButton!"Yellow"));
contentPane.add(new JButton("Blue"));
contentPane.add(new JButton("Red"));
contentPane.add(new JButton("Green"));
contentPane.add(new JButton("Orange"));
43.44.52.57.62.72.75.76.77.83.84.85.86.87.43.44.52.57.62.72.75.76.77.83.84.85.86.87.contentpane.add(new JButton("Fuchsia")); contentPane.add(new JButton("Indigo"));
43.
44.
52.
57.
62.
72.
75.
76.
77.
83.
84.
85.
86.
87.
43.
44.
52.
57.
62.
72.
75.
76.
77.
83.
84.
85.
86.
87.
}
}
/**
Менеджер компоновки, располагающий компоненты по окружности. */ class CircleLayout implements LayoutManager {
public void addLayoutComponent(String name, Component comp)
{}
public void removeLayoutComponent(Component comp) {}
public void setSizes(Container parent) {
if (sizesSet) return;
int n = parent.getComponentCount();
preferredWidth = 0;
preferredHeight = 0;
minwidth = 0;
minHeight = 0;
maxComponentWidth = 0;
maxComponentHeight = 0;
// Вычисляем максимальную ширину и высоту компонентов //и задаем желательный размер как сумму этих
// размеров.
for (int i = 0; i < n; i++) {
Component с = parent.getComponent(i); if (c.isVisible(] ) {
Dimension d = c.getPreferredSize();
maxComponentWidth = Math.max(maxComponentWidth, d.width);
maxComponentHeight = Math.max(maxComponentHeight, d.height);
preferredWidth += d.width; preferredHeight += d.height; }
}
minWidth = preferredWidth / 2;
minHeight = preferredHeight / 2; sizesSet = true;
}
public Dimension preferredLayoutSize(Container parent) {
setSizes(parent);
Insets insets = parent .getlnsets();
int width = preferredWidth + insets.left + insets.right;
int height = preferredHeight + insets.top
+ insets.bottom;
106." 111.106." 111.return new Dimension(width, height); }
106.
" 111.
106.
" 111.
public Dimension minimumLayoutSize(Container parent) {
setSizes(parent);
Insets insets = parent.getlnsets(); int width = minWidth + insets.left + insets.right; int height = minHeight + insets.top + insets.bottom; return new Dimension(width, height);
}
public void layoutcontainer(Container parent) {
setSizes(parent);
// Вычисляем центр окружности.
Insets insets = parent.getlnsets();
int containerWidth = parent.getSize().width -left - insets.right;
int containerHeight = parent.getSize().height --
int xcenter = insets.left + containerWidth / 2; int ycenter = insets.top + containerHeight / 2;
// Вычисляем радиус окружности.
int xradius = (containerWidth - maxComponentWidth) / 2; int yradius = (containerHeight - maxComponentHeight) / 2; int radius = Math.min(xradius, yradius);
// Располагаем компоненты ПС окружности.
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component с = parent.getComponent(i); if (c.isVisible() ) { double angle = 2 * Math.PI * i / n;
// Центр компонента.
int x = xcenter + (int)(Math.cos(angle) * radius); int у = ycenter + (int)(Math.sin(angle) * radius);
// Перемещаем компонент так, чтобы его центр // совпал с точкой (х,у), а его размеры —
//с желательными.
Dimension d = c.getPreferredSize (); c.setBounds(x -/ 2, у - d.height / 2,
d.width, d.height);
}
} }
private int minWidth = 0;
privateprivateprivateprivateprivateprivate1privateprivateprivateprivateprivateprivate1int minHeight = 0;
private
private
private
private
private
private
1
private
private
private
private
private
private
1
int preferredwidth = 0;
int preferredHeight = 0; boolean sizesSet = false;
int maxComponentWidth = 0;
int maxComponentHeight = 0;
ti java.awt.LayoutManager 1.0
■■■■i • void addLayoutComponent(String name, Component comp) Добавляет компонент в схему компоновки.
Параметры: nameСтрока, задающая координаты компонента.
compДобавляемый компонент.
• void removeLayoutComponent(Component comp)
Удаляет компонент из схемы компоновки.
Параметры: compУдаляемый компонент.
Dimension preferredLayoutSize(Container parent)
Возвращает желательные размеры контейнера, в котором выполняется компоновка.
Параметры: parent Контейнер, в котором выполняется компоновка.
Dimension minimumLayoutSize(Container parent)
Возвращает минимальные размеры контейнера, в котором выполняется компоновка.
Параметры: parent Контейнер, в котором выполняется компоновка.
void layoputContainer(Container parent)
Выполняет компоновку внутри контейнера.
Параметры: parent Контейнер, в котором выполняется компоновка.
Порядок обхода
Если в окне содержится много компонентов, нужно подумать о порядке их обхода (traversal order). Когда окно открывается в первый раз, фокус клавиатуры находится на том компоненте, который считается первым при заданном порядке обхода. Каждый раз, когда пользователь нажимает клавишу <ТАВ>, фокус перемещается на следующий компонент. (Напомним, что с компонентом, на котором находится фокус клавиатуры, можно работать с помощью клавиш. Например, на кнопке, находящейся в фокусе, можно "щелкнуть" с помощью клавиши пробела.) Вы лично можете оставаться безразличным к такой возможности, однако есть много пользователей, которые в восторге от этого. К ним относятся люди, боящиеся мышей и не умеющие с ними работать. Возможно, они предпочитают использовать речевой интерфейс. По этой причине программист обязан знать, как в библиотеке Swing задается порядок обхода компонентов.
В пакете Swing реализован вполне разумный порядок обхода: сначала — слева направо, затем — сверху вниз. Например, в диалоговом окне для выбора шрифта обход компонентов осуществлялся в следующем порядке.
Комбинированный список начертаний.
Образец текстовой области.
Комбинированный список размеров.
Флажок опции для выбора полужирного начертания.
Флажок опции для выбора курсива.
Рис. 9.45. Геометрический порядок обходаРис. 9.45. Геометрический порядок обхода
Рис. 9.45. Геометрический порядок обхода
Рис. 9.45. Геометрический порядок обхода
В старой версии библиотеки AWT порядок обхода определялся очередностью, в которой компоненты вставлялись в контейнер. В библиотеке Swing порядок вставки не имеет значения ■ ■ учитывается только вид компоновки.
Если контейнеры вложены друг в друга, ситуация осложняется. Когда фокус перемещается на вложенный контейнер, он автоматически устанавливается на его левый верхний компонент, а затем обходит остальные компоненты. В конце концов фокус останавливается на компоненте, следующем за контейнером. Это обстоятельство можно с выгодой использовать, помещая группу связанных между собой элементов в другой контейнер, например панель.
При работе с пакетом SDK 1.4 для удаления компонента из фокуса обхода следует вызывать метод
component.setFocusable(false);
В старых версиях пакета SDK для этого требовалось замещать метод isFocus- Traversable, но теперь этот метод объявлен нежелательным.
Подводя итог, укажем на две стандартные стратегии, принятые в пакете SDK 1.4.
Приложения, основанные исключительно на библиотеке AWT, используют стратегию DefaultFocusTraversablePolicy. Компоненты попадают в фокус обхода, если они являются видимыми, отображаемыми на экране, доступными и, в принципе, могут находиться в фокусе. Кроме того, требуется, чтобы их машинозависимые эквиваленты (native peers) также могли находиться в фокусе. Компоненты обходятся в порядке их вставки в контейнер.
Приложения, использующие библиотеку Swing, придерживаются стратегии LayoutFocusTraversablePolicy. Компоненты попадают в фокус, если они являются видимыми, отображаемыми на экране, доступными и, в принципе, могут находиться в фокусе. Компоненты обходятся в геометрическом порядке: слева направо и сверху вниз. Однако контейнер создает новый "цикл" — обход его компонентов должен завершиться до того, как в фокус попадет преемник контейнера.
Понятие "цикла" довольно туманно. По достижении последнего элемента дочернего контейнера фокус переместится не назад к его первому элементу, а к преемнику контейнера. Интерфейс прикладных программ содержит средства для реализации истинных циклов, включая поддержку нажатия клавиш, которые перемещают фокус вверх и вниз по циклической иерархии. Однако стандартная стратегия обхода не использует иерархических циклов. Она преобразует циклическую иерархию в линейную иерархию (порядок обхода "сначала в глубину").
В пакете SDK 1.3 существовала возможность изменить порядок обхода, принятый по умолчанию, вызвав метод setNextFocusableComponent класса JComponent. Теперь этот метод объявлен "вне закона". Чтобы изменить порядок обхода, попробуйте сгруппировать связанные между собой компоненты на отдельной панели так, чтобы они образовали циклы. Если этот способ не сработает, нужно либо установить метод сравнения, который упорядочивает компоненты, либо полностью заменить стратегию обхода. Ни один из этих подходов не внушает доверия детали можно найти в документации по интерфейсу Sun API.
Диалоговые окна
До сих пор мы рассматривали только компоненты пользовательского интерфейса, которые находились внутри окна фрейма, создаваемого в приложении. Наиболее часто эта ситуация встречается при разработке аплетов, выполняемых броузером. Однако при создании приложений возникает необходимость в отдельных диалоговых окнах, которые всплывали бы на экране, обмениваясь информацией с пользователем.
Как и большинство систем оконного интерфейса, библиотека AWT различает модальные диалоговые окна (modal dialog boxes) и безмодплъные (modeless dialog boxes). Модальные диалоговые окна не позволяют пользователю одновременно работать с другими окнами приложения. Такие окна необходимы, если пользователь должен ввести информацию, от которой зависит дальнейшая работа приложения. Например, при считывании файла пользователь должен сначала указать его имя. Только когда пользователь закроет модальное диалоговое окно, программа приступит к выполнению операции считывания.
Безмодальное диалоговое окно позволяет пользователю одновременно вводить информацию как в нем, так и в других окнах приложения. Примером такого окна является панель инструментов. Она остается на месте сколь угодно долго, и пользователь может одновременно взаимодействовать как с ней, так и с другими окнами приложения.
Начнем этот раздел с простейшего диалогового окна— модального диалога с одной строкой сообщения. В пакете Swing есть удобный класс JOptionPane, позволяющий выводить на экран модальный диалог, не создавая специального кода. Далее
мы расскажем, как можно реализовать свой собственный диалог. В заключение мы покажем, как передавать информацию из приложения в диалоговое окно и обратно.
Этот раздел завершается анализом двух стандартных диалогов: для работы с файлами и установки цветов. Диалоговое окно для работы с файлами устроено более сложно. Для работы с ним необходимо хорошо знать устройство класса JFile- Chooser из библиотеки Swing. Написать свое собственное диалоговое окно для работы с файлами — задача, достойная профессионала высокого уровня. Для выбора цветов из палитры вам может пригодиться диалоговое окно JColorChooser.
Диалоговые окна для выбора вариантов
В библиотеке Swing содержится много готовых диалоговых окон, которые позволяют вводить отдельную порцию информации. В классе JOptionPane есть четыре
статических метода.
showMessageDialog
Выводит на экран сообщение и ожидает, пока пользователь не щелкнет на кнопке "ОК".
showConfirmDialog
Выводит на экран сообщение и ждет подтверждения ("ОК" или "Cancel").
showOptionDialog
Выводит на экран сообщение и предоставляет пользователю выбор из нескольких вариантов.
showinputDialog
Выводит на экран сообщение и строку, в которой пользователь должен ввести информацию.
На рис. 9.46 показано типичное диалоговое окно. Как видим, оно состоит из следующих компонентов:
пиктограмма;
сообщение;
одна или несколько кнопок.
Диалоговое окно имеет дополнительный компонент для ввода данных. Этим компонентом может быть текстовое поле, в котором пользователь может набрать произвольную строку, или комбинированный список, в котором пользователь должен выбрать один из пунктов.
Компоновка этих диалоговых окон и выбор пиктограмм для стандартных сообщений зависит от стиля интерфейса.
Рис. 9.46. Диалоговое окно для выбора вариантов
Пиктограмма в левой части окна зависит от типа сообщения (message type). Существуют пять типов сообщений:
ERRORMESSAGE
INFORMATION_MESSAGE
WARNING-MESSAGE
QUESTION-MESSAGE
PLAIN_MESSAGE
Тип PLAIN_MESSAGE не имеет пиктограммы. Для каждого из типов диалоговых окон существует также метод, позволяющий использовать свою собственную пиктограмму.
Кроме того, с каждым из типов диалоговых окон можно связать определенное сообщение, которое может быть строкой, пиктограммой, компонентом пользовательского интерфейса или любым другим объектом. Ниже показано, как отображаются на экране эти сообщения.
String:
Выводит строку.
Icon:
Отображает пиктограмму.
Component
Отображает компонент.
Ob j ect [ ] :
Отображает все объекты из стека.
Любой другой объект:
Применяет метод toString и выводит на экран строку, полученную в результате этого преобразования.
Все эти возможности продемонстрированы в программе, приведенной в листинге 9.18.
Разумеется, в большинстве случаев на экран выводят строку сообщения. В то же время возможность выводить в окне диалога объекты класса Component придает программе необычайную гибкость, поскольку с помощью метода paintComponent на экране можно нарисовать что угодно. Применение массива объектов не слишком полезно, так как в диалоговом окне можно отобразить лишь небольшую их часть — оно не может увеличиваться, чтобы уместить все объекты сообщения.
Внешний вид кнопок, размещенных в нижней части окна, зависит от типа диалога и типа вариантов (option type). Вызывая методы showMessageDialog и showlnput- Dialog, вы ограничиваетесь стандартным набором кнопок (“OK и “OK/Cancel соответственно). Вызывая метод showConfirmDialog, можно выбрать один из четырех типов вариантов:
DEFAULT_OPTION
YES_NO_OPTION
YES_NO_CANCEL_OPTION
OK_CANCEL_OPTION
С помощью метода showOptionDialog можно указать произвольный набор вариантов, задав массив объектов, соответствующих каждому из них. Элементы этого массива отображаются на экране следующим образом.
String:Создает кнопку, меткой которой является заданная строка.Icon:Создает кнопку, меткой которой является заданная пиктограмма.Component:Отображает компонент.String:Создает кнопку, меткой которой является заданная строка.Icon:Создает кнопку, меткой которой является заданная пиктограмма.Component:Отображает компонент.Любой другой объект:Любой другой объект:Применяет метод toString и создает кнопку, меткой которой является полученная в результате строка.Применяет метод toString и создает кнопку, меткой которой является полученная в результате строка.
String:
Создает кнопку, меткой которой является заданная строка.
Icon:
Создает кнопку, меткой которой является заданная пиктограмма.
Component:
Отображает компонент.
String:
Создает кнопку, меткой которой является заданная строка.
Icon:
Создает кнопку, меткой которой является заданная пиктограмма.
Component:
Отображает компонент.
Любой другой объект:
Любой другой объект:
Применяет метод toString и создает кнопку, меткой которой является полученная в результате строка.
Применяет метод toString и создает кнопку, меткой которой является полученная в результате строка.
Вот какие значения возвращают статические методы.
showMessageDialog Ничего не возвращает.
showConfirmDialog Целое число, обозначающее выбранный вариант. showOptionDialog Целое число, обозначающее выбранный вариант. showInputDialogСтроку, введенную или выбранную пользователем.
Методы showConfirmDialog и showOptionDialog возвращают целое число, обозначающее кнопку, на которой щелкнул пользователь. В диалоговом окне для выбора вариантов это число является просто порядковым номером или значением CLOSED OPTION, если вместо выбора варианта пользователь закрыл диалоговое окно. В диалоговом окне для подтверждения выбора возвращаемое значение может быть равным одной из следующих констант.
OK_OPTION
CANCEL_OPTION
YESOPTION
NO_OPTION
CLOSED_OPTION
Вся эта масса вариантов выглядит довольно устрашающе, но на практике все намного проще.
Выберите тип диалогового окна (сообщение, подтверждение, выбор или ввод).
Выберите пиктограмму (ошибка, информация, предупреждение, вопрос, своя собственная или никакой).
Выберите сообщение (строка, пиктограмма, пользовательский компонент или стек компонентов).
Если вы выбрали диалоговое окно для подтверждения выбора, задайте тип вариантов (по умолчанию, Yes/No, No/Cancel или OK/Cancel).
Если вы выбрали диалоговое окно для выбора вариантов, задайте варианты (строки, пиктограммы или собственные компоненты), а также вариант, выбираемый по умолчанию.
Если вы выбрали диалоговое окно для ввода данных, выберите текстовое поле или комбинированный список.
Найдите подходящий метод в классе JOptionPane.
Допустим, что мы хотим отобразить на экране диалоговое окно, показанное на рис. 9.46. В этом окне выводится сообщение, а пользователю предлагается подтвердить его или отклонить. Следовательно, это — диалоговое окно для подтверждения выбора. Пиктограмма, изображенная в этом окне, относится к предупреждениям. Сообщение является строкой. Тип вариантов задается константой OK_CANCEL_OPTION. Итак, для создания такого диалогового окна нужно сделать следующее.
int selection = JOptionPane.showConfirmDialog(parent,
"Message", "Title",
JOptionPane.OK_CANCEL_OPTION, JOprionPane.WARNING_MESSAGE); if (selection == JOptionPane.OKOPTION; ...
Строка сообщения может содержать символ перехода на новую строку (' \n * ). В этом случае сообщение выводится в виде нескольких строк.
Программа, приведенная в листинге 9.18, позволяет сделать описанный выше выбор вариантов (рис. 9.47).
Рис.9.47. Программа OptionDialogTest
.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
imp imp imp imp imp imp
ort ort ort ort ort ort
java.awt.*;
java.awt.event.*;
java.awt.geom.*;
java.util.*;
javax.swing.*;
javax.swing.border.*;
public class OptionDialogTest
{.. .
public static void main(String[j args)
OptionDialogFrame frame = new OptionDialogFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE1 ; frame.show!);
/
*/
Панель с переключателями внутри рамки заголовка.
class ButtonPanel extends JPanel
{
/**
Создает панель с кнопками.
@param title заголовок в рамке
@param options массив меток переключателей
public ButtonPanel(String title, String!] options)
{
setBorder(BorderFactory.createTitledBorder
(BorderFactory.createEtchedBorder(), title)1;
setLayout(new BoxLayout(this,
BoxLayout.Y_AXIS));
group = new ButtonGroup();
// Для каждого варианта создаем переключатель.
for (int i = 0; i < options.length; i++)
{
JRadioButton b = new JRadioButton(options[i]);
b.setActionCoirmnand (options [i] );
add(b);
group.add(b);
b.setSelected(i == 0);
}
}
/**
Получает текущий выбранный вариант
@return метка выбранного переключателя
*/
public String getSelection()
(
return group.getSelection().getActionCommand();
}
private ButtonGroup group;
}
/**
Фрейм, содержащий установки для выбора различных
вариантов диалога.
*/
class OptionDialogFrame extends JFrame
{
public OptionDialogFrame(]
{
setTitle("OptionDialogTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
JPanel gridPanel = new JPanel();
gridPane1.setLayout(new GridLayout(2, 3));
typePanel = new ButtonPanel (’’Type”,
new String!]
{
"Message",
"Confirm",
"Option",
"Input"
});
messageTypePanel - new ButtonPanel("Message Type",
new String!]
{
"ERROR_MESSAGE",
"INFORMATION MESSAGE",
87.127.87.127."WARNING_MESSAGE", "QUESTION-MESSAGE", "PLAIN_MESSAGE"
87.
127.
87.
127.
});
messagePanel = new ButtonPanel("Message",
new String[] {
"String",
"Icon",
"Component",
"Other",
"Object[]"
});
optionTypePanel = new ButtonPanel("Confirm", new String[]
{
"DEFAULT_OPTION",.
"YES_NO_OPTION",
"YES_NO_CANCEL_OPTION", "OK_CANCEL_OPTION"
});
optionsPanel = new ButtonPanel("Option",
new String[] {
"String[]",
"Icon[]",
"Object[]"
});
inputPanel = new ButtonPanel("Input",
new String[] {
"Text field", "Combo box"
});
gridPanel.add(typePanel);
gridPanel.add(messageTypePanel);
gridPanel.add(messagepanel);
gridPanel.add(optionTypePanel);
gridPanel.add(optionsPanel);
gridPanel.add(input Panel] ;
// Добавляем панель с кнопкой "Show".
JPanel showPanel = new JPanel ();
JButton showButton = new JButton("Show");
showButton.addActionListener(new ShowActiont));
showPanel.add(showButton) ;
Container contentPane = getContentPane();
contentPane.add(gridPanel, BorderLayout.CENTER; ,contentPane.add(showPanel, BorderLayout.SOUTH);
}
/**
534
146.
147.
148.
149
150.
151
152.
153.
154.
155.
156.
157.
158.
159.
160.
161
162.
163.
164.
165.
166
167.
168.
169.
170
171.
172
173.
174.
175.
176
177.
178
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191
192.
193.
194.
195.
196
197.
198.
199 200.
201
Получает текущее сообщение.
@return строка, пиктограмма или массив объектов в зависимости от выбора панели "Message"
*/ public Object getMessage() {
String s = messagePanel.getSelection();
if (s.equals("String"))
return messageString;
else if (s.equals("Icon")I
return messagelcon;
else if (s.equals("Component")1
return messageComponent;
else if (s.equals("Object[]")1
-return new Object []
{
messageString, messagelcon, messageComponent, messageobject
};
else if (s.equals("Other") ) return messageObject;
else return null;
}
/**
Gets the currently selected options.
@return an array of strings, icons or objects,depending
on the Option panel selection
*/
public Object [] getOptions()
{
String s = optionsPanel.getSelection();
if (s.equals("String[]"))
return new StringC { "Yellow", "Blue", "Red" };
else if (s.equals("Icon[]"))
return new Icon[]
{
new Imageicon("yellow-ball.gif"), new Imageicon("blue-ball.gif") , new Imageicon("red-ball.gif")
}; else
if (s.equals("Object(]")]
return new Object[]
{
messageString, messagelcon, messageComponent, messageobject
};
else return null;
}
/**
202.202.Получает тип текущего сообщения или варианта. @param panel тип Message или панель Confirm §return константа XXX MESSAGE или XXX OPTION
202.
202.
205.206.207.208.209.210.211.212.213.214.215.216.217.218.219.220.221.222.223.224.225.226.227.228.229.230.231.232.233.234.235.236.237.238.239.240.241.242.243.244.245.246.247.248.249.250.251.252.253.254.255.256.257.258.259.260.26; .262.263.205.206.207.208.209.210.211.212.213.214.215.216.217.218.219.220.221.222.223.224.225.226.227.228.229.230.231.232.233.234.235.236.237.238.239.240.241.242.243.244.245.246.247.248.249.250.251.252.253.254.255.256.257.258.259.260.26; .262.263.из класса JOptionPane
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
26; .
262.
263.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
26; .
262.
263.
*/
public int getType(ButtonPanel panel) {
String s = panel.getSelection();
try {
return JOptionPane.class.getFieId(s).getlnt(null);
)
catch(Exception e) {
return -1;
}
}
/**
Слушатель действий, связанных с кнопкой Show.
Выводит на экран диалоговые окна Confirm, Input, Message или Option в зависимости от сделанного выбора.
*/ private class ShowAction implements ActionListener {
public void actionPerformed(ActionEvent evt) {
if (typePanel.getSelection().equals("Confirm")] JOptionPane.showConfirmDialog(
. OptionDialogFrame.this, getMessage(),
"Title",
getType(optionTypePanel), getType(messageTypePanel));
else if (typePanel.getSelection().equals("Input")] {
if (inputPanel.getSelection().equals("Text field")) JOptionPane.showInputDialog(
OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel));
else
JOptionPane.showInputDialog( OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel), null,
new String!] { "Yellow", "Blue", "Red" }, "Blue");
}
else iftypePanel.getSelection().equals("Message"))
JOptionPane.showMessageDialog(
OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel));
else if typePanel.getSelection().equals("Option")) JOptionPane.showOptionDialog(
OptionDialogFrame.this, getMessage (),
"Title",
getType(optionTypePanel),
getType(messageTypePanel),
null,
getOptions(),
getOptions()[0]);
}
}
public static final int WIDTH = 600;
public static final int HEIGHT = 400;
private ButtonPanel typePanel;
private ButtonPanel messagePanel;
private ButtonPanel messageTypePanel;
private ButtonPanel optionTypePanel;
private ButtonPanel optionsPanel;
private ButtonPanel inputPanel;
private String messagestring = "Message";
private Icon messageicon = new Imageicon("blue-ball.gif");
private Object messageobject = new Date();
private Component messageComponent = new SamplePanel();
}
/**
Панель с графическим изображением.
*/
class SamplePanel extends JPanel
{
public void paintcomponent(Graphics g)»
{
super.paintcomponent(g);
Graphics2D g2 = (Graphics2D)g;
Rectangle2D rect = new Rectangle2D.Double(0, 0,
getWidthO - 1, getHeight(] - 1);
g2.setPaint(Color.yellow);
g2.fill(rect);
g2.setPaint(Color.blue);
g2.draw(rect);
}
30'8 . public Dimension getMinimumSize ()
{
310return new DimensiondO, 10);
}
}
javax.swing.JOptionPane 1.2
parent, Object Icon icon)parent, Objectparent, Object Icon icon)parent, Objectstatic void showMessageDialog(Component message, String title, int messageType,
parent, Object Icon icon)
parent, Object
parent, Object Icon icon)
parent, Object
static void showMessageDialog(Component message, String title, int messageType)
static void showMessageDialog(Component parent, Object
message)
static void showInternalMessageDialog(Component parent,
Object message, String title, int messageType, Icon icon)
static void showInternalMessageDialog(Component parent,
Object message, String title, int messageType)
static void showInternalMessageDialog(Component parent,
Object message)
Выводит на экран диалоговое окно с обычным или внутренним сообщением. (Внутреннее диалоговое окно целиком изображается внутри собственного фрейма.)
Параметры: parentРодительский компонент (может быть равен
null).
messageСообщение, которое выводится в диалоговом
окне (может быть строкой, пиктограммой, компонентом или массивом компонентов).
titleСтрока заголовка диалогового окна.
>
messageType Одна из константERROMESSAGE,
INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE.
iconПиктограмма, которая выводится вместо
стандартной пиктограммы.
static int showConfirmDialog(Component parent,
Object message, String title, int optionType, Icon icon) static int showConfirmDialog(Component parent,
Object message, String title, int optionType, int messageType)
static int showConfirmDialog(Component parent,
Object message, String title, int optionType)
static int showConfirmDialog(Component parent,
Object message)
static int showinternalConfirmDialog(Component parent, Object message, String title, int optionType, Icon icon)
static int showinternalConfirmDialog(Component parent, Object message, String title, int optionType, int messageType)
static int showinternalConfirmDialog(Component parent, Object message, String title, int optionType)
static int showInternalConfirmDialog(Component parent, Object message)
Выводит на экран диалоговое или внутреннее диалоговое окно для подтверждения выбора. (Внутреннее диалоговое окно целиком изображается
внутри собственного фрейма.) Возвращает вариант, выбранный пользователем (одну из констант OK_OPTION, YES_OPTION, NO_OPTION) или CLOSED-OPTION, если пользователь закрыл диалоговое окно.
Параметры: parent
Родительский компонент (может быть равен null) .
message
Сообщение, которое выводится в диалоговом окне (может быть строкой, пиктограммой, компонентомилимассивомкомпонентов).
title
Строка заголовка диалогового окна.
messageType
Одна из констант ERRO_MESSAGE,
INFORMATION_ME S SAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE.
optionType
Одна из констант DEFAULT_OPTION, YESNOOPTION, YES_NO_CANCEL_OPTION, OK CANCEL OPTION.
icon
Пиктограмма, которая выводится вместо стандартной пиктограммы.
static int showOptionConfirmDialog(Component parent, Object message, String title, int optionType,
int messageType, Icon icon, Object[ options, Object default) static int showInternalOptionConfirmDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon, Object[ options, Object default) Выводит на экран диалоговое или внутреннее диалоговое окно для выбора варианта. (Внутреннее диалоговое окно целиком изображается внутри собственного фрейма.) Возвращает номер варианта, выбранного пользователем, или константу CLOSED OPTION, если пользователь закрыл диалоговое окно.
Параметры: parent
Родительский компонент (может быть равен null) .
message
Сообщение, которое выводится в диалоговом окне (может быть строкой, пиктограммой, компонентомилимассивомкомпонентов).
title
Строка заголовка диалогового окна.
messageType
Одна из констант ERRO_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE
optionType
Одна из констант DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION, OK CANCEL OPTION.
icon
Пиктограмма, которая выводится вместо стандартной пиктограммы.
optionsМассив вариантов, элементами которого могут
быть строки, пиктограммы или компоненты.
defaultВариант, предлагаемый пользователю по
умолчанию.
static Object showInputDialog(Component parent,
Object message, String title, int messageType, Icon icon, Object[] values, Object default)
• static String showInputDialog(Component parent, Object message,
String title, int messageType)
static String showInputDialog(Component parent, Object message)
static String showInputDialog(Object message)
static Object showInternallnputDialog(Component parent,
Object message, String title, int messageType, Icon icon, Object[] values, Object default)
static String showInternallnputDialog(Component parent, Object message, String title, int messageType)
static Object showInternallnputDialog(Component parent, Object message)
Выводит на экран диалоговое или внутреннее диалоговое окно для ввода данных. (Внутреннее диалоговое окно целиком изображается внутри собственного фрейма.) Возвращает строку, введенную пользователем, или нулевую ссылкуnull, если пользователь закрыл окно диалога.
Параметры: parent
Родительский компонент (может быть равен null).
message
Сообщение, которое выводится в диалоговом окне (может быть строкой, пиктограммой, компонентом или массивом компонентов).
title
Строка заголовка диалогового окна.
messageType
Одна из констант ERRO_MESSAGE,
INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE.
optionType
Одна из констант DEFAULT_OPTION, YES_NO_OPTION,YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION.
icon
Пиктограмма, которая выводится вместо стандартной пиктограммы.
values
Массив значений, предлагаемых в комбинированном списке.
default
Значение, предлагаемое пользователю по умолчанию.
Создание диалоговых окон
В предыдущем подразделе мы увидели, как для создания простого диалогового окна применяется класс JOptionPane. Теперь мы опишем, как создаются свои собственные диалоговые окна.
На рис. 9.48 показано типичное модальное диалоговое окно, представляющее собой некоторое окно, содержащее сообщение, которое отображается на экране, если пользователь щелкнет на кнопке "About".
Для того чтобы реализовать такое окно, нужно создать класс, производный от класса JDialog. По существу, это тотже самый процесс, который мы применяли для создания обычного окна, расширяя класс JFrame. Точнее говоря, нужно сделать следующее.
L В конструкторе вашего диалогового окна следует вызвать конструктор базового класса JDialog. Ему нужно указать собственный фрейм (окно фрейма, в котором будет появляться диалоговое окно), заголовок фрейма и булевскую переменную, означающую, является данное диалоговое окно модальным или нет.
Собственный фрейм нужен для того, чтобы диалоговое окно могло отображаться поверх него. Если вам все равно, где будет отображаться ваше окно, задайте этот параметр равным константе null. Обычно системы оконного интерфейса требуют, чтобы каждое всплывающее окно имело свой собственный фрейм. По этой причине библиотека Swing создает невидимый фрейм, предназначенный для совместного пользования, который становится собственным фреймом всех диалоговых окон, у которых данный параметр равен null.
Добавьте в диалоговое окно компоненты пользовательского интерфейса.
Добавьте обработчики событий.
Задайте размер диалогового окна.
Рис. 9.48. Диалоговое окно About
Ниже приводится фрагмент программы, создающий диалоговое окно. public AboutDialog(JFrame owner) extends JDialog {
public AboutDialog(JFrame owner)
{
super(owner, "About DialogTest ", true);
Container contentPane = getContentPane();
contentPane.add(new Jlabel!
"<HTML><HlxixCore Java</I></H1><HR>"
+ "By Cay Hortsmann and Gary CornelK/HTML>"), BorderLayout.CENTER) ;
JPanel panel = new JPanel();
JButton ok= newJButton("Ok");
ok.addActionListener( new
ActionListener(J
{
public void actionPerformed(ActionEvent evt)
{
setvisible(false);
});
panel.add(ok);
contentPane.add(panel, BorderLayout.SOUTH);
setSize(250, 150);
}
Как видим, конструктор добавляет элементы пользовательского интерфейса, в данном случае — метки и кнопку. Кроме того, он добавляет обработчик кнопки и задает размер окна.
Для того чтобы отобразить диалоговое окно на экране, нужно создать новый объект класса JDialog и вызвать метод show.
JDialog dialog = new AboutDialog(this);
dialog.show();
Фактически в программе, показанной ниже, диалоговое окно создается только однажды, а затем мы используем его повторно каждый раз, когда пользователь щелкает на кнопке "About".
if (dialog == null) // Первый раз.
dialog == new AboutDialog(this);
dialog.show();
Когда пользователь щелкает на кнопке "OK", диалоговое окно должно закрываться. Эта реакция определяется в обработчике событий, связанных с кнопкой "ОК".
ok.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent evt)
{
setvisible(false) ;
}
H;
Когда пользователь закрывает окно диалога, щелкая на кнопке "Close", оно скрывается. Как и в классе JFrame, программист может изменить эту реакцию с помощью метода setDefaultCliseOperation.
Тестовая программа для диалогового окна "About" приведена в листинге 9.19.
1. import java ,'awt. *;
2. import java.awt.event.*;
3. import javax.swing.*;
4.
public class DialogTest
{
public static void main(String[] args)
{
DialogFrame frame = new DialogFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
Фрейм с меню, содержащим пункт File->About, при выборе
которого на экране появляется диалоговое окно.
*/
class DialogFrame extends JFrame
{
public DialogFrame(I
{
setTitle("DialogTest");.
setsize(DEFAULTWIDTH, DEFAULTHEIGHT);
// Создает меню "File".
JMenuBar menuBarnew JMenuBar();
setJMenuBar(menuBar);
JMenu fileMenu = new JMenufFile");
menuBar.add(fileMenu);
// Добавляет в меню пункты "About" и "Exit".
// Пункт "About" выводит на экран диалог "About"
JMenuItem aboutItern = new JMenuItemCAbout");
. aboutltem.addActionListener(new
ActionListener(1
{
public void actionPerformed(ActionEvent event)
{
if (dialog == null) // first time
dialog = new AboutDialog(DialogFrame.this]'
dialog.show(); // pop up dialog
}
});
fileMenu.add(aboutltem);
// Пункт "Exit" завершает работу программы.
JMenuItem exititem = new JMenuItemC'Exit");
exitItem.addActionListener(new
ActionListener(]
{
publicvoidactionPerformed(ActionEvent event)
{
System.exit(0) ;
}
});
fileMenu.add(exitItem);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT HEIGHT - 200;
private AboutDialog dialog;
}
/**
Пример модального диалогового окна, выводящего на экран
сообщение и ожидающего щелчка на кнопке "ОК" .
•/
class AboutDialog extends JDialog
{
public AboutDialog(JFrame owner)
{
super(owner, "About DialogTest", true);
Container contentPane = getContentPane();
// Выводит гиперссылку в центр экрана.
contentPane.add(new JLabel(
"<HTML><Hlxi>Core Java</Ix/H1><HR>"
+ "By Cay Horstmann and Gary Cornell</HTML>"),
BorderLayout.CENTER);
// Кнопка "OK" закрывает диалоговое окно.
JButton ok = new JButton("Ok");
ok.addActionListener(new
ActionListener()
{
publicvoidactionPerformed(ActionEvent evt)
{
setVisible(false);
}
});
// Добавляем кнопку "Ok" к нижней рамке.
JPanel panel = new JPanel();
panel.add(ok);
contentPane.add(panel,BorderLayout.SOUTH);
setSize(250, 150);
}
}
prjvw/
I ' i L javax.swing.JDialog 1.2
S5S. public JDialog(JFrame parent, String title, boolean modal)
Создает диалоговое окно, которое является невидимым, пока не будет принудительно выведено на экран.
Параметры: parent
Родительский компонент (может быть равен null).
title
Строка заголовка диалогового окна.
modal
Если значение этого параметра равно true , диалоговое окно является модальным (блокирует доступ к остальным окнам).
Обменданными
Чаще всего диалоговые окна создаются для того, чтобы дать возможность пользователю вводить данные в программу. Выше было показано, насколько легко создавать объекты класса JDialog: задайте исходные данные, а затем вызывайте метод show (), который выведет окно на экран. Теперь посмотрим, как происходит обмен данными между пользователем и диалоговым окном.
Обратите внимание на диалоговое окно, изображенное на рис. 9.49, которое можно использовать для получения имени пользователя и пароля, необходимого для входа в сеть.
Рис. 9.49. Диалоговое окно для ввода пароля
Диалоговое окно должно предусматривать данные, задаваемые по умолчанию. Например, в классе PasswordChooser из демонстрационной программы есть метод setUser, который задает значения по умолчанию в следующих полях.
public void setUser(User u)
(*
userName.setText(u.getName());
Задав значения no умолчанию (при желании), выведем на экран диалоговое окно. boolean shwoDialog() {
ok = false;
show();
return ok;
}
Затем пользователь должен ввести информацию и щелкнуть на кнопке "ОК" или "Cancel". Только обработчик событий, связанных с кнопкой "ОК", может установить флажок ok равным значению true. Обработчики событий, связанных с обеими кнопками, вызывают метод setVisible (false), прекращающий выполнение метода show. Кроме того, пользователь может прекратить диалог, закрыв окно. Если для диалогового окна не назначен слушатель окон, то по умолчанию выполняются обычные операции по закрытию окон: диалоговое окно становится невидимым, что также прекращает выполнение метода show.
Обратите внимание на то, что вызов метода show () не возвращает управление, пока диалоговое окно не станет невидимым. Этим библиотека Swing выгодно отличается от пакета AWT. В первой версии этого пакета вызов метода show () немедленно возвращал управление, даже если диалоговое окно было модальным. Это чрезвычайно затрудняло получение информации от диалогового окна.
Значение, возвращаемое методомследует проверить. Если оно рав
но значению true, пользователь допускается к диалогу. В этом случае можно вызывать методы, обеспечивающие ввод данных.
public User
{
return new
}
В этом примере мы используем единственный класс для данных, которые обрабатываются диалоговым окном. Некоторые программисты предпочитают для каждого компонента пользовательского интерфейса предусматривать собственные методы getPassword). Однако при работе с более сложными объектами следует искать подходящие методы для их отображения и редактирования. В этом случае удобнее создать отдельный класс.
Обмен данными с безмодальным диалоговым окном является непростой задачей. При отображении такого окна метод show не блокируется, и программа продолжает свою работу. Если пользователь выберет какой-то вариант в безмодальном диалоговом окне и щелкнет на кнопке "ОК", это окно должно послать событие некоторому слушателю. Таким образом, какой-то класс должен реализовывать соответствующий интерфейс слушателя, а объект этого класса должен быть зарегистрирован в качестве слушателя. Это можно сделать, посылая либо собственное событие (custom events) (см. главу 8), либо событие, связанное с изменением свойств (property change event) (см. главу отоме 2).
В демонстрационной программе показаны и другие полезные усовершенствования. При создании объекта класса JDialog необходимо указывать собственный фрейм. Однако зачастую нужно отображать на экране один и тот же диалог с разными собственными фреймами. Собственный фрейм должен задаваться, когда диалоговое окно готово к выводу наа не во время создания объекта класса
Этого можно добиться, если класс PasswordChooser будет расширять класс JPanel, а не класс JDialog. Посмотрим, как объект класса JDialog создается ту" в методе showDialog.
public booleanowner, String title)
{
ok = false;
if (dialog == null!= owner)
{
dialog = newtrue);
dialog.getContentPane().add(this);
dialog.pack();
}
;
;
return ok;
}
Отметим, что для большей безопасности следует полагать ссылку owner равной null.
Более того, иногда собственный фрейм бывает недоступен. Довольно просто вычислить его на основе любого компонента из объекта parent.
Frame owner;
if (parent instanceof Frame)
owner = (Frame) parent;
else
owner = (Frame)Swingutilities.getAncestorOfClass( Frame.class, parent);
Это усовершенствование применяется в демонстрационной программе. Класс JOptionPane также использует этот механизм.
Многие диалоговые окна имеют кнопку, заданную по умолчанию, которая выбирается автоматически, если пользователь нажимает клавишу ввода (в большинстве стилей этой клавишей является <ENTER>). Кнопка, заданная по умолчанию, выделяется среди остальных, чаще всего жирным контуром.
Для установки кнопки по умолчанию на корневой панели (root pane) диалогового окна нужно вызвать метод
dialog.getRootPane().setDefaultButton(okButton);
Если вы последуете нашему совету и поместите диалоговое окно на панель, будьте осторожны: устанавливайте кнопку по умолчанию только после упаковки панели в диалог. Панель сама по себе не имеет корневой панели.
В листинге 9.20 приведен полный текст программы, иллюстрирующей обмен дан-
ными с диалоговым окном.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DataExchangeTest
{
public static void main(String[] args)
{
DataExchangeFrame frame = new DataExchangeFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
Фрейм с меню, содержащим пункт File->Connect, при выборе
которого на экране появляется диалоговое окно "password".
*/
class DataExchangeFrame extends JFrame
{
public DataExchangeFrame()
{
setTitle("DataExchangeTest");
setSize(WIDTH, HEIGHT);
// Создаем меню "File".
JMenuBar mbar = new JMenuBar();
setJMenuBar(mbar);
JMenu fileMenu = new JMenu("File");
mbar.add(fileMenu);
34.35.36.37.41.42.43.44.53.62.75.81.82.83.87.34.35.36.37.41.42.43.44.53.62.75.81.82.83.87.// Добавляем пункты "Connect" и "Exit".
34.
35.
36.
37.
41.
42.
43.
44.
53.
62.
75.
81.
82.
83.
87.
34.
35.
36.
37.
41.
42.
43.
44.
53.
62.
75.
81.
82.
83.
87.
JMenuItem connectitem = new JMenuItem("Connect"); connectItem.addActionListener(new ConnectAction()); fileMenu.add(connectltem);
// Выбор пункта "Exit" завершает работу программы.
JMenuItem exititem - new JMenuItem("Exit"); exititem.addActionListener(new
ActionListener() {
public void actionPerformed(ActionEvent event) {
System.exit(0);
} });
fileMenu.add(exitltem);
textArea = new JTextArea ();
getContentPane().add(new JScrollPane(textArea), BorderLayout.CENTER);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
private PasswordChooser dialog = null; private JTextArea textArea;
/**
При выборе пункта "Connect" на экран выводится диалоговое окно "Password".
*/
private class ConnectAction implements ActionListener {
public void actionPerformed(ActionEvent event) {
// При первом вызове создаем диалоговое окно.
if (dialognull)
dialog = new Passwordchooser();
// Задаем значения ПС умолчанию.
dialog.setuser(new User("yourname", null));
// Выводим на экран диалоговое окно.
if (dialog.showDialog(DataExchangeFrame.this, "Connect"))
{
// Начинаем ввод данных.
User u = dialog.getUser();
textArea.append( "user name = " + u.getName(] + ", password = " + (new
tring(u.getPassword())) + "\n");
}
}
}
}
/**
Компонент для выбора пароля.
*/
class Passwordchooser extends JPanel
{
public PasswordChooser ()
{
setLayout(new BorderLayout());
// Создаем панель с полями для ввода имени и пароля.
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2, 2));
panel. add (new JLabelCUser name:"));
panel.add(username = new JTextFieldf"));
panel.add(new JLabel("Password:"));
panel.add(password = new JPasswordFieldC") );
add(panel, BorderLayout.CENTER);.
// Создаем кнопки "OK" и "Cancel", прекращающую диалог.
JButton okButton = new JButtonCOk") ;
okButton.addActionListener(new
ActionListener ()
{
publicvoid actionPerformed(ActionEvent event)
{
ok = true;
dialog.setvisible(false);
}
});
JButton cancelButton = new JButton("Cancel");
. cancelButton.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event)
'{/
dialog.setvisible(false);
}
});
// Добавляем кнопки к нижней рамке.
JPanel buttonPanel = new JPanel 0;
buttonPanel.add(okButton);
buttonPanel.add(cancelButton);
add(buttonPanel, BorderLayout.SOUTH);
}
/**
Задает значения ПС умолчанию.
@param u информация, задаваемая по умолчанию
*/
149.150.151.152.153.154.184 .192204 .205.}149.150.151.152.153.154.184 .192204 .205.}public void setuser(User u)
149.
150.
151.
152.
153.
154.
184 .
192
204 .
205.}
149.
150.
151.
152.
153.
154.
184 .
192
204 .
205.}
{
username.setText(u.getName());
}
/**
Получает введенные данные.
Sreturn а объект, состояние которого определяется введенными данными
*/
public User getUser() {
return new User(username.getText(), password.getPassword());
}
/**
Вывести панель для выбора в диалоговом окне.
@param parent компонент в собственном фрейме или null
Sparam title заголовок диалогового окна
*/
public boolean showDialog(Component parent, String title) {
ok = false;
// Находим собственный фрейм.
Frame owner = null;
if (parent instanceof Frame) owner = (Frame) parent;
else
owner = (Frame)Swingutilities.getAncestorOfClass( Frame.class, parent);
// Если фрейм только что создан или был изменен, // создаем новое диалоговое окно.
if (dialog == null I I dialog.getOwner() != owner) {
dialognew JDialog(owner, true);
dialog.getContentPane().add(this);
dialog.pack();
}
// Задаем заголовок и выводим диалог на экран.
dialog.setTitle (title) ;
dialog.show(); return ok;
}
private JTextField username;
private JPasswordField password;
private boolean ok;
/**/**private JDialog dialog;
/**
/**
Каждый пользователь имеет имя и пароль. Для безопасности
пароль хранится в виде массива char[], а не String.
*/
class User
{
public User(String aName, chart] aPassword)
{
name = aName;
password = aPassword;
}
public String getNaine () { return name; }
public char[] getPassword(; ( return password; }
public void setName(String aName) { name = aName; }
public void setPassword(char[ ] aPassword)
{ password = aPassword; }
private String name;
private char[" password;
228.}
■ -■_-*»'v•
API javax.swing.Swingutilities 1.2
^^^— < • Container getAncestorOfClass(Vlass c, Component comp]
Возвращает наиболее глубоко вложенный родительский контейнер заданного компонента, принадлежащего заданному классу или одному из его подклассов.
API javax.swing.JComponent 1.2
^■■i • JRootPane getRootPane (!
Определяет корневую панель, содержащую заданный компонент. Если компонент не имеет предка с корневой панелью, возвращает ссылку null. «ьуя/
APIjavax.swing.JRootPane
■■■■i • void setDefaultButton(JButton button)
Устанавливает на корневой панели кнопку по умолчанию. Чтобы заблокировать кнопку по умолчанию, этот метод вызывается с параметром null.
|APIj avax. swing. JButton 1.2
। ™i . boolean isDefaultButton()
Возвращает значение true, если данная кнопка является кнопкой по умолчанию на корневой панели.
Диалоговые окна для выбора файлов
Во многих приложениях нужно открывать и сохранять файлы. Хорошее диалоговое окно, позволяющее пользователю свободно перемещаться по файловой системе, написать довольно трудно. Да это и не нужно — ведь вы не собираетесь изобретать колесо?! К счастью, в библиотеке Swing есть класс JFileChooser, позволяющий отображать на экране диалоговое окно для выбора файлов, которое удовлетворяет потребности боль-
шинства приложений. Это диалоговое окно всегда является модальным. Обратите внимание: класс JFileChooser не расширяет класс JDialog. Вместо вызова метода show для отображения диалогового окна, предназначенного для открытия файлов, вызывается метод showOpenDialog, а для сохранения файлов— метод showSaveDialog. Кнопка, подтверждающая выбор файла, автоматически называется "Open" или "Save". С помощью метода showDialog можно задать свою собственную метку кнопки. На рис. 9.50 показан пример диалогового окна для выбора файлов.
Рис. 9.50. Диалоговое окно для выбора файлов
Вот что нужно сделать, чтобы отобразить на экране диалоговое окно для выбора файлов и определить, какой выбор сделал пользователь.
Создайте объект класса JFileChooser. В отличие от конструктора класса JDialog, родительский компонент указывать не нужно. Это позволяет ло- вторно использовать диалоговое окно для выбора файлов с несколькими фреймами.
JFileChooser chooser = new JFileChooser();
Повторное использование диалогового окна для выбора файлов — прекрасная идея, поскольку конструктор класса JFileChooser может работать довольно медленно, особенно в среде Windows, если файловая система пользователя распределена по сети.
Укажите каталог, вызвав метод setCurrentDirectory. Например, чтобы указать текущий рабочий каталог, нужно выполнить следующий вызов.
chooser.setCurrentDirectory(new File("."));
При этом необходимо задать объект класса File. Детально этот класс описывается в главе 12, а пока вам достаточно знать, что в этом классе есть конструктор File (String filename), преобразовывающий имя файла или каталога в объект класса File.
Если нужно предложить пользователю имя файла, выбираемого по умолчанию, задайте его с помощью метода setSelectedFile.
chooser.setSelectedFile(new File(filename));
Чтобы дать пользователю возможность выбрать одновременно несколько файлов, вызовите метод setMultiSelectionEnabled. Разумеется, делать это совершенно не обязательно, поскольку подобная возможность нужна далеко не всегда.
chooser.setMultiSelectionEnabled(true);
Для того чтобы ограничить выбор пользователя файлами, имеющими определенное расширение (например, расширение -gif)-нужно задать фильтр файлов (file filter). Этот вопрос мы обсудим немного позже.
По умолчанию пользователь может выбирать в диалоговом окне JFileChooser только файлы. Если вы хотите предоставить пользователю возможность выбирать целые каталоги, используйте метод setFileSelectionMode и вызывайте его со следующими параметрами:
JFileChooser.FILES_ONLY (по умолчанию)
JFileChooser.DIRECTORIESONLYили
JFileChooser.FILES_AND_DIRECTORIES.
Диалоговое окно выводится на экран с помощью методов showOpenDialog или showSaveDialog. При вызове этих методов нужно указывать родительский компонент.
int result = chooser.showOpenDialog(parent) ;
int result = chooser.showSaveDialog(parent);
Единственное отличие между этими вызовами заключается в метке "кнопки выбора", т.е. кнопки, на которой пользователь щелкает, выбирая файл. Можно также вызвать метод showDialog и задать явный текст, который должен располагаться на поверхности кнопки.
int result = chooser.showDialog(parent, "Select");
Эти вызовы возвращают управление только тогда, когда пользователь сделал выбор или прекратил диалог, закрыв окно. Возвращаемое значение может быть одной из следующих констант: JFileChooser.APPROVE_OPTION или JFileChooser.CANCEL_OPTION.
Выбранный файл можно получить с помощью методов getSelectedFile () или getSekectedFiles (). Эти методы возвращают либо один объект класса File, либо массив таких объектов. Если пользователю нужно знать лишь имя выбранного файла, вызывайте метод getPath.
String filename = chooser.getSelectedFile().getPath();
Большинство этих действий довольно просты. Основная трудность связана с использованием диалогового окна для выбора файлов, ограничивающего выбор пользователя. Например, допустим, что пользователь должен выбирать только графические файлы в формате GIF. Следовательно, в диалоговом окне должны отображаться только файлы, имеющие расширение .gif. Кроме того, пользователю нужно подсказать, что это за файлы, например "Графические файлы в формате GIF". Однако ситуация может оказаться еще сложнее. Графические файлы в формате JPEG могут иметь расширения .jpg или .jpeg. Для преодоления этих трудностей разработчики предложили элегантный механизм: чтобы ограничить спектр отображаемых файлов, нужно создать объект, реализующий интерфейс javax. swing. filechooser. FileFilter. Диалоговое окно передает фильтру каждый файл и отображает на экране только файлы, прошедшие его.
Чтобы ограничить спектр файлов, отображаемых в диалоговом окне, нужно создать объект класса, расширяющего класс FileFilter. В момент написания этой книги был известен только один такой подкласс: фильтр, задаваемый по умолчанию, пропускающий все файлы.
Создать свой собственный фильтр довольно легко. Нужно просто реализовать два метода интерфейса FileFilter.
public booleanf)
public String
В пакете io существует еще один интерфейс FileFilter (совершенно независимый от описанного выше), в котором есть только один метод boolean accept (File Он используется в методе listFiles класса File. Совершенно непонятно, почему разработчики библиотеки Swing не стали расширять этот возможно, библиотека классов языка Java настолько сложна, что даже программисты компании Sun не знают обо всех стандартных классах и интерфейсах.
Если в программу одновременно импортируются пакеты java.io и javax. filterchooser, нам снова придется разрешать конфликт имен между этими типами. Проще всего вместо пакета f * импортировать пакет j avax . f ilechooser
Первый из указанных методов проверяет, соответствует ли файл ограничениям, наложенным на выбор. Второй метод возвращает описание типа файла, который может отображаться в диалоговом окне. Например, для отбора графических файлов в формате GIF можно использовать следующий фрагмент кода.
public class GetFiler extends FileFilter {
public booleanf)
{
return
| |;
}
public String
{ return "GIF image");
Для того чтобы связать объект класса, реализующего интерфейс FileFilter, с объектом класса JFileChooser, следует применять метод setFileFilter.
chooser.setFileFiIter(new GifFilterl));
В демонстрационной программе предусмотрен класс ExtensionFileFilter, который используется следующим образом.
ExtensionFileFilter filter = new
;
;
files");
Реализация класса ExtensionFileFilter сводится к непосредственному обобщению классаЭтот класс вы можете использовать и в своих программах.
•Пакет Java SDK содержит аналогичный классExampleFileFilter в каталоге demo/jfc/FileChooserDemo.
Инсталлировать несколько фильтров для диалогового окна, предназначенного для выбора файлов, можно с помощью следующих вызовов.
chooser.addChoosableFileFilter(new GifFilter());
chooser.addChoosableFileFilter(new JpegFilterO; ;
Диалоговое окно для выбора файлов можно снабдить несколькими фильтрами, выбранными из комбинированного списка, расположенного внизу окна. По умолчанию в этом списке всегда есть пункт "All files". Это неплохо, особенно если пользователь хочет отсеять файлы с нестандартными расширениями. Однако если потребуется отменить фильтр "All files", следует вызвать следующий метод.
chooser. setAcceptAHFileFilterUsed (false)
Если одно и то же диалоговое окно используется для чтения и записи файлов, следует вызвать метод •
chooser.resetChoosableFilters(1
Это позволит очистить старые фильтры файлов перед добавлением новых.
В заключение отметим, что в диалоговом окне каждый файл можно сопроводить специальной пиктограммой и кратким описанием. Для этого нужно предусмотреть объект класса, расширяющего класс FileView из пакета javax. swing. filechooser. Совершенно очевидно, что это довольно сложно. Обычно внешний вид файла программиста не интересует — настраиваемые стили интерфейса сами решают эту задачу. Однако, если вы захотите сопроводить файлы определенного типа специальными пиктограммами, можно задать свой собственный стиль отображения файлов. Для этого нужно расширить класс FileViewи реализовать пять методов:
Icon getIcon(File f);
String getName(File fl ;
String getDescription(File f);
String getTypeDescription(File f);
Boolean isTraversable(File f);
Затем следует вызвать метод setFileView и связать внешний вид файла с диалоговым окном.
Диалоговое окно вызывает эти методы для каждого файла или каталога, подлежащего выводу на экран. Если для пиктограммы, имени или описания файла метод возвращает ссылку null, диалоговое окно использует стиль, заданный по умолчанию. Это правильно, поскольку позволяет применять особый стиль только к указанным типам файлам.
Для того чтобы решить, открывать ли каталог, выбранный пользователем, диалоговое окно вызывает метод isTraversable. Обратите внимание на то, что этот метод возвращает объект класса Boolean, а не значение типа boolean! Это выглядит довольно загадочно, но удобно — если у вас нет особых требований к стилю, просто возвращайте нулевую ссылку null. Затем диалоговое окно использует стиль отображения файлов, заданный по умолчанию. Иными словами, метод возвращает объект класса
Boolean, чтобы дать вам возможность выбрать одно из трех: да (Boolean. TRUE), нет (Boolean.FALSE) и не важно (null).
Демонстрационная программа содержит простой класс, задающий стиль отображения файлов. Этот класс выводит на экран определенную пиктограмму каждый раз, когда файл проходит фильтр. Мы используем этот класс для отображения на экране палитры пиктограмм для всех графических файлов.
class FilelconView extends FileView
public FileIconView(FileFilter aFilter, Icon anlcon)
filter = aFilter;
icon = anlcon;
public Icon getIcon(File f)
{
if (!f.isDirectory(1 s& filter.accept(f)) return icon;
else return null;
}
private FileFilter filter;
}}private Icon icon;
}
}
В версии SDK 1.2 необходимо определять все пять методов подкласса FileView. Если метод не нужен, можно просто вернуть значение null. В версии SDK 1.3 методы класса FileView больше не являются абстрактными.
Чтобы связать объект класса FileView с диалоговым окном, применяется метод setFileView.
chooser.setFileview(new FileIconView(filter, new Imageicon("palette.gif") ; )) ;
Если соответствующие файлы прошли фильтр, диалоговое окно выведет на экран пиктограмму палитры, а для всех остальных файлов будет использоваться стиль, заданный по умолчанию.
В каталоге demo/j fc/FileChooserDemc пакета SDK можно найти более полезный класс ExampleFileview. Этот класс позволяет связывать пиктограммы и описания с любыми расширениями файлов.
В заключение отметим, что диалоговое окно для выбора файлов можно снабдить вспомогательным компонентом (accessory component). Например, на рис. 9.51 показан вспомогательный компонент, позволяющий отображать наряду со списком файлов содержимое файла, выбранного в данный момент.
Вспомогательным может быть любой компонент библиотеки Swing. В нашем случае мы расширяем класс JLabel и связываем его пиктограмму с уменьшенной копией графического изображения.
class ImagePreviewer extends JLabel
public ImagePreviewer(JFileChooser chooser) {
setPreferredSize (new Dimension (100, 100));
setBorder(BorderFactory.createEtchedBorder());
public void loadimage(File f)
(.
Imafelcon icon = new Imageicon(f.getPath());
if(icon.getlconWidth(] > getWidth ())
icon = new Imageicon(icon.get Image().getScaledlnstance(getWidth(), -1, Image.SCALE_DEFAULT)) ;
seticon(icon);
repaint ();
Осталась лишь одна проблема. Мы хотим обновлять предварительное изображение всякий раз, когда пользователь выбирает новый файл. Для этого диалоговое окно ис- пользуетмеханизмJavaBeans, извещаязарегистрированныхслушателейобизменениях своих свойств. Выбранныйфайл—этосвойство, которое можно отслеживать с помощью метода PropertyChangeListener. Более детально этот механизм обсуждается во второмтоме. Нижепоказано, какимобразомпроисходитизвещениеслушателей.
chooser.addPropertyChangeListener( new
PropertyChangeListener(]
public voidevent)
(
if (event .getPropertyName (0.) ==
JFileChooser.SELECTED FILE CHANGED PROPERTY]
File newfile = (File)event.getNewValue(]
// Обновление вспомогательного компонента.
}
});
В демонстрационной программе этот код находится внутри конструктора класса ImagePreviewer.
Рис. 9.51. Диалоговое окно для выбора файлов с предварительным просмотром
В листинге 9.21 содержится модифицированная программа ImageViewer из главы 2, в которой диалоговое окно для выбора файлов дополнено стилем внешнего вида файлов и компонентом для предварительного просмотра их содержимого.
import
import
import
. import
import
import java.io.*;
.import
import
import
. public class FileChooserTest
{
public static voidargs)
(
frame = new
}
/**
Фрейм, содержащий меню для загрузки графических файлов и область для
отображения загруженных изображений.
*/
class ImageViewerFrame extends
public
// Создаем линейку меню
JMenuBar menuBar = new
JMenu menu = new
;
JMenuItem openltem = new
JMenuItem exitltem = new
public voidevent)
{.....
57.62.73.83.84.85.92.97.57.62.73.83.84.85.92.97.System.exit(0);
57.
62.
73.
83.
84.
85.
92.
97.
57.
62.
73.
83.
84.
85.
92.
97.
} });
// Используем метку для отображения рисунков
label = new JLabel();
Container contentPane = getContentPane() ;
contentPane.add(label);
// Устанавливаем диалоговое окно для выбора файла chooser = new JFileChooser();
// Фильтруем все графические файлы с расширениями
// •jpg. -jpeg, -gif
final ExtensionFileFilter filter
new ExtensionFileFilter();
filter. addExtensionfjpg") ;
filter.addExtension("jpeg");
filter.addExtension("gif") ;
filter.setDescription("Image files");
chooser.setFileFilter(filter);
chooser.setAccessory(new ImagePreviewer(chooser));
chooser.setFileview(new FilelconView(filter, new Imageicon("palette.gif')));
}
/**
Слушатель пункта меню File->Open.
*/
private class FileOpenListener implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
chooser.setCurrentDirectory(new File("."));
// Выводим на экран диалоговое окно для выбора файла int result
=
// Если файл прошел через фильтр, // изображаем его в виде пиктограммы if(result ==
{
String name =
label.seticon(new Imageicon(name));
}
}
}
public static final int DEFAULT_WIDTH = 300;
public static final int DE FAULT_H EIGHT = 400;
private JLabel label;
private JFileChooser chooser;
}
115.122.123.129.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167.115.122.123.129.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167./**
115.
122.
123.
129.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
115.
122.
123.
129.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
Фильтр файлов, сравнивающий расширения всех файлов с заданными.
*/
class ExtensionFileFilter extends FileFilter {
/** •
Добавляет расширение, распознаваемое фильтром.
@param extension расширение файла(например, ".txt")
*/
public void addExtension(String extension) {
if (!extension.startsWith(".")’ extension = "." + extension;
extensions.addtextension.toLowerCase());
}
/**
Задает описание множества файлов, распознаваемых данным фильтром. @param aDescription описание множества файлов
*/
public void setDescription(String aDescription) {
description = aDescription;
}
/**
Возвращает описание множества файлов, распознаваемых указанным фильтром. @return описание множества распознаваемых файлов
*/
public String getDescription(;
{ return description;
}
public boolean accept(File f) {
if (f.isDirectory()) return true;
String name = f.getName().toLowerCase();
// Проверяем расширение файла.
for (int i = 0; i < extensions.size(); i++)
if (name.endsWith((String)extensions.get(i) )) return true;
return false;
}
private String description = "";
private ArrayList extensions = new ArrayList (); }
/**
Стиль изображения файлов, распознаваемых заданным фильтром.
*/
class FilelconView extends FileView {
/**
Создает объект класса FilelconView.
@param aFilter фильтр файлов -- все файлы,
168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.184.183.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204 .205.206.207.208.209.210.211.212.213.214.215.216.217.218.219.220.221.222.223.224.168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.184.183.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204 .205.206.207.208.209.210.211.212.213.214.215.216.217.218.219.220.221.222.223.224.прошедшие через фильтр, изображаются пиктограммами.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
184.
183.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204 .
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
184.
183.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204 .
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
@param anlcon -- заданная пиктограмма.
*/
public FilelconView(FileFilter aFilter, Icon anlcon) {
filter = aFilter;
icon = anlcon;
}
public Icon getIcon(File f)
{
if (!f.isDirectory() && filter.accept(f)I return icon;
else return null;
}
private FileFilter filter;
private Icon icon;
}
/**
Вспомогательный компонент для выбора файла, обеспечивающий предварительный просмотр.
*/
class ImagePreviewer extends JLabel
{
/**
Создает объект класса ImagePreviewer.
@param chooser компонент для выбора файла, свойства
которого влияют на изображение в окне просмотра
*/
public ImagePreviewer(JFileChooser chooser) {
setPreferredSize (new DimensiondOO, 100));
setBorder(BorderFactorу.createEtchedBorder() ; ;
chooser.addPropertyChangeListener(new PropertyChangeListener(;
{
public void propertychange(PropertyChangeEvent event)
{
if==
JFileChooser,SELECTED_FILE_CHANGED_PROPERTY:
{
// Пользователь выбрал новый файл.
File f = (File)event.getNewValue();
// Считываем пиктограмму.
Imageicon icon = new ImageIcon(f.getPath());
// Масштабируем пиктограмму, если она велика.
if(icon.getIconWidth(] > getWidthl))
icon = new Imageiconl
icon.getlmage().getScaledlnstance(
getWidth(), -1, Image.SCALE_DEFAULT;);
225.225.226.226.seticon(icon!'seticon(icon!'227.227.228.228.}}229. }229. }});});230. }230. }
225.
225.
226.
226.
seticon(icon!'
seticon(icon!'
227.
227.
228.
228.
}
}
229. }
229. }
});
});
230. }
230. }
p-^^v
APIjavax. swing. JFileChooser 1.2
< • JFileChooser()
Создает диалоговое окно для выбора файлов, которое можно использовать с несколькимифреймами.
void setCurrentDirectory(File dir)
Задает исходный каталог, просматриваемый в диалоговом окне для выбора файлов.
void setSelectedFile(File file) void setSelectedFiles(File[ ’ file) Задает выбор файла no умолчанию.
void setMultiSelectionEnabled(boolean b)
Устанавливает или отменяет режим выбора нескольких файлов.
void setAcceptAHFileFilterUsed (boolean b) Устанавливает или блокирует фильтр "All files" в комбинированном списке фильтров.
void setFileSelectionMode(int mode)
Позволяет пользователю выбирать только файлы (по умолчанию), только каталоги или и файлы, и каталоги одновременно. Параметр mode может принимать одно из следующих значений.
JFileChooser.FILESONLYТолько файлы
JFileChooser.DIRECTORIES_ONLYТолько каталоги
JFileChooser . FILES_AND_DIRECTORIES И файлы, и каталоги
int showOpenDialog(Component parent) int showSaveDialog(Component parent) int showDialog(Component parent, String approveButtonText) Выводит на экран диалоговое окно, в котором кнопка выбора имеет имя "Open", "Save" или заданное строкой approveButtonText. Возвращает значенияAPPROVE_OPTION и CANCEL—OPTION.
File getSelectedFile() File[] getSelectedFiles() Получает файл или файлы, выбранные пользователем (или возвращает значениеnull, если пользовательничего невыбрал).
void setFileFilter(javax.swing.filechooser.FileFilter filter)
Устанавливает маску фильтра. На экране отображаются только файлы, прошедшие через фильтр filter, accept (для которых он возвращает значение true).
void setFileView(FileView view)
Задает стиль отображения файлов.
void setAccessory(JComponent component)
Задает вспомогательный компонент.
^Pjavax.swing.filechooaer.FileFilter 1.2
boolean accept(File f)
Возвращает значение true, если диалоговое окно должно отображать ука- занныйфайл.
String getDescription()
Возвращает описание указанного фильтра файлов, например "Графические файлы (*.gif, *.jpeg)".
AP!IIjavax.swing.filechooser.FileView 1.2
■■■■I • String getName(File f)
Возвращает имя файла f или нулевую ссылку null. Обычно этот метод просто возвращает значение f. getName ().
String getDescription(File f)
Возвращает читабельное описание файла f или нулевую ссылку null. Например, если файл f представляет собой документ в формате HTML, этот метод может вернуть его заглавие.
String getTypeDescription(File f)
Возвращает читабельное описание типа файла f или нулевую ссылку null. Например, если файл f представляет собой документ в формате HTML, этот метод может вернуть строку "Гипертекстовый документ".
Icon getIcon(File f)
Возвращает пиктограмму, назначенную для файла f, или нулевую ссылку null. Например, если f является файлом в формате JPEG, этот метод может вернуть масштабируемую пиктограмму.
Boolean isTraversable(File f)
Если пользователь может открыть указанный каталог, возвращает значение Boolean . TRUE. Если каталог представляет собой составной документ, может возвращать значение false. Аналогично методу FileView этот метод может возвращать ссылку null, отмечая тот факт, что диалоговое окно должно использовать обычный стиль отображения файлов.
Диалоговое окно для выбора цветов
Как следует из предыдущего подраздела, диалоговое окно для выбора файлов — довольно замысловатый компонент пользовательского интерфейса, который вы вряд ли захотите реализовать самостоятельно. Многие инструментальные средства для
создания пользовательского интерфейса дают возможность создавать диалоговые окна для выбора даты или времени, валюты, шрифтов, цветов и т.п. Это дает двойную выгоду. Программисты могут просто использовать высококачественные компоненты и не изобретать велосипед, а пользователи получают большой выбор.
Пока в пакете Swing предусмотрено только одно дополнительное диалоговое окно для выбора, а именно: для выбора цветов— класс JColorChooser (рис. 9.52-9.54). Как и класс JFileChooser, класс JColorChooser является компонентом, а не диалогом, однако он содержит удобные методы, позволяющие создавать диалоги, содержащие компонент для выбора цветов.
Рис. 9.52. Панель "swatches" диалогового окна для выбора цветов
Рис. 9.53. Панель HSB диалогового окна для выбора цветов
Рис. 9.54. Панель RGB диалогового окна для выбора цветов
Модальное диалоговое окно для выбора цветов создается с помощью следующего оператора.
Color selectedColor = JColorChooser.showDialog(parent, title, initialColor);
Кроме того, можно отобразить на экране и безмодальное диалоговое окно для выбора цветов. Для этого нужны следующие элементы.
Родительский компонент.
Заголовок диалога.
Флажок опции модального или безмодального диалогового окна.
Компонент для выбора цвета.
Слушатель кнопок “OK и "Cancel" (или параметр null, если слушатели не нужны).
Попробуем создать безмодальное диалоговое окно для установки цвета фона с помощью кнопки "ОК".
chooser = new JColorChooser();
dialog = JColorChooser.createDialog(
parent,
"Background Color",
false /* безмодальное окно *.
chooser,
new ActionListener(] // Слушатель кнопки "OK". {
public void actionPerformed(ActionEvent event)
{ setBackground(chooser.getColor()); ) },
null /* Слушателя кнопки Cancel нет */) ;
Еще лучше предоставить пользователю возможность сразу увидеть эффект от сделанного выбора. Для отслеживания выбора нужна модель выбора и слушатель изменений.
(new
ChangeListener()
public voidevent)
Некоторые операции с методом
}
});
В данном случае кнопки "ОК" и "Cancel" не получают никаких преимуществ, предоставляемых диалоговым окном для выбора цветов. Можно просто добавить компонент для выбора цвета в безмодальный диалог.
dialog = newfalse /*
dialog.getContentPane().add(chooser); dialof.pack();
Программа, приведенная в листинге 9.22, показывает три типа диалога. Щелкнув на кнопке "Modal", пользователь должен будет выбрать цвет, и лишь затем сможет продолжить работу. Щелкнув на кнопке "Modal", пользователь создаст безмодальный диалог, но изменение цвета произойдет только после того, как он щелкнет на кнопке "ОК". И наконец, щелкнув на кнопке "Immediate", пользователь создаст безмодаль- ный диалог без кнопок, и, как только он выберет новый цвет, фоновый цвет панели немедленно изменится.
На этом мы завершаем обсуждение компонентов пользовательского интерфейса. Материал, изложенный в главах 7-9, позволит вам самостоятельно создавать простые графические интерфейсы с помощью библиотеки Swing. Более подробно и глубоко эта тема освещена во втором томе.
Листинг 9.22. Программа ColorChooserText.java ■ —
import
import
import
import
. public class ColorChooserTest
{
public static voidargs)
{
12.
13.
14.
15.
}
}
ColorChooserFrame frame = new
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show();
16./**
17.Фрейм с панелью, содержащей компонент для выбора цвета. 18.
class ColorChooserFrame extends JFrame
*/
20.{
public
{.
566
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48 .
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
setTitle("ColorChooserTest");
setSize(WIDTH, HEIGHT);
// Добавляем панель для выбора цвета.
ColorChooserPanel panel = new ColorChooserPanel();
Container contentPane = getContentPane(); contentpane.add(panel);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
/**
Панель с кнопками для выбора трех типов компонентов.
*/ class ColorChooserPanel extends JPanel {
public ColorChooserPanel() {
JButton modalButton = new JButton("Modal");
modalButton.addActionListener(new ModalListener()); add(modalButton);
JButton modelessButton = new JButton("Modeless"); modelessButton.addActionListener I new ModelessListener());
add(modelessButton);
JButton immediateButton = new JButton("Immediate"); immediateButton.addActionListener(
new immediateListener(] ) ;
add(immediateButton); }
/**
Слушатель, выводящий на экран модальное диалоговое окно.
*/
private class ModalListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
Color defaultColor = getBackground();
Color selected = JColorChooser.showDialogI ColorChooserPanel.this, "Set background",
defaultColor);
setBackground(selected);
} }
/**
Слушатель, выводящий на экран безмодальное диалоговое окно. Цвет панели изменяется после щелчка на кнопке "OK".
*/
private class ModelessListener implements ActionListener {
,,public ModelessListener()
,
,
.{
83.83.84.84.85.85.86.86.101.111.112.101.111.112.
83.
83.
84.
84.
85.
85.
86.
86.
101.
111.
112.
101.
111.
112.
113.114 .113.114 .chooser = new JColorChooser();
113.
114 .
113.
114 .
dialog = JColorChooser.createDialog(
ColorchooserPanel.this,
"Background Color",
false /* безмодальное окно */, chooser,
new ActionListener() // Слушатель кнопки "OK".
{
public void actionPerformed(ActionEvent event) {
setBackground(chooser. getColor());
}
},
null /* Слушателя кнопки "Cancel" нет. */) ;
}
public void actionPerformed(ActionEvent event) {
chooser.setcolor(getBackground());
dialog.show();
}
private JDialog dialog;
private JColorChooser chooser;
}
/**
Слушатель, выводящий на экран безмодальное диалоговое окно. Цвет панели изменяется немедленно.
+/
private class ImmediateListener implements ActionListener {
public ImmediateListener(I
{
choosernew JColorChooser();
chooser.getSelectionModel().addChangeListener(new ChangeListener(1
{
public void stateChanged(ChangeEvent event)
{
setBackground(chooser.getColor());
}
});
dialog = new JDialog(
(Frame)null,
false /* безмодальное окно */) ;
dialog.getContentPane().add(chooser);
dialog.pack();
}
public void actionPerformed(ActionEvent event)
{
chooser.setcolor(getBackground());
dialog.show() ;
}
private JDialog dialog;
private JColorChooser chooser;
}
java.swing.JColorChooser1.2
JColorChooser()
Создает компонент для выбора цвета, у которого начальным цветом считается белый.
Color getColor('
void setColor(Color c)
Получают и устанавливают текущий цвет для данного компонента выбора цвета.
static Color showDialog(Component parent, String title, Color initialColor)
Отображает на экране модальное диалоговое окно, содержащее компонент для выбора цвета.
Параметры: parent
Компонент, поверх которого отображает-
title
ся диалоговое окно.
Заголовок фрейма диалогового окна.
initialColor
Начальный цвет в компоненте для выбора цвета.
static JDialog createDialog(Component parent, String title, boolean modal, JColorChooser chooser, ActionListener okListener, ActionListener cancelListener]
Создает диалоговое окно, содержащее компонент для выбора цвета.
Параметры: parent
Компонент, поверх которого отображается диалоговое окно.
title
Заголовок фрейма диалогового окна.
modal
Принимает значение true, если вызов должен блокироваться, пока диалоговое окно не будет закрыто.
chooser
Компонент для выбора цвета, который следует добавить в диалоговое окно.
OkListener,Слушатели кнопок “OK" и "Cancel".
cancelListener
cXvtv&tv 10
Аплеты
В этой главе...
Основные понятия
Т Дескрипторы HTML и атрибуты аплетов
▼ Мультимедиа
Т Контекст аплета
Файлы JAR
Т Упаковка приложений
Технология Java Web Start
Т Хранение параметров настройки приложения
В б т
предыдущих главах подробно рассмотрены практически все свойства языка Java, а также основные средства программирования графических приложений. Мы считаем, что язык Java- прекрасный (если не совершенный) универсальный объектноориентированный язык, и надеемся, что читатели разделяют нашу точку зрения. Однако это совершенно не объясняет шумихи, поднятой вокруг языка Java. Невероятная популярность этого языка в первые годы его существования (как указывалось в главе 1) объясняется способностью "оживлять Интернет". Дело в том, что на языке Java можно создавать особые программы (называемые аплетами), которые загружаются из Интернет и выполняются броузерами. Они предназначались для того, чтобы освободить пользователей от утомительного процесса инсталлирования программного обеспечения, предоставив им доступ к программам с любого компьютера, поддерживающего язык Java, или устройства, имеющего выход в Интернет.
По разным причинам аплеты так не смогли выполнить эти обещания. Недавно компания Sun разработала альтернативный подход к доставке приложений, основанных на использовании Интернет. Эта технология называется Java Web Start. В ней исправлены некоторые из недостатков, характерных для аплетов.
В этой главе показано, как создавать и разворачивать аплеты, а также как запаковывать аплеты и приложения для доставки пользователям.
Основные понятия
До появления языка Java для описания Web-страниц программисты использовали язык HTML (HyperText Markup Language — язык гипертекстовых ссылок). Этот язык просто представляет собой средство для описания элементов гипертекстовой страницы. Например, дескриптор <title> обозначает заголовок страницы, которым является любой текст, помещенный после этого дескриптора. Конец заголовка обозначается дескриптором </title>. (Это одно из основных правил, касающихся дескрипторов: косая черта перед именем элемента означает его конец.)
Основная идея, положенная в основу использования аплетов на Web-страницах, довольно проста: страница, описанная на языке HTML, должна сообщать броузеру, какой аплет загрузить и где его разместить. Как и следовало ожидать, дескриптор, необходимый для использования аплета, должен сообщать броузеру следующую информацию.
Откуда получить файлы с классами.
Как расположить аплет на странице (его размеры, координаты и т.д.).
Броузер загружает файлы с классами из сети (или из каталога на компьютере пользователя) и автоматически запускает аплет на выполнение, используя виртуальную машину языкаJava (Java Virtual Machine).
Кроме аплетов, Web-страница может содержать все остальные элементы языка HTML: разнообразные шрифты, маркированные списки, графические изображения и т.д. Аплеты — это всего лишь часть гипертекстовой страницы. Всегда стоит иметь в виду, что язык Java не является средством для разработки гипертекстовых страниц. Это инструмент, позволяющий их оживлять. Отсюда вовсе не следует, что элементы графического пользовательского интерфейса в аплете, написанном на языке Java, не важны. Однако эти элементы должны быть согласованы с соответствующим дизайном Web-страницы, созданным с помощью языка HTML.
Мы вообще не будем обсуждать дескрипторы языка HTML, предполагая, что вы знаете основы этого языка. Для создания аплетов на языке Java нужны лишь несколько специальных дескрипторов языка HTML. Вот их-то мы, конечно, обсудим.
Когда аплеты появились впервые, программисты должны были использовать броузер Hotjava, созданный компанией Sun специально для того, чтобы просматривать Web-страницы, содержащие аплеты. Естественно, нашлось несколько пользователей, которые пожелали работать с отдельным броузером просто для того, чтобы позабавиться с новыми возможностями Web-страниц. Аплеты стали действительно популярными, когда компания Netscape включила виртуальную машину языка Java в свой броузер. Вскоре появился броузер Internet Explorer. К сожалению, возникли две проблемы. Компания Netscape не стала поддерживать более современные версии языка Java, а компания Microsoft не могла решить: неохотно продолжать поддерживать устаревшие версии или совсем отказаться от языкаJava.
Для того чтобы решить эту проблему, компания Sun разработала и распространила инструмент под названием "Java Plug-In" (ранее известный под именем "Activator"). Используя различные механизмы расширения возможностей броузеров Internet Explorer и Netscape, этот инструмент можно совершенно свободно встроить в оба
этих броузера. Это позволяет использовать их для запуска аплетов с помощью внешней среды поддержки выполнения программ на языке Java (Java Runtime Environment), разработанной компанией Sun. Предполагается, что версии виртуальных машин в этих броузерах будут постоянно обновляться, обеспечивая программистам доступ к самым современным и мощным свойствам языка Java.
Чтобы запустить аплеты, приведенные в главе, с помощью своего броузера, необходимо инсталлировать текущую версию программы Plug-In и убедиться в том, что броузер связан с ней. Для загрузки и настройки броузера зайдите на Web- страницу http://java.sun.com/getjava/. .
Рис. 10.1.Иерархия наследования аплетаРис. 10.1.Иерархия наследования аплета
Рис. 10.1.Иерархия наследования аплета
Рис. 10.1.Иерархия наследования аплета
По общему мнению, если вы разрабатываете Web-страницы для широкой аудитории, не имеет смысла просить посетителя вашей web-страницы инсталлировать программу Java Plug-In, которая довольно долго загружается. В этом случае нужно разрабатывать аплеты, способные работать с виртуальными машинами языка Java, встроенными в броузеры Netscape и Internet Explorer. Кроме того, лучше всего использовать свойства версии Java 1.0 и стремиться сделать свой аплет максимально простым. Однако, откровенно говоря, если ваш аплет будет слишком простым, он вообще может оказаться ненужным, а вместо него для описания логики программы, форм для ввода данных и анимационных элементов графического пользовательского интерфейса достаточно будет применить язык JavaScript, переложив основную работу на сервер, желательно поддерживающий сервлеты (servlets) и серверные страницы (server pages).
С другой стороны, если вы разработали слишком изощренную программу на языке Java, задайте себе вопрос, стоит ли применять броузер в качестве средства доставки этой программы. Если не стоит, то переделайте эту программу в обычное приложение и передайте пользователю, чтобы он запустил ее на своей машине. Вы по- прежнему сможете использовать такие преимущества языка Java, как платформная независимость, управление безопасностью, простой доступ к базам данных и поддержка работы в сети. С точки зрения пользователя, зачастую проще разместить приложение в сети, чем в своей локальной файловой системе. (Это особенно касается приложений, которые не часто используются.) С точки зрения администратора, поддерживать и обновлять приложение, хранящееся на сервере, легче.
Таким образом, наиболее успешными программами, написанными на языке Java, являются приложения, работающие во внутрикорпоративных сетях (intranet). Например, многие компании разместили во внутрикорпоративных сетях программы для финансовых расчетов, обработки заказов и т.д. Эти программы относительно невелики, должны взаимодействовать с базами данных, удовлетворять специфические потребности конкретной компании и обладать гибкостью, которую не могут предоставить обычные web-формы. Аплеты и механизм Java Web Start превосходно подходят для доставки таких программ. Поскольку круг пользователей внутрикорпоративной сети ограничен, проблемы с инсталлированием среды поддержки выполнения программ на языке Java обычно не возникает.
Итак, следуя традиции и учитывая роль, которую играют аплеты в технологии Java WEb Seart, начнем именно с них.
Простой аплет
По традиции перепишем программу NotHelloWord в виде аплета. Прежде, чем сделать это, нужно пояснить, что, с точки зрения программиста, аплет не представляет собой ничего удивительного. Это просто класс, который (прямо или косвенно) расширяет класс java. applet .Applet. Отметим, что, хотя пакет j ava. applet не входит в пакет AWT, он представляет собой компонент библиотеки AWT. Соответствующая цепочка наследования показана на рис. 10.1. В рамках этой книги для реализации аплетов мы будем пользоваться библиотекой Swing. Все наши аплеты будут расширять класс JApplet, являющийся суперклассом всех аплетов, создаваемых с помощью библиотеки Swing. Как показано на рис. 10.1, класс JApplet является непо- дственным подклассом обычного класса Applet.
Если аплет содержит компоненты библиотеки Swing, следует расширять класс JApplet. Компоненты библиотеки Swing внутри объекта обычного класса Applet будут изображаться неправильно.
Эта иерархия наследования имеет очевидные, но, тем не менее, полезные результаты. Например, поскольку аплеты являются компонентами библиотеки AWT, обработка событий в аплетах происходит точно так же, как описано в главе 7.
В листинге 10.1 программа "Not Hello World" показана в виде аплета.
Обратите внимание на сходство этого аплета с программой из главы 7. Однако, поскольку аплет существует внутри web-страницы, здесь нет необходимости указывать метод его выполнения.
1. import java.awt.*;
2. import javax.swing.*;
3.
4 . public class NotHelloWorldApplet extends JApplet
5. {
6. public void init()
7.{
8.Container contentPane = getContentPane() ;
9.JLabel label = new JLabel("Not a Hello, World applet",
10.Swingconstants.CENTER);
11. contentPane.add(label);
12.}
13. }
Просмотр аплетаГ
Для того чтобы выполнить аплет, необходимо сделать следующее.
Скомпилировать исходные файлы в файлы с классами.
Создать файл в формате HTML, сообщающий броузеру, какой файл с классами следует загрузить первым, и какие размеры имеет аплет.
Файл в формате HTML можно (но не обязательно) называть так же, как и класс аплета, содержащийся внутри него. Итак, следуя этой традиции, дадим нашему файлу имя NotHelloWorldApplet.html. Ниже приводится его содержание.
<applet code="NotHelloWorldApplet.class" width=300 height=300>
</applet>
Перед тем как просмотреть аплет в броузере, хорошо бы проверить его с помощью программы для просмотра аплетов (applet viewer), входящей в пакет Java SDK. Для этого нужно выполнить команду appletviewer NotHelloWorldApplet.html
В командной строке указывается имя файла в формате HTML, а не файла с классами. На рис. 10.2 показана программа appletviewer, отображающая на экране аплет NotHelloWorldApplet.
Рис. 10.2. Просмотр аплета с помощью программы appletviewerРис. 10.2. Просмотр аплета с помощью программы appletviewer
Рис. 10.2. Просмотр аплета с помощью программы appletviewer
Рис. 10.2. Просмотр аплета с помощью программы appletviewer
Аплеты можно также запускать на выполнение, находясь внутри текстового редактора или интегрированной среды программирования. В текстовом редакторе выберите из меню команду JDE-> Applet. В текстовом редакторе TextPad выберите команду Tools -> Run Java Applet или нажмите комбинацию клавиш <CTRL+3>. Перед вами откроется диалоговое окно, в котором перечислены все файлы в формате HTML, находящиеся в текущем каталоге. Если вы нажмете клавишу <ESC>, программа TextPad автоматически создаст для вас минимальный файл с формате HTML. В среде ONE Sun Studio нужно просто загрузить гипертекстовую страницу с дескрипторами аплета. Эта среда содержит простой броузер, демонстрирующий работу аплета на web-странице. Помимо этого, можно щелкнуть правой кнопкой мыши на исходном файле и установить значение свойства "Executor" на закладке "Execution" равным "Applet Execution".Аплеты можно также запускать на выполнение, находясь внутри текстового редактора или интегрированной среды программирования. В текстовом редакторе выберите из меню команду JDE-> Applet. В текстовом редакторе TextPad выберите команду Tools -> Run Java Applet или нажмите комбинацию клавиш <CTRL+3>. Перед вами откроется диалоговое окно, в котором перечислены все файлы в формате HTML, находящиеся в текущем каталоге. Если вы нажмете клавишу <ESC>, программа TextPad автоматически создаст для вас минимальный файл с формате HTML. В среде ONE Sun Studio нужно просто загрузить гипертекстовую страницу с дескрипторами аплета. Эта среда содержит простой броузер, демонстрирующий работу аплета на web-странице. Помимо этого, можно щелкнуть правой кнопкой мыши на исходном файле и установить значение свойства "Executor" на закладке "Execution" равным "Applet Execution".Избежать создания дополнительного файла в формате HTML можно с помощью загадочного трюка. Добавьте дескриптор аплета в исходный файл в виде комментария./*width=300</applet>*/public class MyApplet extends JAppletИзбежать создания дополнительного файла в формате HTML можно с помощью загадочного трюка. Добавьте дескриптор аплета в исходный файл в виде комментария./*width=300</applet>*/public class MyApplet extends JAppletЗатем запустите программу appletviewer, задав в качестве аргумента командной строки исходный файл.appletvieweravaЭтот прием нельзя рекомендовать для постоянного использования, но он может оказаться удобным, если вы хотите минимизировать количество файлов, за которыми нужно следить во время отладки.Затем запустите программу appletviewer, задав в качестве аргумента командной строки исходный файл.appletvieweravaЭтот прием нельзя рекомендовать для постоянного использования, но он может оказаться удобным, если вы хотите минимизировать количество файлов, за которыми нужно следить во время отладки.Программа appletviewer удобна на первых этапах тестирования, но в какой-то момент вам понадобится запустить аплет внутри броузера, чтобы почувствовать себя на месте пользователя. В частности, программа appletviewer показывает на экране только аплет без окружающего текста на языке HTML. Если файл в формате HTML содержит много аплетов, то программа appletviewer откроет на экране несколько отдельных окон.Программа appletviewer удобна на первых этапах тестирования, но в какой-то момент вам понадобится запустить аплет внутри броузера, чтобы почувствовать себя на месте пользователя. В частности, программа appletviewer показывает на экране только аплет без окружающего текста на языке HTML. Если файл в формате HTML содержит много аплетов, то программа appletviewer откроет на экране несколько отдельных окон.
Аплеты можно также запускать на выполнение, находясь внутри текстового редактора или интегрированной среды программирования. В текстовом редакторе выберите из меню команду JDE-> Applet. В текстовом редакторе TextPad выберите команду Tools -> Run Java Applet или нажмите комбинацию клавиш <CTRL+3>. Перед вами откроется диалоговое окно, в котором перечислены все файлы в формате HTML, находящиеся в текущем каталоге. Если вы нажмете клавишу <ESC>, программа TextPad автоматически создаст для вас минимальный файл с формате HTML. В среде ONE Sun Studio нужно просто загрузить гипертекстовую страницу с дескрипторами аплета. Эта среда содержит простой броузер, демонстрирующий работу аплета на web-странице. Помимо этого, можно щелкнуть правой кнопкой мыши на исходном файле и установить значение свойства "Executor" на закладке "Execution" равным "Applet Execution".
Аплеты можно также запускать на выполнение, находясь внутри текстового редактора или интегрированной среды программирования. В текстовом редакторе выберите из меню команду JDE-> Applet. В текстовом редакторе TextPad выберите команду Tools -> Run Java Applet или нажмите комбинацию клавиш <CTRL+3>. Перед вами откроется диалоговое окно, в котором перечислены все файлы в формате HTML, находящиеся в текущем каталоге. Если вы нажмете клавишу <ESC>, программа TextPad автоматически создаст для вас минимальный файл с формате HTML. В среде ONE Sun Studio нужно просто загрузить гипертекстовую страницу с дескрипторами аплета. Эта среда содержит простой броузер, демонстрирующий работу аплета на web-странице. Помимо этого, можно щелкнуть правой кнопкой мыши на исходном файле и установить значение свойства "Executor" на закладке "Execution" равным "Applet Execution".
Избежать создания дополнительного файла в формате HTML можно с помощью загадочного трюка. Добавьте дескриптор аплета в исходный файл в виде комментария.
/*
width=300
</applet>
*/
public class MyApplet extends JApplet
Избежать создания дополнительного файла в формате HTML можно с помощью загадочного трюка. Добавьте дескриптор аплета в исходный файл в виде комментария.
/*
width=300
</applet>
*/
public class MyApplet extends JApplet
Затем запустите программу appletviewer, задав в качестве аргумента команд
ной строки исходный файл.
appletviewerava
Этот прием нельзя рекомендовать для постоянного использования, но он может оказаться удобным, если вы хотите минимизировать количество файлов, за которыми нужно следить во время отладки.
Затем запустите программу appletviewer, задав в качестве аргумента команд
ной строки исходный файл.
appletviewerava
Этот прием нельзя рекомендовать для постоянного использования, но он может оказаться удобным, если вы хотите минимизировать количество файлов, за которыми нужно следить во время отладки.
Программа appletviewer удобна на первых этапах тестирования, но в какой-то момент вам понадобится запустить аплет внутри броузера, чтобы почувствовать себя на месте пользователя. В частности, программа appletviewer показывает на экране только аплет без окружающего текста на языке HTML. Если файл в формате HTML содержит много аплетов, то программа appletviewer откроет на экране несколько отдельных окон.
Программа appletviewer удобна на первых этапах тестирования, но в какой-то момент вам понадобится запустить аплет внутри броузера, чтобы почувствовать себя на месте пользователя. В частности, программа appletviewer показывает на экране только аплет без окружающего текста на языке HTML. Если файл в формате HTML содержит много аплетов, то программа appletviewer откроет на экране несколько отдельных окон.
Для просмотра аплета необходим броузер, поддерживающий язык Java 2. После инсталляции и конфигурации программы Java Plug-In нужно просто загрузить HTML- файл в броузер (рис. 10.3). Если аплет не отображается, возможно, броузер использует встроенную виртуальную машину. Чтобы решить эту проблему, вам придется настроить его на использование программы Java Plug-In.Для просмотра аплета необходим броузер, поддерживающий язык Java 2. После инсталляции и конфигурации программы Java Plug-In нужно просто загрузить HTML- файл в броузер (рис. 10.3). Если аплет не отображается, возможно, броузер использует встроенную виртуальную машину. Чтобы решить эту проблему, вам придется настроить его на использование программы Java Plug-In.^^Если вы изменили аплет и скомпилировали его снова, вам нужно запустить броузер, чтобы он смог загрузить новые файлы классов. Простое обновление HTML- страницы не приводит к загрузке нового кода. Утомительной процедуры повторного запуска броузера можно избежать, открыв консоль Java и введя команду которая очищает кэш-память загрузчика классов. В броузерах Netscape и Mozilla консоль Java запускается из меню. Если вы работаете под управлением операционной системы Windows, щелкните на пиктограмме Java Plug-In, расположенной на панели управления, и введите команду вывода на экран консоли Java.Если вы изменили аплет и скомпилировали его снова, вам нужно запустить броузер, чтобы он смог загрузить новые файлы классов. Простое обновление HTML- страницы не приводит к загрузке нового кода. Утомительной процедуры повторного запуска броузера можно избежать, открыв консоль Java и введя команду которая очищает кэш-память загрузчика классов. В броузерах Netscape и Mozilla консоль Java запускается из меню. Если вы работаете под управлением операционной системы Windows, щелкните на пиктограмме Java Plug-In, расположенной на панели управления, и введите команду вывода на экран консоли Java.DDЕсли вы работаете с более старыми броузерами (например Netscape 4), замените дескрипторы applet специальными дескрипторами object или embed. Это заставит броузер загрузить программу Java Plug-In. Этот процесс описан на Web- страницеhttp://java.sun.eom/j2se/l.4/docs/guide/plugin/developer_guide/html_ html. Кроме того, с нее можно загрузить инструментальные средства, автоматизирующие преобразование Web-страниц. При работе с более современными броузерами преобразование Web-страниц больше требуется.Если вы работаете с более старыми броузерами (например Netscape 4), замените дескрипторы applet специальными дескрипторами object или embed. Это заставит броузер загрузить программу Java Plug-In. Этот процесс описан на Web- страницеhttp://java.sun.eom/j2se/l.4/docs/guide/plugin/developer_guide/html_ html. Кроме того, с нее можно загрузить инструментальные средства, автоматизирующие преобразование Web-страниц. При работе с более современными броузерами преобразование Web-страниц больше требуется.10.3. Просмотр аплета с помощью броузера10.3. Просмотр аплета с помощью броузера
Для просмотра аплета необходим броузер, поддерживающий язык Java 2. После инсталляции и конфигурации программы Java Plug-In нужно просто загрузить HTML- файл в броузер (рис. 10.3). Если аплет не отображается, возможно, броузер использует встроенную виртуальную машину. Чтобы решить эту проблему, вам придется настроить его на использование программы Java Plug-In.
Для просмотра аплета необходим броузер, поддерживающий язык Java 2. После инсталляции и конфигурации программы Java Plug-In нужно просто загрузить HTML- файл в броузер (рис. 10.3). Если аплет не отображается, возможно, броузер использует встроенную виртуальную машину. Чтобы решить эту проблему, вам придется настроить его на использование программы Java Plug-In.
^
^
Если вы изменили аплет и скомпилировали его снова, вам нужно запустить броузер, чтобы он смог загрузить новые файлы классов. Простое обновление HTML- страницы не приводит к загрузке нового кода. Утомительной процедуры повторного запуска броузера можно избежать, открыв консоль Java и введя команду которая очищает кэш-память загрузчика классов. В броузерах Netscape и Mozilla консоль Java запускается из меню. Если вы работаете под управлением операционной системы Windows, щелкните на пиктограмме Java Plug-In, расположенной на панели управления, и введите команду вывода на экран консоли Java.
Если вы изменили аплет и скомпилировали его снова, вам нужно запустить броузер, чтобы он смог загрузить новые файлы классов. Простое обновление HTML- страницы не приводит к загрузке нового кода. Утомительной процедуры повторного запуска броузера можно избежать, открыв консоль Java и введя команду которая очищает кэш-память загрузчика классов. В броузерах Netscape и Mozilla консоль Java запускается из меню. Если вы работаете под управлением операционной системы Windows, щелкните на пиктограмме Java Plug-In, расположенной на панели управления, и введите команду вывода на экран консоли Java.
D
D
Если вы работаете с более старыми броузерами (например Netscape 4), замените дескрипторы applet специальными дескрипторами object или embed. Это заставит броузер загрузить программу Java Plug-In. Этот процесс описан на Web- странице
http://java.sun.eom/j2se/l.4/docs/guide/plugin/developer_guide/html_ html. Кроме того, с нее можно загрузить инструментальные средства, ав
томатизирующие преобразование Web-страниц. При работе с более современными броузерами преобразование Web-страниц больше требуется.
Если вы работаете с более старыми броузерами (например Netscape 4), замените дескрипторы applet специальными дескрипторами object или embed. Это заставит броузер загрузить программу Java Plug-In. Этот процесс описан на Web- странице
http://java.sun.eom/j2se/l.4/docs/guide/plugin/developer_guide/html_ html. Кроме того, с нее можно загрузить инструментальные средства, ав
томатизирующие преобразование Web-страниц. При работе с более современными броузерами преобразование Web-страниц больше требуется.
10.3. Просмотр аплета с помощью броузера
10.3. Просмотр аплета с помощью броузера
Преобразование приложения в аплет
Графическое приложение (т.е. приложение, использующее библиотеку AWT, которое можно запускать из командной строки) легко преобразовать в аплет и встроить в Web-страницу, и, что особенно при этом весь код пользовательского интерфейса можно оставить без изменения.
Для этого нужно сделать следующее.
Создать HTML-страницу с соответствующим дескриптором для загрузки кода аплета.
Описать подкласс класса JApplet. Сделать этот класс открытым (public), иначе аплет будет невозможно загрузить.
Удалить из приложения метод main. Окно фрейма для приложения создавать не следует. Приложение будет отображаться внутри броузера.
Перенести все операторы инициализации из конструктора окна фрейма в метод аплета init. Создавать объект аплета явно не обязательно — броузер сам создаст его и вызовет метод init .
Удалить вызов метода setSize. В аплетах размеры задаются с помощью параметров WIDTH и HEIGHT в HTML-файле.
Удалить вызов метода setDefaultCloseOperation. Аплет не сможет закрываться и прекратит работу вместе с броузером.
Если в приложении есть вызов метода setTitle, следует его удалить. Аплеты не имеют заголовков. (Разумеется, можно задать заголовок самой Web- страницы, используя дескриптор title языка HTML.)
Нс вызывать метод show. Аплет отображается на экране автоматически.
В качестве примера рассмотрим превращение приложения Calculator из главы 7 в аплет. На рис. 10.4 показано, как выглядит этот аплет на Web-странице.
Рис. 10.4. Аплет Calculator
В листинге 10.2 показана соответствующая HTML-страница. Обратите внимание на то, что кроме меток аплета в ней содержится дополнительный текст.
<html>
<title>ACalculator</title>
<body>
Here is a calculator, just in case you can't find yours.
<applet code="CalculatorApplet.class" width=180 height=180>
</applet>
</body>
</html>
Код аплета приведен в листинге 10.3. В этой программе описан подкласс класса JApplet, а в метод init добавлен код инициализации. Кроме того, из программы удалены вызовы методов setTitle, setSize, setDefaultCloseOperationи show. Класс CalculatorPanel оставлен без изменения, а его код пропущен.
import java.awt.*;
import javax.swing.*;
public class CalculatorApplet extends JApplet
{
public void init (]
{
Container contentPane = getContentPane();
CalculatorPanel panel = new CalculatorPanel(1;
contentPane.add(panel);
}
)
SAIS/
A P I java. applet. Applet 1.0
I • void init ()
Вызывается при первой загрузке аплета. Заместите этот код и разместите в нем все операторы инициализации.
• void setSize(int width, int height)
Задает новые размеры аплета. Если бы этот метод работал с web- страницами, цены бы ему не было. К сожалению, с современными броузерами этот метод не работает, конфликтуя с их механизмами компоновки.
Жизненный цикл аплета
Основу для создания любого серьезного аплета составляют четыре метода из класса Applet: init, start, stop и destroy. Ниже приводится краткое описание, в котором указано, когда их следует вызывать и какой код они должны содержать.
init
Этот метод используется для инициализации аплета. Он очень похож на конструктор, поскольку автоматически вызывается при первом запуске аплета. Обычно все действия этого метода внутри аплета сводятся к обработке значений param и добавлению компонентов пользовательского интерфейса. Несмотря на то что аплеты могут иметь конструктор по умолчанию, инициализацию следует выполнять с помощью метода init .
start
Этот метод автоматически вызывается после метода init. Он также вызывается, если пользователь вернулся на страницу, содержащую аплет, после просмотра других страниц. Это значит, что, в отличие от метода init, start можно вызывать повторно. По этой причине код, который должен выполняться только один раз, следует помещать в метод init, а не start. Этот метод следует вызывать при повторном запуске потока для аплета, например, для возобновления анимации. Если аплет не выполняет никаких действий, которые нужно приостановить при переходе пользователя на другую страницу, метод start реализовывать не нужно (как и метод stop).
stop
Этот метод автоматически вызывается, когда пользователь покидает страницу, на которой расположен аплет. Таким образом, в одном и том же аплете он может вызываться несколько раз. Метод stop предназначен для остановки процессов, на выполнение которых затрачивается слишком много времени. Это предотвращает замедление работы системы в те моменты, когда пользователь не работает с аплетом. Этот метод не следует вызывать явно. Если аплет не выполняет анимацию, не воспроизводит аудиофайлы и не осуществляет вычислений в потоке, метод stop обычно не нужен.
destroy
Этот метод вызывается только при нормальном завершении работы броузера. Поскольку аплеты предназначены для работы на HTML-страницах, заботиться об освобождении ресурсов после закрытия страницы, содержащей аплет, совершенно не обязательно. Если все же вы решили сделать это самостоятельно, то для освобождения ресурсов вам придется заместить метод destroy. гм/ ■ му
(API java, applet. Applet 1.0
^-"-^ . void start ()
Заместите этот метод кодом, который подлежит выполнению каждый раз, когда пользователь посещает страницу броузера, содержащую данный аплет. Обычно этот метод повторно активирует поток.
void stop ()
Заместите этот метод кодом, который подлежит выполнению каждый раз, когда пользователь покидает страницу броузера, содержащую данный аплет. Обычно этот метод приостанавливает поток.
void destroy()
Заместите этот метод кодом, который подлежит выполнению при выходе из броузера. Обычно этот метод уничтожает системные объекты.
Основы безопасности
Поскольку аплеты предназначены для загрузки с удаленных Web-страниц и последующего выполнения на локальном компьютере, вопросы безопасности становятся крайне важными. Броузер, поддерживающий язык Java, может загрузить весь код аплета на Web- страницу и немедленно его выполнить. Пользователь не имеет никакой возможности вмешаться в этот процесс и предотвратить или приостановить запуск аплета. По этой причине аплеты, в отличие от приложений, ограничены в своих возможностях. Как только аплетпытаетсянарушитьправиладоступа, менеджер безопасностиаплетов (appletsecurity manager) возбуждает исключительную ситуацию SecurityException. (Более подробно менеджеры безопасности рассматриваются в томе 2.)
Что аплеты могут делать на всех платформах? Аплет может выводить на экран графические изображения, воспроизводить звуки, воспринимать нажатия клавиш и щелчки мыши, а также передавать информацию, введенную пользователем, на компьютер, с которого он был загружен. Этого вполне достаточно для того, чтобы передавать пользователю изображения и факты, а взамен получать от него данные. Среда, ограничивающая выполнение аплетов, часто называется "песочницей" (sandbox). Аплеты, "играющие в песочнице", не могут изменять систему пользователя или шпионить за ней. В этой главе мы рассмотрим аплеты, которые можно запускать только в песочнице.
В частности, при работе в песочнице выполняются следующие правила.
Аплеты ни при каких обстоятельствах не могут запускать выполняемые модули на локальном компьютере.
Аплет не может взаимодействовать ни с одним компьютером, кроме сервера, с которого он был загружен. Этот сервер называется станцией отправления (originating host). Это правило часто формулируют так: "Аплет может звонить только домой". Это защищает пользователя от аплетов, пытающихся шпионить за ресурсами внутрикорпоративной сети.
Аплет не может считывать данные из локальной файловой системы или записывать их туда.
Аплеты не способны распознавать никакой информации о локальном компьютере, за исключением номера версии языкаJava, имени и версии операционной системы, а также символов, используемых для разделения файлов (например, \ или /), путей (например, : или ;) и строк (например, \п или \г\п). В частности, аплеты не могут определять имя пользователя, адрес его электронной почты и т.д.
Все окна, открываемые аплетом, сопровождаются предупреждением.
Все это возможно лишь потому, что аплеты интерпретируются виртуальной машиной, а не выполняются непосредственно центральным процессором пользовательского компьютера. Поскольку интерпретатор проверяет все критические команды и области программы, вредный (или небрежно написанный) аплет никогда не сможет вызвать крах операционной системы, перезаписать содержимое системной памяти или изменить привилегии, предоставленные операционной системой.
Эти ограничения в некоторых ситуациях слишком строги. Например, в корпоративной сети легко представить себе аплет, которому нужен доступ к локальным фай-
лам. Для того чтобы в разных ситуациях применять разные ограничения, можно использовать аплеты с электронной подписью (signed aplets). Такие аплеты сопровождаются сертификатом, подтверждающим личность отправителя. Криптографические методы гарантируют, что этот сертификат не может быть подделан. Если вы доверяете человеку, подписавшему аплет, то можете предоставить его аплету дополнительные права. (Электронные подписи рассматриваются в томе 2.)
Если вы уверены в аплете, броузер может предоставить ему дополнительные возможности. Например, аплетам, подписанным сотрудниками вашей корпорации, вы можете доверять больше, чем аплетам, полученным с Web-узла www. hacker . com. Настраиваемая система безопасности языка Java предоставляет огромное количество уровней доступа. Аплетам, которым вы доверяете полностью, можно предоставить права локальных приложений. Программы от сомнительных отправителей могут получить ограниченные привилегии. Совершенно незнакомые аплеты можно отправить "играться в песочнице".
л
Приложения, созданные по технологии Java Web Start (они будут рассмотрены ниже), имеют более гибкий механизм "песочниц". Они получают доступ к некоторым системным ресурсам с согласия пользователя.
Итак, язык Java имеет три отдельных механизма для обеспечения безопасности.
Код программы интерпретируется виртуальной машиной, а не выполняется непосредственно.
Менеджер безопасности отслеживает все потенциально опасные операции в библиотеке системы поддержки выполнения программ (Java runtime library).
Аплеты могут сопровождаться электронной подписью, подтверждающей их происхождение.
В противоположность этому, модель безопасности в технологии ActiveX, разработанной компанией Microsoft, опирается исключительно на электронную подпись. Для того чтобы запустить компонент ActiveX, вы должны полностью доверять ему. Если у вас несколько надежных поставщиков, эта модель работает прекрасно, но сферу ее применения невозможно расширить на всю сеть World Wide Web. Используя броузер Internet Explorer, вы можете увидеть технологию ActiveX в действии. Для того чтобы инсталлировать программу Java Plug-In для работы с броузером Internet Explorer, нужно получить сертификат компании Sun, который сообщает вам, что данный код исходит именно от компании Sun. В нем ничего не говорится о качестве кода. После начала инсталляции пользователь никак не может повлиять на уровень безопасности этой программы.
Всплывающие окна в аплетах
Аплет встраивается в Web-страницу, находясь внутри фрейма. Размеры этого фрейма задаются параметрами width и height, указанными в дескрипторах аплета в HTML-файле. Часто это бывает довольно неудобно. Многие программисты интересуются: можно ли использовать всплывающие окна, чтобы сэкономить доступное
пространство? Да, можно. Вот пример простого аплета с единственной кнопкой "Calculator". После щелчка на этой кнопке калькулятор всплывает на экране в отдельном окне.
Всплывающие окна создаются легко. Просто используйте классно не вы
зывайте метод frame = new
;
200);
После щелчка на кнопке "Calculator" на экране поверх Web-страницы всплывает диалоговое окно. После повторного щелчка калькулятор исчезает.
JButton calcButton = new
calcButton.addActionListener(new
ActionListener()
(
public voidevt)
if;
else
} });
Существует, правда, одна ловушка, о которой вам следует знать, прежде чем поместить этот аплет на свою Web-страницу. Для того чтобы увидеть, как будет выглядеть калькулятор, загрузите Web-страницу в броузер, а не в программу
Этот калькулятор будет окружен рамкой, содержащей предупреждение (рис. 10.5).
10.5. Всплывающее окно внутри броузера
В прежних броузерах это сообщение было весьма зловещим: "Окно подозрительного аплета". С каждой последующей версией это сообщение становилось все более нейтральным: от "Окно аплета неизвестного происхождения" и "Предупреждение: окно аплета" до нынешнего весьма простого сообщения "Окно аплета".
Это сообщение является характеристикой безопасности всех Web-броузеров. Броузер должен быть уверен, что ваш аплет не откроет окно, которое может вызвать ошибку при выполнении локального приложения. Опасение состоит в том, что ниче-
го не подозревающий пользователь может посетить Web-страницу, автоматически запускающую аплеты, и по ошибке набрать пароль или номер кредитной карточки, который аплет может отослать своему хозяину.
Для того чтобы избежать такого мошенничества, все всплывающие окна аплета сопровождаются предупреждением. Если ваш броузер воспринимает аплеты с электронной подписью, то предупреждения для них можно не выводить.
В листинге 10.4 приведен код класса PopupCalculatorApplet. Класс CalculatorPanelиз главы 7 остался без изменения и здесь не приводится.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PopupCalculatorApplet extends JApplet
{
public void initO
{
// Создаем фрейм, содержащий панель калькулятора
frame = new JFrame ();
frame.setTitle("Calculator");
frame.setSize(200, 200);
frame.getContentPane().add(new Calculatorpanel());
// Добавляем кнопку вызова или сокрытия калькулятора
JButton calcButton = new JButton("Calculator");
getContentPane().add(calcButton);
calcButton.addActionListener(new
ActionListener(I
{
public void actionPerformed(ActionEvent evt)
{
if (frame.isVisible()] frame.setVisible(false);
else frame.show();
}
});
}
privateJFrameframe;
}
Дескрипторы HTML и атрибуты аплетов
В самом общем виде применение дескриптора applet выглядит следующим образом.
<applet code>="NotHelloWorldApplet.class" width=100 height=100>
Как мы уже видели, атрибут code задает имя класса и должен содержать расширение . class. Параметры width и height задают размеры окна, в котором будет размещен аплет. Оба параметра измеряются в пикселях. Нужно также указать соответствующий дескриптор applet, замыкающий описание аплета. Текст, размещенный между дескрипторами <applet> и </applet>, выводится на экран, только если броузер не может отобразить аплет. Эти дескрипторы необходимы. Если хотя бы один из них будет пропущен, броузер не сможет загрузить аплет.
Всю эту информацию обычно загружают на HTML-страницу, которая может выглядеть следующим образом.
<html>
<head>
<title>NotHelloWQorldApplet</title>
</head>
<body>
Следующая строка текста выводится под управлением системы Java:
<applet code>="NotHelloWorldApplet.class" WIDTH=10C HEIGHT=100>
Если бы ваш броузер поддерживал язык Java, здесь бы вы увидели аплет. </applet>
</body>
</html>
В соответствии со спецификацией языка HTML дескрипторы и атрибуты, такие как applet, могут быть как прописными, так и строчными. То же относится и к имени класса для аплета. Регистр, в котором набраны буквы, может оказаться важным в других элементах описания, заданных в кавычках, например в именах JAR- файлов, если файловая система Web-сервера учитывает этот факт.
Атрибуты, задающие местоположение аплета
Ниже приводится краткое описание различных атрибутов, которые можно (и нужно) использовать вместе с дескриптором applet для того, чтобы задать местоположение аплета.
weight, height
Эти атрибуты необходимы. Они задают ширину и высоту аплета, измеренную в пикселях. В программе appletviewer эти параметры определяют первоначальный размер аплета. Программист может изменять размер любого окна, созданного программой appletviewer. В броузере изменить размеры аплета невозможно. Для того чтобы определить, сколько места понадобится аплету, нужно хорошенько подумать.
align
Этот атрибут задает вид выравнивания аплета. Есть два основных варианта. Аплет может представлять собой блок, окруженный текстом со всех сторон, или быть подставляемым (inline), т.е рассматриваться как увеличенный текстовый символ. Первые два значения этого параметра (left и right) задают обтекание аплета текстом. Остальные значения вставляют аплет прямо в текст.
Возможные значения этого параметра показаны в табл. 10.1.
>.1. Атрибуты, задаюмлиеместоположени^аил^з £АтрибутСмысл атрибутаleftПомещает аплет на левое поле страницы. Текст размещается справа от аплетаrightПомещает аплет на правое поле страницы. Текст размещается слева от аплетаbottomПомещает аплет на нижней границе текста в текущей строкеtoptexttopПомещает аплет на верхней границе текста в текущей строке Выравнивает верхнюю границу аплета по верхней границе текста в текущей строкеmiddleВыравнивает середину аплета по базовой линии текущей строкиabsmiddleВыравнивает середину аплета по середине текущей строкиbaselineВыравнивает нижнюю границ)' аплета по базовой линии текущей строкиabsbottomВыравнивает нижнюю границу аплета по нижней границе текущей строкиvspace, hspaceНеобязательные атрибуты, указывающие количество пикселей над и под аплетом (vspace), а также по бокам (hspace)>.1. Атрибуты, задаюмлиеместоположени^аил^з £АтрибутСмысл атрибутаleftПомещает аплет на левое поле страницы. Текст размещается справа от аплетаrightПомещает аплет на правое поле страницы. Текст размещается слева от аплетаbottomПомещает аплет на нижней границе текста в текущей строкеtoptexttopПомещает аплет на верхней границе текста в текущей строке Выравнивает верхнюю границу аплета по верхней границе текста в текущей строкеmiddleВыравнивает середину аплета по базовой линии текущей строкиabsmiddleВыравнивает середину аплета по середине текущей строкиbaselineВыравнивает нижнюю границ)' аплета по базовой линии текущей строкиabsbottomВыравнивает нижнюю границу аплета по нижней границе текущей строкиvspace, hspaceНеобязательные атрибуты, указывающие количество пикселей над и под аплетом (vspace), а также по бокам (hspace)ALIGN-ТОР: An eL".e applet fluangwo^i the textALIGN-ТОР: An eL".e applet fluangwo^i the textAPPLETAPPLETALIGN-TEXTTOP: AnrJme applet floatin#! with the textALIGN-TEXTTOP: AnrJme applet floatin#! with the textALIGN-MIDDLE: Anrine ^plufloatingwith the textALIGN-MIDDLE: Anrine ^plufloatingwith the textjjAPPLETAPPLETALIGN-ABSMIDDLE: An rJee applet Boating wdi the textALIGN-ABSMIDDLE: An rJee applet Boating wdi the textALIGN-BASELINE: An mime applet floating with the ^яALIGN-BASELINE: An mime applet floating with the ^яAUGN-BOTTOM: An inline applet floating with the text.AUGN-BOTTOM: An inline applet floating with the text.|aLIGN-ABSBOTTOM: An i^M|aLIGN-ABSBOTTOM: An i^MAPPLETAPPLETAPPLETAPPLETAPPLETAPPLETapplet floatingwehthetext.applet floatingwehthetext.APPLETAPPLET
>.1. Атрибуты, задаюмлиеместоположени^аил^з £
Атрибут
Смысл атрибута
left
Помещает аплет на левое поле страницы. Текст размещается справа от аплета
right
Помещает аплет на правое поле страницы. Текст размещается слева от аплета
bottom
Помещает аплет на нижней границе текста в текущей строке
top
texttop
Помещает аплет на верхней границе текста в текущей строке Выравнивает верхнюю границу аплета по верхней границе текста в текущей строке
middle
Выравнивает середину аплета по базовой линии текущей строки
absmiddle
Выравнивает середину аплета по середине текущей строки
baseline
Выравнивает нижнюю границ)' аплета по базовой линии текущей строки
absbottom
Выравнивает нижнюю границу аплета по нижней границе текущей строки
vspace, hspace
Необязательные атрибуты, указывающие количество пикселей над и под аплетом (vspace), а также по бокам (hspace)
>.1. Атрибуты, задаюмлиеместоположени^аил^з £
Атрибут
Смысл атрибута
left
Помещает аплет на левое поле страницы. Текст размещается справа от аплета
right
Помещает аплет на правое поле страницы. Текст размещается слева от аплета
bottom
Помещает аплет на нижней границе текста в текущей строке
top
texttop
Помещает аплет на верхней границе текста в текущей строке Выравнивает верхнюю границу аплета по верхней границе текста в текущей строке
middle
Выравнивает середину аплета по базовой линии текущей строки
absmiddle
Выравнивает середину аплета по середине текущей строки
baseline
Выравнивает нижнюю границ)' аплета по базовой линии текущей строки
absbottom
Выравнивает нижнюю границу аплета по нижней границе текущей строки
vspace, hspace
Необязательные атрибуты, указывающие количество пикселей над и под аплетом (vspace), а также по бокам (hspace)
ALIGN-ТОР: An eL".e applet fluangwo^i the text
ALIGN-ТОР: An eL".e applet fluangwo^i the text
APPLET
APPLET
ALIGN-TEXTTOP: AnrJme applet floatin#! with the text
ALIGN-TEXTTOP: AnrJme applet floatin#! with the text
ALIGN-MIDDLE: Anrine ^plufloatingwith the text
ALIGN-MIDDLE: Anrine ^plufloatingwith the text
j
j
APPLET
APPLET
ALIGN-ABSMIDDLE: An rJee applet Boating wdi the text
ALIGN-ABSMIDDLE: An rJee applet Boating wdi the text
ALIGN-BASELINE: An mime applet floating with the ^я
ALIGN-BASELINE: An mime applet floating with the ^я
AUGN-BOTTOM: An inline applet floating with the text.
AUGN-BOTTOM: An inline applet floating with the text.
|aLIGN-ABSBOTTOM: An i^M
|aLIGN-ABSBOTTOM: An i^M
APPLET
APPLET
APPLET
APPLET
APPLET
APPLET
applet floatingwehthetext.
applet floatingwehthetext.
APPLET
APPLET
Рис. 10.6. Выравнивание аплетов
На рис. 10.6 показаны варианты выравнивания аплетов по отношению к окружающему его тексту. Вертикальная черта в начале каждой строки представляет собой изображение. Поскольку оно выше, чем текст, по нему можно судить о разнице между выравниванием по нижней и верхней границам строки и нижней и верхней границам текста.
Атрибуты кода аплета
Следующие атрибуты сообщают броузеру, как найти код аплета. Вот их краткое описание.
code
Этот атрибут задает имя файла, содержащего класс аплета. Это имя связано с параметром codebase (см. ниже) или с текущей страницей. Если используется относительное имя пути, оно должно совпадать с названием пакета, в котором находится класс аплета. Например, если класс аплета находится в пакете com.mycompany, то задается атрибут code="com/mycompany/MyApplet. class" или code="com. mycompany.MyApplet.class". Здесь нельзя использовать абсолютный путь. Если файл c классом аплета находится где-то в другом месте, используйте атрибут codebase. Атрибут code задает только имя файла, содержащего класс аплета. Разумеется, аплет может содержать и другие файлы с классами. Загружая класс, содержащий аплет. броузер распознает, какие файлы нужны еще, и загружает их дополнительно. Нужно задавать либо атрибут code, либо атрибут obj ect (см. ниже).
codebase
Этот необязательный атрибут информирует броузер о том, что файлы с классами найдены в каталоге, указанном атрибутом codebase. Например, если аплет с именем CalculatorApplet находится в каталоге MyApplets, и этот каталог вложен в каталог, содержащий Web-страницу, можно задать следующие атрибуты.
Opplet code=CalculatorApplet.class" codebase="MyApplets"
WIDTH=10Q height=150>
Иными словами, шаблон файла выглядит следующим образом.
Adirectory/
CalculatorApplet.html
MyApplets/
CalculatorApplet.class
\
archive
Этот необязательный атрибут перечисляет архивные файлы языка Java или файлы, содержащие классы и другие ресурсы для аплета. (См. раздел, посвященный JAR-файлам.) Такие файлы загружаются с сервера перед загрузкой аплета. Подобная технология значительно ускоряет работу, поскольку для загрузки JAR-файла, содержащего много более мелких файлов, необходим лишь HTML-запрос. В списке J AR-файлы разделяются запятыми.
<applet code="CalculatorAPplet.class” archive=
"Calculatorclasses.jar, corejava/CoreJavaClasses.jar" Width=10C HEIGHT=150>
object
Есть еще один способ указать класс аплета, задав имя файла, содержащего сериализованный объект аплета (serialized applet object), однако его поддерживают не все броузеры. Для того чтобы воспользоваться этой возможностью, вам понадобится программа Java Plug-In. (Объект называется се)>иплгиованнъ1М, если все его поля данных записаны в отдельном файле. Сериализация обсуждается в главе 12.) Чтобы вернуться в первоначальное состояние, объект десериализуется (т.е. все его поля данных возвращаются в файл класса. — Прим. ред.). При использовании этого атрибута вместо метода init вызывается метод аплета start. Перед сериализацией объекта аплета необходимо вызвать его метод stop. Это свойство позволяет реализовать устойчивый броузер, который автоматически загружает и перезагружает аплеты, а по завершении работы возвращает их в первоначальное состояние. Это очень специфическое свойство, которое обычно разработчиками Web-страниц не поддерживается.
В каждом дескрипторе applet должен содержаться атрибут code либо ob j ect.
Opplet object="CalculatorApplet.ser" width=100 height=150>
name
С помощью атрибута name аплету можно присвоить имя, которое затем можно использовать при разработке сценария. Броузеры Netscape и Internet Explorer позволяют вызывать методы аплета, размещенного на странице, с помощью языка JavaScript. В этой книге вопросы, связанные с языком JavaScript, не рассматриваются, поэтому мы лишь вкратце опишем, как программа на языке Java вызывается из программы на языкеJavaScript.
Язык JavaScript представляет собой язык для разработки сценариев, которые можно использовать на Web-странице. Он разработан компанией Netscape и вначале назывался LiveScript. Этот язык никак не связан с языком Java, за исключением небольшого сходства их синтаксиса. Название JavaScript было продиктовано рекламными соображениями. Подмножество этого языка (с хитроумным именем ECMAScript) было стандартизовано и получило название AECMA-262. Однако, что совершенно неудивительно, компании Netscape и Microsoft стали поддерживать в своих броузерах несовместимые между собой расширения этого стандарта.
Для того чтобы получить доступ к аплету из сценария на языке JavaScript, сначала нужно дать ему имя.
Opplet code="CalculatorApplet.class" width=10C height=150 name="calc">
</applet>
Теперьнаэтотобъектможноуказатьспомощьюссылки
document.applets.appletname.
var calcApplet = document.applets.calc;
Используя интеграцию между языками Java и JavaScript, поддерживаемую броузерами Netscape и InternetExplorer, можно вызывать методы аплета.
calcApplet.clear();
(Наш аплет CalculatorApplet не имеет метода clear. Приведенный выше вызов использовался лишь для иллюстрации синтаксиса.)
На Web-странице http: //www. javaworId/com/javat ips/jw-javatip80 . html Франсуа Лю (Francis Lu) использует взаимодействие между языками Java и JavaScript для решения старой задачи: изменить размер аплета, не пользуясь жестко заданными атрибутами width и height. Это хороший пример интеграции между языками Java и JavaScript. Он также иллюстрирует, как тяжело написать программу на языке JavaScript, предназначенную для нескольких броузеров одновременно.
Атрибут name приобретает большое значение, если вы хотите поместить два взаимодействующих аплета на одну и ту же страницу. Сначала нужно присвоить имя каждому объекту аплета. Затем следует передать эту строку методу getApplet класса Appletcomplex. Механизм, называемый взаимодействием внутри аплета (inter-applet communication), обсуждается в этой главе ниже.
Атрибуты аплета для броузеров, распознающих язык Java
Если Web-страница, содержащая дескриптор applet, просматривается броузером, не распознающим аплетов на языке Java, то этот броузер проигнорирует неизвестные ему дескрипторы applet и param. Весь текст, размещенный между дескрипторами <applet> и </applet>, будет выведен на экран. И наоборот, броузеры, поддерживающие язык Java, не отображают на экране текст, заключенный между дескрипторами <applet> и </applet>. Между ними можно размещать текст, адресованный беднягам, до сих пор использующим доисторические броузеры.
<applet code="Calculator.Applet.class" width=100 height=150>
Если бы ваш броузер поддерживал язык Java, здесь вы увидели бы калькуля
тор.
</applet>
Разумеется, большинство современных броузеров распознают язык Java, однако эта возможность может быть заблокирована, например, пользователем или системным администратором, страдающим паранойей. Тогда для вывода на экран сообщения, адресованного этим несчастным, следует воспользоваться атрибутом ALT.
<applet code="CalculatorApplet.class" width=100 height=15C
alt=" Если бы ваш броузер поддерживал язык Java, здесь вы увидели бы калькулятор".
Дескриптор object
Дескриптор ob j ect представляет собой часть стандарта языка HTML 4.0. Он предложен консорциумом W3 взамен дескриптора applet. У этого дескриптора есть 35 атрибутов, большинство из которых (например параметр onkeydown) предназначено для программистов, работающих на языке Dynamic HTML. Атрибуты, определяющие размеры и местоположение аплета, например параметры align и height, ничем не отличаются от таких же атрибутов дескриптора applet. Ключевым атрибутом дескриптора
object, касающимся аплетов на языке Java, является параметр classid. Этот атрибут указывает положение объекта. Разумеется, дескрипторы object могут загружать разные виды объектов, например, аплеты на языке Java или компоненты ActiveX. Природа загружаемого объекта указывается в атрибуте codetype. Пусть аплет, написанный на языке Java, имеет тип кода application/j ava. Для его загрузки в дескрипторе ob j ect нужно задать следующие атрибуты.
<object
codetype="application/java"
classid="j ava:CalculationApplet.class"
width=10C height=150>
Обратите внимание на то, что атрибут classid может сопровождаться атрибутом codebase точно так же, как дескриптор applet.
Дескриптор object можно использовать для загрузки аплетов в текущие версии броузеров Netscape и Internet Explorer, но программа appletviewer и конвертер Java Plug-In HTML этот дескриптор для аплетов не воспринимают.
Передача информации аплетам
Подобно тому, как приложения могут воспринимать информацию, указанную в командной строке, аплеты могут использовать параметры, заданные в HTML-файле. Эта задача решается с помощью дескриптора языка HTML под названием param, который сопровождается атрибутами, определенными программистом. Допустим, что нам нужно дать Web-странице возможность определять стиль шрифта, использованного в аплете. Для этого понадобится задать следующие дескрипторы.
<applet code="FontParamApplet.class" width=200, height=200>
<param name="font" value="Helvetica"
</applet>
Затем мы получаем значение параметра с помощью метода getParameter класса Applet.
public class FontParamApplet extends JApplet
{
public void in.it ()
{
String fontName =
}
}
Метод getParameter можно вызывать только из метода init, но не из конструктора аплета. После выполнения конструктора параметры еще не готовы. Поскольку компоновка нетривиальных аплетов определяется параметрами, мы не рекомендуем использовать конструкторы аплетов. Весь код, необходимый для инициализации, следует поместить в метод init.
Параметры всегда возвращаются как строки. Для того чтобы получить числовую информацию, необходимо преобразовать эти строки в числа. Это можно сделать, пользуясь стандартным способом, например, вызвав метод parselnt класса Integer.
Допустим, мы хотим добавить параметр, задающий размер шрифта. Тогда соответствующий код на языке HTML будет выглядеть следующим образом.
<applet code="FontParamApplet.class" width=200, height=200>
<param name="font" value="Helvetica"
<param name="size"value="24"
</applet>
Считать этот целочисленный параметр можно с помощью такого фрагмента программы.
public class FontParamApplet extends JApplet {
public void init(]
{
String fontName =
} )} )int fontsize = Integer.parselnt(getParameter("size"));
} )
} )
Строки, используемые для определения параметров в дескрипторе param, и строки в методе getParameter должны совпадать абсолютно точно. В частности, в них должны различаться прописные и строчные буквы.
Кроме того, для гарантии, что параметры в нашей программе совпадают, нужно проверить, не пропущено ли значение параметра size. Для этого достаточно просто сравнить его с нулевой ссылкой null.
int fontsize;
String sizeString = getParameter("size1);
if (sizeString == null) fontsize = 12;
else fontsize = Integer.parsent(sizestring);
Ниже показан пример полезного аплета, широко использующего параметры. Этот аплет рисует на экране гистограмму, показанную на рис. 10.7.
Рис. 10.7. Аплет, рисующий гистограммуРис. 10.7. Аплет, рисующий гистограмму
Рис. 10.7. Аплет, рисующий гистограмму
Рис. 10.7. Аплет, рисующий гистограмму
Аплет получает на вход дескрипторы и числа, определяющие высоту столбиков гистограммы, с помощью значений дескриптор param в HTML-файле. Вот как выглядит HTML-файл, соответствующий рис. 10.7.
<applet code="Chart.class" width=40C height=300>
<param name="title" value="Diameters of the Planets">
<param name=”values" value="9">
<param name="name_l" value="Mercury">
<param name="name_2" value="Venus">
<paramname="name_3" value="Earth">
<param name="name 4" value="Mars">
<param name="name_5" value="Jupiter">
<param name-”name_6* value="Saturn">
<param name="name_7" value="Uranus">
<param name=="name_8" value="Neptune">
<param name=="name_9" value="Pluto">
<param name=="value_l' value="3100">
<param name•■-"value 2' value="7500">
<param name="value_3 value="8000">
<param name="value_4 value="4200">
<param name="value_5" value=" 88000">
<param name="value_6" value="71000">
<param name="value_7" value='"32000">
<param name="value_8' value=''30600">
<param name="value_9"vvaalue="'1430">
</applet>
Эти значения можно было бы задать в виде массива строки или чисел, однако у дескрипторов param есть два преимущества. Во-первых, можно создать несколько копий одного и того же аплета на Web-странице, демонстрируя разные гистограммы: для этого достаточно поместить на страницу два дескриптора applet с разными наборами параметров. Во-вторых, данные, которые отображаются в виде гистограмм, можно изменять. Широко известно, что диаметры планет довольно продолжительное время не изменяются, поэтому предположим, что нам нужно поместить на Web- страницу другую гистограмму, отражающую объем еженедельных продаж. Web- страницу изменить легко, поскольку они описываются обычным текстом. Редактировать и перекомпилировать файл на языке Java каждую неделю довольно утомительно.
Разумеется, существуют коммерческие программы, написанные на языке Java, которые создают намного более причудливые графические изображения, чем наш аплет с гистограммой. Купив одну из таких программ, можно поместить ее на свою Web- страницу и ни о чем больше не беспокоиться.
В листинге 10.5 приведен исходный текст нашего аплета. Обратите внимание на то, что метод init считывает параметры, а метод paintComponent рисует гистограмму.
1. import java.awt.*;
2. import java.awt.font.*;
3. import java.awt.geom.*;
4. import javax.swing.*;
5.
6. public class Chart extends JApplet
7. {
32.33.•34.35.36.37.42.43.44 .45.46.47.32.33.•34.35.36.37.42.43.44 .45.46.47.public void init () {
32.
33.
•34.
35.
36.
37.
42.
43.
44 .
45.
46.
47.
32.
33.
•34.
35.
36.
37.
42.
43.
44 .
45.
46.
47.
String v = getParameter("values") ;
if (v == null) return;
int n • Integer.parselnt(v);
double[] values = new double[n];
String[' names = new String[n];
int i;
for (i = 0; i < n; i++) {
values [i] = Double.parseDouble
(getParameter("value_" + (i + 1)));
names[i] = getParameter("name_" + (i + 1)); }
Container contentPane = getContentPane();
content Pane.add(new ChartPanel(values, names, getParameter("title")));
} }
/** Панель с гистограммой.
*/ class ChartPanel extends JPanel {
/**
Создает объект класса ChartPanel.
@param v массив значений гистограммы
@param n массив меток гистограммы
@param t заголовок гистограммы
*/
public ChartPanel(double[" v, String[] n, String t) {
names = n;
values = v;
title = t;
}
public void paintcomponent(Graphics g) {
super.paintcomponent(g);
Graphics2D g2 = (Graphics2D)g;
// Вычисляем максимальное и минимальное значения.
if (values == null) return;
double minValue = 0;
double maxValue = 0;
for (int i = 0; i < values.length; i++) {
if(minValue> values[i])minValue= values[i];
if(maxValue< values[ i ]]maxValue= values [i]:
}
if (maxValue == minValue) return;
int panelwidth = getWidthO;
int panelHeight = getHeight();
FonttitleFont=new Font("SansSerif",Font.BOLD, 20);
FontlabelFont=new Font("SansSerif",Font.PLAIN, 10)
// Вычисляем длину заголовка.
FontRenderContext context = g2.getFontRenderContext();
Rectangle2D titleBounds
= titleFont.getStringBounds(title, context);
double titlewidth =
double top = titleBounds.getHeight();
// Рисуем заголовок.
double у = -titleBounds.getYO ; // ascent
double x = (panelWidth - titleWidth) / 2;
g2.setFont(titleFont);
g2.drawstring(title, (float)x, (float)y);
// Вычисляем размеры меток гистограммы.
LineMetrics labelMetrics
= labelFont.getLineMetrics("", context);
double bottom = labelMetrics.getHeight();
у = panelHeight - labelMetrics.getDescent();
g2.setFont(labelFont; ;
// Вычисляем масштаб и ширину столбиков.
double scale = (panelHeight - top - bottom)
/ (maxValue - minValue);
int barWidth = panelWidth / values.length;
// Рисуем столбики.
for (int i = 0; i < values.length; i++)
{
// Получаем координаты столбиков.
double xl = i * barWidth + 1;
double yi = top;
double height = values[i] * scale;
if (values[i] >= 0)
yl += (maxValue - values[i]; * scale;
else
{
yl += maxValue * scale;
height = -height;
}
// Заполняем И рисуем столбики.
Rectangle2D rect = new Rectangle2D.Double(xl, yl,
barWidth - 2, height);
g2.setPaint(Color.RED);
g2.fill (rect);
g2.setPaint(Color.BLACK);
g2.draw(rect);
// Рисуем центрированную метку под столбиком.
Rectangle2D labelBounds
=context)
121.
double labelWidth = labelBounds.getwidth();
x = i * barWidth + (barWidth - labelWidth) / 2;
g2.drawstring(names[i], (float)x, (float)y);
}
}
127.
privatedouble[ values;
privateString[] names;
privateString title;
}
java.applet.Applet1.0
public String getParameter(String name)
Получает параметры, определенные директивой PARAM на Web-странице, загружающей аплет. Прописные и строчные буквы различаются.
public String getAppletlnfo()
Метод, возвращающий строку, содержащую информацию об авторе, версии программы и авторских правах на данный аплет. Для того чтобы заместить этот метод в своем аплете, сначала нужно задать эту информацию.
public String[][] getParameterlnfo()
Метод, возвращающий массив опций дескриптора param, поддерживаемых данным аплетом. В каждой строке содержится три элемента: имя, тип и описание параметра. Ниже приведен пример:
"fps", "1-10", "фреймов в секунду"
"repeat", "boolean", "повторить вывод изображения в цикле
"images", "url", "каталоги, содержащие изображения"
Мультимедиа
Аплеты могут отображать на экране графические изображения и воспроизводить аудиозаписи. Как известно, графические изображения могут иметь формат GIF или JPEG, а аудиофайлы — форматы AU, AIFF, WAV или MIDI. Изображения в формате GIF могут анимироваться. Обычно файл, содержащий такую информацию, указывается как универсальный адрес ресурса в Интернет (Uniform Resource Location — URL). Эти URL-адреса мы рассмотрим в первую очередь.
Универсальные адреса ресурсов
URL-адреса представляют собой описание ресурса в Интернет. Например, адрес "http://java.sun.com/index.html"сообщает броузеру, что нужно применить гипертекстовый протокол передачи данных (HTTP — HyperText Tranfer Protocol) к файлу index.html, расположенному на Web-странице java.sun.com. В языке Java
есть классинкапсулирующий URL-адреса. Простейший способ создать URL-
адрес — задать строку в конструкторе класса URL u = new
Такой URL-адрес называется абсолютным (absolute URL-address), так как мы указали полное имя ресурса. Другой полезный конструктор класса URL позволяет создавать относительные адреса(relativeURL-addresses).
URL data = new URL(u,
Это конструктор задает адрес файла planet.dat, расположенного в подкаталоге data по URL-адресу и.
Оба конструктора проверяют правильность URL-адреса. Если нарушены синтаксические правила, принятые для URL-адресов, возбуждается исключительная ситуацияЭто одна из тех исключительных ситуаций, которые
компилятор не позволяет игнорировать. try {
String s "http://java.sun.com/index.html";
URL u = new URL(s);
}
exception) {
//ошибки.
exception.printStackTrace(); }
В главе 12, посвященной обработке исключительных ситуаций, мы обсудим синтаксис этого фрагмента подробнее, а пока просто обратите внимание на ключевые слова try и catch.
Обычно для получения URL-адреса определяют, откуда прислан аплет. Для этого нужно получить ответы на два вопроса.
Какой URL-адрес имеет страница, запускающая аплет?
Какой URL-адрес имеет сам аплет?
Ответ на первый вопрос дает метода наметод
getCodeBase.
Либо документация, либо реализация методов getDocumentBase и getCodeBase ошибочны — см. сообщение #4456393 в списке ошибок, обнаруженных пользователями языка Java. Метод getDocumentBase возвращает URL-адрес фактического содержащего дескриптор applet, хотя документация по интерфейсу API утверждает, что он должен возвращать только URL-адрес каталога. Метод getCodeBase возвращает URL-адрес каталога, содержащего класс аплета, хотя документация по интерфейсу API утверждает, что он должен возвращать "URL-адрес самого аплета". Сообщение об ошибке само сформулировано неверно. В нем утверждается, что эти методы следует поменять местами. Это не имеет смысла: если вы используете атрибут codebase, метод getCodeBase правильно определяет каталог, в котором содержится код и не содержится документ.
С помощью аплетов и программы Java Plug-In можно получить доступ к Web- страницам, посвященным средствам безопасности, — см. Web-страницу http:// java.sun.com/products/plugin/1.3/docs/https. html. Эти страницы используют SSL-возможности соответствующего броузера.
Получение мультимедийных файлов
Файлы с графическими изображениями и аудиозаписями можно получить с помощью методов getlmage и getAudioClip.
Image cat = get Image(getDocumentBase(), "images/cat.gif");
AudioClip meow = getAudioClip(getDocumentBase(),
'Audio/meow.au");
Здесь мы применили метод getDocument, возвращающий URL-адрес, с которого загружен аплет. Второй аргумент конструктора класса URL задает местоположение графических изображений и аудиозаписей по отношению к базовому документу. (Аплет не обязательно пропускать через объект класса Toolkit, чтобы извлечь изображение.)
Изображения и аудиозаписи должны размещаться на том же сервере, что и аплет. По соображениям безопасности аплеты не имеют доступа к файлам, расположенным на других серверах ("аплеты могут звонить только домой").
Что можно сделать с полученными изображениями и аудиозаписями? В главе 7 мы показали, как выводится на экран отдельное изображение. В главе, посвященной многопоточности (см. том 2), мы изучим процесс анимации, состоящий из последовательности многочисленных изображений. Для того чтобы воспроизвести аудиозапись, достаточно вызвать метод play.
Метод play можно вызывать и без предварительной загрузки аудиозаписи.
play(getDocumentBase(1 , "audio/meow.au");
Однако, чтобы показать изображение на экране, его нужно сначала загрузить.
Для ускорения загрузки мультимедийные объекты можно размещать в JAR-файлах (см. раздел "JAR-файлы"). Методы getlmage и getAudioClip/play автоматически находят JAR-файлы аплета. Если графическое изображение или аудиозапись содержатся в JAR-файле, они немедленно загружаются. В противном случае броузер запрашивает их с Web-сервера.
j ava. net. URL 1.0
- '—Ie URL(String name)
Создает объект класса URL, используя строку, описывающую абсолютный URL-адрес.
URL(URL base, String name)
Создает относительный объект класса URL. Если строка name описывает абсолютный URL-адрес, то URL-адрес, заданный параметром base, игнорируется. В противном случае он интерпретируется как подкаталог URL- адреса, заданного параметром base.
java.applet.Applet 1.0
URL getDocumentBase ()
Если верить документации по интерфейсу API, определяет URL-адрес каталога, которому принадлежит страница, содержащая аплет. На самом деле в пакете SDK 1.4 этот метод возвращает URL-адрес самой страницы. См. сообщение #4456393 в списке ошибок, обнаруженных пользователями языкаJava.
URL getCodeBase()
Если верить документации по интерфейсу API, определяет URL-адрес класса аплета. На самом деле в пакете SDK 1.4 этот метод возвращает URL- адрес каталога. См. сообщение #4456393 в списке ошибок, обнаруженных пользователями языкаJava.
void play(URL uri;
void play(URL url, String name)
Первый метод воспроизводит аудиофайлы, расположенные по указанному URL-адресу, а второй — использует строку, задающую путь, вычисленный по URL-адресу, указанному в первом аргументе. Если аудиофайл не будет найден, ничего не произойдет.
AudioClip getAudioClip(URL url)
AudioClip getAudioClip(URL url, String name)
Первый метод получает аудиозапись по заданному URL-адресу, а второй использует строку, задающую путь, вычисленный по URL-адресу, указанному в первом аргументе. Если аудиофайл не будет найден, оба метода возвращают нулевую ссылку null.
Image getlmage(URL url)
Image getlmage(URL url, String name)
Возвращает объект класса Image, инкапсулирующий графическое изображение, заданное своим URL-адресом. Если изображения по указанному адресу нет, метод немедленно возвращает нулевую ссылку null. В противном случае запускается отдельный поток для загрузки изображения (см. главу 7).
Контекст аплета
Аплет запускается либо броузером, либо программой appletviewer. Аплет может поручить броузеру выполнить некоторые действия, например, извлечь аудиозапись, показать короткое сообщение в строке состояния или отобразить на экране другую Web-страницу. Броузер, содержащий аплет, может удовлетворить его запросы либо проигнорировать их. Например, если аплет, запускаемый программой appletviewer, попросит ее показать Web-страницу, ничего не произойдет.
Для взаимодействия с броузером аплет вызывает метод getAppletContext. Этот метод возвращает объект, реализующий интерфейс AppletContext. Конкретную реализацию интерфейса AppletContext можно считать способом взаимодействия между аплетом и содержащим его броузером. В дополнение к методам getAudioClip и getlmage интерфейс AppletContext содержит несколько полезных методов, которые обсуждаются ниже.
Взаимодействие между аплетами
Web-страница может содержать несколько аплетов. Если параметр codebase у этих аплетов один и тот же, они могут взаимодействовать друг с другом. Естественно, это довольно сложный процесс, необходимость в котором возникает нечасто.
Если задать атрибуты name каждого аплета в HTML-файле, то с помощью метода ge Applet (String) интерфейса AppletContext можно получить ссылку на любой аплет. Допустим, что HTML-файл содержит дескриптор
<applet code="Chart.class" width=100 height=100 name="Chartl"> При ЭТОМ вызов
Applet chartl = getAppletContext () .getApplet("Chartl");
вернет ссылку на аплет. Что можно сделать с этой ссылкой? Если в классе Chart есть метод, предназначенный для получения новых данных и обновления гистограммы, его можно вызвать, выполнив соответствующее приведение типов.
((Chart)chartl).setData(3, "Earth", 9000);
Кроме того, можно перечислить все аплеты, размещенные на Web-странице, независимо от того, имеют ли они атрибут name или нет. Метод getApplets возвращает так называемый перечислимый объект (enumeration object). Ниже приведен цикл, выводящий на печать имена всех аплетов, расположенных на текущей Web-странице.
Enumeration e = getAppletContext().getApplets();
while (e.hasMoreElements()1
{
Object a = e.nextElements();
System.out.printIn(a.getClass().getName());
}
Аплет не может взаимодействовать с аплетами, находящимися на другой Web- странице.
Отображение элементов в броузере
У программиста есть доступ к двум областям броузера, содержащего аплет: строке состояния и области отображения Web-страницы. Обе области используют методы класса AppletContext.
С помощью метода ShowStatus внизу экрана в строке состояния можно вывести строку сообщения.
showstatus("Загрузка данных ... пожалуйста, ждите");
Наш опыт показывает, что метод showStatus имеет ограниченную область применения. Броузер также использует строку состояния и в большинстве случаев заменяет ранее выведенную строку своими сообщениями, например "Выполняется аплет". Используйте строку состояния для таких ремарок, как "Загрузка данных . . . пожалуйста, ждите", но не для важных сообщений, которые пользователь не должен пропустить.
С помощью метода showDocument можно приказать броузеру показать другую Web-страницу. Простейший способ: вызвать метод showDocument с одним аргументом, задающим URL-адрес нужной Web-страницы.
URLu= newURL("http://java.sun.com/index.html"); getAppletContext().showDocument(u);
С этим вызовом связана одна проблема. Она заключается в том, что броузер открывает новую Web-страницу в том же самом окне, в котором была открыта текущая Web-страница, заменяя таким образом ваш аплет. Чтобы вернуться в свой аплет, пользователь должен щелкнуть на кнопке "Back", расположенной на панели инструментов броузера.
Задав второй параметр в вызове метода showDocument, можно приказать броузеру показать аплет в другом окне. Второй параметр является строкой. Если эта строка представляет собой специальную строку "_blank", броузер откроет новое окно с документом и не будет замещать текущий документ. Что еще важнее, пользуясь преимуществами фреймов, предоставляемыми языком HTML, можно разделить окно броузера на несколько фреймов, каждое из которых имеет свое имя. Затем можно поместить аплет внутрь фрейма, а остальные документы показывать в других фреймах. В следующем разделе мы увидим, как это делается.
Второй аргумент
Место вывода документа
"_self" или отсутствует
Текущий фрейм
"_parent"
Родительский фрейм
"_top"
Фрейм самого высокого уровня
"_blank"
Новое безымянное окно верхнего уровня
Любая другая строка
Фрейм, имеющий заданное имя. Если фрейма с таким именем не существует, открывается новое окно, получающее заданное имя
java.applet.Applet1.2
public AppletContext getAppletContext(]
Открывает доступ к броузеру, содержащему аплет. В большинстве случаев эту информацию можно использовать для управления броузером, в котором запускается аплет.
void showstatus(String msg;
Выводит в строке состояния броузера указанную строку.
AudioClip getAudioClip(URL url]
Возвращает объект класса AudioClip, в котором хранится аудиофайл, находившийся по указанному URL-адресу. Для воспроизведения этого файла используется метод play.
java.applet.Appletcontext 1.2
Enumeration getApplets()
Возвращает перечисление (см. том 2) всех аплетов, находящихся в одном и том же констексте, т.е. на одной и той же Web-странице.
Applet getApplet(String name)
Возвращает аплет, находящийся в текущем контексте и имеющий заданное имя. Если такого аплета не существует, возвращает ссылку null. Поиск производится только на текущей Web-странице.
void showDocument(URL url)
e void showDocument(URL url, String target)
Показывает новую Web-страницу внутри фрейма в броузере. При вызове первого метода новая страница заменяет собой текущую. При вызове второго метода используется строка, идентифицирующая нужный фрейм. Этим параметром могут быть следующие строки: "_self " (показывает страницу в текущем фрейме, как при вызове первого метода), " parent" (показывает страницу в родительском фрейме), "_top " (показывает страницу внутри фрейма самого высокого уровня) и "_blank" (показывает страницу в новом безымянном окне высокого уровня). Строка может задавать имя фрейма.
Программа appletviewer, разработанная компанией Sun, не может показывать Web-страницы. В этой программе команда showDocument игнорируется.
Аплет с закладками
Этот аплет позволяет воспользоваться преимуществами фреймов, предоставленными языком HTML 3.2 и его более поздними версиями. Разделим экран вертикально на два фрейма. Левый фрейм содержит аплет на языке Java, отображающий список закладок. Когда пользователь выбирает одну из них, аплет переходит на соответствующую Web-страницу, которая отображается в правом фрейме (рис. 10.8).
В листинге 10.6 показан HTML-файл, определяющий эти фреймы.
1. <html>2 . <head>3. <title>Bookmark Applet</title>4. </head>5. <frameset cols="320,*">6. <frame name="left" src="Left.html"7. marginheight=2 marginwidth=28.scrolling = "no" noresize>9. <frame name="right" src="Right.html"10. marginheight=2 marginwidth=211.scrolling = "yes" noresize>12. </frameset>13. </html>1. <html>2 . <head>3. <title>Bookmark Applet</title>4. </head>5. <frameset cols="320,*">6. <frame name="left" src="Left.html"7. marginheight=2 marginwidth=28.scrolling = "no" noresize>9. <frame name="right" src="Right.html"10. marginheight=2 marginwidth=211.scrolling = "yes" noresize>12. </frameset>13. </html>
1. <html>
2 . <head>
3. <title>Bookmark Applet</title>
4. </head>
5. <frameset cols="320,*">
6. <frame name="left" src="Left.html"
7. marginheight=2 marginwidth=2
8.scrolling = "no" noresize>
9. <frame name="right" src="Right.html"
10. marginheight=2 marginwidth=2
11.scrolling = "yes" noresize>
12. </frameset>
13. </html>
1. <html>
2 . <head>
3. <title>Bookmark Applet</title>
4. </head>
5. <frameset cols="320,*">
6. <frame name="left" src="Left.html"
7. marginheight=2 marginwidth=2
8.scrolling = "no" noresize>
9. <frame name="right" src="Right.html"
10. marginheight=2 marginwidth=2
11.scrolling = "yes" noresize>
12. </frameset>
13. </html>
Мы не будем рассматривать все элементы синтаксиса. Следует лишь отметить, что каждый фрейм обладает двумя важными свойствами: именем (заданным атрибутом name) и URL-адресом (заданным атрибутом src). Мы не придумали ничего лучше и просто назвали фреймы именами "left"и "right".
10.8. Аплет с закладками
Левый фрейм (листинг загружает файл с именем html, который в свою очередь загружает в него аплет. В этом файле просто указаны аплет и закладки. Этот файл можно приспособить к собственной Web-странице, изменив закладки.
<html>
<title>A Bookmark Applet</title>
<body>
<p>
Щелкните на одной из переключателей.
Соответствующая Web-страница отобразится в правом фрейме.
<applet
name=link_l
<param
<param name=link_3
<param
<param
<param name=link_6
<param
</applet>
</body>
</html>
Правый фрейм (листинг 10.8) загружает фиктивный файл под названием
Right.html.
1. <html>
2. <title>
3. Здесь будет отображаться Web-страница.
4. </title>
5. <body>
6. Щелкните на переключателе в левом фрейме.
7. Здесь отобразится соответствующая Web-страница.
8. </body>
9. </html>
Код аплета с закладками, приведенный в листинге 10.9, довольно прост. Он считывает значения параметров link link 2 и так далее и превращает их в переключатели. При выборе переключателя метод showDocument отображает в правом фрейме соответствующую страницу.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
import java.net.*;
import;
public class Bookmark extends JApplet
{
public void init(1
.
Box box =
.ButtonGroup group = new ButtonGroup();
int i = 1;
String urlString;
// Считываем все параметры link_n.
while ((urlString =getParameter("link" + i)) != null)
try
final URL url = new URL(urlString);
// Создаем переключатель для каждой ссылки.
JRadioButton button = new JRadioButton(urlString);
box.add(button);
group.add(button);
30// Выбирая переключатели, отображаем Web-страницы
31// в правом фрейме.
32.33.34.35.36.37.38..39.53.32.33.34.35.36.37.38..39.53.}}11button.addActionListener(new
32.
33.
34.
35.
36.
37.
38..
39.
53.
32.
33.
34.
35.
36.
37.
38..
39.
53.
}
}
1
1
ActionListener(]
{
public void actionPerformed(ActionEvent event) {
AppletContext context = getAppletContext(); context.showDocument(url, "right");
});
}
catch(MalformedURLException exception)
{
j exception.printStackTrace() ;
i*+;
}
Container contentPane = getContentPane^)$
contentPane.add(box] ;
Это — аплет. Это — приложение. А это — и то, и другое.
Несколько лет назад юмористическая программа "Субботний вечер в прямом эфире" на американском коммерческом телевизионном канале показала спор супругов о белой желеобразной субстанции. Муж сказал: "Это — десертное блюдо". Жена сказала: "Это — мастика для паркета". А ведущий триумфально подвел итог: "Это — и то, и другое!".
В этом разделе мы покажем, как писать программы, представляющие собой и аплет, и приложение одновременно. Эту программу можно загружать в appletviewer, в броузер или запускать из командной строки с помощью интерпретатора ava. Мы не уверены, что это так уж необходимо — просто интересно, что это вообще оказалось возможным.
Рис. 10.9. Калькулятор как приложениеРис. 10.9. Калькулятор как приложениеНа рис. 10.9 и 10.10 показана одна и та же программа, запущенная из командной строки как приложение и изнутри программы appletviewer как аплет.
Рис. 10.9. Калькулятор как приложение
Рис. 10.9. Калькулятор как приложение
Рис. 10.10. Калькулятор как аплет
Посмотрим, как это сделано. Каждый файл классов содержит только один открытый класс. Чтобы запустить аплет, этот класс должен стать производным от класса Applet. Чтобы запустить приложение, нужен статический метод main. Итак, нужно написать такой фрагмент кода.
class MyAppletApplication extends JApplet
public void init(; { ... }
static public void main(String[] args) { ... } }
Что поместить внутрь метода main? Обычно создается объект данного класса и вызывается метод show. В нашем случае все не так просто. Мы не можем показать "голый" аплет. Аплет должен находиться внутри фрейма, а поэтому нужно вызвать метод init.
Для того чтобы создать фрейм, создадим класс AppletFrame.
public class AppletFrame extends JFrame (
public AppletFrame(Applet anApplet)
applet = anApplet;
Container contentPane = getContentPane () ;
contentPane.add(applet);
)
}
Конструктор фрейма помещает в него аплет, расширяющий класс Component. В методе main нашего аплета/приложения создадим новый фрейм этого вида.
class MyAppletApplication extends JApplet {
public void init (; { ... }
public static void main(String args[])
AppletFrame frame
= new AppletFrame(new MyAppletApplication();
frame.setTitle("MyAppletApplication");
frame.setsize(WIDTH, HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show О;
}
}
Здесь таится ловушка. Если программа запускается интерпретатором, а не программой appletviewer,и вызывает метод getAppletContext, она получит в ответ ссылку null, поскольку запускается не внутри броузера. Во время выполнения программы попытка выполнить вызов
getAppletContext(I.showstatus(message; ;
закончитсянеудачей. Посколькумынесобираемсяписатьзаконченныйброузер,доста- точно правильно выполнить этот вызов. Он не выводит на экран никаких сообщений, но, по крайней мере, не приводит к краху программы. Для того чтобы правильно его выполнить, достаточно реализовать два интерфейса: AppletStub и AppletContext.
Мыужевиделиконтекстыаплстонвдействии. Онивыполняютзагрузкуграфическихи аудиофайлов, атакжеотображаютнаэкранеWeb-страницы. Однакоконтекстымогут веж- ливоотказатьсявыполнятькоманды, иименноэтосделалконтекстнашегоаплета. Основная цель интерфейса AppletStub— обнаружить контекст аплета. Каждый аплет имеет свою заглушку (множество, содержащее методse Stub классаApplet).
В нашем случае класс AppletFrame реализует интерфейсы AppletStub и AppletContext одновременно. Мы предусмотрели минимум функциональных возможностей, необходимых для реализации этих интерфейсов.
public class AppletFrame extends JFrame
implements AppletStub, AppletContext {
// Методы интерфейса AppletStub.
public boolean isActive() { return true; }
public URL getDocumentBase() { return null; }
public URL getCodeBase(; { return null; }
public String getParapmeter(String name) { return ""; }
public AppletContext getAppletContext(] { return this; }
public void appletResize(int width, int height) (}
// Методы интерфейса AppletContext.
public AudioClip getAudioClip(URL uri; { return null; }
public Image getImage(URL url) { return null; }
public Applet getApplet(String name) { return null; }
public Enumeration getAplets(; { return null; }
public void showDocument(URL url) {};
public void showDocument(URL url, String target) (}
public void showStatus(String status) {}
public void setStream(String key, Inputstream stream) {};
public InputStream getStream(String key) {return null;}
public Iterator getTSreamKeys(){ return null; }
}
Затем конструктор фрейма вызывает метод setStub для аплета, и создается его заглушка.
public AppletFrame(Applet anApplet)
{
applet = anApplet;
Container contentPane = getContentPane();
contentPane.add(applet);
;
}
При работе с пакетом SDK 1.3 в процессе компиляции этого файла вы получите предупреждение, что класс java.awt.Windowуже имеет метод isActive с областью видимости, ограниченной пакетом. Поскольку этот класс и классWindow находятся в разных пакетах, метод Window.isActive заместить невозможно. Это нам ПОДХОДИТ — ведь мы хотим создать новый метод isActive для интерфейса Appletstub. Довольно интересно, что в подкласс можно совершенно законно добавить новый метод с уже существующей сигнатурой. Как только программа получит доступ к объекту через ссылку Window внутри пакета java. awt, будет вызван методWindow. isActive, область видимости которого ограничена пакетом. Однако если доступ к объекту обеспечивается через класс AppletFrame или интерфейс Appletstub, вызывается методAppletFrame . isActive.
В финале возможен еще один трюк. Допустим, что мы хотим использовать калькулятор в качестве аплета и приложения одновременно. Вместо того чтобы перемещать методы класса CalculatorApplet в класс CalculatorAppletApplication, мы просто применим интерфейс. Вот код класса, который делает это.
public class CalculatorAppletApplication extends CalculatorApplet {
public'static void main(String args[])
{
AppletFrame frame
= new AppletFrame(new CalculatorApplet());
Такое можно проделать с любым аплетом, а не только с калькулятором. Для этого нужно лишь вывести класс MyAppletApplication из класса вашего аплета и передать объект new MyApplet () методу AppletFrame в методе main. В результате вы получите класс, представляющий собой и аплет, и приложение одновременно.
Просто ради забавы мы применили описанный выше трюк, связанный с добавлением в исходный файл дескриптора applet в виде комментария. Теперь можно вызвать программу appletviewer, задав в командной строке имя исходного (!) файла и не прибегая к помощи HTML-файлов.
Соответствующий код приведен в листингах 10.10 и 10.11. Чтобы скомпилировать эту программу, нужно скопировать файл CalculatorApplet.java в ее каталог. Запустить аплет/приложение можно с помощью следующих команд.
appletviewer CalculatorAppletApplication.java
java CalculatorAppletApplication
13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.public class AppletFrame extends JFrame implements AppletStub, AppletContext {
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
public AppletFrame(Applet anApplet)
{
applet = anApplet;
Container contentPane = getContentPane ();
contentpane.add(applet);
applet.setstub(this) ;
public void show() {
applet.init();
super.show();
applet.start ();
}
// Методы интерфейса AppletStub.
public boolean isActive() { return true; }
public URL getDocumentBase() { return null; }
public URL getCodeBase() { return null; }
public String getParameter(String name) { return ""; }
public AppletContext getAppletContext() { return this; } public void appletResize(int width, int height) {}
// Методы интерфейса AppletContext.
public AudioClip getAudioClip(URL url) { return null; }
public Image getImage(URL url) ( return null; }
public Applet getApplet(String name) { return null; }
public Enumeration getApplets() { return null; }
public void showDocument(URL url) {}
public void showDocument(URL url, String target) {}
public void showStatus(String status) {}
public void setStream(String key, InputStream stream){}; public InputStream getStream(String key) {return null;}
private Applet applet;private Applet applet;public Iterator getTSreamKeys() ( return null; }
private Applet applet;
private Applet applet;
1./
Программа appletviewer считывает указанные ниже дескрипторы
по команде appletviewer. CalculatorAppletApplication.java (!)
Отдельный HTML-файл не нужен.
<applet code="CalculatorAppletApplication.class"
width=200 height=200>
</applet>
' /
import javax.swing.*;
public class CalculatorAppletApplication
extends CalculatorApplet
// Это и аплет, и приложение!
{
public static void main(String[] args). - . .
{
AppletFrame frame
= new AppletFrame(new CalculatorApplet());
frame.setTitle("CalculatorAppletApplication"); ,
frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
public static final int DEFAULT_WIDTH = 200;
public static final int DEFAULT_HEIGHT = 200;
}
ФайлыJAR
Аплет, имитирующий работу калькулятора, использует четыре класса: CalculatorApplet, CalculatorPanel и два внутренних класса. Дескриптор аплета ссылается на файл, содержащий класс, производный от класса JApplet:
<applet code="CalculatorApplet.class" width=100 height=150>
Прочитав этот файл, броузер соединится с Web-сервером и загрузит файл CalculatorApplet.class. Затем загрузчик классов (class loader) интерпретатора языка Java, встроенный в броузер, извлечет класс CalculatorApplet из этого файла. В это время загрузчик должен выявить (resolve) другие классы, используемые в загружаемом классе. Сделав это, загрузчик обнаружит, что для запуска аплета нужны дополнительные классы, и тогда броузер вновь соединится с Web-сервером. Большинство аплетов состоит из нескольких классов, и броузер вынужден выполнять много соединений с Web-сервером, загружая каждый класс отдельно. Загрузка такого аплета может происходить довольно медленно.
Следует помнить, что загрузка выполняется так медленно не из-за большого размера файлов, содержащих классы, — они достаточно малы. Причина заключается в излишних затратах времени на соединение с Web-сервером.
Язык Java поддерживает улучшенный способ загрузки файлов, содержащих классы, позволяющий упаковывать все необходимые классы в один файл. Затем этот файл можно загрузить с помощью единственного запроса, направленного на Web-сервер. Файлы, в которых архивируются файлы классов, называются J AR-файлами (Java Archive). J AR-файлы могут содержать не только файлы классов, но и файлы других типов, например, графические изображения и аудиозаписи. JAR-файлы сжимаются с помощью хорошо известного формата ZIP, сокращая время их загрузки. (По умолчанию они загружаются в каталогjdk/bin.) Чаще всего для создания НОВОГО JAR-файла используется следующий синтаксис.
jar cvf JARFileName Filel File2 ...
Например,
jar cvf Calculatorclasses.jar *.java icon.gif
Общий формат команды jar выглядит так.
jar опции Файл1 Файл2 .. .
В табл. 10.3 перечислены все опции программы jar. Они напоминают опции команды tar в операционной системе UNIX.
Опция
Описание
с
Создает новый или пустой архив и добавляет в него файлы. Если в качестве имени файла указано имя каталога, программа j arобрабатывает его рекурсивно
t
Отображает на экране содержание архива
и
Обновляет существующийJAR-файл
X
Извлекает файлы. Если указано несколько имен файлов, извлекаются только они. В противном случае извлекаются все файлы
f
Задает ИМЯJAR-файла в качестве второго аргумента командной строки. Если этот параметр пропущен, то программаjarзапишет результат в стандартный поток вывода (при создании JAR-файла) или считает его из стандартного потока ввода (при извлечении или просмотре JAR-файла)
V
Генерирует подробный отчет о файле
m
Добавляет вJAR-файл манифест (manifest), представляющий собой описание содержимого архива и его происхождения. Каждый архив по умолчанию имеет манифест, но для подробного описания содержимого архива можно создать свой собственный манифест. Эта тема подробно обсуждается во втором томе в главе, посвященной безопасности
c
Сохраняет файл, не упаковывая его в формате ZIP
M
Отменяет создание манифеста
Создает индексный файл (см. ниже)
С
Временно изменяет каталог. Например, команда jar cvf JARFileName.jar -C classes *.class направляет файлы в подкаталог■ classes
Если у нас есть JAR-файл, нужно как-то ссылаться на него в дескрипторе applet. Вот как это можно сделать.
<applet code="CalculatorApplet.class”
archive="CalculatorClasses.jar"
width=10C height=150>
Учтите, что атрибут code необходим. Он сообщает броузеру имя аплета. Атрибут archive задает имя файла, в котором находится класс аплета и другие файлы. Если в программе нужны файлы, содержащие классы, графические изображения или аудиозаписи, сначала броузер ищет JAR-файлы в списке, указанном параметром archive. Только если этих файлов там нет, броузер соединяется с Web-сервером.
Если ваш аплет слишком велик, не все смогут полностью использовать все его функциональные возможности. Для того чтобы сократить время загрузки, можно разбить код аплета, записав части в несколько JAR-файлов и добавив в главный JAR-файл указатель (index). Затем загрузчик классов распознает, какой JAR-файл содержит конкретный пакет или ресурс. Подробное описание процедуры индексирования содержится на Web-странице
http://java.sun.eom/j2se/l.4/docs/guide/jar/jar.html#JAR%20lndex.
Кэширование JAR-файлов
По умолчанию для кэширования кода аплета броузеры используют собственный кэш. Это значит, что если вы вернетесь повторно на Web-страницу, использующую данный аплет, а кэш броузера по-прежнему будет хранить JAR-файл, не подвергшийся изменениям, то повторной загрузки JAR-файла не будет. Все это хорошо, но кэш броузера для аплета является недостаточно долговременным хранилищем. Например, если вы раз в месяц посещаете аплет, подчитывающий затраты, то вполне вероятно, что до следующего вашего посещения вся информация об этом аплете из кэша будет удалена.
ПрограммаJava Plug-In поддерживает механизм, позволяющий продлить срок жизни аплета. Если вам нужно оставить аплет на компьютере пользователя на достаточно долгое время, используйте ключи CACHE_OPTION, CACHE_ARCHIVE и CACHE_VERSION.
Эти ключи нужно указать в качестве параметров или атрибутов, в зависимости от того, какой дескриптор используется: ob j ect или embed.
<object >
<param name="archive" value-"...">
<param name="cache_option" value="...">
<param name="cache_archive" value="...">
<param name="cache_version" value="...">
</object>
или
<embed...
archive="..."
"cache_option="..."
"cache_archive="..."
"cacheversion"="..."
Ключ cache_optionпринимает три значения.
No: не кэшировать аплет вообще.
Browser: разрешить броузеру кэшировать аплет (по умолчанию).
Plugin: разрешить программе Plug-In кэшировать аплет.
Значение ключа cache_archive указывает файлы, подлежащие кэшированию, например“MyApplet.jar,MyLibrary.jar".
JAR-файл должен быть указан либо в атрибуте archive, либо в атрибуте cache_archive, но не в обоих одновременно.
Ключ cache_version является необязательным. Его значение задает список номеров версий соответствующих JAR-файлов, указанных в списке cache_archive. Если эти версии файлов у клиента уже есть, они больше не загружаются. Если никакие версии не указаны, то для сравнения используются даты создания JAR-файлов. Номера версий нужны, если вы кэшируете JAR-файлы с использованием механизма SSL, поскольку в этом случае последняя версия файла программе Java Plug-In не доступна.
Кэшированию аплетов посвящена Web-страница
http://java.sun.com/products/plugin/1.3/docs/appletcaching/html
Упаковка приложений
Покинем мир аплетов и займемся упаковкой приложений, написанных на языке Java. Пересылая приложения, никто не хочет возиться с большим количеством файлов. Как и при использовании аплетов, нужно запаковать все классы и другие ресурсы, необходимые вашей программе, в JAR-файл. Запакованную программу можно загрузить с помощью простой команды или, если операционная система настроена соответствующим образом, путем двойного щелчка мышью на пиктограмме J AR-файла.
Манифест
В JAR-файлы можно упаковывать приложения, компоненты (иногда называемые "Java beans" — см. главу 8 в томе 2) и библиотеки программ. Например, рабочая библиотека пакета SDK содержится в очень большом файле rt.j ar.
Jar-файл представляет собой обычный ZIP-файл, содержащий классы, другие файлы, необходимые для программы (например пиктограммы), и файл манифеста (manifest file), описывающий особенности архива.
Файл манифеста называется MANI FEST.MF и находится в специальном подкаталоге МЕТА- INF. Минимально допустимый манифест довольно неинтересен: Manifest-Version:1.0
Сложные манифесты содержат гораздо больше описаний, которые группируются в разделы. Первый раздел манифеста называется главным (main section). Он относится ко всему JAR-файлу. Остальные описания относятся к поименованным сущностям, т.е. к отдельным файлам, пакетам или URL-адресам. Эти записи должны начинаться словом Name. Разделы отделяются друг от друга пробелами.
Manifest-Vewsrion: 1.0
Строки описания этого архива.
Name: Woozle.class
Строки описания этого файла.Строки описания этого файла....,... , ,
Строки описания этого файла.
Строки описания этого файла.
Name: foo/bar
Строки описания этого пакета.
Для того чтобы отредактировать этот манифест, поместите в него нужные строки. Затем выполните команду
jar cfm ИмяЗАКфайла ИмяФайлаМанифеста
Например, для того чтобы создать новый JAR-файл с манифестом, запустите программу
jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class
Чтобы добавить в манифест существующего JAR-файла новые записи, запишите эти строки в текстовый файл, а затем выполните команду
jar ufm MyArchive.jar manfest-additions.mf
Более подробную информацию о форматах JAR-файлов и файлов манифеста можно найти на Web-странице
http://java.sun.eom/j2se/l.3/docs/guide/jar/jar.html.
Самораспаковывающиеся JAR-файлы
До сих пор нас интересовало, как запаковать аплеты в JAR-файлы. Это позволяет сократить количество файлов, которые броузер должен загружать при запуске аплета. Однако JAR-файлы оказываются полезными и для распространения приложений, позволяя избежать беспорядка в файловой системе пользователя. Нужно просто запаковать все файлы, необходимые для вашего приложения, в JAR-файл, а затем добавить в манифест запись, указывающую главный класс вашей программы— класс, который нужно указать интерпретатору j ava.
Создайте файл, скажем, mainclass .mf, содержащий строку
Main-Class com/mypackage/MainAppClass
Последняя строка манифеста должна заканчиваться символом перехода на новую строку. В противном случае манифест не будет правильно считан. Программисты часто ошибаются, записывая в текстовый файл строку Main-Class, забыв указать символ перехода на новую строку.
Не указывайте расширения .classпосле имени главного класса. Затем выполните командуjar.
jar evfm MyProgram.jar mainclass.mf добавляемые файлы
Теперь пользователь сможет просто запустить программу из командной строки.
java -jar MyProgram.jar
В зависимости от конфигурации операционной системы приложение можно запустить, дважды щелкнув на пиктограмме JAR-файла.
На платформе Windows система поддержки выполнения программ, написанных на языке Java, связывает расширение файла ". jar" с командой javaw - jar, которая запускает приложение. (В отличие от команды java, команда j a vaw не открывает окно оболочки.)
На платформе Solaris операционная система распознает "волшебный номер" J AR-файла и запускает приложение с помощью команды j ava -jar.
На платформе Mac OS X операционная система распознает расширение файла " . j ar и выполняет программу, написанную на языке Java, в ответ на двойной щелчок мышью на соответствующей пиктограмме. Более того, существует утилита MRJAppBuilder, которую можно использовать для превращения JAR- файла в приложение операционной системы Mac. Это приложение имеет пути к классам, пиктограммы и т.п. Кроме того, оно также запускается с помощью двойного щелчка на пиктограмме файла. Детали изложены на Web-странице http://developer.apple.com/qa/java/java29. html.
Ресурсы
Классы, используемые в аплетах и приложениях, часто, в свою очередь, связаны с файлами, содержащими разнообразные данные. К таким файлам относятся:
графические файлы и аудиозаписи;
текстовые файлы, содержащие строки состояния и метки кнопок;
бинарные файлы, например, содержащие описание компоновки.
В языке Java все эти файлы называются ресурсами (resources).
В операционной системе Windows слово "ресурсы" имеет более узкий смысл. В этой системе к ресурсам также относятся пиктограммы, метки кнопок и тому по- добное/но они должны быть связаны с исполняемыми файлами, а доступ к ним должен обеспечивать стандартный программный интерфейс. В противоположность этому ресурсы в языке Java находятся в отдельных файлах, а не в файлах, содержащих классы, и доступ к ним имеет любой класс.
Рассмотрим класс AboutPanel, выводящий на экран сообщение (рис. 10.11).
Рис. 10.11.Отображение
ресурса из JAR-файла
Конечно, название книги и год выпуска, указанные на панели, в следующем издании книги изменятся. Для того чтобы легко отслеживать такие изменения, текст нужно поместить в отдельный файл, а не встраивать в программу в качестве строки.
Но где же следует разместить файл about. txt? Разумеется, было бы удобно просто поместить его вместе с остальными файлами программы, например, вJ AR-файле.
Загрузчик классов знает, где находятся файлы классов, если ему указан путь к соответствующим каталогам, архивам или Web-серверу. Механизм ресурсов дает программисту такие же удобства при работе с файлами, не содержащими классы. Для этого нужно выполнить следующие действия.
Получить объект класса Class, обладающий ресурсом, например About- Panel.class.
Вызвать метод getResource ( filename] и определить URL-адрес ресурса.
Если ресурс представляет собой графический или звуковой файл, прочитать его непосредственно с помощью методов getImage или getAudioClip.
В противном случае применить метод openStream к URL-адресу и прочитать данные из файла (см. главу 12).
Дело в том, что загрузчик класса помнит, где он располагается, и может обнаружить там же файлы ресурсов.
Например, чтобы создать пиктограмму, хранящуюся в файле about.gif, нужно выполнить следующие операторы.
URL url =
Imageicon icon = new Imageicon(url);
Эти операторы означают: "искать файл about.gif там же, где находился файл AboutPanel .class".
Чтобы прочитать содержимое файла about.txt, можно использовать аналогичные команды.
URL url = AboutPanel.class.getResource("about.gif"); Inputstream in = url.openStream();
Поскольку эта комбинация операторов очень широко распространена, удобно применить сокращение: метод getResourceAsStream возвращает объект класса InputStream, а не URL.
InputStream in =
Для того чтобы прочитать информацию из этого потока, нужно знать, как обрабатывается ввод (см. главу 12). В программе поток прочитывается строка за строкой. InputStream in = AboutPanel.class.
;
BufferedReader reader = new BufferedReader(new
;
String line;
while (dine = reader. readLine () != null)
textArea.append(1ine+ "\n");
Среди файлов с Демонстрационными программами из нашей книги, которые можно загрузить с Web-страницы www.phptr.com/corejava, есть JAR-файл Resourcetest.jar, содержащий все файлы классов для данного примера и файлы ресурсов about.gif и about.txt". Как видим, программа находит файл ресурсов вместе с файлами классов в одном и том же JAR-файле. Исходный текст этой программы содержится в листинге 10.12.
Как мы уже видели в предыдущем разделе, изображение и аудиозаписи можно упаковывать в JAR-файл, а затем обращаться к ним с помощью методов getlmage и getAudioClip (эти методы автоматически обнаруживают JAR-файлы). Однако, чтобы загрузить другие файлы из JAR-файла, нужно использовать метод getResourceAsStream.
Классы можно не помещать в один каталог с файлом классов. Вместо этого их можно записать в подкаталог. Иерархическое имя ресурса выглядит так: data/text/about.txt
Это — относительное имя ресурса, поэтому оно интерпретируется относительно пакета, которому принадлежит класс, загружающий этот ресурс. Обратите внимание на то, что в этом имени всегда нужно использовать разделитель /, независимо от того, какие разделители применяются для каталогов, в которых размещены файлы ресурсов. Например, в файловой системе на платформе Windows загрузчик ресурсов автоматически преобразует разделитель / в разделитель \.
Имяресурса,начинающеесяссимвола/,называется абсолютным именем ресурса. По этому имени ресурс обнаруживается точно так же, как и класс. Например, ресурс /corejavaZtitle.txt
находится в каталоге corejava. Он может быть подкаталогом каталога, в котором расположен файл класса, а также находиться внутри JAR-файла или на Web-сервере.
Несмотря на то что загрузка ресурса производится автоматически, стандартных методов интерпретации содержимого файла ресурсов не существует. Каждый аплет по-своему интерпретирует содержимое файлов ресурсов.
Кроме того, ресурсы применяются для интернационализации аплетов и приложений. В файлах ресурсов, например, содержатся сообщения и метки на разных языках. Каждый такой файл соответствует отдельному языку. Интернационализация интерфейса прикладных программ (internationalization API), которая обсуждается во втором томе в главе 10, поддерживает стандартный метод организации и доступа К этим файлам локализации.
1. import java.awt.*;2. import java.awt.event.*;3. import java.io.*;4. import java.net.*;5. import javax.swing.*;6.7. public class ResourceTest8. {9. public static void main(String[] args)10.{11.ResourceTestFrame frame = new ResourceTestFrame();12.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);13.frame.showO ;14.}15.}1. import java.awt.*;2. import java.awt.event.*;3. import java.io.*;4. import java.net.*;5. import javax.swing.*;6.7. public class ResourceTest8. {9. public static void main(String[] args)10.{11.ResourceTestFrame frame = new ResourceTestFrame();12.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);13.frame.showO ;14.}15.}В листинге 10.12 приведен HTML-файл для тестирования ресурса, а в листинге — программа на языке Java.
1. import java.awt.*;
2. import java.awt.event.*;
3. import java.io.*;
4. import java.net.*;
5. import javax.swing.*;
6.
7. public class ResourceTest
8. {
9. public static void main(String[] args)
10.{
11.ResourceTestFrame frame = new ResourceTestFrame();
12.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
13.frame.showO ;
14.}
15.}
1. import java.awt.*;
2. import java.awt.event.*;
3. import java.io.*;
4. import java.net.*;
5. import javax.swing.*;
6.
7. public class ResourceTest
8. {
9. public static void main(String[] args)
10.{
11.ResourceTestFrame frame = new ResourceTestFrame();
12.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
13.frame.showO ;
14.}
15.}
/**
Фрейм, содержащий панель с компонентами, демонстрирующими
доступ к ресурсам из JAR-файла.
*/
class ResourceTestFrame extends JFrame
{
public ResourceTestFrame(1
{
setTitle("ResourceTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setContentPane(new AboutPanel());
}
publicstaticfinalintDEFAULT_WIDTH = 300;
publicstaticfinalintDEFAULT_HEIGHT - 300;
}
/**
Панель с текстовой областью и кнопкой "About". Щелчок
на кнопке заполняет текстовую область текстом, полученным
из ресурса.
*/
class AboutPanel extends JPanel
{
public AboutPanel()
{
setLayout(new BorderLayout());
// Добавляем текстовую область
textArea = new JTextArea();
add(new JScrollPane(textArea), BorderLayout.CENTER);
// Добавляем кнопку "About"
URL aboutURL = AboutPanel.class.getResource("about.gif");
JButton aboutButtonnew JButton("About",
new Imageicon(aboutURL));
aboutButton.addActionListener(new AboutAction());
add (aboutButton, BorderLayout .SOUTH) ;
)
private JTextArea textArea;
private class AboutAction implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
try
{
// Считываем в текстовую область текст из ресурса
InputStream in = AboutPanel.class.
getResourceAsStreamCabout.txt");
BufferedReader reader = new BufferedReader(new
_InputStreamReader(in));
77.}}77.}}String line;
77.
}
}
77.
}
}
while ((line = reader.readLine()) != null) textArea.append(line + "\n");
}
catch(lOException exception)
{
exception.printStackTrace();
}
}
java.lang.Class
URL getResource(String name)
InputStream getResourceAsStream(String name)
Находят ресурс там же, где расположен класс, и возвращают его URL-адрес или поток ввода, который можно использовать для загрузки ресурса. Если ресурс не найден, методы возвращают ссылку null, не возбуждая исключительных ситуаций и не сообщая об ошибках ввода-вывода.
Параметры: name Имя ресурса.
Вспомогательные пакеты
Итак, J AR-файлы подходят для упаковки как аплетов, так и приложений. Кроме того, они часто используются для упаковки библиотек программ. В JAR-классы можно добавить любой набор классов, а затем сделать их доступными, указав соответствующие пути доступа к ним. Для библиотек, предназначенных для совместного использования, этот шаг можно пропустить, погрузив J AR-файл во вспомогательный пакет (optional package).
В предыдущих версиях пакета J2SE вспомогательные пакеты назывались расширениями (extensions). Термин "пакет" немного неточен — вспомогательный пакет может содержать классы из нескольких пакетов языка Java.
Главный раздел манифеста вспомогательного пакета описывает его содержание (см. пример ниже).
Extension-Name: com.mycompany.myextension
Specification-Vendor: My Company, Inc.
Specification-Version: 1.0
Implementation-Vendor-Id: com.mycompany
Implementation-Vendor: My Company, Inc
Implementation-Version: 1.0.3
Имя расширения может быть совершенно произвольным. Как и в пакетах языка Java, его уникальность можно гарантировать, выбрав адрес своего домена, записанный в обратном порядке.
Программа, в которой необходимы расширения, указывает их в главном разделе манифеста.
Extension-List: myext otherext
myext-Extension-Name: com.mycompany.myextenmsion myext-Specification-Version: l.C
myext-Implementat ion-Version: 1.0.1
myext-Implementation-Vendor-Id: com.mycompany otherext-Extension-Name: com.hal.util
otherext-Specification-Version: 2.0
Данное конкретное приложение использует два вспомогательных пакета, имеющих псевдонимы (aliases) myext и otherext. Строка Лсевдоним-Extension-Name задает фактическое имя расширения. Приложение заявляет, что ему нужен пакет myext, соответствующий спецификации 1.0 или выше, а также реализации 1.0.1 или выше, и настаивает на том, чтобы этот пакет был реализован указанным поставщиком. Для других расширений программа требует лишь, чтобы они соответствовали спецификации 2.0 или выше. Допускаются любые реализации и поставщики.
При запуске программы ей нужно указать, где находятся вспомогательные пакеты. Их можно просто поместить в каталог j re/lib/ext. Кроме того, в программе можно указать URL-адрес, с которого следует загружать пакеты, например,
псевдоним-Implementation-URL:
http//www.mycompany.com/packages/myextension.jar
Более подробную информацию о вспомогательных пакетах можно найти на Web- странице
http://java.sun.com/j 2se/1.З/docs/quide/extensions/index.htmL
Запечатывание пакета
В главе 4 уже упоминалось, что пакет языка Java можно "запечатать" (seal), чтобы в него больше нельзя было добавить новые классы. Запечатывание защищает сущности, имеющие область видимости, ограниченную пакетом. Теперь, чтобы обратиться к ним, нужно поместить все классы пакета в JAR-файл. По умолчанию пакеты в JAR- файлах считаются распечатанными. Это правило можно изменить, поместив в главный раздел манифеста строку Sealed: true
Для каждого пакета можно отдельно указать, запечатывать его или нет, добавив в JAR-файл новый раздел.
Name: com/mycompany/mypackage
Sealed: true
Name: com/hal/util/
Sealed: false
Для того чтобы запечатать пакет, нужно создать текстовый файл с инструкциями манифеста, а затем выполнить следующую команду.
jar cvmf MyPackage.jar sealed.mf добавляемые файлы
Технология Java Web Start
Java Web Start — это новая технология, направленная на облегчение работы с программами, доставляемыми через Интернет. Между технологией Java Web Start и аплетами существуют принципиальные различия.
Технология Java Web Start используется для доставки обычных приложений, написанных на языке Java и начинающихся с вызова метода main, содержащегося в одном из классов.
Приложения, созданные с помощью технологии Java Web Start, не запускаются внутри броузера. Они отображаются вне броузера.
Приложения, созданные с помощью технологии Java Web Start, можно запустить с помощью броузера, однако механизм, лежащий в основе этого процесса, совершенно отличен от запуска аплетов. Броузеры тесно связаны с системой поддержки выполнения программ, написанных на языке Java, которая запускает аплеты. Технология Java Web Start гораздо самостоятельнее. Броузер просто запустит внешнюю программу, как только загрузит дескриптор приложения, созданного с помощью технологии Java Web Start. Для этого используется тот же механизм, что и в приложениях Adobe Acrobat или RealAudio. Даже конкурирующие поставщики броузеров не могут вмешаться в работу этого механизма.
После загрузки приложения, созданного по технологии Java Web Start, оно запускается вне броузера.
Технология Java Web Start обеспечивает более мощную поддержку кэширова
ния и автоматического обновления программ по сравнению с технологией Java Plug-In. (В будущем эти два подхода объединятся, с тем чтобы использовать одни и те же средства управления процессом развертывания программ.)
Механизм "песочниц" в технологии Java Web Start более гибок и позволяет неподписанным приложениям получать доступ к локальным ресурсам.
Для того чтобы подготовить приложение к доставке с помощью технологии Java Web Start, необходимо запаковать его в один или несколько JAR-файлов. Затем нужно подготовить дескрипторный файл в формате JNLP (Java Network Launch Protocol — сетевой протокол запуска приложений на языке Java). Теперь разместите файлы на Web-сервере. После этого необходимо убедиться, что ваш Web-сервер распознает тип MIME в каталоге application/x-java-jnip-fileв файлах с расширением . jnlp. (Броузеры используют тип MIME для распознавания программы, которую следует запустить.) Детали можно найти в документации, сопровождающей ваш Web-сервер.
Чтобы поэкспериментировать с технологией Java Web Start, инсталлируйте программу Tomcat с Web-страницы j akarta.apache. org/tomcat. Tomcat— это контейнер для сервлетов и серверных страниц на языке Java, но он также может содержать и Web- страницы. Текущая версия этого контейнера настроена на обработку JNLP-файлов.
Попробуйте доставить с помощью технологии Java Web Start приложение, имитирующее работу калькулятора, описанное в главе 9. Для этого следует выполнить следующие инструкции.
Скомпилируйте программу Calculator.j ava.
Подготовьте манифестный файл Calculator.mf, содержащий строку
Main-Class: Calculator
Создайте JAR-файл с помощью команды
jar cvfm Calculator.jar Calculator.mf *.class
Подготовьте файл запуска Calculator.j nip, содержащий следующие строки.
<?xml version="l.0м encoding="utf-8"?>
<jnlp
spec="l.0+"
codebase="http://localhost:8080/examples/"
href“"Calculator.jnip">
<information>
<title>Calculator DemoApplication</title>
<vendor>Cay S. Horstmann</vendor>
<description>A Calculator</descriptions
<offline-allowed/>
</information>
<resources>
<j2se version*"1.3+">
<jar href="Calculator.jar"/s
</resources>
<application-desc/s
</jnlp>
Формат файла запуска самоочевиден. Его полная спецификация содержится на Web-странице
http://java.sun.com/products/javawebstart/docs/ developersguide.html.
Разместите J AR-файл и файл запуска на вашем Web-сервере, с тем чтобы URL- адрес соответствовал пункту codebase В JNLP-файле. Если вы используете контейнер Tomcat, эти файлы можно поместить в каталог webapps /examples.
Убедитесь, что инструментальные средства Java Web Start правильно настроены, проверив, что ваш броузер связывает файл application/x-java-jnip- file типа MIME с приложениемjavaw. Если на вашем компьютере инсталлирован пакет SDK, то процесс установки должен выполняться автоматически.
Настройте ваш броузер на обработку JNLP-файлов. Например, при работе с контейнером Tomcat, перейдите на Web-страницу http: //localhost: 8 080/ examples/Calculator.jnlp.
Вы должны увидеть окно запуска программы Java Web Start (рис. 10.12). Затем в окне должен появиться калькулятор, обрамление которого свидетельствует о том, что он является приложением, созданным no технологии Java Web Start (рис. 10.13).
Теперь попробуйте запустить приложение самостоятельно. Откройте окно Java Web Start Application Manager (рис. 10.14). На платформе Windows для этого достаточно просто выбрать пиктограмму Java Web Start на панели задач.
На платформе Linux следует запустить программуjavaws в каталоге JR.E. Найдите приложение Calculator Demo Application и запустите его. Оно будет запущено, даже если ваш компьютер не подсоединен к сети.
*.. .*; ► манма
JAVA" WEB START
Рис. 10.13.Приложение Calculator, доставленное программой Java Web StartРис. 10.13.Приложение Calculator, доставленное программой Java Web StartРис. 10.14.Окно Java Web Start Application ManagerРис. 10.14.Окно Java Web Start Application ManagerРис. 10.12Запуск программы JavaWeb Start
Рис. 10.13.Приложение Calculator, доставленное программой Java Web Start
Рис. 10.13.Приложение Calculator, доставленное программой Java Web Start
Рис. 10.14.Окно Java Web Start Application Manager
Рис. 10.14.Окно Java Web Start Application Manager
Интерфейс JNLPAPI
Для того чтобы приложение, созданное по технологии Java Web Start, получило полный доступ к локальному компьютеру, оно должно иметь электронную подпись. (Более подробно эта тема рассматривается в главе 9 в томе 2.) Неподписанный аплет, загруженный из сети, представляет опасность для локального компьютера и запускается в "песочнице", получая минимальный доступ к локальному компьютеру. Однако, предоставляя минимальные привилегии, интерфейс JNLP API позволяет разработчиками приложения получать доступ к некоторым локальным ресурсам.
Например, существуют возможности чтения и записи файлов, правда они довольно ограниченны. Приложение не может просматривать файловую систему и указывать имена файлов. Вместо этого всплывает диалоговое окно, и пользователь программы может выбрать в нем требуемый файл. Перед появлением диалогового окна для выбора файлов пользователь получает предупреждение и должен выразить согласие на продолжение работы (рис. 10.15). Более того, на самом деле интерфейс API не предоставляет программе доступа к объекту класса File. В частности, приложение не может самостоятельно обнаружить файл. Следовательно, программисты получают средство для реализации стандартных операций открытия и закрытия файлов. При этом должно быть обеспечено максимальное сокрытие информации об операционной системе от сомнительных приложений.
□
JAVA™ WEB START
Thia аррЧсавоп hae requeued readAwnte access tn ante or tna focai fHetyitem A/lowng tnn action win only gw tho «sweater attest to the 1\ ®ets: selected тЭДЗДкпиодгё^ Dn
YOU want Ie allow this action?
d Do not я how this wcMaory again
Рис. 10.15.Окно Java Web Start Security Advisory
Интерфейс API предоставляет следующие функциональные возможности.
Чтение и запись файлов.
Доступ к клавиатуре.
Загрузка файла.
Печать.
Хранение и извлечение текущей информации о конфигурации компьютера.
Отображение документа в броузере, заданном по умолчанию.
Чтобы воспользоваться этими возможностями, необходимо использовать класс ServiceManager.
FileSaveService service = (FileSaveService)
ServiceManager.lookup("javax.jnlp.FileSaveService");
Если соответствующая функциональная возможность оказалась недоступной, этот вызов возбуждает исключительную ситуацию UnavailableServiceException.
Для компиляции программы, использующей интерфейс JNLP API, необходимо включить в нее файл jnlp. jar. Для этого не обязательно включать JAR-файл в свое приложение. Он будет включен программой запуска приложений, созданных по технологии Java Web Start.
Теперь перейдем к обсуждению наиболее полезных возможностей интерфейса JNLP. Его полное описание можно найти на Web-странице
http://java.sun.com/prosucts/javawebstart/docs/developersguide.htmL
Чтобы сохранить файл, следует указать в диалоговом окне первоначальный путь и расширения файлов, а также сохраняемые данные и нужное имя файла. Рассмотрим пример.
service.saveFileDialog(". ", new String[] { "txt" }, data, "calc.txt"};
Данные должны быть доставлены в поток Inputstream. Иногда эта задача оказывается довольно сложной. Программа, приведенная в листинге 10.13, следует такой стратегии.
Создается объект класса ByteArrayOutputStream, предназначенный для хранения байтов, подлежащих записи на диск.
Создается объект класса Printstream, посылающий эти данные в поток байтов.
Печатается информация, подлежащая сохранению в потоке печати.
Создается объект класса ByteArrayInputStream, считывающий сохраненные байты.
Этот поток передается методу saveFileDialog.
Более подробно потоки описываются в главе 12. Пока мы можем не обращать внимание на детали, указанные в демонстрационной программе.
Чтобы считать данные из файла, используется класс FileOpenService. Его метод openFileDialog получает первоначальное имя пути и расширения файлов, и возвращает объект класса FileContent. Затем можно вызвать метод getInputStrearnи считать данные из файла. Если пользователь не выбрал файл, метод openFileDialog возвращает ссылку null,
FileOpenService service = (FileOpenService)
ServiceManager.lookup("javax.jnlp.FileOpenService");
FileContents contents • service.openFileDialog(".”,
new String(] { "txt" 1);
if (contents != null)
{
InputStream in = contents.getInputstream(0;
Для того чтобы отобразить документ в броузере, заданном по умолчанию (аналогично методу showDocument, который используется для отображения аплетов), применяется класс BasicService. Учтите, что некоторые системы (в частности многие системы Unix и Linux) могут не иметь броузера, заданного по умолчанию. BasicService service = (BasicService)
ServiceManager.lookup("javax.jnlp.BasicService");
if (service.isWebBrowserSupported()J
service.showDocument(url);
else . . .
Существует очень устаревший метод PersistentService, позволяющий приложению сохранять малые порции информации о настройках и извлекать ее, если приложение запускается вновь. Эта возможность необходима, поскольку сомнительное приложение не может задавать местоположение файла конфигурации.
Этот механизм напоминает протокол HTTP. В качестве ключей он использует URL-адреса. Эти адреса не должны относиться к реальным Web-ресурсам. Они просто используются в качестве удобной иерархической схемы именования файлов. По каждому URL-ключу приложение может записать произвольные бинарные данные. (Объем записи может быть ограничен размером блоков.)
Для того чтобы изолировать приложения друг от друга, каждое приложение должно использовать URL-адреса, начинающиеся с имени каталога, содержащего его код (указанного в JNLP файле). Например, если приложение загружено с Web-страницы http: //myserver.com.apps, то оно может использовать лишь ключи, имеющие вид http://myserver.com.apps/subkeyl/subkey2/ Попытка получить доступ к другим ключам будет заведомо безуспешной.
Чтобы определить каталог, в котором хранится код, приложение может вызвать метод getCodebase из класса BasicService.
Новый ключ создается с помощью методаcreate из класса Persistenceservice.
URL url = new URL(codebase, "mykey”); servise.create(url, maxSize);
Для доступа к информации, связанной с конкретным ключом, вызывается метод get. Этот метод возвращает объект класса FileContent, с помощью которого можно читать и записывать данные, соответствующие указанному ключу.
FileContents contents = service = service.get(url);
Inputstream in = contents.getlnputstream() ;
Outputstream out = contents.getOutputStream(true);
// true = перезапись
К сожалению, определить, существует указанный ключ или его нужно создать заново, довольно сложно. Мы можем лишь надеяться, что ключ существует, и вызывать метод get. Если вызов возбуждает исключительную ситуацию FileNotFoundException, необходимо создать новый ключ.
Программа, приведенная в листинге 10.13, представляет собой простое усовершенствование приложения, имитирующего работу калькулятора. Этот калькулятор имеет виртуальную бумажную ленту, на которую записываются результаты всех вычислений. Список всех предыдущих вычислений можно сохранять и считывать из файла. Для демонстрации способа постоянного хранения приложение позволяет задавать заголовок фрейма. Если программу запустить вновь, она извлечет этот заголовок из места постоянного хранения.
import java.awt.*;
import
import java.io.*;
import java.net.*;
import
import
import
/**
Калькулятор, сохраняющий результаты вычислений.
Разворачивается в виде приложения, созданного по
технологии Java Web Start.
*/
public class
{
public static voidargs)
{
frame = new
* frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE;;
Фрейм, содержащий панель калькулятора и меню
для чтения и записи предыдущих результатов вычислений.
*/
class CalculatorFrame extends JFrame
public
Container contentPane =
panel = new
JMenu fileMenu = new
JMenuItem openltem =
ActionListener ()
public voidevent)
JMenuItem=
72.75.76.77.81.82.84.87.72.75.76.77.81.82.84.87.{
72.
75.
76.
77.
81.
82.
84.
87.
72.
75.
76.
77.
81.
82.
84.
87.
public void actionPerformed(ActionEvent event)
{
save();
}
});
JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
setJMenuBar(menuBar);
pack();
}
/**
Извлекаем заголовок из места постоянного хранения или запрашиваем его с экрана, если ранее он не был задан. */ public void setTitle() {
try
{
String title = null;
BasicService basic = (BasicService)
ServiceManager.lookup("j avax.jnlp.BasicService") ; URL CodeBase = basic.getCodeBase();
PersistenceService service = (PersistenceService) ServiceManager.lookup(
"javax.jnlp.Persistenceservice");
URL key = new URL(CodeBase, "title");
try {
FileContents contents = service.get(key) ;
Inputstream in = contents.getlnputStreamO;
BufferedReader reader = new BufferedReader( new InputStreamReader(in));
title = reader.readLine();
} catch (FileNotFoundException exception) {
title = JOptionPane.showInputDialog( "Please supply a frame title:");
if (title == null) return;
service.create(key, 100);
FileContents contents = service.get(key);
Outputstream out
=
PrintStream printout = new Printstream(out); printout.print(title);
setTitle(title);
}.
119.*119.*setTitle(title);
119.*
119.*
}
catch (UnavailableSeryiceException exception)
{
JOptionPane.showMessageDialog(this, exception);
}
catch (MalformedURLException exception) {
JOptionPane.showMessageDialog(this, exception) ;
} catch (IOException exception)
{
JOptionPane.showMessageDialog(this, exception) ;
}
}
/**
Открывает файл, в котором хранятся результаты предыдущих вычислений, и обновляет экран.
*/
public void open()
{
try
{
FileOpenService service = (FileOpenService)
ServiceManager.lookup(
"javax.jnip.FileOpenService");
FileContents contents = service.openFileDialog(".", new String!] { "txt" });
JOptionPane.showMessageDialog(this,contents.getName());
if (contents != null) {
InputStream in = contents.getInputstream();
BufferedReader reader = new BufferedReaderI new InputStreamReader(in));
String line;
while ((line = reader.readLine() 1 != null) (
panel.append(line);
pane1.append("\n");
}
}
}
catch (UnavailableServiceException exception) {
JOptionPane.showMessageDialog(this, exception);
}
catch (IOException exception) {
JOptionPane.showMessageDialog(this, exception);
}
}
160.161.167.171.174 .177 .182.192.193.194.202.207.212.213.160.161.167.171.174 .177 .182.192.193.194.202.207.212.213./**
160.
161.
167.
171.
174 .
177 .
182.
192.
193.
194.
202.
207.
212.
213.
160.
161.
167.
171.
174 .
177 .
182.
192.
193.
194.
202.
207.
212.
213.
Записывает результаты предыдущих вычислений в файл.
*/
public void save()
{
try
{
ByteArrayOutputStream out =
new ByteArrayOutputStream();
Printstream printout = new PrintStream(out);
printout.print(panel.getText());
InputStream data = new ByteArraylnputStream( out.toByteArray());
FileSaveService service = (FileSaveService)
ServiceManager.lookup 1
"javax.jnlp.FileSaveService");
service.saveFileDialog(".", new String[] { "txt" }, data, "calc.txt");
}
catch (UnavailableServiceException exception) {
JOptionPane.showMessageDialog(this, exception);
}
catch (IOException exception)
{
JOptionPane.showMessageDialog(this, exception);
)
}
private CalculatorPanel panel;
}
/**
Панель с кнопками калькулятора и дисплеем для вывода результатов.
*/ class CalculatorPanel extends JPanel
{
/**
Компоновка панели.
*/
public CalculatorPanel ()
{
setLayout(new BorderLayout());
result = 0;
lastCommand"=";
start = true;
// Добавляем дисплей.
display = new JTextArea(10, 20);
add (new JScrollPane (display! , BorderLayout.NORTH) ;
ActionListener insert = new InsertAction();
ActionListener command = new CommandAction();
// добавляем кнопки в сетке4x4.
panel = new JPanelO;
panel.setLayout(new GridLayout(4, 4));
addButton("7",insert);
addButton("8",insert);
addButton("9",insert);
addButton!"/",command);
addButton("4",insert);
addButton("5",insert);
addButton("6",insert);
addButtonf"*",command);
addButton("1",insert);
addButton("2",insert);
addButton("3",insert);
addButton("-",command);
addButton("0",insert);
addbutton (".",insert);
addButton("=",command);
addButton("+",command);
add(panel, BorderLayout.CENTER);
}'
/**
извлекает результаты предыдущих вычислений.
Sreturn результаты предыдущих вычислений
*/
publicstringgetText(I
{"
return display.getText ();
)
/**
добавляет строку в текст. содержащий результаты
предыдущих вычислений.
Sparam s добавляемая строка
*/
public void append(String s)
{
display.append(s);
)
274 .287.274 .287./**
274 .
287.
274 .
287.
Добавляет кнопку к центральной панели.
Sparam label метка кнопки
Sparam listener слушатель кнопки
/
private voidlabel,
ActionListener listener)
{
JButton button = new
;
;
}
/**
Вставляем строку, соответствующую нажатию кнопки, в конец текста, выведенного на дисплей.
/ private class InsertAction implements ActionListener
{ public voidevent)
{
String input = start = false; display.append(input);
}
}
/**
Выполняем команду, заданную строкой, описывающей • реакцию кнопки на нажатие.
/
private classimplements ActionListener
{
public voidevt)
{
String command =
if (start) {
if { display.append(command); start = false;• } else
= command;
} else
{ try { int lines = int lineStart
=- 1)
321.322.323.324.325.326.327.328.329.330.331.332.333.334.335.336.337.338.339.340.341.342.343.344.345.346.347.348.349.350.351.352.353.354.355.356.357.358.359.360.361.321.322.323.324.325.326.327.328.329.330.331.332.333.334.335.336.337.338.339.340.341.342.343.344.345.346.347.348.349.350.351.352.353.354.355.356.357.358.359.360.361.))int lineEnd
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
)
)
=- 1);
String value = display.getText(linestart, lineEnd - lineStart);
display.append("");
display.append(command);
calculate(Double.parseDouble(value)); if (command == "=")
display.append("\n" + result);
lastCommand = command;
display.append("\n");
start = true;
catch (BadLocationException exception)
{
exception.printStackTrace();
}
)
/**
Выполняет отложенные вычисления.
@param x значение, накопленное в результате предыдущих вычислений.
/
public void calculate(double x)
if (lastcommand.equals("+")) result += x;elseif(lastcommand.equals("-"))result-= xelseif(lastcommand.equals("*")]resultxelseif(lastcommand.equals("/")]result/= xelseif(lastcommand.equals("="))result= x;if (lastcommand.equals("+")) result += x;elseif(lastcommand.equals("-"))result-= xelseif(lastcommand.equals("*")]resultxelseif(lastcommand.equals("/")]result/= xelseif(lastcommand.equals("="))result= x;private JTextArea display;
if (lastcommand.equals("+")) result += x;
else
if
(lastcommand.equals("-"))
result
-= x
else
if
(lastcommand.equals("*")]
result
x
else
if
(lastcommand.equals("/")]
result
/= x
else
if
(lastcommand.equals("="))
result
= x;
if (lastcommand.equals("+")) result += x;
else
if
(lastcommand.equals("-"))
result
-= x
else
if
(lastcommand.equals("*")]
result
x
else
if
(lastcommand.equals("/")]
result
/= x
else
if
(lastcommand.equals("="))
result
= x;
private JPanel panel;
private double result;
private String lastCommand; private boolean start;
javax.jnip.ServiceManager
static String[" getServiceNames(]
Возвращает имена объектов, обеспечивающих все доступные функциональные возможности.
static Object lookup(String name)
Возвращает объект, обеспечивающий одну из функциональных возможностей и имеющий заданное имя.
javax.jnip.Baa icService
URL getCodeBase
Возвращает имя каталога, содержащего код данного приложения.
boolean isWebBrowserSupported()
Возвращает значение true, если среда Java Web Start может запустить web- броузер.
boolean showDocument(URL url)
Пытается показать заданный URL-адрес с помощью броузера. Если запрос выполнен успешно, возвращает значение true.
javax.jnlp.FileContenta
I • inputStream getlnputStreamf]
Возвращает входной поток, предназначенный для чтения содержимого файла.
Outputstream getOutputStream(boolean overwrite)
Возвращает выходной поток, предназначенный для записи содержимого файла; если значение параметра overwrite равно true, существующее содержимое файла перезаписывается.
String getName()
Возвращается имя файла (но не полный путь).
boolean canRead()
* boolean canWrite()
Эти методы возвращают значение true, если соответствующий файл мож-
но прочитать или записать.
|API javax. jnlp. FileOpenService
e FileContentsopenFileDialog(StringpathHint,String[]
extensions)
FileContentsopenMultiDialog(String pathHint,String[]
extensions)
Эти методы выводят предупреждение и диалоговое окно для выбора файлов. Они возвращают дескрипторы содержимого файла или файлов, выбранных пользователем, или ссылку null, если пользователь не выбрал ни одного файла.
javax.jnlp.FileSaveService
pathHint, String!]pathHint, ’String!]pathHint, String!]pathHint, ’String!]FileContentssaveFileDialog(String
pathHint, String!]
pathHint, ’String!]
pathHint, String!]
pathHint, ’String!]
extensions, String nameHint)
FileContentssaveFileDialog(String
extensions, FileContents data)
Эти методы выводят предупреждение и диалоговое окно для выбора файлов. Они записывают данные и возвращают дескрипторы содержимого файла или файлов, выбранных пользователем, или ссылку null, если пользователь не выбрал ни один файл.
javax.jnlp.Persistenceservice long create(URL key, long maxsize)
Создает единицу постоянного хранения по заданному ключу. Возвращает максимальный размер памяти, предоставленной элементу постоянного хранения.
void delete(URL key)
Удаляет единицу хранения по заданному ключу.
String[] getNames(URL url)
Возвращает относительные имена всех ключей, начинающихся с заданного URL-адреса.
FileContents get(URL key)
Определяет дескриптор содержимого, с помощью которого можно модифицировать данные, связанные с заданным ключом. Если заданному ключу ничего не соответствует, возбуждается исключительная ситуация File- No tFpundExcept ion.
Хранение параметров настройки приложения
Большинство программ могут настраиваться своими пользователями. Затем эти программы должны сохранить параметры, которым пользователь отдал предпочтение, чтобы учесть их при следующем запуске. Мы уже видели, что приложения, созданные по технологии Java Web Start, могут использовать постоянные запоминающие устройства. Однако в пакете SDK 1.4 был реализован иной, более мощный механизм локальных приложений. Мы опишем этот механизм, но сначала рассмотрим более простой, традиционный подход, позволяющий хранить информацию о настройках приложений, написанных на языке Java.
Множества свойств
Множество свойств (property set) — это структура данных, в которой хранятся пары ключ/значение. Множества свойств часто используются для хранения информации о параметрах настройки и характеризуются тремя особенностями.
Ключи и значения являются строками.
Множество легко записать в файл и прочитать оттуда.
Существует вспомогательная таблица, заданная по умолчанию.
Класс, реализующий множество свойств, называется Properties.
Множества свойств позволяют указать параметры конфигурации программы. Рассмотрим пример.
Properties settings = new Properties();
settings.put("font", "Courier");
settings.put("size", "10");
settings.put("message", "Hello, World");
Для записи списка свойств в файл используется метод store. В данном случае мы просто выводим множество свойств в стандартный поток вывода. Второй аргумент представляет собой комментарий, который вставляется в файл.
FileOutputStream out
=newFileOutputStream("Myprog.properties" 1 ;: settings.store(out, "Myprog Properties");
В результате на печать будут выведены следующие строки.
#Myprog Properties
#Sun Jul 14 07:22:52 2002
font=Courier
size=10
message=Hello,World
Чтобы загрузить эти свойства из файла, следует выполнить следующие операторы.
FileInputstream in
= new FilelnputStream("Myprog.properties");
settings.load(in);
Применим этот подход, чтобы дать пользователю возможность настраивать программу NotHelloWorld по своему усмотрению. Для этого позволим ему задавать следующие параметры в файле конфигурации CustomWorld.properties.
Размер окна.
Название шрифта.
Размер шрифта.
Цвет фона.
Строка сообщения.
Если пользователь не указал какой-либо параметр, он задается по умолчанию.
Класс Properties имеет два механизма для указания параметров по умолчанию. Во-первых, всякий раз, просматривая значение строки, пользователь может задать ее по умолчанию. Это позволяет автоматически использовать заданное значение, если соответствующий ключ не указан.
String font = setings.getProperty("font", "Courier");
Если в таблице есть свойство "font",имя шрифта задается указанной строкой. В противном случае в качестве шрифта по умолчанию будет использоваться шрифт "Courier ".
Если пользователь сочтет, что задавать значения по умолчанию при каждом вызове метода getProperty слишком утомительно, он может упаковать все значения, принятые по умолчанию, во вспомогательную таблицу свойств и передать ее конструктору искомой таблицы.
Properties defaultSettings =newProperties();
defaultsettings.put("font", "Courier");
defaultsettings.put("size", "10");
defaultSettings.put("color.red","0");
Properties settings = new Properties(defaultsettings);
FileStream in
= new Fileinputstream("CustomWorld.properties"); settings.load(in) ;
Можно даже указать noумолчанию сами значения по умолчанию, если передать конструкторуdefaultsettings в качестве параметра другое множество свойств, однако обычно этого не делают.
На рис. 10.17 показана настраиваемая программа "Hello, Custom World". Для чтобы изменить внешний вид этой программы на свой вкус, достаточно отредактировать файл с расширением
10.17. Настраиваемая программа Hello Custom World
Множество свойств представляет собой простую таблицу, не имеющую иерархической структуры. Как правило, используется фиктивная иерархия с именами ключей red, green и т.п. Однако в классе Properties нет методов, позволяющих создать такую иерархию. Если в файле хранится сложная информация о конфигурации программы, следует использовать классerencs (см. следующий раздел).
Класс Properties расширяет класс Hashtable. Это значит, что все методы класса Hashtable доступны объектам класса Properties. Некоторые из них являются полезными. Например, методвозвращает количество возможных свойств
(но несвойства, заданные по умолчанию, он не учитывает). Аналогично
метод keys возвращает перечисление, состоящее из всех ключей, за исключением ключей, заданных по умолчанию. Существует еще одна функция, под названиемвозвращающая все ключи. Функция put представляет оче
видную опасность. Она не проверяет, какие строки вы вводите в таблицу.
Существует ли между этими классами отношение "является"? Каждое ли множество свойств является таблицей хэширования? Конечно, нет. Об этом со всей очевидностью свидетельствуют детали реализации. Возможно, было бы лучше считать множество свойств классом, содержащим таблицу хэширования. Но в этом случае таблица хэширования должна была бы быть закрытой переменной. На самом деле множество свойств использует две таблицы хэширования: значений, задаваемых по умолчанию, а вторую — для других значений.
Мы считаем, что класс Properties следовало бы организовать иначе.
class Properties
{
public String{ .. . }
public voidString) { .. . )
private Hashtable nonDefaults;
private Hashtable defaults;
}
Мы вовсе не призываем игнорировать класс Propertiers. Если проявлять осторожность и не вводить ничего, кроме строк, он работает просто прекрасно. Но следует дважды подумать, прежде чем использовать этот сляпанный наспех механизм наследования в своей программе.
import java.awt.*;
import java. awt.event.*;
import java.util.*;
import java.io.*;
import javax.swing.*;
/**
Эта программа демонстрирует настройку программы "Hello, World" с помощью файла свойств.
*/
public class CustomWorld
public static void main(String[] args)
{■
CustomWorldFrame frame = new CustomWorldFrame();
frame.setDefaultCloseOperation(JFrame,EXIT_ON_CLOSE]; frame.showO;
}
}
/**
В этом фрейме выводится сообщение. Размер фрейма, текст сообщения, шрифт и цвет задаются в файле свойств.
*/
class CustomWorldFrame extends JFrame {
public CustomWorldFrame(1 {
Properties defaultSettings = new Properties();
defaultsettings.put("font", "Monospaced");
defaultsettings.put("width", "300");
defaultSettings.put("height", "200");
defaultsettings.put("message", "Hello, World");
defaultsettings.put("color.red", "0 50 50");
defaultsettings.put("color.green", "50");
defaultsettings.put("color.blue", "50");
defaultsettings.put("ptsize", "12");
Properties settings = new Properties(defaultsettings;; try (
FilelnputStream in
= new Fileinputstream("Customworld.properties"1; settings.load(in);
} catch (IOException exception) (
exception.printStackTrace();
}
int red = Integer.parselnt(
settings.getProperty("color.red") ) ;
int green = Integer.parselnt(
settings.getProperty("color.green"); ;
int blue = Integer.parselnt(
settings.getProperty("color.blue"));
Color foreground = new Color(red, green, blue);
String name = settings.getProperty("font");
int ptsize = Integer.parselnt(
settings.getProperty("ptsize"));
Font f = new Font(name, Font.BOLD, ptsize);
int hsize = Integer.parselnt(
settings.getProperty("width"));
int vsize = Integer.parselntI
settings.getProperty("height"));
setsize(hsize, vsize);
setTitle(settings.getProperty("message"));
JLabel label
= new JLabel(settings.getProperty("message"),
Swingconstants.CENTER);
label.setFont(f);
label.setForeground(foreground);
getContentPane() . add (label)';
}
}
^P^ java. util. Properties1.0
Properties ()
Создает пустой список свойств.
Properties ()
Создает пустой список свойств со значениями, заданными по умолчанию.
Параметры:defaultsЗначения, заданные по умолчанию.
String getProperty(String key)
Задает соответствие между ключами и свойствами. Возвращает строку, связанную с заданным ключом, или строку, связанную с ключом из таблицы значений, принятых по умолчанию, если ключ в основной таблице отсутствует, или ссылку null, если указанного ключа в таблице значений, принятых по умолчанию, тоже нет.
Параметры: keyПараметры: keyКлюч, связанный с возвращаемой строкой.
Параметры: key
Параметры: key
String getProperty(String key, String defaultValue) Возвращает строку, связанную с указанным ключом. Если ключа в таблице нет, возвращает свойство, заданное по умолчанию.
Параметры:keyПараметры:keyКлюч, связанный с возвращаемой строкой.
Параметры:key
Параметры:key
defaultvalue Строка, которая возвращается методом, если ключа в таблице нет.
void load(Inputstream in) throws IOException Считывает множество свойств из входного потока.
Параметры: inПараметры: inВходной поток.Входной поток.
Параметры: in
Параметры: in
Входной поток.
Входной поток.
void store(Outputstream out, String header)1.2
Записывает множество свойств в выходной поток.
Параметры:outПараметры:outВыходной поток.Выходной поток.
Параметры:out
Параметры:out
Выходной поток.
Выходной поток.
headerheaderЗаголовок, указанный в первой строке файла.
header
header
Информация о системе
Рассмотрим еще один пример,Рассмотрим еще один пример,характеризующий вездесущность класса
Рассмотрим еще один пример,
Рассмотрим еще один пример,
Properties. Информация об операционной системе хранится в объекте класса Properties, который возвращается методом getProperties класса System. Properties sysprops = System.getProperties();
Чтобы получить доступ к определенному свойству, нужно выполнить следующий
вызов.
String value = System.getProperty(key);
Приложения, которые запускаются без участия менеджера безопасности, имеют полный доступ к этой информации, однако аплеты и другие сомнительные программы имеют доступ только к перечисленным ниже ключам. java.version java.vendor java.vendor.url java.class.version os.name os.version os.arch file.separator path.separator line.separator java.specification.version java.vm.specification.version java. vm.specification.vendor java.vm.specification.name java.vm.version java.vm. name
Если аплет пытается прочитать другой ключ (или все свойства), возбуждается исключительная ситуация.
Имена доступных свойств системы указаны в файле security/java.pollсу в каталоге системы поддержки выполнения программ.
Программа, приведенная в листинге 10.15, выводит на печать пары ключ/свойство, хранящиеся в объекте Properties, в котором содержится информация о системе.
Ниже показаны строки, полученные в результате работы этой программы. Легко заметить, что все эти значения хранятся в объекте Properties. (Разумеется, содержание этих строк зависит от конкретных настроек компьютера.)
java.run.tyme.name™Java(TM) 2 Runtime Environment, Standart Edition sun.boot. library,path=/usr/local/j2sdkl. 4 .0/jre/lib/i386
java.vm.version™!.4.0
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=http://java.sun.com/
path.separator™:,
j ava.vm.name™Java HorSpot(TM) Client VM
file.encoding,pkg=sun.io
sun.os.patch.level=unknown
java.vm.specification.name™Java Virtual Machine Specification user,dir=/home/cay
java.runtime.version™!.4.0
j ava.awt.graphicsenv=sun.awt.XIIGraphicsEnvironment os.arch=i386
java.io.tmpdir=/tmp
line.separator™
java.vm.pesification.vendor=Sun Microsystems Inc.
j
os.name=Linux
java.library.path=/usr/local/j2sdkl.4.0/jre/lib/i386:/usr/local/j2sdkl.4.0 /jre/lib/i386/native_threads/:/usr/local/j2sdkl.4.0/jre/lib/i386/client: /usr/local/j2sdkl.4.О/jre/.../Iib/i386
java.specification.name™Java Platform API Specification
java.class.version=48. C
java.util.prifs.PreferencesFactory=java.util.prefs.FileystemPreferences Factory
os.version=2.4.3-20mdk
user.home=/home/cay•*
user.timezone™
java.awt.printerjob=sun.print.PsPrinterJob
file.encoding=ISO-8859-l
java.specification.version=l. 4
user.name=cay
java.class.path™
java.vm.specification.version™!. 0
sun.arch.data,model=32
java.home=/usr/local/j2sdkl.4.0/jre
java.specification.vendor=Sun Microsystems Inc.
user.language=en
java.vm.info=mixed mode
java.version™!.4.0-beta
java.ext.dirs=/usr/local/j 2sdkl.4.0/j re/lib/ext
sun.boot.class/path=/usr/local/j2sdkl.4.0/jre/lib/rt.jar:/usr/local
/j2sdkl.4.0/jre/lib/il8n.jar:/usr/local/j2sdkl.4.0/jre/lib/sunrsasign.jar: /usr/local/j2sdkl.4.0/jre/lib/jsse.jar/usr/local/j2sdkl.4.0/jre/lib
/jce.jar/usr/local/j2sdkl.4.0/jre/lib/charsets.jar:/usr/local/j2sdkl.4.0 /jre/classes
java.vendor=Sun Microsystems Inc.
file.separator»/
java.vendor.ur1.bug=http://java.sun.com/cgi-bin/bugreport.cgi
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
user.region=US
sun.cpu.isalist™
1.import2.import3.import4.5./ ♦6.Эта7.*/8.public9.{10pub1112131415161718192021)22}1.import2.import3.import4.5./ ♦6.Эта7.*/8.public9.{10pub1112131415161718192021)22}java.applet.*;java.io.*;java.applet.*;java.io.*;программа выводит на печать все свойства системы.программа выводит на печать все свойства системы.class Systeminfoclass Systeminfotry (try (static void main(String args[])static void main(String args[])Properties sysprops = System.getProperties(); sysprops.store(System.out, "System PropertiesProperties sysprops = System.getProperties(); sysprops.store(System.out, "System Properties}catch (IOException exception) (exception.printStackTrace ();}}catch (IOException exception) (exception.printStackTrace ();}java.lang.System
1.
import
2.
import
3.
import
4.
5.
/ ♦
6.
Эта
7.
*/
8.
public
9.
{
10
pub
11
12
13
14
15
16
17
18
19
20
21
)
22
}
1.
import
2.
import
3.
import
4.
5.
/ ♦
6.
Эта
7.
*/
8.
public
9.
{
10
pub
11
12
13
14
15
16
17
18
19
20
21
)
22
}
java.applet.*;
java.io.*;
java.applet.*;
java.io.*;
программа выводит на печать все свойства системы.
программа выводит на печать все свойства системы.
class Systeminfo
class Systeminfo
try (
try (
static void main(String args[])
static void main(String args[])
Properties sysprops = System.getProperties(); sysprops.store(System.out, "System Properties
Properties sysprops = System.getProperties(); sysprops.store(System.out, "System Properties
}
catch (IOException exception) (
exception.printStackTrace ();
}
}
catch (IOException exception) (
exception.printStackTrace ();
}
Properties getProperties()
Извлекает все свойства системы. Приложение должно иметь доступ ко всем свойствам системы, в противном случае возбуждается исключительная ситуация.
String getProperty(String key)
Извлекает все свойства системы, связанные с заданным ключом. Приложение должно иметь доступ ко всем свойствам системы, в противном случае возбуждается исключительная ситуация.
Класс Preferences
Как видим, класс Properties позволяет легко загружать и сохранять информацию о конфигурации системы. Однако использование файла свойств обладает рядом недостатков.
Файлы конфигурации не всегда хранятся в домашнем каталоге пользователя. Некоторые операционные системы (например Windows 9x) вообще не используют концепцию домашних каталогов.
Нет стандартного соглашения об именах файлов конфигурации. Это увеличивает вероятность конфликтов имен, если пользователи инсталлируют несколько приложений одновременно.
Некоторые операционные системы хранят информацию о своей конфигурации в центральном хранилище информации. Наиболее широко известным примером такого хранилища является регистр операционной системы Windows. Способ, с помощью которого класс Preferences из пакета SDK 1.4 создает такое центральное хранилище, совершенно не зависит от операционной системы. На платформе Windows класс Preferences использует регистр, на платформе Linux информация хранится в локальном системном файле. Разумеется, реализация хранилища абсолютно прозрачна для программиста, использующего класс Preferences.
Хранилище Preferences имеет древовидную структуру, в которой узлы хранят имена путей, например /com/mycompany/myapp. Во избежание конфликта имен следует называть пакеты с помощью путей, записанных в обратном порядке. Разработчики интерфейса API предполагают, что пути к файлу конфигурации совпадают с именами пакетов, используемых в программе.
Каждый узел в хранилище содержит отдельную таблицу пар ключ/значение, которую можно использовать для записи чисел, строк или массивов байтов. Возможность хранить сериализованные объекты не предусмотрена. Разработчики интерфейса API чувствовали, что формат сериализации слишком хрупок и не пригоден для долговременного хранения. Разумеется, если вы не согласны с этим, можно хранить сериализованные объекты в байтовых массивах.
Для дополнительной гибкости предусмотрено несколько параллельных деревьев. Каждая программа использует одно дерево. Кроме того, существует дополнительное дерево, называемое системным и предназначенное для хранения настроек, общих для все пользователей. Для доступа к соответствующему пользовательскому дереву класс Preferences использует понятие "текущий пользователь".
Чтобы получить доступ к узлу дерева, поиск начинается с пользовательского или системного корня.
Preferences root = Preferences.userRoot();
и
Preferences root = Preferences.systemRoot();
Теперь для доступа к узлу достаточно указать соответствующий путь.
Preferences node = root.node("/com/mycompany/myapp");
Существует удобное сокращение, позволяющее получить доступ к узлу, путь к которому совпадает с именем пакета, содержащего класс. Для этого нужно просто взять объект этого класса и осуществить вызовы
Preferences node = Prefercnes.userNodeForPackage(obj);
или
Preferences node = Prefercnes.systemNodeForPackage(obj);
Как правило, параметр objявляется ссылкой this.
Получив доступ к узлу, можно обратиться к таблице ключ/значение, используя указанные ниже методы.
String get(String key, String defval1 int getlnt(String key, int defval) long getLong(String key, long defval) float getFloat(String key, float defval) double getDouble(String key, double defval) boolean getBoolean(String key, boolean defval) byte[] getByteArray(String key, byte[] defval)
Обратите внимание на то, что если хранилище недоступно, то при считывании этой информации необходимо указывать значение, предусмотренное по умолчанию. Эти значения необходимы по нескольким причинам. Во-первых, данные могут быть неполными, если пользователи никогда не задают все параметры настройки. Во- вторых, некоторые платформы, испытывающие недостаток ресурсов, не могут создавать хранилища. В-третьих, переносные устройства могут быть временно отсоединены от хранилища.
И, наоборот, с помощью методов put можно записать данные в хранилище.
put(String key, String value) putln(String key, int value) и т.д.
Все ключи, хранящиеся в узле, можно перечислить с помощью метода
String[] keys
Однако в настоящий момент не существует способа, позволяющего определить тип значения, хранящегося в таблице по заданному ключу.
Центральные хранилища, например, регистр в системе Windows, традиционно страдают от двух недостатков.
Они превращаются в свалку, заполненную устаревшей информацией.
Данные о конфигурации системы перепутываются в хранилище, затрудняя перенос параметров настройки на новую платформу.
Класс Preferences позволяет решить вторую проблему. Можно экспортировать данные в поддерево (или, в некоторых случаях, в отдельный узел), вызвав метод void exportsubtree(outputstream out) void exportNode(outputstream out)
Данные хранятся в XML-формате. Их можно импортировать в другое хранилище, вызвав метод void importpreferences(inputstream in)
Рассмотрим пример.
<?xml version="l. 0" encoding="UTF-8"?>
<!DOCTYPE preferences
SYSTEM 1 http://j ava.sun.com/dtd/preferences.dtd'> preferences EXTERNAL_XML_VERSION="1.0">
<root type="user">
<map />
<node name="com">
<map />
<node name="hortsmann">
<map />
cnode name="corejava">
стар />
<entry key="left" value="ll" />
<entry key="top" value="9" />
<entry key="width" value="453" />
<entry key="height" value="365" />
</map>
</node
</node
</node
</root>
</preferences>
Если ваша программа использует параметры настройки, можно предоставить ее пользователям возможность экспортировать и импортировать их, чтобы легко переносить программу с одного компьютера на другой. Этот подход продемонстрирован в программе, приведенной в листинге 10.16. Она просто сохраняет координаты и размер главного окна. Попробуйте изменить размеры окна, затем выйдите и запустите приложение снова. Окно должно принять размеры, указанные перед завершением работы программы.
import j ava.awt.*;
import j ava.awt.event.*;
import java.io.*;
import java.util.logging.*;
import java.util.prefs.*;
import javax.swing.*;
import javax.swing.event.*;
Программа тестирования настройки.
Запоминает координаты и размер фрейма.
*/
public class PreferencesTest
{
public static void main(String(] args)
(
PreferencesFrame frame = new PreferencesFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE;;
frame.show();
}
}
/**
Фрейм восстанавливает координаты и размер, заданные
пользователем, и обновляет их перед выходом из программы.
*/
class PreferencesFrame extends JFrame
{
public PreferencesFrame(I .
{
setTitle("PreferencesTest");
34.37.42.43.44.45.51.52.53.54.55.64.67.75.82.85.86.87.34.37.42.43.44.45.51.52.53.54.55.64.67.75.82.85.86.87.// Определяем координаты и размер фрейма.
34.
37.
42.
43.
44.
45.
51.
52.
53.
54.
55.
64.
67.
75.
82.
85.
86.
87.
34.
37.
42.
43.
44.
45.
51.
52.
53.
54.
55.
64.
67.
75.
82.
85.
86.
87.
Preferences root = Preferences.userRoot(); final Preferences node
=);
int left = node.getlnt("left", 0);
int top = node.getlnt("top", 0);
int width = node.getlnt("width", DEFAULT_WIDTH);
int height = node.getlnt("height", DEFAULT_HEIGHT); setBounds(left, top, width, height);
// Настраиваем диалоговое окно для выбора XML-файлов
final JFileChooser chooser = new JFileChooser(); chooser.setcurrentDirectory(new File("."));
// Допускаются все файлы, имеющие расширение . xml chooser.setFileFilter(new
javax.swing.filechooser.FileFilter(;
{ public boolean accept(File f) {
return f.getName().toLowerCase(]
.endsWith(".xml":
I I f. isDirectory();
} public String getDescription(] {
return "XML files";
}
});
// Настраиваем меню
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu menu = new JMenu("File");
menuBar.add(menu);
JMenuItem exportitem = new JMenuItem
("Export preferences"): menu.add(exportltem);
exportltem.addActionListener(new
ActionListener ()
{ public void actionPerformed(ActionEvent event) {
if(chooser.showSaveDialog(PreferencesFrame.this)
== { try { OutputStream out = new FileOutputStream( chooser.getSelectedFile ());
node.exportsubtree(out);
102.107.114 .143.}102.107.114 .143.}out.close();
102.
107.
114 .
143.}
102.
107.
114 .
143.}
}
catch (Exception exception) {
Logger.getLogger("global").log(Level.INFO, "Export preferences", exception);
}
}
} • });
JMenuItem importItem = new JMenuItem
("Import preferences");
menu.add(impor111 em);
importitem.addActionListener(new
ActionListener(]
{
public void actionPerformed(ActionEvent event) {
if(chooser.showOpenDialog(PreferencesFrame.this; ==
{ try {
InputStream in = new FileInput St ream I chooser.getSelectedFile());
node.importPreferences(in); in.close();
}
catch (Exception exception) {
Logger.getLogger("global").log(Level.INFO, "Import preferences", exception);
}
}
}
});
JMenuItem exititem = new JMenuItem("Exit"); menu.add(exitltem);
exititem.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event) {
node.putlnt ("left", getXO);
node.putInt ("top", getYO);
node.putlnt("width", getWidthl));
node.putlnt("height", getHeight());
System.exit(0);
}
});
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
java.util.prefs.Preferences 1.4
Preferences userRoot()
Возвращает корневой узел пользователя из дерева настроек вызывающей программы.
Preference systemRoot()
Возвращает системный корневой узел из дерева настроек.
Preferences node(String path)
Возвращает узел, лежащий на заданном пути из текущего узла. Если параметр path задает абсолютный путь (т.е. начинается с косой черты /), то узел достигается из корня дерева, содержащего узел настройки. Если узел не лежит на заданном пути, то он создается.
Preferences userNodeForPackage(Class cl]
Preferences systemNodeForPackage(Class cl)
Возвращает узел из дерева текущего пользователя или системного дерева, абсолютный путь к которому соответствует имени пакета, содержащего класс cl.
String[" keys(]
Возвращает все ключи, принадлежащие данному узлу.
String get(String key, String defval)
int getlnt(String key, int defval)
long getLong(String key, long defval)
float getFloat(String key, float defval)
double getDouble(String key, double defval)
boolean getBoolean(String key, boolean defval)
byte(I getByteArray(String key, byte[] defval)
Эти методы возвращают значение, связанное с заданным ключом. В противном случае, если значения в таблице мет, либо оно имеет неверный тип, либо сама таблица недоступна, возвращается значение, предусмотренное по умолчанию.
void put(String key, String value)
void putlnt(String key, int value)
void putLong(String key, long value)
void putFloat(String key, float value)
void putDouble(String key, double value)
void putBoolean(String key, boolean value)
void putByteArray(String key, byte [ value)
Эти методы записывают пару ключ/значение в заданный узел.
void exportsubtree(Outputstream out)
Выводит в указанный поток параметры настройки, хранящиеся в заданном узле и его узлах-потомках.
void exportNode(Outputstream out)
Выводит в указанный поток параметры настройки, хранящиеся в заданном узле (игнорируя узлы-потомки).
void importpreferences(Inputstream in)
Импортирует параметры настройки, содержащиеся в указанном потоке.
На этом мы завершаем обсуждение способов развертывания программного обеспечения, написанного на языке Java. В следующей главе описана обработка исключительных ситуаций, возникающих во время работы программы. Кроме того, мы дадим несколько советов, касающихся способов тестирования и отладки программ и позволяющих избежать многих проблем в ходе их выполнения.
cTvVA^A 11
ситуации и отладкаситуации и отладкаИсключительные
ситуации и отладка
ситуации и отладка
. вэтой главе...
V Обработка ошибок
Перехват исключительных ситуаций
Как использовать исключительные ситуации
Регистрация ошибок
Т Диагностические утверждения
Применение отладчика
В идеальном мире люди никогда не делают ошибок при вводе данных, файлы, которые они пытаются открыть, всегда существуют, а программы всегда работают правильно. До сих пор мы предполагали, что живем именно в таком мире. Теперь пришла пора спуститься на грешную землю, населенную реальными людьми, делающими ошибки при программировании и вводе данных, и рассмотреть механизмы языка Java, которые позволяют в этом мире как-то существовать.
Обнаруживать ошибки всегда неприятно. Если пользователь вашей программы однажды потеряет все результаты своей работы из-за скрытой ошибки или непредвиденного стечения обстоятельств, он может навсегда отвернуться от такой программы. В подобной ситуации необходимо сделать по крайней мере три вещи.
Сообщить пользователю об обнаруженной ошибке.
Сохранить все результаты его работы.
Дать ему возможность аккуратно завершить работу программы.
В исключительных ситуациях, например, при некорректном вводе данных, которые могут привести к разрушению программы, язык Java использует механизм исправления ошибок, который, вполне естественно, называется обработкой исключительных ситуаций (exception handling). Этот механизм аналогичен обработке исключительных ситуаций в языках C++ и Delphi. Он описывается в первой части этой главы.
Вторая часть главы посвящена способам обнаружения ошибок до того, как они приведут к возникновению исключительных ситуаций во время выполнения программы. К сожалению, средства обнаружения ошибок, предусмотренные в пакете SDK, относятся к каменному веку. Для того чтобы как-то облегчить вашу участь, мы дадим несколько советов. Затем мы объясним, как использовать отладчик, запускаемый в режиме командной строки, в качестве спасательного круга. Для разработки серьезных программ на языке Java следует воспользоваться средствами отладки, предусмотренными в интегрированных средах Sun ONE Studio компании Sun, Cafe компании Simantec или JBuilder компании Inpraise. В данной главе описывается отладчик среды Sun ONE Studio.
Обработка ошибок
Допустим, что в ходе выполнения программы, написанной на языке Java, обнаруживается ошибка. Она может быть вызвана файлом, содержащим неверную информацию, неправильным сетевым соединением или выходом за пределы допустимого диапазона изменения индексов массива (ошибка, которую ненавидит большинство программистов), а может— попыткой сослаться на несуществующий объект. Вполне естественно, пользователи надеются, что программа сама справится с возникшими неприятностями. Если из-за ошибки какая-либо операция не может быть завершена, то программа должна сделать одно из двух:
либо вернуться в безопасное состояние и позволить пользователю выполнить другие команды;
дать пользователю возможность сохранить результаты своей работы и аккуратно завершить работу программы.
Это может оказаться нелегко: фрагмент кода, обнаруживающий (или даже вызывающий) ошибки, обычно довольно далек от фрагмента кода, который может вернуть данные в безопасное состояние, а также от кода, который может сохранить полученные пользователем результаты и дать ему возможность безопасно покинуть тонущий корабль. Предназначение механизма обработки исключительных ситуаций — передать данные из того места, где возник сбой, обработчику ошибок, который может справиться с ними. Предусматривая обработку исключительных ситуаций в программе, нужно предвидеть возможные ошибки и проблемы, связанные с ними. Какие же проблемы следует рассмотреть в первую очередь?
Ошибки ввода. Пользователи часто делают опечатки, причем некоторые из них любят идти напролом, не обращая внимания на инструкции. Допустим, пользователь хочет соединиться с URL-адресом, в котором допущена синтаксическая ошибка. Ваш код должен был бы проверять синтаксис, но, предположим, вы забыли это сделать. Тогда сетевое программное обеспечение сообщит об ошибке.
Сбои оборудования. Аппаратное обеспечение не всегда работает так, как вы ожидаете. Принтер может оказаться выключен, а Web-страница— временно недоступной. Оборудование часто отказывает на середине работы. Например, принтер может дать сбой на середине страницы.
Физические ограничения. Диск может оказаться переполненным, а программа исчерпать оперативную память.
Ошибки программирования. Какой-нибудь метод может работать неправильно. Например, он может возвращать неверный ответ или некорректно вызывать другие методы. Выход за пределы массива, попытка найти несуществующий элемент таблицы хэширования, выталкивание элемента из пустого стека— все это примеры ошибок программирования.
Обычно в качестве ответной реакции на ошибку метод возвращает специальный код, который анализируется методом, выполнявшим вызов. Например, методы, считывающие информацию из файлов по достижении конца файла, обычно возвращают значение -1 . Такой способ обработки ошибок часто оказывается эффективным. В других случаях в качестве признака ошибки возвращается ссылка null. В главе 10 мы рассмотрели пример, в котором метод getParameter класса Applet возвращал ссылку null, если требуемый параметр отсутствовал.
К сожалению, возвращать код ошибки не всегда оказывается возможным. Иногда невозможно различить правильные и неправильные данные. Метод, возвращающий целое число, не может, обнаружив ошибку, просто вернуть число -1 , поскольку оно может быть совершенно правильным результатом каких-то вычислений.
Как мы упоминали в главе 5, язык Java в каждом методе предусматривает альтернативный выход, которым следует воспользоваться, если задачу невозможно выполнить до конца. В данной ситуации метод не станет возвращать значение, а возбудит (throw) объект, инкапсулирующий информацию об ошибке. Обратите внимание на то, что выход из метода выполняется незамедлительно, и он не возвращает своего нормального значения (или не возвращает никакого). Более того, возобновить выполнение кода, вызвавшего данный метод, невозможно. Вместо этого начинается поиск обработчика исключительной ситуации, который может справиться с возникшей ошибкой.
Исключительные ситуации имеют свой собственный синтаксис и являются частью особой иерархии наследования. Сначала мы рассмотрим синтаксические вопросы, а затем дадим несколько советов по эффективному использованию обработки исключительных ситуаций.
Классификация исключительных ситуаций
В языке Java объект исключительной ситуации всегда является экземпляром класса, производного от класса Throwable. Как мы вскоре убедимся, при необходимости можно создавать и свои собственные классы исключительных ситуаций, если стандартных классов недостаточно.
На рис. 11.1 показана упрощенная диаграмма иерархии исключительных ситуаций в языке Java.
Обратите внимание на то, что, хотя все исключительные ситуации происходят от класса Throwable, иерархия сразу разделяется на две ветви: Error и Exception.
Иерархия Error описывает внутренние ошибки и ситуации, связанные с исчерпанием ресурсов, в системе поддержки выполнения программ на языке Java. Ни один объект этого типа самому возбудить невозможно. Если возникла внутренняя ошибка, то перед тем, как сообщить об этом пользователю и обеспечить безопасное завершение программы, про граммист может сделать немногое. Эти ситуации возникают довольно редко.
Рис. 11.1.Иерархия наследования исключительных ситуаций в языке Java
Программируя на языке Java, основное внимание следует уделять иерархии Exception. Эта иерархия также разделяется на две ветки: исключительные ситуации, производные от класса RuntimeException, и остальные. Общее правило таково.
Исключительные ситуации типа RuntimeException возникают вследствие ошибок программирования. Все другие исключительные ситуации являются следствием непредвиденного стечения обстоятельств, например, ошибок ввода-вывода, возникающих при выполнении вполне правильных программ.
Исключительные ситуации, производные от класса RuntimeException, связаны со следующими проблемами.
Неверное приведение типов.
Выход за допустимые пределы массива.
Попытка обратиться к объекту по ссылкеnull.
Остальные исключительные ситуации возникают в следующих случаях.
Обрыв файла.
Попытка соединиться с неправильным URL-адресом.
Попытка обнаружить несуществующий объект класса Class.
Правило "Если возникла исключительная ситуация RuntimeException, то это — ваша вина" выполняется практически всегда. Исключительной ситуации Array- IndexOutBoundsException можно избежать, если всегда проверять индексы массива. Исключительная ситуация NullPointerException никогда не возникнет, если перед использованием переменной вы проверите, не равна ли она ссылке null.
А как насчет неверного URL-адреса? Всегда ли можно обнаружить ошибку заранее? Разные броузеры обрабатывают разные URL-адреса. Например, броузер Netscape может работать с адресами вида raailto :URL, а программа appletviewer — нет. Следовательно,' термин "неправильный" (malformed) зависит от среды, а не от программы.
В спецификации языка Java любая исключительная ситуация, производная от класса Error или RuntimeError, называется “ неком тролируемой (unchecked). Все остальные исключительные ситуации называются контролируемыми (checked). Это разумная терминология, которую мы одобряем.
Имя RuntimeException ("исключительная ситуация во время выполнения программы"— прим. ред.) иногда вводит в заблуждение. Разумеется, все ошибки, которые мы обсуждаем, возникают в ходе выполнения программы.
Если вы знакомы с (намного более ограниченной) иерархией исключительных ситуаций из стандартной библиотеки языка C++, то будете сильно удивлены. В языке C++ есть два основных класса исключительных ситуаций: runtime_error и logic_error.Класс logic_error эквивалентен классу RuntimeException языка Java. Класс runtime_errorявляется базовым для исключительных ситуаций, вызванных непредвиденным стечением обстоятельств. Он служит эквивалентом всех исключительных ситуаций, предусмотренных в языке Java, не относящихся к типу Runt imeException.
Объявление исключительных ситуаций, возбуждаемых методом
Метод может возбуждать исключительные ситуации, если возникают обстоятельства, с которыми он не справляется. Идея проста: метод не только сообщает компилятору, какие значения он может возвращать, но и предсказывает, какие ошибки могут возникнуть. Например, код, считывающий информацию из некоего файла, может предположить, что такого файла не существует или он пуст. Следовательно, код, обрабатывающий информацию, прочитанную из файла, должен сообщить компилятору, что он может возбудить исключительную ситуацию класса IOException.
Объявление о том, что данный метод может возбудить исключительную ситуацию, помещается в его заголовок. В зависимости от того, какие контролируемые исключительные ситуации может возбудить данный метод, его заголовок может изменяться. Например, ниже приведен заголовок метода класса BufferedReader из стандартной библиотеки. Этот метод считывает текстовую строку из потока ввода, например, из файла или сетевого соединения. (Потоки рассматриваются в главе 12.) public String readLine(: throws IOException
В заголовке указано, что данный метод возвращает строку, но, кроме того, при возникновении ошибок может возбуждать исключительную ситуацию IOException. Это означает, что в случае ошибки метод не вернет строку, а возбудит объект класса IOException. Если это произойдет, система поддержки выполнения программ начнет поиск обработчика исключительной ситуации, предназначенного для работы с объектами класса IOException.
Создавая свой собственный метод, не нужно объявлять все возможные объекты, который ваш метод фактически может возбуждать. Чтобы понять, когда (и что) следует объявлять в разделе throws, имейте в виду, что исключительная ситуация возбуждается в следующих четырех ситуациях.
Выполняется вызов метода, возбуждающего контролируемую исключительную ситуацию, например, вызывается метод readLine класса BufferedReader.
Обнаружена ошибка, и с помощью оператора throw возбуждается контролируемая исключительная ситуация (оператор throw обсуждается в следующем подразделе).
Обнаружена ошибка программирования, например а [-1] = 0, возбуждающая неконтролируемую исключительную ситуацию, например ArraylndexOutOf- BoundsException.
Возникла внутренняя ошибка в виртуальной машине или оперативной библиотеке.
Если осуществляются первые два сценария, нужно сообщить программистам, которые будут использовать ваш метод, что возможна исключительная ситуация. Зачем? Любой метод, возбуждающий исключительную ситуацию, представляет собой смертельную угрозу. Если в программе не предусмотреть обработку исключительной ситуации, то текущий поток выполнения прерывается.
Поскольку любой метод языка Java является частью какого-то класса, в заголовке метода нужно объявить, что он может возбуждать исключительную ситуацию. Для этого используется спецификация исключительной ситуации (exception specification). class MyAnimation {
public Image loadimage(String s) throws lOException
(
}
}
Если метод может возбуждать несколько контролируемых исключительных ситуаций, всех их нужно перечислить в его заголовке, разделяя запятыми.
class MyAnimation
{
public Images)
throws EOFException, MalformedURLException
(
}
}
Однако внутренние ошибки, т.е. исключительные ситуации, производные от класса Error, объявлять не нужно. Эти ситуации могут возбуждаться любыми методами, причем повлиять на них невозможно.
Аналогично не нужно объявлять неконтролируемые исключительные ситуации, производные от класса RuntimeException.
class MyAnimation {
void drawlmage(int i) throws ArraylndexOutOfBoundsException // Нет!!!
{
}
Эти ошибки лежат полностью на вашей совести. Если вас так беспокоит возможность выхода индекса массива за пределы допустимых диапазонов, лучше уделите больше внимания ее предотвращению, а не заявляйте, что она, дескать, может возникнуть.
В заключение отметим, что метод должен объявлять все контролируемые исключительные ситуации, которые он может возбудить. Неконтролируемые исключительные ситуации либо находятся вне вашей компетенции (класс Error), либо являются следствием ваших логических ошибок, которые не следовало допускать (класс RuntimeException). Если ваш метод не сообщает обо всех контролируемых исключительных ситуациях, компилятор выдаст сообщение об ошибке.
Разумеется, как мы уже видели в нескольких примерах, вместо объявления исключительных ситуаций можно их перехватывать. В этом случае исключительная ситуация не возбуждается, и спецификация throws не нужна. Позднее в этой главе мы рассмотрим вопрос, что лучше: перехватывать исключительную ситуацию самому или поручить это кому-нибудь другому.
Метод подкласса не может возбуждать больше исключительных ситуаций, чем предусмотрено в замещаемом им методе суперкласса. (Меньше— пожалуйста.) В частности, если метод суперкласса вообще не возбуждает контролируемые исключительные ситуации, то и в подклассе этого сделать нельзя. Например, если вы замещаете методJComponent.paintcomponent, ваш метод paintComponent не должен возбуждать ни одной контролируемой исключительной ситуации, поскольку соответствующий метод суперкласса этого не делает.
Если метод объявляет, что может возбуждать исключительную ситуацию определенного класса, то он может возбуждать также исключительные ситуации его подклассов. Например, метод readLine класса BufferedReaderобъявляет, что он может возбуждать исключительную ситуацию класса IOException, причем нам неизвестно, какую именно. Это может быть ситуация как самого класса IOException, так и его потомков, например, класса EOFException.
Спецификатор throws совпадает со спецификатором throws языка C++. Однако существует одно важное отличие. В языке C++ спецификатор throws срабатывает только во время выполнения программы, а не на этапе компиляции. Это значит, что компилятор языка C++ игнорирует спецификации исключительных ситуаций. Однако, если исключительная ситуация возбуждается в функции, не являющейся частью списка throw, то вызывается функция unexpected, и по умолчанию выполнение программы прекращается. Кроме того, в языке C++, если спецификация throw не указана, функция может возбудить любую исключительную ситуацию.
Как возбудить исключительную ситуацию
Предположим, что с вашей программой случилось нечто ужасное. У вас есть метод readData, считывающий данные из файла, в заголовке которого указано следующее. Content-length: 1024
Однако чтение файла прервалось после ввода 733 символов. Вы считаете эту ситуацию настолько ненормальной, что решаете возбудить исключительную ситуацию.
Теперь нужно решить, какого типа исключительную ситуацию нужно возбудить. Нам подходит одна из разновидностей класса IOException. Просматривая документацию, вы обнаруживаете описание исключительной ситуации EOFException: "Сигнализирует от том, что во время ввода данных неожиданно обнаружен маркер конца файла EOF". Отлично. Эту ситуацию можно возбудить двумя способами: throw new EOFException();
или, если хотите,
EOFException e = new EOFException(); throw e;
Итак, вот какой фрагмент программы нужно написать.
String readData(ButferReader in) throws EOFException {
while (...)
{
if (ch == -1) // Обнаружен маркер конца файла EOF. { if(n < len) throw new EOFException();
} return s;
В классе EOFException есть второй конструктор, получающий в качестве аргумента строку. Его можно использовать для более подробного описания исключительной ситуации.
String gripe = "Content-length: " + len ", Received: + n; throw new EOFException(gripe);
Задача облегчается, если в вашем распоряжении есть один из существующих классов исключительных ситуаций. В этом случае нужно сделать следующее.
Найти подходящий класс исключительных ситуаций.
Создать объект этого класса.
Возбудить его.
Возбудив исключительную ситуацию, метод уже не сможет вернуть управление в вызывающий модель. Это означает, что беспокоиться о возвращении значения, предусмотренного по умолчанию, или кода ошибки не нужно.
Возбуждение исключительных ситуаций в языках C++ и Java происходит одинаково, однако есть одно небольшое различие. В языке Java можно возбуждать только объекты классов, производных от класса Throwable, а в языке C++ можно возбуждать объекты любого типа.
Создание классов исключительных ситуаций
В программе может возникнуть ситуация, не предусмотренная ни одним из существующих классов. В этом случае можно достаточно просто создать свой собственный класс исключительных ситуаций. Для этого нужно просто вывести новый класс из класса Exception или одного из его дочерних классов, например IOException. Этот класс можно снабдить конструктором по умолчанию и конструктором, содержащим подробное сообщение. (Это сообщение, полезное для отладки, выводится на печать с помощью метода toString базового класса Throwable.) class FileFormatException extends IOException {
public FileFormatException(] {}
public FileFormatException(String gripe){}
{
super(gripe);
}
Теперь мы готовы возбудить исключительную ситуацию своего собственного типа. String readData(BufferReader in) throws FileFormatException {
while (... )
{
if (ch == -1) // Обнаружен маркер конца файла EOF.
if ( n < len)
throw new FileFormatException();
)
return s;
java.lang.Throwable 1.0
Throwable ()
Создает новый объект класса Throwable, не сопровождая его подробным сообщением.
Throwable(String message)
Создает новый объект класса Throwable, сопровождая его подробным сообщением. По умолчанию все производные классы исключительных ситуаций содержат оба конструктора.
String getMessage(!
Получает подробное сообщение, предусмотренное в объекте класса Throwable.
Перехват исключительных ситуаций
Теперь мы знаем, как возбудить исключительную ситуацию. Это просто: возбудили и забыли. Разумеется, нужен код, позволяющий перехватить исключительную ситуацию! Для этого придется потрудиться.
Если исключительная ситуация возникает в обычном (не графическом) приложении и нигде не перехватывается, то программа прекращает работу, выводя на консоль сообщение о типе исключительной ситуации и содержимое стека. Графические программы (аплеты и приложения) выводят то же сообщение, продолжая при этом поддерживать интерфейс пользователя. (Отлаживая графические программы, лучше выводить сообщения на консоль.)
Чтобы перехватить исключительную ситуацию, нужно задать блок try/catch. Простейшая форма этого блока приведена ниже.
try
{
Код.
}
catch (ТипИсключительнойСитуации е)
{
Обработчик данного типа.
}
Если один из методов блока try возбуждает исключительную ситуацию, принадлежащую классу, указанному в разделе catch, то происходит следующее.
Программа пропускает оставшуюся часть кода в блоке
Программа выполняет код обработчика, указанный в блоке catch.
Если методы блока try не возбуждают исключительные ситуации, то программа игнорирует блок catch.
Если какой-либо из операторов блока try возбуждает исключительную ситуацию, принадлежащую классу, не указанному в разделе catch, выполнение этого метода немедленно прекращается. (Будем надеяться, что в вызывающих его методах перехват соответствующей исключительной ситуации все же предусмотрен.)
Для иллюстрации рассмотрим следующий пример.
public voidreader)
try
boolean done = false;
while
{
String line =
if (line == null) // Конец файла.
done =
else
{ Обработка строки line;
}
catchexception)
{
exception.printStackTrace() ;
}
Обратите внимание на то, что большая часть кода в блоке try элементарна: он считывает и обрабатывает строки, пока не обнаружится конец файла. Как указано в документации, существует возможность того, что метод readLine возбудит исключительную ситуацию IOException. В данном случае мы пропускаем весь цикл while, входим в раздел catch и генерируем трассировку стека. Для небольших программ это вполне разумно. А какие еще существуют возможности?
Зачастую лучше ничего не делать вообще. Если в методе readLine возникла ошибка, возложите ответственность за обработку этой ситуации на вызывающий метод! Придерживаясь этого подхода, достаточно указать, что метод readLine может возбуждать исключительную ситуацию класса IOException.
public void read(BufferReader reader) throws IOException
{
boolean done = false;
while (!done)
{
String line = reader.readLine();
if (line == null) // Конец файла.
done = true;
else
{ Обработка строки line;
)
}
Запомните, компилятор строго следит за спецификаторами throws. Вызывая метод, возбуждающий контролируемую исключительную ситуацию, нужно либо самому обрабатывать ее, либо передавать на обработку другому методу.
Что лучше? Как правило, следует перехватывать лишь те исключительные ситуации, которые вы сами можете обработать, а остальные передавать дальше. Передавая исключительную ситуацию, нужно добавлять спецификатор throws, чтобы предупредить вызывающий метод.
Просмотрите документацию no API и обратите внимание на методы, которые могут возбуждать исключительные ситуации. Затем примите решение, обрабатывать исключительную ситуацию или включить в список throws. Во втором варианте нет ничего постыдного— лучше предоставить обработку исключительной ситуации более компетентному обработчику.
Учтите, пожалуйста, что у этого правила есть одно исключение. Если вы создаете метод, который замещает метод суперкласса, не возбуждающий ни одной исключительной ситуации (например, метод paintComponentиз класса JComponent), вы обязаны сами перехватывать каждую проверяемую исключительную ситуацию в коде метода. В этот метод подкласса нельзя добавить спецификатор throws, присутствующий в методе суперкласса.
Не бойтесь возбуждать или передавать исключительные ситуации для проблем, которые вы не можете решить. С другой стороны, ваши товарищи-программисты возненавидят вас за то, что вы создаете методы, которые перекладывают обработку исключительных ситуаций на их плечи. Если вы в этом хоть немного разбираетесь, в исключительных ситуациях не пользуйтесь кувалдой.
Перехват исключительных ситуаций в языках Java и C++ осуществляется одинаково. Грубоговоря, выражение
catch (Exception e) // Java
являетсяаналогомвыражения
catch (Exceptions e) // C++
В языке Java нет аналога выражения(...) из языка C++. В этом нет необходи
мости, поскольку все исключительные ситуации выводятся из общего суперкласса.
Перехват нескольких исключительных ситуаций
В блоке try можно перехватить несколько исключительных ситуаций, обработав их по отдельности. Для каждого типа исключительных ситуаций следует предусмотреть свой раздел try {
Код, который может возбуждать исключительные ситуации.
}
catchel)
{
// Действия в случае, если URL-адрес указан неверно. } catchе2)
{
// Действия в случае, если URL-адрес не существует. }
catchеЗ)
{
// Действия при возникновении ошибок ввода-вывода. }
Объект исключительной ситуации (el, e2 или еЗ) может содержать информацию о ней. Для того чтобы узнать содержание сообщения (если оно есть), нужно вызвать метод e3.getMessage(i
Фактический тип объекта можно определить с помощью метода ()
Повторное возбуждение исключительной ситуации
Иногда нужно перехватить исключительную ситуацию, не интересуясь причинами ее возникновения. Обычно это происходит, когда программист выполняет доводку программы, но не может полностью ее завершить. В таком случае он пытается выполнить некоторые действия и повторно вызвать метод throw, посылая исключительную ситуацию обратно по цепочке. Вот типичный пример.
Код, который может возбуждать исключительные ситуации.Код, который может возбуждать исключительные ситуации.Graphics g= try { }
Код, который может возбуждать исключительные ситуации.
Код, который может возбуждать исключительные ситуации.
catch (MalformedURLException e) {
9 - dispose О;
throw e;
)
Этот фрагмент программы иллюстрирует одну из наиболее распространенных причин повторного возбуждения перехваченной исключительной ситуации. Если объект графического контекста не был бы удален из памяти в разделе catch, он вообще никогда не был бы удален. (Разумеется, метод finalizeудалил бы объект, но до его вызова прошло бы много времени.)
С другой стороны, основная причина, неверный URL-адрес, не исчезла. Мы по- прежнему должны сообщить о ней программам, предназначенным для обработки именно таких исключительных ситуаций. (В следующем разделе описан более элегантный способ.)
Кроме того, можно возбудить исключительную ситуацию, которой нет в разделе catch.
try {
acme.util.Widget a = new acme.util.Widget();
a.load(s);
a.paint(g) ;
}
catch (RuntimeExcept ion e) {
// Еще одна АСМЕ-ошибка.
throw new Exception("ACME error");
Раздел finally
Когда код возбуждает исключительную ситуацию, выполнение остальных операторов останавливается, и происходит выход из метода. Если метод зарезервировал какие-то локальные ресурсы, о которых известно лишь ему, то освободить их окажется невозможно. Для этого нужно перехватить и повторно возбудить все исключительные ситуации. Однако это решение не слишком удачно, поскольку ресурсы нужно освобождать в двух местах: в нормальном коде и в коде исключительной ситуации.
В языке Java есть лучшее решение — использовать раздел finalize.
Graphics g = image.getGraphics(; ;
try {
Код, который может возбуждать исключительные ситуации.
}
catch (IOException e)
{
Выводим диалоговое окно.
}
finally
{
j g.disposed ;
Эта программа выполняет код из раздела finallyнезависимо от того, будет ли перехвачена исключительная ситуация. Следовательно, в приведенном выше коде программаосвободитграфическийконтекст при любых обстоятельствах.
Рассмотрим три возможные ситуации, в которых программа выполняет раздел finally.
Код не возбуждает никаких исключительных ситуаций. В этом случае программа сначала полностью выполняет блок try, а затем блок finalize. Иными словами, выполнение программы продолжается с первой строки, следующей за блоком try.
Код возбуждает исключительную ситуацию, которая перехватывается в разделе catch, в данном случае — исключительную ситуацию класса IOException. В этом случае программа сначала полностью выполняет блок try вплоть до той точки, в которой возбуждается исключительная ситуация. Остальная часть блока try игнорируется. Затем программа выполняет код из соответствующего блока catch и в заключение — раздел finally.
Если в разделе catch исключительные ситуации не возбуждаются, то программа выполняет первую строку вслед за блоком try. Затем исключительная ситуация передается обратно в вызывающий метод.
Код возбуждает исключительную ситуацию, которая не перехватывается в разделе catch. В этом случае программа полностью выполняет блок try вплоть до той точки, в которой возбуждается исключительная ситуация. Оставшаяся часть блока try игнорируется. Затем программа выполняет код из раздела finallyи передает исключительную ситуацию обратно в вызывающий метод.
Раздел finally можно использовать без блока catch. Например, рассмотрим следующий оператор try.
Graphics g = image.getGraphics();
try
{
{
Код, который может возбуждать исключительные ситуации.
}
finally
{
g.dispose();
}
Оператор g . dispose () в блоке finall выполняется независимо от того, возникает ли какая-либо исключительная ситуация в блоке try. Разумеется, если какая-либо исключительная ситуация возникает, она возбуждается повторно и должна перехватываться другим разделом catch.
Иногда раздел finallyповергает программистов в уныние, в частности, если методы очистки памяти могут возбуждать исключительные ситуации. Типичный случай— закрытие потока (см. главу 12). Допустим, при возникновении исключительной ситуации мы хотим закрыть поток.
InputStream in;
try
{
catche)
{
Выводим диалоговое окно.
}
finally
{
in.dispose();
1
Если внутри блока try происходит выход из метода с помощью оператора return, разделможет нарушить управление потоком выполнения про
граммы. Перед выполнением оператора return обязательно выполняется блок Если он также содержит оператор return, то исходное возвращаемое значение будет замаскировано. Рассмотрим пример.
public static int f(int n)
{
try
{
int r = n * n;
return r;
}
finally
{ if (n == 2) return 0;
}
Если выполнить вызов f (2), то блок try вычислит значение = 4 и выполнит оператор return. Однако на самом деле до оператора return будет выполнен код разделаЭтот раздел заставит метод вернуть значение 0, игнорируя
исходное значение, равное 4.
Предположим также, что код в блоке try возбуждает некоторую исключительную ситуацию, отличающуюся от класса lOException, для которого предназначен вызывающий метод. Выполняется блок finally и вызывается метод close. Этот метод сам может возбуждать исключительную ситуацию класса lOException! При этом исходная исключительная ситуация будет потеряна и вместо нее будет возвращен объект класса lOException. Это в корне противоречит духу обработки исключительных ситуаций.
Лучше всего (к сожалению, разработчики классас нами не соглас
ны) совсем не возбуждать в операциях, связанных с очисткой памяти, например, в методах dispose, close и тому подобных, исключительные ситуации, которые пользователь может вызвать в блоке
Между языками C++ и Java есть принципиальное различие, связанное с обработкой исключительных ситуаций. В языке Java нет деструкторов. Следовательно, нет и разворачивания стека, как в языке C++. Это значит, что программист на языке Java должен вручную размещать код, освобождающий ресурсы в блоках Разумеется, поскольку в языке Java применяется механизм сборки мусора, ресурсы редко приходится освобождать вручную.
Цепочки исключительных ситуаций
Довольно часто одна исключительная ситуация превращается в другую. Допустим, например, что мы вызываем метод, возбуждающий контролируемую исключительную ситуацию в обработчике событий.
public void actionPerformed(ActionEvent event) {
out = new Filewriter(filename);
// Может возбуждать исключительную ситуацию
// из класса IOException
)
Метод actionPerformed не может возбуждать контролируемые исключительные ситуации. Если вы не можете справиться с исключительной ситуацией в этом обработчике событий, следует перехватить ее и превратить в неконтролируемую исключительную ситуацию. До появления пакета SDK 1.4 для этого приходилось вставлять текстовое описание объекта контролируемой исключительной ситуации в новый объект неконтролируемой исключительтной ситуации, теряя при этом часть информации. При работе с пакетом SDK 1.4 достаточно просто передать объект исключительной ситуации конструктору другой исключительной ситуации, а затем извлечь его с помощью метода getClause.
RuntimeException unchecked = new RuntimeException(checked);
Throwable t = e. getClause();
Многие классы, такие как ClassesNotFoundException, InvocationTargetException и RuntimeException, имели свои собственные схемы формирован и я цепочек исключительных ситуаций. Теперь они приведены в соответствие с общим механизмом. Сцепленные исключительные ситуации можно по-прежнему извлекать традиционным способом или просто вызывать метод getClause.
К сожалению, сцепленные исключительные ситуации из классаSQLException, наиболее часто используемого прикладными программистами, остались прежними.
Фреймы стека
До появления пакета SDK 1.4 доступ к текстовому описанию результатов трассировки стека обеспечивался вызовом метода printStackTrace класса Throwable. Теперь можно вызвать метод getStackTrace, получить массив объектов класса StackTraceElementи проанализировать их в своей программе.
Throwable t = new Throwable() ;
StackTraceElement[j frames = t . getStackTrace();
for (int i = 0; i < frames.length; i++)
анализ объекта frame[i];
Класс StackTraceElement содержит методы, позволяющие получить имя файла, номер выполняемой строки, а также имя класса и выполняемого метода. Форматированную строку, содержащую эту информацию, порождает метод toString.
Программа, приведенная в листинге 11.2, выводит на печать результаты трассировки рекурсивной функции, вычисляющей факториал. Например, при вычислении значения factorial(3) на печать будут выведены следующие строки.
factorial(3) ;
StackTraceTest.factorial(StackTraceTest.java:8]
StackTraceTest.main(StackTraceTest.java:23) factorial (2);
StackTraceTest.factorial(StackTraceTest.java:8)
StackTraceTest.factorial(StackTraceTest.java: 14)
StackTraceTest.main(StackTraceTest.java:231 factorial(3);
StackTraceTest.factorial(StackTraceTest.java:8)
StackTraceTest.factorial(StackTraceTest.java:14)
StackTraceTest.factorial(StackTraceTest.java:14)
StackTraceTest.main(StackTraceTest.java:23] return 1 return 2 return 3
import javax.swing.*;
/**
Программа выводит на печать результаты
трассировки метода.
*/
public class StackTraceTest
{
/**
Вычисляет факториал
@param n неотрицательное число
@return n!=l*2*...*n
*/
public static int factorial(int n)
{
System.out.printIn("factorial(" + n + "):");
Throwable t = new Throwable();
StackTraceElement[] frames = t;
for (int i = 0; i < frames.length; i++)
;
int r;
if (n <= 1) r = 1;
else r n * factorial(n- 1);
System.out.printIn("return " + r) ;
return r;
}
public static void main(String[args)
{
String input = JOptionPane.showInputDialog("Enter n:")
int n = Integer.parselnt(input);
;
System.exit(0);
}
.}
java.lang.Throwable 1.0
Throwable(Throwable cause)1.4
Throwable(String message, Throwable cause)1.4
Создает объект класса Throwable, соответствующий объекту cause, описывающему причины исключительной ситуации.
Throwable initCause(Throwable cause)1.4
Указывает причину создания данного объекта. Если причина создания объекта уже задана, возбуждает исключительную ситуацию. Возвращает указатель this.
Throwable getCause() 1.4
Возвращает объект исключительной ситуации, задавший причину создания данного объекта. Если причина создания объекта не была задана, возвращает ссылку nul1.
StackTraceElement[] getStackTrace()1.4
Возвращает результаты трассировки стека вызова в момент создания данного объекта.
java.lang.Exception 1.0
Exception(Throwable cause)1.4
Exception(String message, Throwable cause)
Создает объект класса Exception, соответствующий объекту cause, описывающему причины исключительной ситуации.
java.lang.RuntimeException 1.0
RuntimeException(Throwable cause)1.4
RuntimeException(String message, Throwable cause)1.4
Создает объект класса RuntimeException, соответствующий объекту cause, описывающему причины исключительной ситуации.
java.lang.StackTraceElement 1.4
String getFileName()
Возвращает имя исходного файла, содержащего точку, в которой выполняется указанный элемент. Если информация недоступна, возвращает ссылкуnull.
int getLineNumber(J
Возвращает номер строки, содержащей точку, в которой выполняется указанный элемент. Если информация недоступна, возвращает число -1.
String getClassName(;
Возвращает полное имя класса, содержащего точку, в которой выполняется указанный элемент.
String getMethodName(]
Возвращает имя метода, содержащего точку, в которой выполняется указанный элемент. Имя конструктора—<init>. Имя статического инициализатора— <clinit>. Перегруженные методы, имеющие одинаковые имена, не различаются.
boolean isNativeMethod(I
Если точка выолнения данного элемента находится внутри машинозависимого метода, возвращает значение true.
String toStringf)
Возвращает форматированную строку, содержащую имя класса и метода, а также имя файла и номер строки, если эта информация доступна.
Заключительный обзор обработки ошибок
и исключительных ситуаций в языке Java
В программе, представленной в листинге 11.2, произвольно генерируется несколько ошибок и перехватываются различные исключительные ситуации (рис. 11.2).
Рис. 11.2.Программа, порождающая исключительные ситуации
Запустите ее, пощелкайте на кнопках и вы увидите, как возбуждаются исключительные ситуации.
Как известно, ошибки программирования, например, выход индекса массива за пределы допустимого диапазона, возбуждают исключительные ситуации класса RuntimeException. Попытка открыть несуществующий файл приводит к возникновению исключительной ситуации класса IOException. Как ни странно, ошибки, связанные с действиями над числами с плавающей точкой, например, деление на 0.0 или извлечение квадратного корня из числа -1, не порождают исключительных ситуаций. (Целочисленное деление на 0 возбуждает исключительную ситуацию класса ArithmeticException.)
Мы перехватываем исключительные ситуации, возбуждаемые методом actionPerformedв методе fireActionPerformed, связанном с переключателями, и отображаем их на дисплее в текстовом поле. Однако в заголовке метода action- Performed не указано, что он возбуждает проверяемые исключительные ситуации. Следовательно, обработчик кнопки "No such file должен перехватывать исключительную ситуацию класса IOException.
При щелчке на кнопке "Throw unknown" возбуждается объект класса UnknownError. Этот класс не расширяет класс Exception, поэтому наша программа его не перехватывает. Вместо этого код пользовательского интерфейса выводит на консоль сообщение об ошибке и содержимое стека.
12.13.14.22.23.24.34.42.45.46.47.53.54.12.13.14.22.23.24.34.42.45.46.47.53.54.
12.
13.
14.
22.
23.
24.
34.
42.
45.
46.
47.
53.
54.
12.
13.
14.
22.
23.
24.
34.
42.
45.
46.
47.
53.
54.
import java.awt.*;
import j ava.awt.event.*;
import javax.swing.*;
import java.io.*;
public class ExceptTest
public static void main(String(] args)
{
ExceptTestFrame frame = new ExceptTestFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.showf] ;
/**
Фрейм с панелью для тестирования исключительных ситуаций. */ class ExceptTestFrame extends JFrame
(
public ExceptTestFrame(I
{
setTitle("ExceptTest") ;
Container contentPane = getContentPane();
ExceptTestPanel panel = new ExceptTestPanel(); contentPane.add(panel);
} pack();
}
/**
Панель с переключателями для фрагментов кода
и анализа их поведения в исключительных ситуациях.
*/
class ExceptTestPanel extends Box
public ExceptTestPanel ()
super(BoxLayout.Y_AXIS);
group = new ButtonGroup();
// Добавляем переключатели для фрагментов кода.
addRadioButton("Integer divide by zero", new
ActionListener()
(
public void actionPerformed(ActionEvent event)
( a[l] = 1 / (a.length -
});
addRadioButton("Floating point divide by zero", new ActionListener (]
64.864.8{ public voidevent)
64.
8
64.
8
{.
a[l] = a[2] I (a[3] - a[3]); } )); bounds", new () { public voidevent)
{ a[l] =;
} });
addRadioButton("Bad cast", new ActionListener () { public voidevent)
{ a = } }); addRadioButton("Null pointer", new ActionListener () { public voidevent)
{ ; } }); addRadioButton("sqrt(-1)", new ActionListener(: { public voidevent)
{ a[l]=
} }) ; new ActionListener(; { public voidevent)
{ a[l] = 1000 * 1000 * 1000 * 1000; int n = (int)a[l]; } }); such file", new ActionListener(I { public voidevent)
(
122.16С .169.170.122.16С .169.170.
122.
16С .
169.
170.
122.
16С .
169.
170.
try {
FileInputStream is
= new Fileinputstream("Nc such file");
}
catch (IOException exception) {
textField.setText(exception.toString());
} }
});
addRadioButton("Throw unknown", new ActionListener(]
{
public void actionPerformed(ActionEvent event) {
throw new UnknownError();
} });
// Добавляем текстовое поле.
textField = new JTextField(30);
add(textField);
}
/**
Добавляем переключатель с заданным слушателем панели. Перехват исключительных ситуаций выполняется методом actionPerformed, принадлежащим слушателю.
@param s метка радиокнопки
@param listener слушатель переключателя
*/
private void addRadioButton(String s, ActionListener listener)
{
JRadioButton button = new JRadioButton(s, false) {
// Кнопка вызывает этот метод для активации события.
// Мы заместили его для перехвата исключительных ситуаций. protected void fireActionPerformed(ActionEvent t)
{
try
{
textField.setText("Nc exception");super.fireActionPerformed(event);
}
catch (Exception exception) {
textField.setText(exception.toString(; ) } '}
};
button.addActionListener(listener) ;
add(button);
group.add(button);
}
private ButtonGroup group;
private JTextField textField;
private double[' a = new double[10];
}
Как использовать исключительные ситуации
Обычно исключительные ситуации применяют слишком часто. И, правда, зачем ломать голову над методом, выполняющим синтаксический разбор вводных данных, если для решения этой задачи можно использовать исключительную ситуацию? Вместо анализа URL-адреса передайте его методу, перехватывающему исключительную ситуацию MalformedURLException. Ни забот, ни хлопот. Ошибаетесь! Экономя время на создании собственного обработчика, вы потеряете его при обработке исключительной ситуации стандартными средствами. Таким образом, злоупотребление исключительными ситуациями может привести к значительному замедлению работы вашей программы. Прислушайтесь к нашим советам.
Обработка исключительных ситуаций не может заменить собой простую проверку.
Для иллюстрации мы написали фрагмент программы, использующей встроенный класс Stack. Программа, приведенная в листинге 11.3, делает 1000000 попыток вытолкнуть элемент из пустого стека. Сначала она определяет, пуст ли стек.
if (! s.empty)) s.popO;
Затем мы заставляем ее выталкивать элемент из стека, не обращая внимания на то, что он пуст. После этого мы перехватываем исключительную ситуацию EmptyStackException, которая сообщает нам, что мы не правы.
try () {
s . pop();
}
catch {EmptyStackException e)
(•
Время, которое мы затратили на эти операции, указано в табл. 11.1.
ПроверкаВозбуждение/перехват
ПОмс24550 мс
Как видим, перехват исключительной ситуации занял намного больше времени, чем простая проверка. Мораль сей басни такова: используйте исключительные ситуации только в действительно исключительных обстоятельствах.
Не мелочитесь!
Многие программисты каждый оператор помещают в отдельный блок try.
InputStream is;
Stack s;
for (i= 0.; i<100; i++)
(
try
{
n = s.pop();
catch (EmptyStackException s) {
// Стек пуст.
}
try
{
out.writelnt(n);.
}
catch (IOException e)
(
// Проблемы при записи данных в файл.
)
}
При таком подходе размер вашей программы стремительно увеличивается. Подумайте о задаче, которую должна решать ваша программа. В данном случае мы хотим вытолкнуть из стека 100 чисел и записать их в файл. (Неважно как — это всего лишь пример.) Само по себе возбуждение исключительной ситуации не решает проблем. Если стек пуст, он не заполнится по мановению волшебной палочки. Если при записи в файл возникает ошибка, она не исчезнет сама собой. Следовательно, имеет смысл поместить в блок try весь фрагмент программы. Если хотя бы одна из операций даст сбой, можно отказаться от решения всей задачи.
try .
for (i =0; i < 100; i++) {
n = s.pop();
out.writelnt(n);
} .
catch(lOException e)
// Проблемы при записи данных в файл.
)
catch(EmptySTackException e)
(
// Стек пуст.
)
Этот фрагмент намного яснее. Он выполняет одно из своих основных заданий — отделяет нормальную работу программы от обработки исключительных ситуаций.
Не заглушайте исключительные ситуации!
В языке Java существует большой соблазн заглушить исключительные ситуации. Допустим, вы пишете метод, вызывающий другой метод, который может
возбудить исключительную ситуацию один раз в сто лет. Компилятор пожалуется на это, поскольку вы не объявили эту исключительную ситуацию в списке throws вашего метода. Предположим, вам не хочется указывать ее в списке throws, так как компилятор начнет жаловаться на все методы, вызывающие ваш метод. В этом случае исключительную ситуацию можно просто заглушить.
public Image loadimage(String s)
{
try
{
Много разных операторов.
}
catch (Exception e)
{} //Воттак!
}
Теперь ваш код будет скомпилирован без заминок. Он будет прекрасно работать, но не в исключительной ситуации. Если она возникнет, то будет молча проигнорирована. Если вы считаете, что эта исключительная ситуация очень важна, приложите усилия для ее обработки.
Не стыдитесь передавать исключительные ситуации другим обработчикам!
Некоторые программисты считают себя обязанными перехватывать все исключительные ситуации, которые они возбуждают. Вызывая метод, возбуждающий некоторую исключительную ситуацию, например, конструктор класса FileInputstream или метод readLine, они инстинктивно перехватывают исключительные ситуации, которые могут быть сгенерированы ими. Зачастую оказывается предпочтительнее передать (propagate) исключительную ситуацию другому обработчику, а не обрабатывать ее самому.
public void readstuff(String name) throws IOEXception• •
{
FilelnputStream in = new FilelnputStream(name);
}
Методы более высокого уровня лучше информируют пользователя об ошибке или отказываюся выполнять неправильные операторы.
import java.util. *;
public class ExceptionalTest
{
public static void main(String[1 args)
{
int i = 0;
int ntry=1000000;
Stack s=newStack();
long si;
long s2;
// Проверка, пуст ли стек. Выполняется ntry раз.
System.out.printin("Проверка, пуст ли стек. ") ;
15.16.17.25.26.27.32.33.34.}15.16.17.25.26.27.32.33.34.}si = new Date () .getTime();
15.
16.
17.
25.
26.
27.
32.
33.
34.
}
15.
16.
17.
25.
26.
27.
32.
33.
34.
}
for (i = 0; i <= ntry; i++)
if (! s. empty ()) s.pop();
s2new Date().getTime();
System.out.println((s2 - si! + " миллисекунд");
// Вытолкнуть элемент из пустого стека ntry раз //и перехватить возникшую исключительную ситуацию.
System.out.printin("Перехват EmptyStackException");
si = new Date().getTime();
for (i = 0; i <= ntry; i++)
{
try
{
s.pop();
}
catch(EmptyStackException e)
{
}
}
s2 = new Date().getTime();
System.out.printin((s2 - si) + " миллисекунд");
)
Регистрация ошибок
Программисты часто прибегают к вызовам метода System. out .printinдля отладки ненадежного кода. Разумеется, после устранения ошибки следует убрать промежуточный вывод и поместить его в другое место для исправления новой ошибки. Для решения этой проблемы в пакете SDK 1.4 предусмотрены средства регистрации ошибок. В этом проявляется принципиальное преимущество интерфейса API над традиционными подходами.
Все регистрационные записи легко заблокировать и также легко разблокировать.
Заблокированные регистрационные записи занимают немного места и практически не влияют на эффективность приложения.
Регистрационные записи можно направить разным обработчикам, вывести на консоль, записать в файл и т.п.
Регистраторы и обработчики могут фильтровать записи. Фильтры отбрасывают ненужные записи, используя критерии, предложенные программистом.
Регистрационные записи можно форматировать. Например, их можно представить в виде простого текста либо в формате XML.
Приложения могут использовать несколько регистраторов, имеющих иерархи
ческие имена, например com.mycompany.myapp, подобно именам пакета.
По умолчанию параметры настройки средств регистрации задаются в файле конфигурации. Приложение может изменить этот способ задания параметров.
Простаярегистрация
Начнем с наиболее простого случая. Система регистрации управляет регистратором global, заданным по умолчанию. Получим ссылку на него, а затем вызовем метод info, logger.logger =;
logger.info("File->Open Menu item selected");
По умолчанию на экране появится следующая запись.
Feb 2, 2002 10:12:15 PM Loggingimageviewer fileOpen
INFO: File->Open menu item selected
(Обратите внимание на то, что время и имена вызываемых класса и метода включаются автоматически.) Однако, если в соответствующем месте (например, в начале метода ma in) вызвать метод logger.setLevel(Level.OFF),
запись будет заблокирована. Иными словами, отладочную информацию можно распечатать, просто вызвав метод Logger . getLogger ( "global") . info, не прибегая к методу Systsem.out.printin.
Сложная регистрация
Теперь, ознакомившись с "регистрацией для чайников", перейдем к промышленным средствам регистрации. В профессиональном приложении, как правило, все записи не концентрируются в одном глобальном регистраторе. Вместо этого можно определить свои собственные средства регистрации.
Регистратор создается, когда он вызывается по имени в первый раз.
Logger myLogger = Logger.getLogger("com.mycompany.myapp");
При последующих вызовах регистратора с тем же именем будет возвращаться тот же объект.
Как и имена пакетов, имена регистраторов образуют иерархию. Фактически они являются еще более иерархическими, чем имена пакетов. Между пакетом и его предком нет семантической связи, а регистратор и его дочерние классы обладают общими свойствами. Например, если в регистраторе "com. my company" задать уровень регистрации, то дочерний регистратор унаследует этот уровень.
Существует семь уровней регистрации.
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
По умолчанию регистрируются первые три уровня. Остальные уровни нужно задавать с помощью метода setLevel:
logger.setLevel(level.FINE) ;
Теперь будут регистрироваться все сообщения, начиная с уровня FINE и выше.
Кроме того, можно использовать константу Level.ALLдля включения регистрации всех уровней и константу Level. OFF для отключения регистрации.
Существуют методы регистрации, которые можно применять на всех уровнях, например, logger.warning(message);
logger.fine(message);
и т.д. Помимо этого, можно воспользоваться методом log и явно указать уровень: logger.log(Level.FINE, message);
По умолчанию регистрируются все записи, имеющие уровень INFO И выше. Следовательно, для отладочных сообщений, необходимых для диагностики программы, но совершенно не интересующих пользователя, следует использовать уровни CONFIG, FINE И FINEST.
Если уровень регистрации превышает значение INFO, необходимо изменить конфигурацию обработчика регистрации. По умолчанию обработчик регистрации блокирует сообщения, имеющие уровень ниже, чем INFO.
Запись регистрации, созданная по умолчанию, состоит из имени класса и метода, содержащего вызов регистратора. Однако, если виртуальная машина оптимизирует процесс выполнения, точная информация о вызываемых методах может стать недоступной. Для уточнения вызывающих класса и метода следует применять метод logp, имеющий следующую сигнатуру.
void logp(Level 1, String className, String methodName, String message)
Для трассировки выполнения программы существуют удобные методы.
void entering(String className, String methodName)
void entering(String className, String methodName,
Object param)
void entering(String className, String methodName,
Object[1 params]
void exiting(String className, String methodName)
void exiting(String className, String methodName,
Object result)
Рассмотрим пример.
int read(String file, String pattern)
{
entering("com.mycompany.nylib.Reader", "read", new Object(] { file, pattern });
exiting("com.mycompany.mylib.Reader", "read", count);
return count;
}
Эти вызовы генерируют записи регистрации, имеющие уровень FINER и начинающиеся строками ENTRY и RETURN.
Обычно регистрация используется для обработки неожиданных исключительных ситуаций. Есть два удобных метода, позволяющих вставить имя исключительной ситуации в регистрационную запись.
void throwing(String className, String methodName, Throwable t)
void log(Level 1, String message, Throwable t)
Эти методы, как правило, используются следующим образом.
if(...)
{
IOException exception = new lOException("...");
throwning("com.mycompany.mylib.Reader", "read", exception);
throw exception;
)
и try (
}
catch (lOException e)
{
Logger. getLogger ("com.mycompany.myapp",. 1og (Level. WARN ING,
}"reading image", e);
Вызов метода throwning регистрирует запись, имеющую уровень FINER, и сообщение, начинающееся словом THROW.
Настройка менеджера регистрации
Свойства системы регистрации можно изменять, редактируя файл конфигурации. По умолчанию этот файл находится по адресу
jre_directory/lib/Iggin.properties
Если вы хотите использовать другой файл, нужно при запуске приложения задать опцию java.util.logging.config.file:
java -Djava.util.logging.config.file=file MainClass
Вызов метода System.setproperty(java.util.logging.config, file, file) из метода main ничего не дает, поскольку менеджер регистрации инициализируется при запуске виртуальной машины, еще до начала выполнения методаmain.
Чтобы изменить уровень регистрации, принятый по умолчанию, нужно отредактировать файл конфигурации, изменив строку
. level=!NFO
В собственных регистраторах уровни регистрации задаются с помощью строк наподобие следующей
com.mycompany.myapp.level=FINE
Иначе говоря, нужно добавить к имени регистратора суффикс . level.
Как мы увидим в дальнейшем, регистраторы на самом деле не посылают сообщения на консоль — это забота обработчиков. Обработчики также имеют уровни. Чтобы
увидеть на консоли сообщения, имеющие уровень FINE, необходимо задать соответствующий параметр:
java.util.logging.ConsoleHandler.level=FINE
Параметры настройки менеджера регистрации не являются системными свойствами. Запуск программы с опциями -Doom.mycompany.myapp. level=FINE никак не отражается на регистраторе.
Файл, содержащий свойства системы регистрации, обрабатывается классом java.util .logging.LogManager. Задав в качестве системного свойства java . util.logging .manager имя подкласса, можно указать другой менеджер регистрации. Кроме того, можно оставить стандартный менеджер регистрации, проигнорировав параметры, заданные в файле свойств системы регистрации. Для этого нужно задать в качестве системного свойства java.util.logging.config. class имя подкласса, который задает другие параметры менеджера регистрации. Детальное описание класса LogManager содержится в документации по интерфейсу API.
Локализация
Иногда возникает необходимость локализовать регистрационные сообщения, чтобы они стали понятны пользователям в других странах. Интернационализация приложений обсуждается в главе 10 тома 2. Короче говоря, существуют проблемы, которые следует иметь в виду при локализации регистрационных сообщений.
Информация, необходимая для локализации приложения, содержится в пакетах ресурсов (resource bundles). Пакет ресурсов состоит из совокупности таблиц, предназначенных для локализации приложения в разных странах (например, в США или Германии). Каждая таблица определяет соответствие между именами и объектами. Например, пакет ресурсов может отображать строку "reading_file" в строку "Reading file" или "Achtung! Datei wird eingelesen."
Программа может содержать несколько пакетов ресурсов, например, один для меню, а другой — для регистрационных сообщений. Каждый пакет ресурсов имеет имя (например "log-messages"). В главе 10 тома 2 показано, как подготовить пакеты ресурсов и присоединить их к своей программе.
При вызове регистратора можно указать конкретный пакет ресурсов.
Logger logger = Logger.getlogger(loggerName, bundlename);
Затем для создания регистрационного сообщения нужно задать ключ пакета ресурсов (но не фактическую строку).
logger.info("reading_file");
Часто возникает необходимость включать в локализованные сообщения какие-либо аргументы. В таком случае сообщение должно иметь заполнители (0}, {1) и т.д. Например, может потребоваться включить имя файла в регистрационное сообщение "reading file". Тогда локализованные сообщения могли бы выглядеть следующим образом.
Reading file{0};
Achtung: Datei {о) wird eingelesen.
Более подробную информацию о заполнителях можно найти в главе 10 тома 2 или в описании классаMessage Format, содержащемся в документации по интерфейсу API.
Существуют удобные методы log, получающие один параметр или целый массив параметров.
logger.log(Level.INFO. "reading_file", filename);
logger.log(Level.INFO, "readingfile",
new Object[] { oldname, newName });
В заключение упомянем о совокупности методов iogrb, которые позволяют явно задавать имена пакетов ресурсов. Эти методы могут понадобиться при распределении локализованных строк по разным пакетам ресурсов.
logger.logrb(Level.INFO, "com.mycompany.mylib.Reader", "read", "log_messages", "reading_file", fileName);
Обработчики
По умолчанию регистраторы посылают сообщения методу ConsoleHandler, который выводит их в поток System.err. Точнее говоря, регистраторы посылают запись предку обработчика, а его последний наследник (с именем " ") содержит метод ConsoleHandler.
Как и регистраторы, обработчики имеют уровни. Уровень регистрации записи, подлежащей выводу, должен превышать пороговое значение и регистратора, и обработчика. По умолчанию уровень консольного обработчика задается в файле, содержащем параметры настройки менеджера регистрации:
java.util.logging.ConsoleHandler.level=INFC
Для регистрации записи, имеющей уровень FINE, необходимо изменить в файле конфигурации как уровень регистрации, так и уровень обработчика, предусмотренные по умолчанию. Кроме того, можно совсем проигнорировать файл конфигурации и установить свой собственный обработчик.
Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);.
logger.addHandler(handler);
По умолчанию регистратор посылает сообщения своему обработчику и его предку. Наш регистратор является наследником первоначального регистратора (имеющего имя "") , посылающего на консоль все записи уровня INFO и выше. Однако никому не нужно, чтобы все регистрационные записи выводились на экран дважды, поэтому свойство useParentHandlers следует задать равным false.
Для того чтобы послать регистрационную запись еще куда-нибудь, нужно добавить другой обработчик. С этой целью система регистрации предоставляет в наше распоряжение два полезных обработчика: FileHandler и SocketHandler. Обработчик SocketHandler посылает сообщения на указанные главный компьютер или порт. Наибольший интерес для нас представляет обработчик FileHandler, собирающий записи в файле.
Можно просто послать записи файловому обработчику, заданному по умолчанию. FileHandler handler = new FileHandler();
logger.addHandler(handler);
Записи посылаются в файл javan. log, находящийся в домашнем каталоге пользователя. Число л обеспечивает уникальность этого файла. Если платформа не использует концепцию домашнего каталога пользователя (например Windows 95), файл записывается в каталог, предусмотренный по умолчанию, например, в каталог С: \Windows. Записи хранятся в формате XML. Обычная регистрационная запись имеет такой вид. <record>
<date> 2002-02-04T07:45:15</date>
<millis>10128 37515710</millis>
<sequence>l</sequence>
<1ogger>com.mycompany.myapp</1ogger>
<level<INFO</level>
<class>com.mycompany.mylib.Reader</class>
<method>read</method>
<thread>10</thread>
.cmessage.reading file corejava.gif</message?
</record>
Работу файлового обработчика, определенную по умолчанию, можно изменить, задав другие параметры настройки менеджера регистрации (см. табл. 11.2) или используя другой конструктор (см. замечания об интерфейсе API, помещенные в конце этого раздела).
Программисты, как правило, не желают использовать имя файла регистрации, предлагаемое по умолчанию. Следовательно, нужно задать другой шаблон, например %h/myapp. log. (Смысл переменных шаблона раскрывается в табл. 11.3.)
Если несколько приложений (или несколько копий одного и того же приложения) используют один и тот же файл регистрации, его флажок "append" следует установить в положение "on". Кроме того, можно использовать шаблон имени файла %u так, чтобы каждое приложение создавало уникальную копию файла регистрации.
Хорошей идеей является также вращение имен файла по кольцу. В этом случае файлы регистрации образуют кольцо, например, myapp.log. О, myapp.log.1, myapp.log. 2 и т.д. Как только файл превысит допустимый размер, наиболее старый файл регистрации удаляется, а остальные файлы переименовываются, и новый файл получает имя с номером 0.
Многие программисты используют регистрацию как дополнительную помощь службе технической поддержки. Если программа начинает сбоить, пользователь может вернуть файл регистрации для проверки. В этом случае следует установить флажок "append" в положение "on" либо применить кольцо файлов, или и то, и другое одновременно.
Параметр настройки
ОписаниеПараметр по умолчанию
java.util.logging. FileHandler.level
Уровень обработчикаLevel.ALL
java.util.logging. FileHandler.append
Устанавливает, должен ли об-false
работчик добавлять записи в существующий файл или открывать новый файл при каждом новом запуске программы
Окончание табл. 11.2
Параметр настройки
Описание
Параметр по умолчанию
java.util.logging. FileHandler.limit
Приблизительное минимальное количество байтов, которые следует записать В файл перед открытием другого файла
(О = неограниченное количество)
В классе FileHandler — 0 (неограниченное количество), в конфигурации менеджера регистрации, предусмотренного по умолчанию — 50000
java.util.logging. FileHandler.pattern
Шаблон имени файла регистрации (см. табл. 11.3)
%h/java%u.log
java.util.logging. FileHandler.count
Количество файлов регистрации в кольце
1 (кольцо не используется)
j
FileHandler.filter
Используемый фильтр
Без фильтрации
java.util.logging. FileHandler.encoding
Используемая кодировка символов
Кодировка, принятая в текущей операционной системе
java.util.logging.
FileHandler.formatter
Средство форматирования записей
j ava.util.logging. XMLformatter
Можно также определить свои собственные обработчики, заместив классы Handler или StreamHandler. Такой обработчик будет определен в демонстрационной программе, приведенной в конце раздела. Этот обработчик выводит записи в окно на экране (см. рис. 11.4).
Переменная
Описание
% h
Значение системного свойстваuser. home
% t
Временный системный каталог
%u
Уникальный номер, позволяющий избежать конфликта имен
% g
Номер в кольце файлов регистрации (используется, если задано вращение, а шаблон не содержит суффикс %д)
% %
Символ %
Обработчик расширяет класс StreanHandler и инсталлирует поток, содержащий метод write, который выводит информацию в текстовую область.
class WindowHandler extends StreamHandler {
public WindowHandler()
1
final JTextArea output = new JTextArea();
setoutputstream(new
Outputstream()
public void write(int b)
{
output.append("" + (char)b);
}
public void write(byte[] b, int off, int len) (
output.append(new String(b, off, len));
}
});
66HRBHRBJ"'1™ кв ENTRJ"'1™ кв ENTRmIageVeiwerFrameImageVei ImageVeiwerFrameImageVeimIageVeiwerFrameImageVei ImageVeiwerFrameImageVeiЗОЛОЛ ] 16 У 47 LoaoingimaaeViewei maniЗОЛОЛ ] 16 У 47 LoaoingimaaeViewei manifinefineINER: RETURN 5 'INER: RETURN 5 'java awl event AtbonEvenQACTlON. erframeSFileOpejava awl event AtbonEvenQACTlON. erframeSFileOpeРис. 11.3. Обработчик регистрации, выводящий записи в окно на экранеРис. 11.3. Обработчик регистрации, выводящий записи в окно на экране
6
6
HRB
HRB
J"'1™ кв ENTR
J"'1™ кв ENTR
mIageVeiwerFrameImageVei ImageVeiwerFrameImageVei
mIageVeiwerFrameImageVei ImageVeiwerFrameImageVei
ЗОЛОЛ ] 16 У 47 LoaoingimaaeViewei mani
ЗОЛОЛ ] 16 У 47 LoaoingimaaeViewei mani
fine
fine
INER: RETURN 5 '
INER: RETURN 5 '
java awl event AtbonEvenQACTlON. erframeSFileOpe
java awl event AtbonEvenQACTlON. erframeSFileOpe
Рис. 11.3. Обработчик регистрации, выводящий записи в окно на экране
Рис. 11.3. Обработчик регистрации, выводящий записи в окно на экране
С этим подходом связана лишь одна проблема — обработчик помещает записи в буфер и выводит их в поток только тогда, когда буфер переполняется. Следовательно, нужно заместить метод publish, чтобы очистить буфер после вывода очередной записи.
class WindowHandler extends StreamHandler
public void publish(LoigRecord record)
(
super.publish(record; ;
flush();
Logger.getLogger("global").fine("publish");
)
При желании можно написать более экзотические обработчики. Для этого достаточно расширить класс Handler и определить методы publish, flushи close.
Фильтры
По умолчанию записи фильтруются в соответствии со своими уровнями регистрации. Каждый регистратор и обработчик может иметь свой фильтр, выполняющий дополнительную фильтрацию. Для этого нужно реализовать интерфейс Filter и определить метод
boolean isloggable(LogRecord record)
Этот метод анализирует записи регистрации, используя любой указанный критерий, и возвращает значение true для тех записей, которые нужно поместить в файл регистрации. Например, фильтр может пропускать только записи, созданные методами entering и exiting. Затем фильтр должен вызвать метод record. getMessage () и проверить, начинается запись со слов ENTRY или RETURN.
Для того чтобы задать фильтр в регистраторе или обработчике, просто вызовите метод setFilter. Обратите внимание на то, что в каждый момент времени в вашем распоряжении может быть только один фильтр.
Средства форматирования
Классы ConsoleHandler и FileHandler порождают записи регистрации в текстовом и XML-форматах. Однако существует возможность определить свой собственный формат. Для этого необходимо расширить класс Formatter и заместить метод String format(logRecord record)
Затем следует форматировать запись любым выбранным способом и вернуть получившуюся строку. В методе format может потребоваться вызов метода
String formatMessage(LogRecord record)
Этот метод форматирует сообщение, содержащееся в регистрационной записи, заменяя параметры и применяялокализацию.
Многие форматы файлов (например XML) предполагают существование заголовка и хвоста файла, охватывающих форматированные записи. В этом случае следует заместить методы
String getHead(Handler h)
String getTail(Handler h)
В заключение следует вызвать метод setFormatter, инсталлирующий средства форматирования для обработчика.
Имея столько возможностей для регистрации, легко запутаться. Врезка "Рецепты регистрации" позволит вам разобраться в основных операциях.
Рецепты регистрации
Для простых приложений выбирайте один регистратор. Хорошо, если имя регистратора совпадает с именем основного пакета, например com.mycompany .myprog. Создать регистратор можно с помощью вызова
Logger.getLogger("com.mycompany.myprog");
Для удобства можно добавить в классы, интенсивно использующие регистрацию, статические поля
private static final Logger logger =
По умолчанию все сообщения, имеющие уровень INFO и выше, выводятся на консоль. Пользователи могут заместить конфигурацию, предусмотренную по
значению, но, как мы уже видели, это довольно сложный процесс. Следовательно, лучше задать более разумные параметры приложения.
Приведенный ниже код гарантирует, что все сообщения будут зарегистрированы в файле, связанном с конкретным приложением. Поместите его в метод main вашего приложения.
if (System.getProperty("java.util.loging.config.class")== null
&& (System.getProperty("java.util.loging.config.file")== null
{
try
{
Logger.getlogger ("").setLevel(Level.ALL);
final int LOG_ROTATION_COUNT = 10;
Handler handler = new FileHandler(%h/myapp.log,
0, LOG_ROTATION_COUNT);
Logger.getLogger("").addHandler(handler) ;
}
catch (IOException exception)
{
Logger.getLogger('com.mycompany.myprog").log(
Level.SEVERE, "Can* create log file handler", exception);
Теперь мы готовы регистрировать сообщения. Помните, что все сообщения, имеющие уровень INFO, WARNING и SEVERE, выводятся на консоль. Следовательно, эти уровни нужно зарезервировать для сообщений, имеющих ценность для пользователей вашей программы. Уровень FINE лучше присвоить сообщениям, предназначенным для программистов. Там, где вы обычно использовали метод System.out.println, регистрируйте сообщения:
Logger.getLogger("com.mycompany.myprog").fine(
File open dialog canceled");
Следует регистрировать также неожиданные исключительные ситуации, например, так.
try
{
i
catch (SomeException exception)
Logger.getlogger(1com.mycompany.myprog".log(Level.FINE, "explanation", exception);
}
Рассмотрим код, регистрирующий события, происходящие при работе программы, предназначенной для просмотра изображений.
14. 14.
14.
14.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.logging.*;
import javax.swing.*;
/**
Модификация программы для просмотра изображений, регистрирующая различные события.
*/
public class LogginglmageViewer 1
public static void main(String[; args) {
if (System.getProperty|
"java.util.logging.config.class") == null
ss System.getProperty(
"java.util.logging.config.file") == null) {
try (
Logger.getLogger("").setLevel(Level.ALL);
final int LOG_ROTATION_COUNT = 10;
Handler handier = new FileHandler(
"%h/LoagingImageViewer.log", 0, LOG_ROTATION_COUNT);
Logger.getLogger("").addHandler(handler);
)
catch (lOException exception) (
Logger.getLogger("com.horstmann.corejava").log Level.SEVERE,
"Can't create log file handler", exception);
}~
Handler windowHandler = new WindowHandler();
windowHandler.setLevel(Level.ALL);
Logger.getLogger("").addHandler(windowHandler);
JFrame frame = new ImageViewerFrame();
frame.setTitie("Logging Imageviewer") ;
frame.setSize(300, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Logger.getLogger("com.horstmann.corejava").fine(
"Showing frame");
frame.show();
)
/**
Фрейм для просмотра изображения
*/
class ImageViewerFrame extends JFrame
58. { 59'. 60.61.67.74.77.78.79.80.81.82.85.90.91.92.93.94.95.96.97.98.99.105.106.107.108.58. { 59'. 60.61.67.74.77.78.79.80.81.82.85.90.91.92.93.94.95.96.97.98.99.105.106.107.108.public ImageViewerFrame()
58. { 59'. 60.
61.
67.
74.
77.
78.
79.
80.
81.
82.
85.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
105.
106.
107.
108.
58. { 59'. 60.
61.
67.
74.
77.
78.
79.
80.
81.
82.
85.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
105.
106.
107.
108.
{
logger.entering("ImageViewerFrame", "ImageViewerFrame"); // Устанавливаем меню
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu menu = new JMenuC'File");
menuBar.add(menu);
JMenuItem openitem = new JMenuItem("Open");
menu.add(openitem);
openitem.addActionListener(new FileOpenListener());
JMenuItem exititem = new JMenuItem("Exit");
menu.add(exitItem);
exititem.addActionListener(new
ActionListener()
{
public void actionPerformed(ActionEvent event) {
logger.fine("Exiting.");
System.exit(0);
} });
// Для вывода изображения используется метка label = new JLabeK) ;
Container contentPane = getContentPane();
contentPane.add(label);
logger.exiting("ImageViewerFrame", "ImageViewerFrame");
}
private class FileOpenListener implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", evt);
// Устанавливаем диалоговое окно для выбора файлов. JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));
// Пропускаем только файлы с расширением .gif. chooser.setFileFilter(new
javax.swing.filechooser.FileFiIter()
{ public boolean accept(File f) {
return f.getName().toLowerCase()
. endsWithC.gif"]
I I f.isDirectory(); }
public String getDescription(; {
return "GIF Images"; }
});
152.152.// Открываем диалоговое окно для выбора файлов.
152.
152.
int r = chooser.showOpenDialog(ImageViewerFrame.this);
// Если файл выбран, устанавливаем пиктограмму на метку. if(r == JFileChooser,APPROVE_OPTION]
{ String name
=
logger.log(Level.FINE, "Reading file <0}", name); label.seticon(new Imageicon(name));
} else
logger.fine("File open dialog canceled.");
logger.exiting("ImageViewerFrame.FileOpenListener", "actionPerformed");
} }
private JLabel label;
private static Logger logger =
}
/**
Обработчик, выводящий регистрационные записи в окно.
*/
class WindowHandler extends StreamHandler {
public WindowHandler() {
JFrame frame = new JFrame();
final JTextArea output = new JTextArea();
frame.setSize(200, 200);
frame.setcontentPane(new JScrollPane(output));
frame.show ();
setOutputStream(new
OutputStream(] {
public void write(int b) f
output.append("" + (char)b);
}
public void write(byte[] b, int off, int len) {
output.append(new String(b, off, len));
} }); }
public void publish(LogRecord record) {
super.publish(record);
flush();
}
}
java.util.logging.Logger 1.4
Logger getLogger (String loggerName)
Logger getLogger(String loggerName, String bundleName)
Возвращают регистратор с указанным именем. Если регистратор не существовал, он создается.
Параметры: loggerName Иерархическое имя регистратора, например com.mycompany.myapp.
bundleName Имя пакета ресурсов для просмотра локализованных сообщений.
void severe(String message)
void warning(String message)
void info(String message)
void config(String message)
void fine(String message)
void finer (String message)
void finest(String message)
Регистрируют запись, имеющую уровень, указанный в имени метода, и содержащую заданное сообщение.
void entering(String className, String methodName)
void entering(String className, String methodName, Object param)
void entering(String className, String methodName, Object [] param)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
void exiting(String className, String methodName, Object [] result)
Регистрируют запись, описывающую вход и выход из метода, имеющую заданные параметры или возвращаемое значение соответственно.
void throwing(String className,StringmethodName,
Throwable t)
Регистрирует запись, описывающую процесс генерации объекта заданной исключительной ситуации.
void log(Level level, String message)
void log(Level level, String message, Object ob j)
void log(Level level, String message, Object[] obj)
void log(Level level, String message, Throwable t) Регистрируют запись, имеющую заданный уровень и содержащую указанное сообщение. Кроме того, запись может содержать обычные объекты или объекты исключительных ситуаций. Для этого в сообщение могут включаться форматированные символы-заполнители (0), (1) и т.д.
void logp(Level level, String className, String methodName, Stringmessage)
void logp(Level level, String className, String methodName, Stringmessage,
Object ob j;
void logp(Level level, String className, String methodName, Stringmessage,
Object[] objs)
void logp(Level level, String className, String methodName, Stringmessage,
Throwablet)
Регистрируют запись, имеющую заданный уровень и содержащую указанное сообщение. Кроме того, запись может содержать обычные объекты или объекты исключительных ситуаций.
void logrb(Level level, String className, String methodName, String bundleName, String message)
void logrb(Level level, String className, String methodName, String bundleName, String message, Object obj;
void logrb(Level level, String className, String methodName, String bundleName, String message, Object [] objs)
void logrb(Level level, String className, String methodName, String bundleName, String message, Throwable t)
Регистрируют запись, имеющую заданный уровень и содержащую указанное сообщение. Кроме того, запись может содержать обычные объекты или объекты исключительных ситуаций.
.• Level getLevel()
Эти методы возвращают и устанавливают уровень указанного регистратора.
Logger getParent()
void setParent(Logger 1)
Эти методы возвращают и устанавливают базовый регистратор для указанного регистратора.
Handler[] getHandlers()
Возвращает все обработчики указанного регистратора.
void addHandler(Handler h)
void removeHandler(Hadnler h)
Этиметодыдобавляютиудаляютвсеобработчикиуказанногорегистратора.
boolean getUseParentHandlers(I
void setUseParentHandlers(boolean b)
Этиметодывозвращаютиустанавливаютсвойство "использоватьбазовый об- работчик".Еслизначениеэтогосвойстваравноtrue,регистраторнаправляет все зарегистрированные записи обработчикам своего базового регистратора.
Filter getFilter()
void setFilter(Filter f)
Эти методы возвращают и устанавливают фильтры для указанного регистратора.
java.util.logging.Handler 1.4
abstract void publish(LogRecord record) Посылает сообщение no указанномуадресу.
abstract void flush(I Очищаетбуферданных. abstract void close()
Очищает буфер данных и освобождает все связанные ресурсы.
Filter getFilter() void setFilter(Filter f)
Эти методы возвращают и устанавливают фильтры для указанного обработчика.
Formatter getFormatter()•
void setFormatter(Formatter f)
Эти методы возвращают и устанавливают средства форматирования для указанного регистратора.
Level getlevel()
void setLevel(Level 1)
Эти методы возвращают и устанавливают уровень указанного регистратора.
pT^ java.util.logging.ConsoleHandler 1.4 e ConsoleHandler(]
Создает новый консольный обработчик.
|a pT^java.util.logging.FileHandler 1.4
~ ■< » FileHandler (String pattern)
FileHandler(String pattern, boolean append)
FileHandler(String pattern, int limit, int count)
FileHandler(String pattern, int limit, int count, boolean append)
Создают файловый обработчик.
Параметры: pattern
Шаблон для создания имени файла регистрации (см. табл. 11.3).
limit
Приблизительное минимальное количество байтов, которое следует записать в файл регистрации перед открытием следующего.
count
Количество файлов в кольце.
append
Принимает значение true, если вновь созданный объект файлового обработчика следует добавить в существующий файл регистрации.
java.uti1.logging.LogRecord 1.4
Level getLevelO
Возвращает уровень регистрации, установленный для данной записи.
String getLoggerName(J
Возвращает имя регистратора данной записи.
ResourceBundle getResourceBundle()
String getResourceBundleName(]
Возвращают пакет ресурсов, предназначенный для локализации сообщения, или его имя. Если пакет не предусмотрен, возвращает ссылку null.
String getMessage('
Возвращает "первичное" сообщение, не прошедшее локализацию и форматирование.
Object[] getParameters(]
Возвращает параметры объектов. Если объектов нет, возвращает ссылкуnull.
Throwable getThrown()
Возвращает объект возбуждаемой исключительной ситуации или ссылку null, если объект не существует.
String getSourceClassName()
String getSourceMethodName()
Определяют местоположение кода, зарегистрировавшего данную запись. Эта информация может быть предоставлена кодом регистрации или автоматически извлечена из стека выполнения программы. Если код регистрации содержит неверное значение, или программа была оптимизирована так, что определить точное местоположение кода невозможно, информация может оказаться неточной.
long getMillis()
Определяет время создания записи в миллисекундах, считая от 1970 года.
long getSequenceNumber()
Возвращает уникальный номер записи.
int getThreadID()
Возвращает уникальный идентификационный номер потока (thread), в котором была создана запись. Эти идентификационные номера присваиваются классом LogRecord и не имеют отношения к другим идентификационным номерам потоков.
java.util.logging.Filter1.4
boolean isLoggable(LogRecord record)
Если указанная запись должна быть зарегистрирована, возвращает значение true.
java.util.logging.Formatter 1.4
abstract String format(LogRecord record)
Возвращает строку, полученную в результате форматирования регистрационной записи.
String getHead(Handler h)
String getTail(Handler h)
Возвращают строки, которые должны появиться в заголовке и в конце документа, содержащего регистрационную запись. Эти методы определены в суперклассе Formatter и по умолчанию возвращают нулевую строку. При необходимости их следует заместить.
String format(LogRecord record)
Возвращает локализованное и форматированное сообщение, содержащееся в регистрационной записи.
Диагностические утверждения
Диагностические утверждения (assertions) — это широко распространенные идиомы, применяемые для обеспечения надежности программирования. Допустим, вы убеждены в том, что конкретное свойство уже задано, и ссылаетесь на него в своей программе. Например, рассмотрим оператор double у = Math.sqrt(x);
Вы уверены, что число х — неотрицательное? Возможно, оно получено в результате других вычислений, которые не могут порождать отрицательные числа, либо является параметром метода, которому можно передавать лишь положительные числа. Следует дважды перепроверить свои предположения, чтобы в ходе выполнения программы не возникло непоправимой ошибки. Разумеется, можно было бы просто возбудить исключительную ситуацию.
if (х < 0) throw new IllegalArgumentException("x < 0 ");
Однако у этого кода есть досадный недостаток — он остается в программе, даже если тестирование давно закончено. Если в тексте программы расставлено много таких проверок, ее выполнение может замедлиться.
Механизм диагностических утверждений позволяет вставлять в текст программы проверки, предназначенные для ее тестирования, а затем удалять их из окончательного варианта.
Начиная с пакета SDK 1.4, в языке Java появилось новое ключевое слово — assert. Оно имеет две формы: assert условие;
и
assert условие : выражение;
Оба оператора вычисляют некое условие и возбуждают исключительную ситуацию AssertionError, если оно не выполняется. Во втором операторе выражение передается конструктору объекта класса AssertionErrorи превращается в строку сообщения.
Единственная цель выражения— создать строку сообщения. Объект класса AssertionError не хранит фактическое значение выражения, поэтому его невозможно использовать в дальнейшем. В документации, сопровождающей пакет SDK 1.4, с искренней отеческой заботой о пользователях указывается, что это сделано для того, чтобы "утешить программистов, если диагностическое утверждение окажется неверным".
Чтобы проверить, является ли значение х неотрицательным числом, можно просто использовать оператор assert х >= 0;
Можно также передать фактическое значение переменной х объекту класса AssertionError, чтобы он впоследствии вывел его на экран.
assert х >= 0 : х;
Макрос assert в языке С превращает проверяемое условие в строку, которая выводится на экран, если оно не выполняется. Например, если выражение ь.assert (x>=0) имеет значение false, на экран выводится строка, сообщающая, что
условие "х >= о " не выполняется. В языке Java условие не является неотъемлемой частью сообщения об ошибке. Если вы хотите увидеть его, передайте его в виде строки объекту классаAssertionError: assert x >= 0 : "х >= О".
Поскольку ключевое слово assert появилось совсем недавно, необходимо научить компилятор использовать преимущества, которые оно предоставляет. Для этого необходимо указать опцию -source 1.4, например:
javac -source 1.4 MyClass.class
Разумеется, в следующих версиях пакета SDK диагностические утверждения будут поддерживаться по умолчанию.
Подключение и отключение диагностических утверждений
По умолчанию диагностические утверждения считаются заблокированными. Разблокировать их в ходе выполнения программы можно с помощью опции -enableassertions или -еа.
java -enableassertions MyApp
Обратите внимание на то, что для подключения и отключения диагностических утверждений не нужно заново компилировать программу. Это — задача загрузчика классов. Если диагностические утверждения заблокированы, загрузчик классов игнорирует их код, чтобы не замедлять выполнение программы.
Диагностические утверждения можно включать даже в отдельные классы и целые пакеты.
java -ea:MyClass -ea:com.mycompany.mylib ... MyApp
Эта команда включает диагностические утверждения в класс MyClass, а также во все классы пакета com.mycompany.mylib и его подпакетов. Опция -еа подключает диагностические утверждения во все классы пакета, предусмотренного по умолчанию.
Кроме того, можно отключить диагностические утверждения в определенных классах и пакетах. Для этого достаточно задать опции -diableassertions или -da. java -ea:... -da:MyClass MyApp
Некоторые классы загружаются не загрузчиком классов, а непосредственно виртуальной машиной. Для выборочного включения или отключения диагностических утверждений, содержащихся в этих классах, также можно использовать переключатели -еа и -da. Однако эти опции, включающие и отключающие все диагностические ут-
верждения одновременно, не могут применяться к "системным классам" без помощи загрузчика классов. Для включения диагностических утверждений, находящихся в системных классах, следует использовать опцию -enablesystemassertions/-esa.
Можно даже управлять состоянием диагностических утверждений с помощью программных средств. Детали изложены в замечаниях по интерфейсу API в конце раздела.
Использование диагностических утверждений для проверки предусловий
В языке Java есть три механизма обработки системных сбоев.
Возбуждение исключительных ситуаций.
Регистрация ошибок.
Диагностические утверждения.
Когда следует применять диагностические утверждения? Чтобы правильно ответить на этот вопрос, учтите следующее.
Диагностические утверждения оказываются ложными, если произошла непоправимая ошибка.
Диагностические утверждения включаются только на период отладки и тестирования программы. (Иногда это шутливо формулируют так: "одевайте спасательный жилет, плавая у берега, — в открытом море он вам не понадобится".)
Итак, диагностические утверждения не следует применять в качестве индикаторов несущественных и легко исправимых ошибок. Они предназначены для локализации серьезных неполадок во время тестирования.
Представим себе следующий сценарий — проверку параметров метода. Следует ли применять диагностические утверждения для проверки выхода индекса за пределы допустимого диапазона или для распознавания нулевой ссылки? Чтобы ответить на этот вопрос, необходимо обратиться к документации, описывающей данный метод. Например, рассмотрим методArray. sort из стандартной библиотеки.
/**
Упорядочивает указанный отрезок заданного массива
в порядке возрастания. Упорядочиваемый отрезок начинается
•с индекса fromlndex включительно и заканчивается индексом tolndex исключительно.
@param упорядочиваемый массив
@param fromlndex индекс первого элемента (включительно) упорядочиваемого отрезка
Oparam tolndex индекс последнего элемента (исключительно) упорядочиваемого отрезка
§throws IllegalArgumentException, если fromindex > tolndex @throws ArraylndexOutOfBoundsException, если fromlndex < 0 или tolndex > a.length
*/
static void sort []static void sort []a, int fromlndex, int tolndex
static void sort []
static void sort []
В документации утверждается, что этот метод возбуждает исключительную ситуацию, если значение индекса задано некорректно. Это поведение метода является частью контракта, который он заключает с вызывающим модулем. Если вы реализуете этот метод, то должны придерживаться контракта и возбуждать соответствующую исключительную ситуацию. В этом случае диагностические утверждения не подходят.
Следует ли применять их для распознавания нулевой ссылки? Как и прежде, ответ отрицательный. В документации о методе ничего не сказано о его реакции, если параметр а равен константе null. Вызывающий модуль имеет основания предполагать, что в этом случае метод будет успешно выполнен и не будет возбуждать исключительную ситуацию AssertionError.
Допустим, однако, что контракт метода слегка изменен.
@param а упорядочиваемый массив. (Не должен равняться null.)
Теперь вызывающий модуль знает, что передавать нулевую ссылку данному методу запрещено. Следовательно, метод может начинаться с диагностического утверждения assert(a != null);
Специалисты по компьютерным наукам называют такой контракт предусловием (precondition). Исходный метод не накладывал на свои параметры никаких предварительных условий — он обещал хорошо работать во всех случаях. Модифицированный метод имеет одно предусловие— параметр а не должен принимать значение null. Если вызывающий модуль нарушает указанное предусловие, то контракт аннулируется и метод отказывается выполнять работу. Фактически, имея диагностическое утверждение, при неправильном вызове метод может повести себя непредсказуемо. Иногда он возбуждает исключительную ситуацию assertionError, а иногда— NullPointerexception, в зависимости от конфигурации загрузчика классов.
При передаче параметра null метод Array. sort из пакета SDK 1.4 возбуждает исключительную ситуацию NullPointerException. Ошибка содержится либо в документации, либо в реализации.
Для документирования диагностических утверждений программисты часто используют комментарии.
if (i % 3 == 0)
else if (i % 3 == 1)
else // (i % 3 == 2)
В данном случае намного разумнее использовать диагностические утверждения.
if (i % 3 == 0)
else if (i % 3 == 1)
else
assert(i % 3 == 2)
Разумеется, было бы еще более разумно тщательнее сформулировать проверки. Какие значения может принимать выражение i % 3? Если переменная i положи-
тельна, остаток может равняться 0, 1 или 2. Если отрицательна — остаток принимает значения -1 или -2. Таким образом, намного логичнее предположить просто, что переменная i не является отрицательным числом. Итак, более осмысленное диагностическое утверждение имеет следующий вид assert (i >= 0);
Его следует разместить перед оператором if.
Во всяком случае этот пример показывает образец правильного использования диагностических утверждений в качестве средства для самопроверки. Итак, диагностические утверждения — это тактическое средство, предназначенное для тестирования и отладки, а регистрация— стратегический механизм, сопровождающий программу на протяжении всего ее существования.
java.lang.ClassLoader1.0
void setDefaultAssertionStatus(boolean b)1.4
Включает и отключает диагностические утверждения во всех классах, загруженных указанным загрузчиком классов, в которых статус диагностических утверждений не задан явно.
void setClassAssertionStatus(String className, boolean b)1.4
Включает и отключает диагностические утверждения в заданном классе и его подклассах.
void setPackageAssertionStatus(String packageName, boolean b)1.4
Включает и отключает диагностические утверждения во всех классах заданного пакета и его подпакетов.
void clearAssertionStatus(;1.4
Удаляет все диагностические утверждения, имеющие явно заданный статус, и блокирует все диагностические утверждения во всех классах, загруженных данным загрузчиком классов.
Приемы отладки
Допустим, что вы написали программу и оснастили ее средствами для перехвата и соответствующей обработки исключительных ситуаций. Затем вы запускаете ее на выполнение, и она работает неправильно. Что теперь? (Если перед вами никогда не возникала такая проблема, можете не читать остальную часть главы.)
Разумеется, лучше всего было бы иметь под рукой удобный и мощный отладчик. Такие отладчики являются частью профессиональных сред программирования, например, JBuilder компании Inprise, СаГё компании Symantec или Sun ONE Studio компании Sun. Однако при работе с новой версией языка Java, который еще не поддерживается этими интегрированными средами программирования, а также при отсутствии средств или при работе на непривычной платформе приходится уделять отладке очень много времени, расставляя по всей программе промежуточные операторы вывода.
Полезные приемы отладки
Если вам самому придется заняться отладкой, воспользуйтесь нашими советами.
Значение любой переменной можно вывести на печать с помощью следующего оператора.
System.out.printin("x = " + х) ;
Если переменная х — число, она преобразуется в эквивалентную строку. Если переменная х— объект, то вызывается метод toString. В большинстве классов языка Java метод toString замещается таким образом, чтобы предоставить пользователю как можно больше полезной информации. Это очень удобно для отладки. В своих классах следует самому позаботиться об этом.
Для того чтобы получить состояние текущего объекта, примените к объекту this следующий метод.
.' System, out .printin! "Ввод loadimage.this = " + this);
Этот оператор вызовет метод toString из текущего класса, а вы получите на экране значения всех полей экземпляра. Разумеется, этот подход оправдывает себя, если метод toString работает правильно и действительно выводит на печать значения всех полей.
Один малоизвестный, но очень полезный прием, заключается в том, что в каждый класс можно включить отдельный метод main. Внутри него можно разместить проверочные заглушки, позволяющие протестировать этот класс отдельно от других.
public class MyClass
методы и поля
public static void main(String[j args)
код проверки
}
Создайте несколько объектов, вызовите все методы и проверьте, правильно ли они работают. Для этого все методы main можно оставить на месте, вызвав интерпретатор для каждого файла по отдельности. Если проверяется аплет, ни один из этих методов main никогда не будет вызван, если приложение- интерпретатор вызовет лишь метод main из стартового класса.
С помощью метода printStackTrace из класса Throwable можно получить трассировку стека для любой исключительной ситуации. Приведенный ниже код перехватывает исключительную ситуацию, выводит на печать соответствующий объект и трассировку стека, а затем повторно возбуждает исключительную ситуацию, чтобы найти предназначенный для нее обработчик.
try
{
}
catch (Throwable t)
{
t.printStackTrace();
throw t;
}
Для трассировки стека не нужно даже перехватывать исключительную ситуацию. Просто вставьте следующий оператор в любое место кода.
Thread.dumpstack();
Обычно трассировка кода выводится в поток System. out. С помощью метода void PrintStackTrace (Printwriter s) ее результаты можно записать в файл. При желании результаты трассировки стека можно вывести в окне, предварительно преобразовав их в строки.
Stringwriter out = nw Stringwriter();
new Throwable().printStackTrace(new Printwriter(out));
String trace = out.toString;
(Классы Printerwriterи StringWriter описываются в главе 12.)
Ошибки, возникающие при выполнении программы, часто удобно записывать в файл. Однако обычно ошибки посылаются в поток System. err, а не System.out. Следовательно, нельзя вывести ошибки в файл с помощью обычной команды
java MyProgram > errors.txt
В системе UNIX ЭТО не проблема. Например, если вы работаете в оболочке bash, просто перехватите поток ошибок с помощью команды
java MyProgram 2> errors.txt
Для того чтобы направить оба потока Stream.err и Stream. out в один и тот же файл, используйте команду
java MyProgram 2>&1 errors.txt
Некоторые операционные системы (например Windows 95 или 98) не имеют такого удобного метода. В этой ситуации нужно воспользоваться следующей программой на языке Java.
import java.io.*
public class Errout
{
public static void main(String[ args) throws IOException
{
Process p = Runtime.getRuntime().exec(args);
BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStreamO));
String line;
while ((line = err.readLine(); != null)
System.out.printin(line);
)
}
Затем запустите эту программу на выполнение.
java Errour java MyProgram.java > errors.txt
В системе Windows тот же результат можно получить более эффективным способом, скомпилировав следующую программу в файл errout.exe.
♦include <io.h>
♦ include <stdio.h>
#include <process.h>
int main(int argc, char* argv[];
{
dup2(l,2); /* Переключаем поток stderr на поток stdout. */
execvp(argv[1], argv + 1) ;
return 0;
}
Затем этот файл можно запустить с помощью команды
errout java MyProgram.java > errors.txt
Для того чтобы отследить загрузку класса, запустите интерпретатор java с опцией -verbose. На экране появятся такие строки.
[Opened C:\PROGRAM FILESXJAVASOFTXJREX1.3\lib\rt.jar]
[Opened C:XPROGRAM FILESXJAVASOFTXJREX1.3\lib\il8n.jar]
[Opened C:\PROGRAM FILESXJAVASOFTXJREX1.3Xlib\sunrsasign.jar]
[loaded java.lang.Object from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
[loaded java.io.Seriazable from C:\PROGRAM
FILESXJAVASOFTXJRE\1.3\lib\rt.jar!
[loaded java.lang.Comparatible from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
[loaded java.lang.String from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
[loaded java.lang.Class from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
[loaded java.lang.Cloneable from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
[loaded java.lang.Classloader from C:\PROGRAM
FILESXJAVASOFTXJREX1.3\lib\rt.jar!
Этот способ оказывается полезным для диагностики ошибок, связанных с поиском файлов.
Любуясь окнами из библиотеки Swing и удивляясь, как красиво и ровно расположены компоненты, естественно поинтересоваться, как именно они сделаны. Для этого нужно нажать клавиши <CTRL+SHIFT+F1>, и все компоненты будут выведены на экран по порядку.
FontDialog(frameO, 0, 0, 300x200, layout™java.awt.BorderLayout,...
javax.swing.JRootPane[, 4, 23,292x173, layout-javax.swing.JRootPaneSRootLayout,...
javax.swing.JPanel(null.glassPane,0,0,292x173,hidden,layout-java.awt.FlowLayout...
javax.swing.JLayeredPane(null.layeredPane,0,0,292x173,...
javax.swing.JPanel(null.contentPane, 0,0,292x173,layout-java.awt.GridBaglayout ...
javax.swing.JList(,0,0,73x152,alignmentX-null,alignmentY-null, ...
javax.swing.CellRenderetPane(,0,0,0x0,hidden]
javax.swing.DefaultListCellRendererSUIResource(,-73,-19, 0x0, ...
javax.swing.JCheckBox[, 157, 13,50x25,layout-javax.swing.OverlayLayout, ...
javax.swing.JCheckBox(, 156, 65,50x25,layout-javax.swing.OverlayLayout, ...
javax.swing.JCheckBox(,114,119,30x17,alignmentX-0.0,alignmentY-null, .
javax.swing.JTextField(, 186,117,105x21,alignmentX-null,alignmentY-null, .,.
javax.swing.JTextField(,0,152,291x21,alignmentX-null,alignmentY-null, ...
Если разработанный вами собственный компонент пользовательского интерфейса на экране отображается некорректно, вы оцените удобство отладчика графики из пакета Swing (Swing graphics debugger). Даже если вы никогда не создавали свои собственные компоненты, очень поучительно проследить, как именно компоненты изображаются на экране. Чтобы переключиться на отладку компонента библиотеки Swing, нужно вызвать метод setDebugGraphicsOptions класса JComponent. Этот метод имеет такие опции.
DebugGraphics . FLASH_OPTIONПеред тем, как отобразить строку,
прямоугольник и текст на экране, показывает их в кадре.
DebugGraphics . LOG_OPTIONВыводит на экран сообщение о каждой
графической операции.
DebugGraphics . BUFFERED_OPTION Отображает операции, которые выполняются в видеобуфере.
DebugGraphics . NONE_OPTIONОтключает отладку графики.
Для того чтобы опция FLASH работала правильно, необходимо отключить опцию "double buffering", которая служит для подавления мерцания экрана при обновлении окна. Это можно сделать с помощью волшебного заклинания, приведенного ниже.
RepaintManager.currentManager(getRootPane() J
. set DoubleBufferingEnabled(false]
((JComponent)getContentPane())
.setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
Просто поместите эти строки в конец конструктора вашего фрейма. При выполнении программы вы увидите панель, в которой медленно прокручивается ее содержимое. Для локальной отладки каждого компонента воспользуйтесь методом setDebugGraphicsOptions. Продолжительность, количество и цвет кадров можно регулировать (подробности— в описании класса DebugGraphics в документации).
Если запустить интерпретатор языка Java с опцией -Xprof , он запустит устаревший профилировщик (profiler), определяющий наиболее часто используемые методы. Профилировщик выводит информацию в поток System. out. В этом потоке также будет содержаться информация о том, какие методы были скомпилированы с помощью синхронной компиляции.
Использование консольного окна
Если вы запускаете аплет внутри броузера, то можете не увидеть никаких сообщений, передаваемых в поток System. out. Большинство броузеров поддерживают разновидности консольных окон. (Обратитесь к справочной системе вашего броузера.) Например, броузеры Netscape Navigator, Internet Explorer 4 и последующие версии имеют консольное окно. Если вы работаете с программой Java Plug-In, проверьте флажок опции “ShiowjavaConsole" на панели конфигурации (см. главу 10).
Более того, консольное окно языкаJava имеет несколько полос прокрутки, так что в нем можно просматривать довольно пространные сообщения. Пользователи системы Windows имеют несомненное преимущество над пользователями системы DOS, в окне которой обычно появляются данные из потока вывода System. out.
Мы опишем аналогичный класс так, чтобы вы смогли получить удовольствие, просматривая сообщения отладчика в окне. Действие класса Consolewindow продемонстрировано на рис. 11.4.
Рис. 11.4. Консольное окно
Использовать этот класс очень просто. Достаточно выполнить следующий вызов.
Consolewindow.init();
Затем выведите информацию в потоки System.out или System.err, как обычно.
В листинге 11.5 показан код класса ConsoleWindow. Как видим, этот класс очень прост. Сообщения отображаются в текстовой области JTextArea на панели JScrollPane. Для переключения потока вывода или потока ошибок на особый поток, выводящий сообщения в текстовую область, вызываются методы System. setOut и System. setErr. (Потоки рассматриваются в главе 12.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
/**
Окно, отображающее информацию из потоков System.out
и System.err
*/
public class Consolewindow
{
public static void init(;
{
JFrame frame = new JFrame();
frame.setTitle("Consolewindow");
final JTextArea output = new JTextAreaO;
output.setEditable(false);
frame.getContentPaneO.add(new JScrollPane(output));
frame.setSize(WIDTH, HEIGHT);
frame.setLocation(LEFT, TOP);
frame.showO;
// Определяем поток PrintStream, выводящий сообщения
//в текстовую область.
PrintStream consolestream = new Printstream(new
Outputstream()
{
public void write(int b) {} // never called
public void write(byte[ b, int off, int len]
{
output.append(new String(b, off, len));
}
));
// Переключаем потоки System.out и System.err.
System.setOut(consolestream);
System.setErr(consolestream);
}
40.
public
static
final
int
WIDTH
= 300;
41.
public
static
final
int
HEIGHT
= 200
42.
public
static
final
int
LEFT =
200;
43.
public
static
final
int
TOP =
200;
44. }
Отслеживание событийAWT
Создавая пользовательский интерфейс на языке Java, мы должны знать, какие происходят события и каким компонентам они передаются. К сожалению, в документации библиотеки AWT об этом говорится мало. Допустим, например, что мы хотим выводить подсказку в строке состояния в зависимости от того, в какой части экрана перемещается курсор мыши. Библиотека AWT генерирует события, связанные с мышью и фокусом, которые можно перехватить.
Рассмотрим полезный класс EventTracer, предназначенный для слежения за этими событиями. Он выводит на печать все методы, обрабатывающие события, а также их параметры. На рис. 11.5 показаны отслеженные события.
Рис. 11.5. Результат работы класса EventTracer
Для того чтобы разобраться в этих сообщениях, добавьте компонент, события которого вы хотите отслеживать, в объект класса EventTracer.
EventTracer tracer = new EventTracer();
tracer.add(frame);
ОнВЫВОДИТнапечатьтекстовоеописаниевсехсобытий.
public abstract void java.awt.event.ComponentListener
.componentShown(java.awt.event.ComponentEvent):java.awt.event
.ComponentEvent[COMPONENT_SHOWN] on frameC
public abstract void java.awt.event.WindowListener
.windowOpened(java.awt.event.WindowEvent] :java.awt.event
.WindowEvent[WINDOWOPENED]on frameO
Эти сообщения можно вывести в консольном окне или записать в файл. Как это сделать, показано в предыдущих разделах.
Класс EventTracer приведен в листинге 11.6. Идея, лежащая в основе этого класса, довольно проста, хотя его реализация выглядит немного таинственно.
Когда вы добавляете некий компонент в объект класса EventTracer с помощью метода add, класс из пакетаJavaBeans, предназначенный для наблюдения за компонентами, ищет в нем методы, имеющие вид void addXxxListener (XxxEvent). (Пакет JavaBeans описывается во втором томе в главе 7.) Для каждого найденного метода генерируется объект класса EventSetDescriptor. Каждый объект этого класса передается методу addListener.
Если компонент является контейнером, следует перечислить вложенные в него компоненты и рекурсивно вызвать метод add для каждого из них.
Метод addListener вызывается с двумя параметрами: компонентом, за событиями которого мы следим, и объектом класса EventSetDescriptor. Метод getListenerType класса EventSetDescriptor возвращает объект класса Class, описывающий интерфейс слушателя события, например, ActionListener или ChangeListener. Для интерфейса создается прокси-объект. Он просто выводит имя и параметр вызванного метода события. Метод getAddListenerMethod класса EventSetDescriptor возвращает объект класса Method. Этот объект используется для добавления прокси-объекта в компонент, в котором тот выступает в качестве слушателя события.
Эта программа представляет собой хорошую иллюстрацию возможностей механизма отражения. Мы можем не знать, что в классе JButton есть метод addActionListener, а в классе Jslider— метод addChangeListener. Механизм отражения выявит эти факты за нас.
Механизм прокси значительно упрощает программу. В предыдущих изданиях этой книги нам приходилось определять слушатель, который одновременно реализовывал интерфейсы MouseListener, ComponentListener, FocusListener, KeyListener, Listener, ActionListener и ItemListener, а также несколько десятков методов, выводящих на печать соответствующее событие. Механизм прокси описан в конце главы 5.
В листинге 11.6 приведена программа, иллюстрирующая работу класса EventTracer. Эта программа выводит на экран фрейм с кнопкой и ползунковым регулятором и отслеживает события, генерируемые этими компонентами.
9.13.14.9.13.14.import java.awt.*;import java.awt.event.*;import java.beans.*;import java.lang.reflect.*;public class EventTracer{public EventTracer (){// Обработчик всех прокси.handler = newInvocationHandler(){public Object invoke(Object proxy, Method method, Object(] args){+ ":" return null;}};}import java.awt.*;import java.awt.event.*;import java.beans.*;import java.lang.reflect.*;public class EventTracer{public EventTracer (){// Обработчик всех прокси.handler = newInvocationHandler(){public Object invoke(Object proxy, Method method, Object(] args){+ ":" return null;}};}+ args[O’);+ args[O’);
9.
13.
14.
9.
13.
14.
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
public class EventTracer
{
public EventTracer ()
{
// Обработчик всех прокси.
handler = new
InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object(] args)
{
+ ":" return null;
}
};
}
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
public class EventTracer
{
public EventTracer ()
{
// Обработчик всех прокси.
handler = new
InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object(] args)
{
+ ":" return null;
}
};
}
+ args[O’);
+ args[O’);
22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77./**
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
Добавляет объекты класса EventTracer для всех событий, которые можно прослушивать у данного компонента и его дочерних классов.
Sparam с компонент
*/
public void add(Component с)
{
try
{
// Получает все события, которые можно прослушать //у данного компонента.
Beaninfo info = introspector.getBeanlnfo(c.getClass ());
EventSetDescriptor(] eventSets =
for (int i = 0; i < eventSets.length; i++) addListener(c, eventSets[i]);
}
catch (IntrospectionException exception) {}
// Если возбуждается исключительная ситуация, // слушатели не добавляются.
if (с instanceof Container) {
// Получает все объекты дочерних классов
//и рекурсивно вызывает метод add.
Component!] a = ((Container)c).getcomponents();
for (int i = 0; i < a.length; i++) add(a[i]);
}
}
/**
Добавляет слушатель в заданное множество события.
Sparam с компонент
@param eventSet описание интерфейса слушателя
*/
public void addListener(Component с, EventSetDescriptor eventSet)
{
// Создает прокси-объект для данного типа слушателей //и выполняет все вызовы обработчика.
Object proxy = Proxy.newProxylnstance(null, new Class [ ] { eventSet.getListenerType(] }, handler);
// Добавляет прокси в качестве слушателя компонента. Method addListenerMethod
=
try
{
addListenerMethod.invoke(c, new Object[] { proxy }) }
catch(InvocationTargetException e) {}
catch(IllegalAccessException e) {}
// Если возбуждается исключительная ситуация,
// слушатели не добавляются.
}
private InvocationHandler handler;
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class EventTracerTest
(
public static void main(String[] args)
(
JFrame frame = new EventTracerFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.showO;
}
}
class EventTracerFrame extends JFrame
(
public EventTracerFrame(I
{
setTitleC'EventTracerTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Добавляет ползунковый регулятор и кнопку.
Container contentPane = getContentPane();
contentPane.add(newJSlider(), BorderLayout.NORTH);
contentPane.add(new JButtonCTest"), BorderLayout.SOUTH);
// Перехватывает все события, связанные с компонентами,
// находящимися внутри фрейма.
EventTracer tracer = new EventTracer();
tracer.add(this);>
}
public static final int DEFAULT_WIDTH = 400;
public static final int DEFAULT_HEIGHT400;
}
Класс Robot из библиотеки AWT
В версии 1.3 языка Java 2 появился класс Robot, который можно использовать для передачи нажатий клавиш и щелчков мыши в любую программу библиотеки AWT. Этот класс предназначен для автоматической проверки пользовательского интерфейса.
Для того чтобы создать объект класса Robot, нужно сначала получить объект класса GraphicsDevice. Графическое устройство экрана, заданное по умолчанию, можно получить с помощью следующей последовательности вызовов.
GraphicsEnvitonment environment
=
GraphicsDevice screen =
Затем создается объект класса Robot.
Robot robot = new Robot(screen);
Чтобы передать нажатие клавиш в программу, прикажите объекту класса Robot имитировать нажатие и освобождение клавиш.
robot.keyPress(KeyEvent,VK_TAB); robot.keyRelease(KeyEvent.VK_TAB);
Для имитации щелчка мыши сначала нужно переместить ее курсор, а затем нажать и отпустить кнопку.
robot.mouseMove(x, у) ;
robot.mousePress(InputEvent.BUTTONl_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
Здесь параметры х и у задают абсолютные координаты экрана, выраженные в пикселях.
Основная идея проверки состоит в том, что, имитируя нажатие клавиш и щелчки мыши, можно увидеть, правильно ли приложение реагирует на них. Затем нужно зафиксировать состояние экрана с помощью метода createScreenCapture.
Rectangle rect = new Rectangle(x, y, width, height);
Bufferedlmage image = robot.createScreenCapture(rect);
Координаты прямоугольника также выражаются в пикселях.
Кроме того, чтобы приложение могло перехватить команды, выполняемые объектами класса Robot, между ними нужны небольшие задержки. Для этого предназначен метод delay, в котором нужно указать количество миллисекунд.
robot.delay(1000); // Задержка на 1000 MC.
Программа, приведенная в листинге 11.8, иллюстрирует применение объектов класса Robot. Эти объекты тестируют программу для работы с кнопками, рассмотренную нами в главе 8. Сначала в ответ на нажатие клавиши пробела активируется крайняя слева кнопка. Затем объект класса Robot ожидает две секунды, чтобы дать пользователю возможность увидеть состояние экрана. По истечении этого срока объект класса Robot имитирует нажатие клавиши <Tab> и клавиши пробела для активизации следующей кнопки. После этого имитируется щелчок мыши на третьей кнопке. (Для того чтобы действительно щелкнуть на этой кнопке, в программе нужно правильно задать параметры х и у.) В заключение состояние экрана фиксируется и отображается в отдельном фрейме.
Этот пример показывает, что класс Robot сам по себе для тестирования пользовательского интерфейса не совсем удобен. Скорее, он представляет собой базовые строительные блоки, из которых можно сконструировать средство для тестирования программ. Профессиональные средства тестирования могут фиксировать, хранить и воспроизводить сценарии поведения пользователя, а также определять местоположение компонентов на экране, чтобы щелчки мыши не происходили наугад. В момент написания книги класс Robot был новинкой, и нам пока неизвестны средства для тестирования пользовательского интерфейса, которые были бы созданы с его помощью. Надеемся, в будущем они все же появятся.
•3.12.13.14.17.18.19.27.33.42.47.52.53.54.•3.12.13.14.17.18.19.27.33.42.47.52.53.54.import java.awt.*;
•3.
12.
13.
14.
17.
18.
19.
27.
33.
42.
47.
52.
53.
54.
•3.
12.
13.
14.
17.
18.
19.
27.
33.
42.
47.
52.
53.
54.
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class RobotTest
{
public static void main(String[] args)
{
// Создаем фрейм с кнопкой на панели.
ButtonFrame frame = new ButtonFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.showO;
// Связываем объект класса Robot и графическое
// устройство экрана.
GraphicsEnvironment environment
=
GraphicsDevice screen
=
try
{
Robot robot = new Robot(screen);
run(robot);
}
catch (AWTException exception)
{
System.err.printIn("Робот создать невозможно.");
)
/**
Запускает простую процедуру тестирования.
@param robot объект класса Robor, связанный с экраном
*/
public static void run(Robot robot)
{
// Имитируем нажатие пробела.
robot.keyPress(1 1);
robot.keyRelease(' ');
// Имитируем нажатие клавиш <Tab> и <Space>.
robot.delay(2000);
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_TAB) ;
robot.keyPress(1 ');
robot.keyRelease('') ;
// Имитируем щелчок мыши на крйней справа кнопке.
robot.delay(2000);
robot.mouseMove(200, 50);
robot.mousePress(InputEvent.BUTTON1MASK);
55, 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.55, 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.robot.mouseRelease(InputEvent.BUTTON1_MASK);
55, 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.
55, 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.
// Фиксирует экран и выводит окончательное изображение. robot.delay(2000);
Bufferedlmage image = robot.createScreenCapture(
new Rectangle(0, 0, 400, 300));
ImageFrame frame = new ImageFrame(image);
frame.show();
}
/**
Фрейм для отображения зафиксированного состояния экрана. 69. */ class ImageFrame extends JFrame {
/•♦
@param image отображаемое состояние экрана
V
public ImageFrame(Image image)
I
setTitle("Capture");
setsize(WIDTH, HEIGHT);
Container contentPane = getContentPane();
JLabel label = new JLabellnew Imageicon(image));
contentPane.add(label) ;
public static final int DEFAULT_WIDTH = 450;
public static final int DEFAULT_HEIGHT = 350;
java.awt.GraphicsEnvironment 1.2
static GraphicsEnvironment getLocalGraphicsEnvironment (
Возвращаетлокальноеграфическоеокружение.
GraphicsDevice getDefaultScreenDevice()
Возвращает графическое устройство экрана, заданное по умолчанию. Обратите внимание на то, что компьютеры с несколькими мониторами имеют по одному графическому устройству на каждый экран — чтобы получить массив, содержащий все графические устройства экранов, используйте метод getScreenDevices.
APIjava.awt.Robot 1.3
Robot(GraphicsDevice device)
Создает объект класса Robot, который может взаимодействовать с данным устройством.
void keyPress(int key)
void keyPress(int key)
Имитируют нажатие и освобождение клавиши.
Параметры: keyКод клавиши. Подробная информация о ко
дах клавиш содержится в классе Keystroke.
void mouseMove(int x, int y)
Имитирует перемещение курсора мыши.
Параметры: х, уАбсолютные координаты курсора мыши, вы
раженные в пикселях.
void mousePress(int eventMask)
void mouseRelease(int eventMask)
Имитируют нажатие и освобождение кнопки мыши.
Параметры: eventMask Маска события, описывающая кнопку мыши.
Подробная информация о масках событий содержится в классе InputEvent.
void delay(int milliseconds)
Задерживает работу объекта класса Robot на заданное количество миллисекунд.
Bufferedlmage createScreenCapture(rectangle rect) Фиксирует часть экрана.
Параметры: rectФиксируемая часть экрана, заданная в абсо
лютных координатах.
Профилирование
Ваша программа может работать правильно, но слишком медленно. Разумеется, прежде чем писать ее заново, следует разобраться, отчего это происходит. В этой работе нельзя действовать наугад. Пытаясь угадать причину, программисты часто тратят много времени на оптимизацию методов, которые вызываются очень редко и не могут повлиять на скорость выполнения программы. Эту работу следует поручить профилировщику (profiler).
В пакете Java 2 SDK предусмотрено очень древнее средство для профилирования программ под названием HPROF, которое нельзя воспринимать серьезно. Зато виртуальная машинаJava содержит интерфейс профилировщика (a profiler interface), который независимые поставщики могут использовать для создания более совершенных средств профилирования. Инофрмацию об этом интерфейсе можно найти на Web- странице
http://java.sun.com/products/jdk/1.3/docs/guide/jvmpi/jvmpi.html. Например, программа HPROF использует интерфейс профилировщика виртуальной машины Java. На рынке программного обеспечения существуют и другие, более мощные, средства профилирования.
Чтобы запустить профилировщик, выполните следующую команд)'.
java -Xrunhprof:опция1=значение1, опция1=значение!, ... MyProg
Опции профилировщика приведены в табл. 11.4.
|табли a 11 4On ии .
:^.Tt ^^.^' -.Tv. -? •’:,-v>r ‘• t. ••. ‘
1
Имя и значение опции
Описание
Опция по умолчанию
cpu=samplesI times
Использовать ли центральный процессор?
Нет
heap=dump | sites | all
Профилировать ли динамиче- скуюпамять?
all
monitor=y | n
Есть ли конкуренция мониторов?
п
format=a|b
Текст (ASCII) или бинарный код?
а
file=name
Записывать ли данные в файл?
j ava.prof.textдля текста, java . hprofдля бинарного кода
net=host:port
Передавать ли данные в сеть?
Записывать в файл
depth=size
Глубина трассировки стека
4
cutoff=value
Точность округления
0.0001
lineno=yIn
Отслеживать ли количество строк?
У
theread=yIn
Отслеживать ли потоки?
п
doe=y I n
Выводить ли на печать содержимое памяти при выходе из программы?
У
Наиболее полезными являются опции cpu и heap. Опция cpu=samples периодически проверяет стеки времени выполнения программы во всех потоках и отслеживает, как часто фиксируется тот или иной фрейм стека. Позднее мы рассмотрим этот процесс на конкретном примере. Опция cpu=times отслеживает, когда вызывается каждый метод и выполняется возврат из него. Опция heap=sites наблюдает за размещением объектов в динамической памяти и удалением их оттуда.
Рассмотрим пример. Программа, приведенная в листинге 11.9, подсчитывает количество слов аналогично утилите wc системы UNIX. Она подсчитывает количество строк, слов и символов в потоке System.in. Например, чтобы подсчитать слова в файлеjdkl. 3/README . txt, следует выполнить следующую команду.
java Wordcount < jdk/README.txt
Программа WordCount очень проста. Она считывает данные строка за строкой, используя метод BufferedReader . readLine. Для подсчета символов определяется длина введенной строки. Для подсчета слов применяется метод countTokens класса StringTokenizer. (Более подробно классы BufferedReader и StringTokenizer рассматриваются в главе 12.)
Для больших файлов программа работает правильно, но невероятно долго. Подсчет слов, содержащихся в книге "Алиса в Стране Чудес" (28195 слов, что составляет
примерно 150 Кбайт), на нашей машине занял 80 секунд, в то время как утилита wc ту же самую работу проделала менее чем за одну секунду.
Разумеется, для простых программ, наподобие нашей, нетрудно найти причину такой медлительности. И все же пусть профилировщик подтвердит наши подозрения. Догадаться, в чем причина, можно, взглянув на числа. Очевидно, конкатенация ввода в длинную строку — слишком затратная и ненужная операция. Попробуем применить класс St^ingTokenizer.Эффективен ли он?
Запустим профилировщик с помощью следующей команды.
java =Xrunhprof:cpu=samples WordCount < gutenberg/alice30.txt
По умолчанию профилировщик записывает введенные данные в файл java.hprof. txt. Изменить файл вывода можно с помощью опции file.
Откроем файл и посмотрим, что в разделе CPU SAMPLES.
rank
self
accum
count
trace
method
1
37.23%
37.23%
1246
3
java.lang.StringBuffer. expandCapacity
2
32.24%
69.47%
1079
10
j
expandCapacity
3
28.56%
98.03%
956
e
java.lang.String.getChars
4
0.30%
98.33%
10
17
WordCount.main
5
0.21%
98.54%
7
7
java.lang.StringBuffer. toString
6
0.18%
98.72%
6
20
WodrCount.main
7
0.15%
98.86%
5
4
java.io.BifferedReader. readLine
8
0.15%
99.01%
5
16
java.lang.String.<init>
9
0.12%
99.13%
4
11
java.lang.StringBuffer. append
10
0.12%
99.25%
4
13
WordCount.main
Для правильной интерпретации этой таблицы нужно сравнить числа в столбце TRACE с листингами TRACE, полученными ранее. Эти листинги появляются в порядке, который кажется случайным. Ниже приведены листинги, соответствующие первым трем элементам таблицы, когда опция cpu имеет значение samples.
TRACE 3:
j
java.lang.StringBuffer.append(StringBuffer.java:401)
WordCount.main(WordCount.java:18]
TRACE 10:
j
java.lang.StringBuffer.append(StringBuffer.java:401)
WordCount.main(WordCount.java:18)
TRACE 6:
java. lang. String, get Chars (String, java .'Unknown line)
java.lang.StringBuffer.append(StringBuffer.java:402]
WordCount.main(WordCount.java:18)
Каквидим, всеониуказываютнаоднуитужестрокупрораммы—настроку18.
input += line;
У читателя может возникнуть вопрос, почему появляются две внешне идентичные трассировки стека. Чтобы раскрыть эту тайну, запустите декомпилятор javap.
javap -с -1 WordCount
Вот как выглядит код, соответствующий восемнадцатой строке.
48 new #8 <Class java.lang.StrinaBuffer>
51 dup
52 invokespecial #9 <Method java.lang.StringBuffer()>
55 aload_l
56 invokevirtual #10 <Method java.lang.StringBufferapend- (java.lang.String!
59 aload 4
61 invokevirtual #10 <Method java.lang.StringBufferappend- (java,lang.String)>
64 invokevirtual #12 <Method java.lang.String toString()>
67 astore__l
Инымисловами,компиляторпереводитоператор inpiu += line;
воператоры
StringBuffer temp = new StringBuffer();
;
;
input = temp.toString(; ;
Следовательно, в программе есть два вызова метода append, причем, по всей видимости, на их выполнение затрачивается много времени. Разумеется, эта проблема решается легко. Нет никакой необходимости конкатенировать все входные данные. Нужно просто подсчитать количество символов и слов отдельно для каждой строки, и тогда наша программа станет работать так же быстро, как и утилиты системы UNIX.
Кроме данных о затраченном времени, программа HPROF может показывать содержимое динамической памяти. Например, выполните команду java -Xrunhprof:heap=sites WordCount < alice30.txt
Результаты будут начинаться следующими строками.
rank
self
ac cum
bytes
objs
bytes
objs
trace
name
1
65.49%
65.49%
398250
3
477753252
7703
468
[C
2
24.59%
90.08%
149524
157
149524
157
1
[I
3
3.17%
93.25%
19282
739
19478
755
1
[C
Это означает, что элемент стека под номером 468 разместил в памяти огромное количество символьных массивов ([С). Вот трассировка этого элемента.
TRACE 4 68 :
java.lang.StringBuffer.expandcapacity(StringBuffer.java: 202) j
WordCount.main(WordCount.java:18)
Он снова ссылается на печально известную строку 18.
В этом разделе мы показали, как можно использовать информацию, генерируемую профилировщиком HPROF, для обнаружения узких мест в вашей программе. Однако при анализе более сложных программ разобраться в этих данных почти невозможно, и следует применить профессиональные средства профилирования.
15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.import java.io.*;
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
import java.util.*;
/**
Программа для подсчета строк, слов и символов, содержащихся в потоке System.in
*/
public class WordCount
{ public static void main(String(j args) {
String input = "";
int lines = 0;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
// Считываем строки, пока не закончится файл.
while ((line = reader.readLine(); != null) {
line += "\n";
input += line; // Добавляем новую строку lines++; // Увеличиваем счетчик строк.
}
}
catch (IOException exception)
{ exception.printStackTrace();
}
// Разбиваем ввод на лексемы для подсчета слов.
StringTokenizer tokenizer = new StringTokenizer(input);
int words = tokenizer.countTokens();.
// Выводим на печать количество строк, слов и символов.
System.out.printin(lines + " " + words + " " + input.length ());
}
}
Тестирование охвата
В пакетеJ2SE SDK есть средство для тестирования охвата (coverage testing), называемое JCOV. При таком тестировании определяются фрагменты кода, выполненные во время работы программы. Если в ходе тестирования разных вариантов эти фрагменты никогда не выполняются, вполне вероятно, что здесь скрываются необнаруженные ошибки.
Программа JCOV еще более примитивна, чем профилировщик HPROF, поэтом)- мы опишем ее лишь вкратце. Например, чтобы проверить, какие методы вызываются при запуске программы OptionPaneTest, описанной в главе 9, выполните следующую команду. java -Xrunjcov:type=M OptionDialogTest
Программа JCOV записывает результаты анализа в файл с именем j ava . j cov. Вот типичный пример.
JCOV-DATA-FILE-VERSION; 2.0
CLASS: ButtonPanel []
SRCFILE: OptionDialogTest.java
TIMESTAMP: 0
DATA: M
#kind line position count
METHOD: getSelection()Ljava/lang/String; []
1 60 08
METHOD: <init>(Ljava/lang/String;[Ljava/lang/String;)V [public]
1 36 06
CLASS: OptionDialogFrameSShowAction []
SRCFILE: OptionDialogTest.java
TIMESTAMP:0
DATA: M
#kind line position count
METHOD: actionPerformed(Ljava/awt/event/ActionEvent;)V [public]
123302
METHOD: <init>(LOptionDialogFrame;) V [private]
1 229 01
METHOD: <init> (LOptionDialogFrame; LOptionDialogFrame$l) V
1 229 01
Class: OptionDialogTest [public]
SRCFILE: OptionDialogTest.java
TIMESTAMP:0
DATA: M
#kind line position count
METHOD: main([Ljava/lang/String;)V [public static]
1 17 01
METHOD: <init>()V [public]1
1 13 00
Например, метод <init> класса ButtonPanel выполнялся шесть раз, создавая шесть панелей, а конструктор по умолчанию класса OptionDialogTest не вызывался вообще. Это вполне возможно, поскольку мы нигде не создавали объекты этого класса, вызывая лишь статический метод main.
Чтобы получить справку о программеJCOV, выполните команду
java -Xrunjcov:help
и просмотрите файл j vm.jcovb. txt в каталогеjre/lib.
Программа JCOV, как и HPROF, представляет собой скорее иллюстрацию концепции, а не полезный инструмент. Профессиональные средства профилирования позволяют выполнить тестирование охвата в виде, который легче поддается интерпретации.
Применение отладчика
Отладка программы с помощью вывода на экран промежуточных результатов — не самое приятное занятие. Вам приходится постоянно добавлять и удалять операторы, а затем компилировать программу заново. Отладчик намного облегчит вашу работу, поскольку в нем можно установить точку прерывания (breakpoint). Вплоть до этой точки программа выполняется без задержек, а начиная с нее программист может запустить проверку программы.
Отладчик JDB
В пакет SDK входит программа JDB, чрезвычайно древний отладчик, работающий в режиме командной строки. Пользовательский интерфейс этой программы настолько примитивен, что пользоваться им можно лишь в крайнем случае. Действительно, это лишь иллюстрация, а не профессиональная программа. Тем не менее мы кратко опишем ее, поскольку лучше иметь такой отладчик, чем не иметь никакого. Разумеется, во многих профессиональных средах программирования на языке Java есть более удобные отладчики. Основные принципы работы всех отладчиков одинаковы, поэтому примеры из этого раздела можно использовать для обучения.
В листингах 11.10-11.12 показана слегка измененная версия программы ButtonTest из главы 8. (Мы разделили программу на части и поместили каждый класс в отдельный файл для того, чтобы преодолеть ограничения, накладываемые некоторыми отладчиками на работу с несколькими классами одновременно.)
При щелчке на любой кнопке ничего не произойдет. Посмотрите на код — при щелчке на кнопке фон должен окраситься в цвет, указанный на кнопке.
1. import javax.swing.*;
2.
3. public class BuggyButtonTest
4.{
5 * public static void main(String[" args)
6.(
7.BuggyButtonFrame frame = new BuggyButtonFrame();
8.frame.setDefaultCloseOperationfJFrame.EXIT_ON_CLOSE);
9.frame.show();
10.}
11. }
import java.awt.*;
import javax.swing.♦;
public class BuggyButtonFrame extends JFrame
{
public BuggyButtonFrame(;
{
setTitle("BuggyButtonTest”);
setSize(WIDTH^ HEIGHT);
// Добавляем во фрейм панель.
BuggyButtonPanel panel = new BuggyButtonPanel();
Container contentPane = getContentPane();
contentPane.add(panel);
}
publicstaticfinalintWIDTH = 300;
publicstaticfinalintHEIGHT = 200;
}
14.15.16.17.18.25.14.15.16.17.18.25.import java.awt.*;
14.
15.
16.
17.
18.
25.
14.
15.
16.
17.
18.
25.
import java.awt.event.♦;
import javax.swing.*;
class BuggyButtonPanel extends JPanel
{
public BuggyButtonPanel()
{
ActionListener listener = new ButtonListener();
JButton yellowButton = new JButton("Yellow");
add(yellowButton);
yellowButton.addActionListener(listener);
JButton blueButton = new JButton("Blue");
add(blueButton);
blueButton.addActionListener(listener);
JButton redButton = new JButtonl "Red");
add(redButton] ;
redButton.addActionListener(listener! ;
private class ButtonListener implements ActionListener
public void actionPerformed(ActionEvent event) {
String arg = event.getActionCommand();
if (arg.equals("yellow")]
setBackground(Color.YELLOW) ;
else if (arg.equals("blue")]
setBackground(Color.BLUE);
else if (arg.equals("red")) setBackground(Color.RED) ;
)
}
}
Ошибку в такой короткой программе можно обнаружить и без отладчика, просто внимательно ее прочитав. Допустим, что наша программа настолько сложна, что для обнаружения ошибки читать ее код не имеет смысла. В этой ситуации нам нужен отладчик.
Для того чтобы применить отладчик JDB, нужно сначала скомпилировать программу с опцией -д.
javac -g BuggyButtonTest.java BuggyButtonFrame.java
BuggyButtonPane.j ava
В этом случае компилятор добавляет в файлы классов имена локальных переменных и другую отладочную информацию. После этого следует запустить отладчик.
jdb BuggyButtontest
На экране появится что-то наподобие следующей строки.
Initializing jdb...
Приглашение > означает, что отладчик ожидает команду. В табл. 11.5 приведены все команды отладчика. Элементы, заключенные в квадратные скобки [...], являются необязательными.
threads [ группапотоков]
Перечисляет потоки
threadномер_ по то к а
Задает поток по умолчанию
suspend [номер (а)_потока (ов)]
Приостанавливает потоки (по умолчанию: all)
resume [номер (а)_потока (ов) ]
Возобновляет потоки (по умолчанию: all)
where [номер (а) потока (ов)} или all
Выводит на печать содержимое стека потока
wherei [номер (а) _потока (ов) ]
Выводит на печать содержимое стека потока
или all
и счетчик программы
threadgroups
Перечисляет группы потоков
threadgroup имя
Задает текущую группу потоков
print имя (тлена)
Выводит на печать объект или поле
dump имя(имена)
Выводит на печать информацию обо всех объектах
locals
Выводит на печать значения всех локальных переменных
classes
Перечисляет известные в данный момент классы
methods класс
Перечисляет методы класса
stop in класс.метод
Устанавливает точку прерывания в методе
stop at класс:строка
Устанавливает точку прерывания в строке
up[л]
Перемещение вверх по стеку потока
down [л]
Перемещение вниз по стеку потока
clearкласс:строка
Удаляет точку прерывания
step
Выполняет текущую строку, перемещаясь по строкам внутри класса
stepi
Выполняет текущий оператор
step up
Выполняет все операторы до конца текущего метода
next
Выполняет текущую строку, перескакивая через вызовы
Окончание табл. 11.5
cont
Продолжает выполнение, начиная с точки прерывания
catch класс
Прерывает работу программы, возбуждая указанную исключительную ситуацию
ignore класс
Игнорирует указанную исключительную ситуацию
list [строка] use [путь]
Выводит на печать текст программы
Отображает или изменяет путь к исходному файл)'
memory
Информирует об использовании памяти
gc
load класс
Удаляет ненужные объекты
Загружает класс, предназначенный для отладки
run [класс [аргументы]] I ;
Начинает выполнение загруженного класса Повторяет последнюю команду
help (или ?) exit (или quit)
Перечисляет команды
Выход из отладчика
В этом разделе мы рассматриваем лишь наиболее полезные команды отладчика JDB. Основная идея проста: установить одну или несколько точек прерывания, а затем запустить программу. Когда программа достигает одну из точек прерывания, она останавливается. В этот момент можно проверить значения локальных переменных и сравнить их с ожидаемыми.
Для того чтобы установить точку прерывания, используются команды
stop in класс.метод
или
stop at класс:строка
Например, установим точку прерывания в методе actionPerformed класса BuggyButtonTest.
stop in BuggyButtonPanel$ButtonListener.actionPerformed
Теперь для того, чтобы запустить программу и выполнить ее до точки прерывания, нужно выполнить команду run
Начнется выполнение программы, но точка прерывания никак не проявит себя, пока не начнется обработка кода в методе actionPerformed. Чтобы это случилось, щелкните на кнопке "Yellow". Отладчик прервет работу в начале метода actionPerformed. На экране появится следующее сообщение.
Breakpoint hit: thread="AWT-EventQueue-O",BuggyButtonPanelSBut- tonListener.actionPerformedO. line=28, bci=C
28String arg = event.getActionCommand();
Поскольку отладчик не показывает в отдельном окне текст программы, в этих данных легко заблудиться. Чтобы этого не произошло, используйте команду list. Если программа остановится после того, как вы выполните команду list, отладчик станет выводить на экран текущую строку вместе с предыдущей и последующей строками, а также их номера. 24 private class ButtonListener implements ActionListener 25 { 26public void actionPerformed(ActionEvent event)
{
String arg = event.getActionCommand();
if (arg.equals("yellow")]
setBackground(Color.yellow);
else if (arg.equals('blue")I
setBackground(Color.blue);
else if (arg.equals("red")]
Для того чтобы увидеть все локальные переменные, наберите команду locals.
Method arguments:
event = instance of java.awt.event.ActionEvent(id=698] Local variables:
Для получения более подробной информации используется команда dump. dump переменные
Например, для отображения все полей в переменной evt нужно выполнить команду dump event
Тогда на экране появится следующая информация.
event = instance of java.awt.event.ActionEvent(id=698] {
SHIFT_MASK: 1
CTRL_MASK: 2
META_MASK: 4
ALT_MASK: 8
ACTION_FIRST: 1001
ACTION_LAST: 1001
"1001
actionCommand: "Yellow" modifiers: 0
serialVersionUID: -7 6710787 96273832149
Для пошаговой отладки программы предназначены две команды. Команда step позволяет выполнять отладку, заходя внутрь вызываемых методов, а команда next выполняет переход к следующей строке, не погружаясь в вызываемые методы. Чтобы обнаружить точку программы, в который вы находитесь в данный момент, дважды наберите команду next, а затем — команду list.
Выполнение программы прервано на строке 31.
{
String arg = event.getActionCommand();
if (arg.equals("yellow")]
setBackground(Color.yellow);
else if (arg.equals('blue") )
setBackground(Color.blue);
else if (arg.equals("red"))
setBackground(Color.blue]
repaint()
)
Это не совсем то, чего мы хотели. Предполагалось, что будет выполнен вызов метода setColor (Color.yellow), а затем — оператор repaint.
Просмотрим содержимое переменной arg.
arg = "Yellow"
Теперь понятно, что произошло. Значение переменной arg было равно "Yellow" с прописной буквы "Y", а сравнение if (arg.equals("yellow") ]
рассчитано на строчную букву "у". Тайна раскрыта.
Для того чтобы выйти из отладчика, наберите команду quit
Этот пример показывает, что отладчик можно использовать для поиска ошибок, вот только интерфейс в режиме командной строки очень неудобен. Помните: если вы потеряли ориентацию и не знаете, в каком месте программы находитесь в данный момент, зовите на помощь команды list и locals. Однако для отладки серьезных программ все же следует использовать более удобный отладчик.
Отладчик среды Sun ONE Studio
Среда Sun ONE Studio имеет современный и удобный отладчик, который доставит вам большое удовольствие. В частности, с его помощью можно устанавливать точки прерывания, отслеживать значения переменных и пошагово выполнять программу.
Для того чтобы установить точку прерывания, переместите курсор на нужную строку и выберите в меню пункт Debug -> Add Breakpoint или нажмите комбинацию клавиш <CTRL+F8>. Точка прерывания подсвечивается (рис. 11.6).
Чтобы начать отладку, выберите пункт Debug -> Start в меню или нажмите клавишу <F5>. Программа начнет свою работу. Затем установите точку прерывания на первой строке метода actionPerformed.
Когда отладчик остановится на выбранной точке прерывания, можно просмотреть стек вызовов и значения локальных переменных (рис. 11.7).
Чтобы пошагово выполнить приложение, выберите пункт Debug -> Step <F8> или Debug -> Step into <F7>. В нашем примере достаточно дважды нажать клавишу <F8>, чтобы увидеть, как программа перескакивает через команду setBackground (Color.yellow). Причину такого поведения можно обнаружить, изучив значение переменной args.
Как видим, отладчик Sun ONE Studio намного удобнее, чем JDB, поскольку он обладает визуальными средствами, позволяющими выводить на экран полезную информацию. Установка точки прерывания и проверка переменных также выполняются намного проще. Это типичный отладчик, являющийся частью интегрированной среды разработки программ.
В этой главе мы ознакомили читателей с обработкой исключительных ситуаций, а также дали несколько советов, касающихся тестирования и отладки. В последней главе рассматриваются потоки и классы для работы с файлами, необходимые для ввода и вывода данных.
Точки прерывания в отладчике Sun ONE Studio
Остановка в точке прерывания
Потоки и файлыПотоки и файлыcTv-VtvktV 12
Потоки и файлы
Потоки и файлы
В этой главе...
Т Потоки
Полное семейство потоков
Потоки ZIP-файлов
Использование потоков
Объектные потоки
Работа с файлами
▼ Новые средства ввода-вывода
• Регулярные выражения
этой главе излагаются методы работы с файлами и каталогами, а также способы считывания и записи информации. Кроме того, здесь описан механизм сериализации, позволяющий сохранять объекты так же легко, как текст или числовые данные. Глава завершается обсуждением регулярных выражений, хотя они не связаны с потоками и файлами. Мы не нашли более подходящего места для этой темы, впрочем, как и группа разработчиков языка Java — в документации, сопровождающей пакет SDK 1.4, спецификация регулярных выражений находится в разделе "новые средства ввода-вывода".
Потоки
Способы ввода-вывода не слишком интересны, но программа, не обладающая возможностью считывать и записывать данные, никому не нужна. В этой главе описываются способы считывания информации из источника данных, который может пере-
давать последовательность байтов, и дальнейшей их передачи любым адресатам, которые эту последовательность байтов могут воспринимать. Этими источниками и адресатами могут быть (и часто являются) файлы, а также сетевые соединения и даже блоки памяти. Следует иметь этот факт в виду: например, информация, размещенная в файлах, и информация, полученная из сетевого соединения, обрабатываются, по существу, одинаково. (Сетевое программирование обсуждается во втором томе.) Разумеется, поскольку все данные в конечном итоге представляют собой некую последовательность байтов, их удобнее представлять в виде некоторой структуры высокого уровня, например, последовательности символов или объектов. По этой причине мы лишь коснемся вопросов, связанных с низкоуровневым вводом-выводом, и сконцентрируемся на средствах высокого уровня.
В языке Java объект, из которого можно считывать последовательность байтов, называется потоком ввода (input stream), а объект, в который эту последовательность данных можно записывать, называется потоком вывода (output stream). Они определены в абстрактных классах Inputstreamи Outputstream. Потоки, ориентированные на последовательности байтов, неудобны для обработки информации, записанной с помощью кодировки UNICODE (напомним, что в этом случае один символ кодируется двумя байтами). По этой причине классы для обработки символов в кодировке UNICODE образуют отдельную иерархию, в основе которой лежат классы Reader и Writer. Операции ввода и вывода, предусмотренные в этих классах, основаны на двухбайтовой, а не однобайтовой кодировке.
Абстрактные классы были описаны в главе 5. Напомним, что эти классы образуют механизм, который дает возможность выделять общие закономерности поведения классов и описывать их на более высоком уровне абстракции. Это позволяет создавать более ясные программы и облегчает анализ дерева наследования. Этот механизм используется и для обеспечения ввода-вывода в языке Java.
Как мы вскоре убедимся, из упомянутых четырех классов в языке Java формируется целое семейство классов для ввода-вывода: мы сможем рассмотреть почти каждый из них.
Чтение и запись байтов
Класс InputStream содержит абстрактный метод
abstract int read(I
Этот метод считывает и возвращает один байт. Обнаружив конец потока, этот метод возвращает число -1 . Разработчик конкретного класса для потока ввода должен заместить этот метод, предусмотрев необходимые функциональные возможности. Например, в классе FilelnputStream этот метод считывает один байт из файла. Поток System. in является встроенным объектом подкласса InputStream. Он позволяет вводить информацию с клавиатуры.
В классе InputStream есть и неабстрактные методы, предназначенные для чтения массива байтов, а также для игнорирования заданного количества байтов при вводе. Эти методы вызывают абстрактный метод read, поэтому в подклассах нужно заместить только один метод.
Аналогично в классе OutputStream определен абстрактный метод abstract void write(int b)
Этот метод позволяет записывать один байт в поток вывода.
Оба метода read и write могут блокировать (block) поток, пока байт не будет действительно считан или записан. Таким образом, если данные невозможно прочитать или записать немедленно (например, из-за загруженности сети), поток, содержащий вызов одного из этих методов, приостанавливается. Это дает другим потокам шанс выполнить за это время полезную работу. (Потоки рассматриваются во втором томе.)
Метод available позволяет проверять количество байтов, доступных для чтения в данный момент. Это значит, что приведенный ниже фрагмент программы вряд ли когда-нибудь заблокирует поток.
int bytesAvailable = System.in.available();
if (bytesAvailable > 0)
{
byte[] data = new byte(byteAvailable);
System.in.read(data);
}
Завершив чтение или запись в поток, его следует закрыть, вызвав метод close. Этот вызов освобождает ограниченные ресурсы операционной системы. Если приложение открывает слишком много потоков, не закрывая их, системные ресурсы могут оказаться исчерпанными. Закрытие потока вывода очищает (flushes) буфер, использованный этим потоком: все символы, которые временно накапливались в этом буфере, удаляются. В частности, если вы забыли закрыть файл, последний пакет байтов, записанный в буфере, может пропасть. Буфер можно очистить и вручную, воспользовавшись методом flush.
Даже если бы в классе потока были предусмотрены конкретные методы для работы с функциями read и write, программисты редко бы использовали их, так как в программах нечасто возникает необходимость считывать и записывать именно потоки байтов. Данные, которые интересуют пользователей, обычно представляют собой числа, строки и объекты.
В языке Java есть много классов потока, производных от основных классов InputStream и Outputstream. Эти классы позволяют работать с файлами в той форме, в которой они обычно представлены, а не на уровне байтов.
ps^s/
API java. io. Inputstream 1.0
abstract int read()
Считывает и возвращает байты. Если обнаружен конец потока, метод read возвращает число -1 .
int read(byte(1 b)
Считывает массив байтов и возвращает количество фактически прочитанных байтов или число -1 , если обнаружен конец потока. Максимальное количество байтов, которые может прочитать метод read, определяется полем b. length.
int read(byte[] b, int off, int len)
Считывает массив байтов. Метод read возвращает фактическое количество байтов или число -1 , если обнаружен конец потока.
Параметры: bМассив, в который записываются считанные байты.
offСмещение, определяющее место первого байта в
массиве Ь.
len Максимально возможное количество считанных байтов.
long skip(long n)
Пропускает п байтов в потоке ввода. Возвращает фактическое количество пропущенных байтов (которое может быть меньше п, если обнаружен конец потока).
int available()
Возвращает количество байтов, доступных без блокировки. (Блокировка означает, что текущий поток приостановлен.)
void closet)
Закрывает поток ввода.
void mark(int readlimit)
Помещает маркер в текущую позицию входного потока. (Этим свойством обладают не все потоки.) Если из потока ввода считано больше, чем readlimit байтов, то поток может удалить маркер.
void reset()
Возвращается к последнему маркеру. Последовательность вызовов метода read приводит к повторному считыванию байтов. Если текущего маркера нет, поток не возвращается в исходное состояние.
« boolean markSupported()
Если поток можно маркировать, возвращает значение true
S4si4iV
API java.io.Outputstream 1.0
SS^i. abstract void write(int n)
Записывает байты.
void write(byte[" b)
Записывает все байты в массив b.
void write(byte[j
b, int off, int len)
Параметры: b
Массив, в который записываются байты.
off
Смещение, определяющее место первого байта в массиве b.
len
Максимально возможное количество записанных байтов.
void close()
Очищает и закрывает поток вывода.
void flush ()
Очищает поток вывода, посылая все данные из буфера по назначению.
Полное семейство потоков
В отличие от языка С, в котором есть только один тип FILE*, в языке Java существует целое семейство, состоящее из более чем 60 (!) типов потоков (см. рис. 12.1 и 12.2). Разработчики библиотеки заявляют, что у них есть веская причина предоставить пользователям такой широкий выбор потоков: предполагается, что это снизит количество ошибок программирования. Например, некоторые программисты на языке С считают самой распространенной ошибкой попытку записать данные в файл, открытый только для чтения. (На самом деле это не самая распространенная ошибка.) Обычно при этом вывод игнорируется. В языкахJava и C++ компилятор распознает ошибки такого рода, поскольку в классе InputStream (Java) или istream (C++) нет методов, предназначенных для вывода данных.
(В ответ мы можем сказать, что в языке C++ и еще в большей степени в языке Java основной инструмент для предотвращения ошибок программирования — запугивание. Крайняя сложность библиотек потоков заставляет программистов ходить на цыпочках.)
Стандарт ANSI C++ предоставляет больше потоков, чем нужно, в частности, потоки isteam, ostream, iostream, ifstream, ofstream, fstream, wistream, wifstream, istrstream и т.д. Однако язык Java действительно перегружен потоками и предоставляет отдельные классы для буферизации, просмотра, случайного доступа, форматирования текста и бинарных данных.
Распределим членов этого многочисленного семейства потоков по их предназначению. Во главе этого семейства стоят четыре абстрактных класса: InputStream, OutputStream, Reader и Writer. Мы не можем создавать объекты этих классов, но другие методы могут возвращать их. Например, как мы видели в главе 10, в классе URL есть метод openStream, возвращающий объект класса InputStream. Затем этот объект можно применять для считывания данных с конкретного URL-адреса. Как указывалось, классы InputStream и OutputStream позволяют считывать и записывать только отдельные байты и массивы байтов. В них нет методов для чтения и записи строк и чисел. Для этого нужно создать специальные дочерние классы. Например, классы DatalnputStreamи DataOutputStream позволяют считывать и записывать все основные типы данных, предусмотренные в языке Java.
С другой стороны, для текста в кодировке Unicode используются классы, производные от классов Reader и Writer. Основные методы классов Reader и Writer аналогичны методам классов InputStream и OutputStream.
abstract int read(]
abstract void write(int b)
Они работают так же, как и соответствующие методы из классов InputStream и OutputStream, за исключением, разумеется, того факта, что метод read возвращает либо символ в кодировке Unicode (в виде целого числа в диапазоне от 0 до 65535) либо число -1, если обнаружен конец файла.
Рис. 12.1.Иерархия потоков ввода и вывода
В заключение отметим, что существуют полезные потоки, например, ZipInputStream и ZipOutputStream, позволяющие читать и записывать файлы в форматеZIP.
Рис. 12.2. Иерархия классов Reader и WriterРис. 12.2. Иерархия классов Reader и Writer
Рис. 12.2. Иерархия классов Reader и Writer
Рис. 12.2. Иерархия классов Reader и Writer
Фильтры ПОТОКОВ
Потоки FileInputstream и FileOutputStream позволяют читать данные из файла и записывать их туда. Имя файла или полный путь к нему задается в конструкторе. Например, с помощью следующего оператора можно найти файл с именем "employee . dat" в текущем каталоге.
FilelnputStream fin = new Fileinputstream("employee.dat");
Поскольку символ "обратная косая черта" ("\") в строках языка Java является управляющим, при записи путей в среде Windows следует использовать символ "\\" (например "С: WWindowsWwin. ini"). Кроме того, в среде Windows можно использовать одну косую черту (например "C:/Windows/win.ini"), поскольку большинство файловых систем на платформе Windows интерпретирует косую черту как разделитель файлов. Однако пользоваться этим разделителем не рекомендуется, поскольку поведение функций в системе Windows постоянно меняется, а в других операционных системах могут использоваться другие разделители файлов. Для гарантии платформной независимости программы нужно применять правильный разделитель файлов. Он хранится в константной строке File.separator.
Кроме того, можно использовать объекты класса File (они подробно описываются в конце главы).
File f = new File("employee.dat");
Fileinputstream fin = new Fileinputstream(f);
Подобно абстрактным классам InputStream и OutputStream, эти классы поддерживают чтение и запись только на уровне байтов. Таким образом, из объекта fin можно читать лишь отдельные байты или массивы байтов.
byte b = (byte)fin.read();
Поскольку все классы из пакета java.io интерпретируют относительные имена путей, считая отправной точкой рабочий каталог, может понадобиться определить точное имя данного каталога. Для этого следует вызвать метод System. getPropertyCuser.dat").
Создав объект класса DataInputStream, можно считывать числовые данные. dataInputStream din = . . . ;
double s = din.readDouble(: ;
Однако аналогично классу FilelnputStream, в котором нет методов для чтения числовых данных, класс DatalnputStreamне имеет методов для считывания данных из файла.
В языке Java предусмотрен продуманный механизм для разделения двух видов ответственности. Некоторые потоки (например, FilelnputStream и поток ввода, возвращаемый методом openStream класса URL) могут извлекать из файла байты и другие более экзотические элементы. Другие потоки (такие как DatalnputStream и PrintWriter) могут объединять байты, создавая более полезные типы данных. Программист на языке Java должен объединять эти две разновидности потоков, создавая так называемые профильтрованные потоки. Для этого нужно передать существующий поток конструктору другого потока. Например, для считывания чисел из файла необходимо сначала создать объект класса FilelnputStream, а затем передать его конструктору класса DatalnputStream.
FilelnputStream fin = new Fileinputstream("employee.dat");
DatalnputStream din = new DatalnputStream(fin);
double s = din.readDouble();
Следует иметь в виду, что поток входных данных, созданный с помощью приведенного выше кода, не связан с новым файлом на диске. Этот поток по-прежнему имеет доступ к данным, хранящимся в соответствующем файле, связанном с потоком ввода. Однако теперь интерфейс потока приобрел новые функциональные возможности.
На рис. 12.1 показаны классы FilterInputstreamи FilterOutputStream. Объединяя их дочерние классы, можно создать свой собственный поток. Например, no умолчанию буферизация потоков не предусмотрена. Это значит, что каждая очередная операция чтения обращается к операционной системе с просьбой выдать очередной байт. Чтобы выполнить буферизацию ввода данных из файла с именем employee. dat, хранящегося в текущем каталоге, нужно реализовать следующую довольно громоздкую последовательность вызовов конструкторов.
DatalnputStream din new DatalnputStream
(new BufferedlnputStream
(new FileInputStreamCemployee.dat")));
Обратите внимание на то, что конструктор DatalnputStream в этой цепочке вызывается последним, поскольку мы хотим одновременно использовать его методы и бу-
феризованный метод read. Несмотря на то что приведенный выше код выглядит устрашающе, это необходимо: мы должны углублять иерархию конструкторов потоков, пока не достигнем нужного результата.
Иногда возникает необходимость отслеживать состояние промежуточных потоков, связанных в цепочку. Например, при считывании данных часто требуется предварительно просмотреть следующий байт и определить, хранится ли там ожидаемое значение. Для этой цели в языке Java предусмотрен класс PushbacklnputStream. PushbacklnputStream pbin = new PushbacklnputStream
(new BufferedlnputStream
(new FileInputStream("employee.dat"; ) ) ;
Теперь можно условно прочитать следующий байт с помощью оператора int b = pbin.read();
и вернуть его обратно, если его значение нам не подходит.
if (Ь != '<'I pbin.unread(b);
Однако чтение и возврат можно выполнять только с потоками класса PushbacklnputStream. Если нужно просмотреть заранее и прочитать числа, следует одновременно применить потоки классов PushbacklnputStream и DatalnputStream. DatalnputStream din new DatalnputStream
(pbin = new PushbacklnputStream
new BufferedlnputStream
(new FileInputStream("employee.dat")));
Разумеется, в библиотеках потоков, написанных на других языках, такие операции, как буферизация и предварительный просмотр, предусмотрены автоматически, поэтому то, что в языке Java для этого требуется образовывать иерархические цепочки конструкторов, создает некоторые трудности. И все же возможность смешивать и подбирать фильтры для создания нужной последовательности потоков обеспечивает невероятную гибкость. Например, можно считывать числа из архивных файлов, записанных в формате ZIP. Для этого нужно создать следующую цепочку потоков (рис. 12.3).
ZipInputStream zin
= new ZipInputStream(new FileInputStreamCemployee.zip"));
Рис. 12.3. Последовательность профильтрованных потоков
(Потоки ZIP-файл он рассматриваются в следующем разделе.)
В общем, не считая довольно громоздких конструкторов, необходимых для фильтрации потоков, возможность смешивать и подбирать нужные потоки представляет собой весьма полезное свойство языка Java!
java.io.FilelnputStream 1.0
FileInputstream(String name)
Создает новый поток ввода из файла, имя которого задается строкой name.
FileInputStream(File f)
Создает новый поток ввода из файла, используя информацию, инкапсулированную в объекте класса File. (Этот класс рассматривается в конце главы.)
java.io.FileOutputStream1.0
FileOutputStream(String name)
Создает новый поток вывода данных в файл, имя которого задается строкой name. Путь к файлу считается относительным. Его отправной точкой является текущий каталог. Внимание. этот метод автоматически удаляет любой ранее существовавший файл с указанным именем.
FileOutputStream(String name, boolean append)
Создает новый поток вывода данных в файл, имя которого задается строкой name. Путь к файлу считается относительным. Его отправной точкой является текущий каталог. Если параметр append имеет значение true, данные дописываются в конец файла. Ранее существовавший файл с указанным именем не удаляется.
FileOutputStream(File f)
Создает новый поток ввода из файла, используя информацию, инкапсулированную в объекте класса File. Внимание. этот метод автоматически удаляет любой ранее существовавший файл, имя которого совпадает с именем файла
java.io.BufferedlnputStream 1.0
BufferedlnputStream(Inputstream in)
Создает новый буферизованный поток ввода. Размер буфера задается по умолчанию. Символы из буферизованного потока ввода считываются по мере их накопления. Если буфер пуст, в него считывается новая порция данных.
BufferedlnputStream(Inputstream in, int n)
Создает новый буферизованный поток ввода. Размер буфера задается пользователем.
java.io.BufferedOutputStream 1.0
BufferedOutputStream(Outputstream out)
Создает новый буферизованный поток вывода. Размера буфера задается по умолчанию. Символы из буферизованного потока выводятся по мере их накопления в буфере. Если буфер полностью заполнен или поток очищен, выводится новая порция данных.
BufferedOutputStream(Outputstream out, int n)
Создает новый буферизованный поток вывода. Размер буфера задается пользователем.
java.io.PushbacklnputStream 1.0
PushbacklnputStream(inputstream in)
Создает поток, в котором можно предварительно просмотреть следующий байт.
PushbacklnputStream(inputstream in, int size)
Создает поток, в котором можно предварительно просмотреть следующий байт, с буфером заданного размера.
void unread(int b)
Заталкивает прочитанный байт обратно. Этот байт будет вновь извлечен при выполнении следующей операции чтения.
Параметры: bБайт, который следует прочитать повторно.
потоки данных
Часто возникает необходимость записать результат вычислений или прочитать его снова. В потоках предусмотрены методы для повторного считывания данных всех основных типов языка Java. Для того чтобы записать число, символ, булевское значение или строку, используется один из перечисленных ниже методов интерфейса DateOutput. writeChars writeByte writelnt writeShort writeLong writeFloat writeDouble writeChar writeBoolean writeUTF
Например, метод writelnt всегда записывает целое число в 4-байтовом бинарном виде, независимо от количества его цифр, а метод writeDouble всегда записывает число типа double в виде 8-байтовой бинарной величины. Текст, полученный в результате такого вывода, нормальный человек прочитать не способен. Однако при этом для записи любого значения определенного типа необходим точно заданный и одинаковый объем памяти. Это значительно ускоряет процесс обратного считывания. (Как вывести числа в удобном для человека виде, вы узнаете в разделе, посвященном классу Printwriter.)
Метод writeUTF записывает строковые данные в формате Unicode Text Format (UTF). Этот формат характеризуется следующими свойствами. Значение, записанное в 7-битовом ACSCII-коде (т.е. 16-битовый символ в кодировке Unicode, у которого старшие 9 битов равны 0), сохраняется как один байт:
036353433323130
Есть два способа хранения чисел в памяти. Они зависят от платформы, на которой вы работаете. Допустим, что вы работаете с 4-байтовыми величинами, скажем, числами типа int или float. Их можно записать таким образом, чтобы первые 4 байт в памяти представляли собой старшие разряды (most significant byte — MSB) числа (прямой порядок записи байтов (big-endian method)), а можно наоборот — записать в них младшие разряды (least significant byte — LSB) числа (обратный порядок записи (little-endian method)). Например, процессоры SPARC используют прямой порядок, а процессоры Pentium — обратный. Это может породить проблемы. Например, при записи файла из программы на языках С или C++ данные будут сохранены в том порядке, в каком они хранятся в процессоре. Таким образом, при переносе такой программы с одной платформы на другую возникнут недоразумения. В языке Java все числа записываются в прямом порядке, независимо от процессора. Это гарантирует платформную независимость файлов, созданных программами на языке Java.
16-битовый символ в кодировке Unicode, 5 старших разрядов которого равны О, записывается в виде 2-байтовой последовательности.
Юзюз^зеЭзЗв10353433323130
(Старший нулевой байт не хранится.)
Все другие символы в кодировке Unicode хранятся в виде 3-байтовой последовательности.
1110315314 31 з 312ЮЗцЗюЗдЗбЗзЗб ЮЗ5З4З3З2З1З0
Этот формат удобен для хранения текстов, состоящих, в основном, из символов в кодировке ASCII, поскольку они по-прежнему записываются в виде одного байта. С другой стороны, для азиатских языков этот формат не подходит, поскольку символы этих языков удобнее записывать непосредственно в 2-байтовой кодировке Unicode. Для этой цели следует использовать метод writeChars.
Обратите внимание на то, что старшие биты в байтах формата UTF определяют природу байта в схеме кодирования.
Оххххххх:ASCII-код.
Юхххххх:Второй или третий байт.
ПОххххх:Первый байт 2-байтовой последовательности.
IllОхххх:Первый байт 3-байтовой последовательности.
Для того чтобы прочитать данные вновь, используются следующие методы.
resdlntreadDouble
readShortreadChar
readLongreadBoolean
readFloatreadUTF
Бинарный формат данных компактен и не зависит от платформы, на которой выполняется программа. За исключением строк в формате UTF, он удобен для произвольного доступа. Его основной недостаток — плохая читабельность.
API
java.io.Datalnput 1.0
boolean readBoolean(;
Считывает булевское значение.
byte readByte(I
Считывает байт, состоящий из 8 бит.
char readChar(I
Считывает 16-битовый символ в кодировке Unicode.
double readDouble()
Считывает 64-битовое число типа double.
float readFloat()
Считывает 32-битовое число типа float.
void readFully(byte[ b)
Считывает байты в массив b, блокируя поток, пока не будут считаны все байты.
Параметры: bБуфер, в который считываются данные.
void readFully(byte[] b, int off, int len)
Считывает байты в массив b, блокируя поток, пока не будут считаны все байты.
Параметры: b
Буфер, в который считываются данные.
off
Начальное смещение данных.
len
Максимальное количество считываемых байтов.
int readlnt()
Считывает 32-битовое целое число.
String readLine(]
Считывает данные в строку, заканчивающуюся управляющими символами "\n", "\r", "\r\n" или маркером конца файла EOF.
long readLong(;
Считывает 64-битовое целое число типа Long.
short readShort()
Считывает 16-битовое целое число типа Short.
String readUTF(]
Считывает строку символов в формате UTF.
int skipBytes(int n)
Пропускает п байтов, блокируя поток, пока все байты не будут пропущены.
Параметры: пКоличество пропускаемых байтов.
java.io.DataOutput1.0
void writeBoolean(boolean b) Записывает булевское значение.
void writeByte(byte b)
Записывает 8-битовый байт.
void writeChar(char с)
Записывает 16-битовый символ в кодировке Unicode.
void writeChars(String s)
Записывает все символы в строке.
void writeDouble(double d)
Записывает 64-битовое число типа double.
void writeFloat(float f)
Записывает 32-битовое число типа float.
void writelnt(int i)
Записывает 32-битовое целое число.
void writeLong(long 1)
Записывает 64-битовое целое число типа Long.
void writeShort(short s)
Записывает 16-битовое целое число типа Short.
void writeUTF(String s)
Записывает строку символов в формате UTF.
Потоки файлов прямого доступа
Класс RandomAccessFile обеспечивает чтение и запись данных в любом месте файла. Он одновременно реализует интерфейсы Datalnput и DataOutput. Файлы, записанные на дисках, обладают произвольным доступом, а потоки данных из сетевых соединений — нет. Файлы с произвольным доступом могут открываться как только для чтения, так и для одновременного чтения и записи. Это зависит от второго параметра конструктора, который принимает значение “r” (доступ для чтения) или “rw” (доступ для чтения и записи).
RandomAccessFile in
= new RandomAccessFile("employee.dat", "r");
RandomAccessFile out
= new RandomAccessFile("employee.dat", "rw");
Если существующий файл открывается с помощью метода RandomAccessFile, он не удаляется.
Файл с произвольным доступом имеет указатель файла (file pointer), который всегда указывает на положение новой записи. Метод seek устанавливает этот указатель на произвольный байт внутри файла. Аргумент метода seek имеет тип long и принимает значение от 0 до максимальной длины файла, выраженной в байтах.
Метод getFilePointer возвращает текущую позицию указателя файла.
Чтобы прочитать данные из файла с произвольным доступом, используются уже известные вам методы, например, readlnt и readUTF, как и для объектов класса DataInputstream. Это не случайно, поскольку данные методы определены в интерфейсе DataOutputStream, который реализуется как классом DatalnputStream, так и классом RandomAccessFile.
Аналогично для того, чтобы записать данные в файл с произвольным доступом, применяются те же методы writelnt и writeUTF, что и в классе DataOutputStream. Эти методы определены в интерфейсе DataOutput, общем для обоих классов.
Преимущество, получаемое в результате одновременной реализации классом RandomAccessFile интерфейсов Datalnput и DataOutput, состоит в том, что программист может создавать методы, типы параметров которого могут быть интерфейсами Datalnput и DataOutput.
class Employee {
read(Datalnput in) { ... }
write(DataOutput out) { ... }
}
Обратите внимание на то, что метод read может обрабатывать объекты классов DataInputstream и RandomAccessFile, поскольку оба эти класса реализуют интерфейс Datalnput. To же относится и к методу write.
java.io.RandomAccessFile 1.0
RandomAccessFile(String name, String mode)
Параметры: name
Имя файла, зависящее от системы.
mode
Принимает значениеесли файл открывается
только для чтения, и "rw", если он открывается для чтения и записи.
RandomAccessFile(File file, String mode)
Параметры: file
Объект класса File, инкапсулирующий системнозависимое имя файла.
mode
Принимает значениеесли файл открывается
только для чтения, и "rw", если он открывается для чтения и записи.
long getFilePointer()
Возвращает текущую позицию указателя файла.
void seek(long pos)
Перемещает указатель файла на pos байтов от начала файла.
long length(]
Возвращает длину файла, выраженную в байтах.
Текстовые потоки
В этом подразделе мы обсудим бинарный ввод и вывод. Насколько бинарный ввод-вывод быстр и эффективен, настолько же он не удобен для чтения человеком. Мы сосредоточимся на вводе-выводе текста. Например, если кисло 12234 сохранить в бинарном виде, оно будет выглядеть как последовательность байтов о 0 00 04 D2 (в шестнадцатеричной системе). В текстовом формате это число будет сохранено в виде строки " 1234 ".
К сожалению, в языке Java для этого нужно выполнить довольно много работы, поскольку в нем используются кодировки Unicode. Таким образом, кодировка символа для строки "1234" фактически будет выглядеть как 00 31 00 32 00 33 00 34в шестнадцатеричной системе). Однако в настоящее время большинство сред, в которых запускаются программы на языке Java, используют свою собственную систему кодирования символов. Она может базироваться на схемах с одним, двумя или несколькими байтами. Например, в среде Windows строка может записываться с помощью ASCII-кода как 31 32 33 34 без дополнительных нулевых байтов. Если текст в файле записан в формате Unicode, весьма вероятно, что адресат не сможет его прочитать. Чтобы преодолеть эту трудность, в языке Java предусмотрен набор фильтров, позволяющих преобразовать текст в формате Unicode в текст, записанный с помощью системы кодирования, принятой в локальной операционной системе. Все эти фильтры расширяют абстрактные классы Reader и Writer, а их имена напоминают имена классов, используемых для чтения и записи бинарных данных. Например, класс InputStreamReader преобразовывает поток ввода, содержащий байты, соответствующие определенной системе кодирования, в объект класса Reader, порождающий символы в системе кодирования Unicode. Аналогично класс OutputStreamWriter преобразует поток символов в системе Unicode в поток байтов, соответствующих заданной системе кодирования.
Рассмотрим, например, как создать объект для распознавания нажатых клавиш и кодировки соответствующих символов с помощью системы Unicode. InputStreamReader in = new InputStreamReader(Syaten.ini
Предполагается, что в локальной системе используется обычная система кодирования символов. Например, в операционной системе Windows применяется кодировка ISO 8859-1 (известная также под названием ISO Latin-1 и получившая в среде программистов прозвище “ANSI-код”).В конструкторе класса InputStreamReader можно задать и другую систему кодирования.
InputStreamReader(Inputstream, String)
Здесь строка описывает схему кодирования, коорую вы хотите использовать. InputStreamReader in = new Inputstreamreader(new
FileinputStream("kremlin.dat" I , "8859_5");
В табл. 12.1 и 12.2 перечислены известные в данный момент схемы кодирования. Локальные схемы кодирования не могут представить все символы, доступные в системе Unicode. Если какой-то символ невозможно перекодировать, он заменяется знаком вопроса"?".
Таблица 12.1. Основные системы кодиров
ИмяОписание
ASCIIАмериканский стандартный код для обмена инфор
мацией
Cpl252Windows Latin-1
ISO68591ISO 8859-1, Latin aplhabet No. 1
Окончание табл.
Имя
Описание
UnicodeBig
16-битовый формат UTF, прямой порядок записи байтов с индикатором порядка записи
UnicodeBigUnmarked
16-битовый формат UTF, прямой порядок записи байтов
UnicodeLittle
16-битовый формат UTF, прямой порядок записи байтов с индикатором порядка записи
UnicodeLittleUnmarked
формат UTF, обратный порядок записи байтов
UTF8
8-битовый формат UTF
UTF-16
16-битовый формат UTF, прямой порядок записи байтов с обязательным индикатором порядка в первом байте
Имя
Название
Big5
Traditional Chinese
СрО37
USA, Canada (Bilingual, French), Netherlands, Portugal, Brazil, Australia
Ср273
IBM Austria, Germany
Ср277
IBM Denmark, Norway
8
IBM Finland, Sweden
Ср2
IBM Italy
Ср2
IBM Catalan/Spain, Spanish Latin America
Ср285
IBM United Kingdom, Ireland
Ср297
IBM France
Ср420
IBM Arabic
Ср42 4
IBM Hebrew
Ср437
MS-DOS United States, Australia, New Zealand, South Africa
Ср500
EBCDIC 500V1
Ср737
PC Greek
Продолжение табл. 12.2
ИмяНазвание
Ср775
Ср838
PC Baltic
IBM Thailand extended SBCS
Ср850
MS-DOS Latin-1
Ср852
MS-DOS Latin-2
Ср855
IBM Cyrillic
Ср85б
IBM Hebrew
Ср857
IBM Turkish
Ср858
ВариантСр850 с Euro-символами
Ср860
MS-DOS Portuguese
Ср861
MS-DOSIcelandic
Ср862
Ср863
PC Hebrew
MS-DOS Canadian French
Ср864
PC Arabic
Ср865
MS-DOSNordic
Ср866
MS-DOS Russian
Ср868
MS-DOS Pakistan
Ср869
IBM Modern Greek
Ср870
IBM Multilingual Latin-2
Ср871
IBM Iceland
Ср874
IBM Thai
Ср875
IBM Greek
Ср918
IBM Pakistan (Urdu)
Ср921
IBM Latvia, Lithuania (AIX, DOS)
Ср922
IBM Estonia (ATX, DOS)
Ср930
КомбинацияJapanese Katakana-K;mji и 4370 UDC, расширение 5026
СрЭЗЗ
Комбинация Korean с 1880 UDC, расширение 5029
Ср935
Комбинация упрошенной схемы Chinese Host с 1880 UDC, расширение 5031
Ср937
Комбинация Traditional Chinese Host с 6204 UDC, расширение 5033
Продолжение табл. 12.2
Имя
Название
Ср939
КомбинацияJapanese Latin Kanji с 4370 UDC, расширение 5035
Ср942
IBM OS/2Japanese, расширение Ср932
Ср942С
ВариантСр942
Ср943
IBM OS/2Japanese, расширение Ср932 и Shift-JIS
Ср943С
ВариантСр943
Ср948
OS/2 Chinese (Taiwan), расширение 938
Ср949
PC Korean
Ср94 9С
Ср950
ВариантСр949
PC Chinese (Hong Kong, Taiwan)
Ср964
AIX Chinese (Taiwan)
Ср970
AIX Korean
СрЮОб
IBM AIX Pakistan (Urdu)
СрЮ25
IBM Multilingual Cyrillic: Bulgaria, Dosnia, Herzegovinia, Macedonia (FYR)
СрЮ26
IBM Latin-5, Turkey
СрЮ46
СрЮ97
IBM Arabic-Windows
IBMIran(Farsi)/Persian
СрЮ98
Ср1112
IBMIran(Farsi)/Persian(PC)
IBM Latvia, Lithuania
Ср1122
IBMEstonia
Ср1123
IBM Ukraine
Ср1124
IBM AIX Ukraine
Ср1140
Комбинация варианта СрО37 и Euro-символов
Ср1141
Комбинация варианта Ср273 и Euro-символов
Ср1142
Комбинация варианта Ср277 и Euro-символов
Ср1143
Ср1144
Комбинация варианта Ср2 78 и Euro-символов
Комбинация варианта Ср2 8 0 и Euro-символов
Ср1145
Комбинация варианта Ср2 8 4 и Euro-символов
Ср1146
Комбинация варианта Ср2 85 и Euro-символов
Продолжение табл. 12.2
Имя
Название
Ср1147
Ср1148
Ср1149
Ср1250
Ср1251
Ср1253
Ср1254
Ср1255
Ср125б
Ср1257
Ср1258
Ср1381
Ср1383
Ср33722 EUC_CN
Комбинация варианта Ср2 97 и Euro-символов
Комбинация варианта Cp50C и Euro-символов
Комбинация варианта Cp871 и Euro-символов
Windows Eastern European
Windows Cyrillic
Windows Greek
Windows Turkish
Windows Hebrew
Windows Arabic
Windows Baltic
Windows Vietnamese
IBMOS/2, DOSPeople's Republic ofChina (PRC)
IBM AIX People's Republic of China (PRC)
IBM-eucJP-Japanese (расширение 5050)
GB2321. EUC encoding, SimlificdChinese
EUC_JP
JIS X 0201, 0208, 0212, EUC encoding, Japanese
EUC_KR
EUCTW
GBK
KSС5601, EUCencoding, Korean
CNS11643 (Plane 1-3), EUC encoding, Traditional Chainese
GBK, Simlified Chinise
ISO2022CN
ISO 2022 CN, Chinese (преобразование только в Unicode)
ISO2022CN_CNS
CNS 2312 в форме ISO 2022 CN, Traditional Chinese (преобразование только в Unicode)
ISO2022CN_GB
GB 2312 в форме ISO 2022 CN, Simplified Chinese (преобразование только в Unicode)
ISO2022JP
Jis 0201, 0208 в форме ISO 2022, Japanese
ISO2022KR
ISO 2022 KR, Korean
ISOS 8 59_2 ISO8859_3 ISO8859_4 IS08859 5
ISO 8859-2, Latin aplhabetNo. 2
ISO 8859-3, Latin aplhabetNo. 3
ISO 8859-4, Latin aplhabetNo. 4
ISO 8859-5, Latin/Cyrillicalphabet
Продолжение табл. 12.2
Имя
Название
ISO8859_€
ISO 8859-6. Latin/Arabic alphabet
ISO8859_7
ISO 8859-7, Latin/Greek alphabet
ISO88598
ISO 8859-8, Latin/Hebrew alphabet
ISO8859_9
ISO 8859-9, Latin aplhabet No. 5
ISO885913
ISO 8859-13, Latin aplhabet No. 7
ISO8859_15_FDIS
ISO 8859-15, Latin aplhabet No. 9
JIS0201
JIS X 0201, Japanese
JIS0208
JISX 0208, Japanese
JIS0212
JISX 0212, Japanese
JISAutoDetect
Распознает и конвертирует Shift-JIS, EUC-JP, ISO 2022 JP (только в Unicode)
Johab
Johab, Korean
K0I8_R
KOI8-R, Russian
MS874
Windows Thai
MS932
Windows Japanese
MS936
Windows Simplified Chinese
MS949
Windows Korean
MS950
Windows Traditional Chinese
MacArabic
Macintosh Arabic
MacCentralEurope
Macintosh Latin-2
MacCroatian
Macintosh Chroatian
MacCirillic
Macintosh Cyrillic
MacDingbat
Macintosh Dingbat
MacGreek
Macintosh Greek
MacHebrew
Macintosh Hebrew
Maclceland
Macintosh Iceland
MacRoman
Macintosh Roman
MacRomania
Macintosh Romania
MacSymbol
Macintosh Symbol
Окончание табл. 12.2
Имя
Название
MacThai
Macintosh Thai
MacTurkish
Macintosh Turkish
MacUkraine
Macintosh Ukraine
SJIS
Shift-JIS, Japanese
TIS62C
TIS620, Thai
Поскольку вполне естественно связать объекты для чтения и записи данных с файлом, была создана пара классов: FileReader и FileWriter. Например, определение объекта
FileWriter out = new FleWriter("output.txt");
эквивалентно такому определению
OutputStreamWriter out = new Outputstreamwriter(new
FileOutputStream("output.txt")) ;
Запись текста в потоквывода
Для вывода текста следует использовать класс Printerwriter. Объекты этого класса могут выводить на печать строки и числа в текстовом формате. Как и класс DataOutputStream, имеющий полезный метод вывода, в котором не уточняется, куда следует выводить данные, класс Printwriter нужно объединять с объектом, определяющим адресата.
PrintWriter out = new Printwriter(new
FileWriter("employee.txt"));
Кроме того, объект класса PrintWriter можно объединять с потоком вывода. PrintWriter out - new Printwriter(new
FileOutputStream("employee.txt"); ;
Конструктор Printwriter(OutputStream) автоматически добавляет объект класса OutputStreamWriter, предназначенный для преобразования символов, записанных в кодировке Unicode, в байты потока.
Записать данные в объект класса PrintWriter можно с помощью методов print и println, которые обычно используются вместе с потоком System. out. Эти методы применяются для вывода чисел, имеющих тип int, short, long, float или double, символов, булевских значений, строк и объектов.
В качестве примера рассмотрим следующий фрагмент программы.
String name = "Harry Hacker";
double salary = 75000;
out.ptint(name);
out.print(' ');
out.printIn(salary);
В результате выполнения этого фрагмента строка
Harry Hacker 75000
будет записана в поток out. Затем символы будут преобразованы в байты и записаны в файл employee.txt.
ветераны программирования на языке Java могут спросить, что случилось с классами Printstream и System.out? В версии Java 1.0 класс Printstream просто усекал все символы, закодированные в системе Unicode, отбрасывая старшие байты, и получал ASCII-коды. И, наоборот, метод readLine класса DatalnputStream преобразовывал ASCII-код символа в код системы Unicode, полагая, что старшие байты равны 0. Очевидно, что такой подход не обеспечивает платформную независимость и ясность программ. В следующей версии (Java 1.1) этот недостаток был исправлен путем внедрения классов Reader и Writer. Для поддержки совместимости с существующими программами классы System.in, System. out и System.err остались потоками. Однако теперь классы PrintStream и PrintWriter конвертируют символы в кодировке Unicode, используя кодировку, предусмотренную по умолчанию в локальной операционной системе. Объекты типа PrintStream функционируют так же, как и объекты класса PrintWriter, в которых предусмотрены методы print и println, за одним исключением: они позволяют посылать им необработанные байты с помощью методов write (inti И write(byte[]).
Метод println автоматически добавляет корректный символ конца строки, принятый в данной операционной системе ("\r\n" — Windows, "\n" — Unix и—
Macintosh). Именно такую строку возвращает вызов System.getProperty ("line.separator").
ЕслиобъектклассаWriterзадает режим автоматической очистки буфера (autoflash mode), то при вызове метода println все символы, находящиеся в буфере, будут отправлены по назначению. По умолчанию режим автоматической очистки отключен. Включить или выключить этот режим можно с помощью конструктора PrintWriter (Writer, boolean), задав в качестве второго параметра соответствующее булевское значение.
PrintWriter out = new Printwriter(new
FileWriter("employee.txt") , true); // Автоматическая очистка.
Методы print не возбуждают никаких исключительных ситуаций. Для проверки ошибок в потоке следует вызывать метод checkError.
Методу PrintWriter нельзя передавать необработанные байты. Этот метод предназначен только для вывода текста.
java.io.Printwriter 1.1
Printwriter(Writer out)
Создает новый объект класса PrintWriter. Режим автоматической очистки буфера отключен.
Параметры: outОбъект класса Writer, предназначенный для вывода
символов.
Printwriter(Writer out, boolean autoflush)
Создает новый объект класса PrintWriter.
Параметры: outОбъект класса Writer, предназначенный для
вывода символов.
autoflush Если задано значение true, методы println будут очищать буфер вывода.
Printwriter(Outputstream out)
Создает новый объект класса PrintWriter. Режим автоматической очистки буфера отключен. Существующий объект класса OutputStream автоматически создает необходимый вспомогательный объект класса OutputStreamWriter.
Параметры: outПоток вывода.
Printwriter(Outputstream out, boolean autoflush)
Создает новый объект класса PrintWriter на основе существующего объекта класса OutputStream. Режим автоматической очистки буфера можно включать и отключать.
Параметры: out
Поток вывода.
autoflush
Если задано значение true, методы println будут очищать буфер вывода.
void print(Object objI
Выводит объект на печать в виде строки, полученной после применения метода toString.
Параметры: objОбъект, подлежащий выводу.
void print(String s)
Выводит на печать строку в системе кодирования Unicode.
void printin(String s)
Выводит на печать строку. Если установлен режим автоматической очистки, очищает поток.
void print(char[; s)
Выводит на печать массив символов в системе кодирования Unicode.
void print(char s)
Выводит на печать символ в системе кодирования Unicode.
void print(int i)
Выводит на печать целое число в текстовом формате.
void print (long i)
Выводит на печать целое число типа long в текстовом формате.
void print(float f)
Выводит на печать число типа floatв текстовом формате.
void print(double d)
Выводит на печать число типа double двойной точности в текстовом формате.
void print(boolean b)
Выводит на печать булевское значение в текстовом формате.
boolean checkError ()
Возвращает значение true, если обнаружена ошибка форматирования или ошибка вывода. После обнаружения ошибки информация в потоке считается испорченной, и все вызовы метода checkError будут возвращать значения true.
Чтение текста из потока ввода
Как мы знаем, для того, чтобы записать данные в бинарном формате, нужно использовать метод DataOutputStream, а для записи данных в текстовом формате применяется метод Printwriter. Следовательно, логично было бы ожидать, что существует аналог класса DataInputStream, позволяющий считывать данные в текстовом формате. К сожалению, в языкеJava такого класса нет. (Именно поэтому в первых главах нашей книги мы были вынуждены создать свой собственный класс Console.) Единственный класс, предназначенный для обработки входных данных в текстовом формате, — класс BufferedReader, в котором есть метод readLine, позволяющий считывать текстовые строки. Объекты этого класса нужно комбинировать с источником данных.
BufferedReader in = new BufferedReader(new
FileReaderfemployee.txt"));
Если дальнейший ввод данных невозможен, метод readLine возвращает ссылку null. Типичный цикл для ввода данных выглядит следующим образом.
String line;
while ((line = in.readLine() 1 != null)
{
Обработка строки
}
Класс FileReader уже конвертирует байты в символы системы Unicode. Для других источников входных данных нужно использовать класс InputstreamReader. В отличие от класса PrintWriter в классе InputStreamReader нет метода, позволяющего преобразовать байты в Unicode.
BufferedReader in2 = new BifferedReader(new
InputStreamReader(System.in));
BufferedReader in3 = new BifferedReader(new
InputStreamReader(url.openstream));
Для того чтобы считать числа, представленные в текстовом формате, нужно сначала прочитать строку, а затем преобразовать ее в число.
String s = in.readLn();
double x = Double.parseDouble(s);
Эти операторы рассчитаны на ввод чисел, записанных по одному в каждой строке. В противном случае нужно приложить больше усилий, например, разбить входную строку с помощью метода StringTokenizer.
В языке Java есть классы и позволяющие работать со строками так, как если бы они были потоками данных. Это довольно удобно, если один и тот же код нужно применить для обработки и строк, и данных в потоке.
Потоки ZIP-файлов
— это архивы, в которых один или несколько файлов хранятся в сжатом виде. В языке 1.1 применяются форматы GZIP и ZIP. (Информацию о форматах RFC 1950, RFC 1951 и 1952 можно найти на
org/rfcs.) В этом разделе мы сосредоточимся на более известном (но иногда и более сложном) формате ZIP, а с форматом читатель при необходимости разберется сам. (Этот формат мало отличается от формата ZIP.)
Классы для обработки ZIP-файлов находятся в файле zip, а не в пакете java.io, поэтому не забудьте добавить оператор import. Несмотря на то что классы GZIP и ZIP не относятся к пакету io, они являются подклассами классов java. и Пакеты также содержат классы для вычисления контрольных сумм с помощьюциклическогоизбыточногокода (CRC). (Метод CRC генерируетпсевдо- случайный код для того, чтобы получатель мог использовать его для проверки целостности файла.)
Каждыйимеет заголовок, в котором хранится информация о нем, на
пример, имя файла и примененный метод сжатия. В языке Java для чтения ZIP-файлов используется класспри этом конструктор такого класса получает в
видеобъект классаЗатем нужно найти конкретный
элемент архива. Метод getNextEntry возвращает объект типа ZipEntry, описывающий этот элемент. Метод read классавозвращает значение -1,
если обнаружен конец текущего элемента (а не конец всегоПосле этого
следует вызвать метод closeEntry и прочитать следующий элемент. Ниже приведена типичная последовательность операторов для чтения ZIP-файлов.
ZipInputStream zin = new ZipInputStream
(new) ;
ZipEntry entry;
while ((entrynull)
{
анализ элемента
чтение содержимого в поток zin;
zin.closeEntry();
}
zin.closed ;
При чтении элемента ZIP-файла можно обойтись без метода read. Обычно программисты предпочитают использовать более тонкие фильтры потоков. Например, для чтения текстового файла из ZIP-архива можно воспользоваться следующим циклом.
in = new
(new;
String s;
while ((s = in.readLineO ) != null) обработка строки s;
Программа, приведенная в листинге 12.1, позволяет открывать ZIP-файл. Затем она отображает файлы, хранящиеся в архиве, в списке, расположенном в верхней части экрана. Если дважды щелкнуть на имени файла, в текстовой области отобразится его содержимое (рис. 12.4).
■■№IB№IB
■
■
№IB
№IB
'i'iHMC»1 rIArlcti rtHchHMC»1 rIArlcti rtHchИ victimИ victimРис. 12.4. Программа ZipTestРис. 12.4. Программа ZipTest
'i
'i
HMC»1 rIArlcti rtHch
HMC»1 rIArlcti rtHch
И victim
И victim
Рис. 12.4. Программа ZipTest
Рис. 12.4. Программа ZipTest
Package com horstmann, mportjMlo*
Aclassforformattingnumbersthatfollows<6»prtntf</tt> con
AlsoimplementsC-llke «в»И01«Л» ana «H»atof</tl* functions
mostextremenegatrva number(-922337:35<:
12.13.14.15.16.17.18.19.20.21.22.23.24.12.13.14.15.16.17.18.19.20.21.22.23.24.import java.awt.*;
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
public class ZipTest
(
public static void main(String[] args) (
ZipTestFrame frame = new ZipTestFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) , j frame.show() ;
}
/**
Фрейм текстовой области, предназначенной для отображения содержимого файла в ZIP-архиве, комбинированный, список для выбора разных файлов в архиве и меню для загрузки нового архива.
*/
42.55.56.57.62.72.73.74.82.42.55.56.57.62.72.73.74.82.class ZipTestFrame extends JFrame
42.
55.
56.
57.
62.
72.
73.
74.
82.
42.
55.
56.
57.
62.
72.
73.
74.
82.
{
public ZipTestFrame()
{
setTitle("ZipTest") ;
setsize(WIDTH, HEIGHT);
// Добавляем меню и пункты "Open" и "Exit".
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("File") ;
JMenuItem openItem = new JMenuItem("Open");
menu.add(openitem);
openitem.addActionListener(new OpenAction());
JMenuItem exititem = new JMenuItem("Exit");
menu.add(exitItern);
exititem.addActionListener(new
ActionListener(;
{
public void actionPerformed(ActionEvent event)
{
System.exit(0);
}
});
menuBar.add(menu);
setJMenuBar(menuBar);
// Добавляем текстовую область и комбинированный список.
fileText = new JTextArea();
fileCombc = new JComboBox();
fileCombo.addActionListener(new
ActionListener ()
{
public void actionPerformed(ActionEvent event)
{
loadZipFile((String)fileCombo.getSelectedltem());
}
}) ;
Container contentPane = getContentPane();
contentPane.add(fileCombo, BorderLayout.SOUTH);
contentPane.add(fileText, BorderLayout.CENTER);
}
/**
Слушатель пункта "File->Open".
*/
private class OpenAction implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
// Предлагаем пользователю выбрать файл.
JFileChooser chooser = new JFileChooser();
chooser.setcurrentDirectory(new File("."));
ExtensionFileFilter filter = new ExtensionFileFilter( ;
filter.addExtension(".zip");
filter.addExtension(".jar”);
85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.109. no. in.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.109. no. in.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.filter.setDescription("ZIP archives");
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
109. no. in.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
109. no. in.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
chooser.setFileFilter(filter; ;
int r = chooser.showOpenDialog(ZipTestFrame.this; ; if (r == JFileChooser.APPROVE_OPTION)
{
zipname = chooser.getSelectedFile().getPath(); scanZipFile();
}
}
}
/**
Просматривает содержимое ZIP-архива и заполняет комбинированный список.
*/
public void scanZipFile(;
<
fileCombo.removeAllItems();
try
{
ZipInputStream zin = new Zipinputstream(new FilelnputStream(zipname));
ZipEntry entry;
while ((entry = zin.getNextEntry()) != null) {
fileCombo.additem(entry.getName());
zin.closeEntry(J ;
}
zin.closet);
}
catch (IOException e)
{
e.printStackTrace();
}
}
/**
Загружает файл из ZIP-архива в текстовую область.
@param name имя файла, хранящегося в архиве
*/
public void loadZipFile(String name)
{
try
{
ZipInputStream zin = new Zipinputstream(new FilelnputStream(zipname));
ZipEntry entry;
fileText.setText("");
// Найти нужный элемент в архиве.
while ((entry = zin.getNextEntryf)) != null) {
if (entry.getName().equals(name)1 {
// Считываем элемент в текстовую область.
BufferedReader in = new BufferedReader(new InputStreamReader(zin)) ;
String line;
while ((line = in.readLine()) != null) {
fileText.append(line);
6.fileText.appendf\n");
}
}
zin.closeEntry();
}
zin.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
publicstaticfinalintWIDTH = 400;
publicstaticfinalintHEIGHT = 300;
private JComboBox fileCombo;
private JTextArea fileText;
private String zipname;
}
/**
Данный фильтр файлов выбирает все файлы с заданными
расширениями. Принадлежит классу FileChooserTest.
*/
class ExtensionFileFilter extends FileFilter
{
/**
Добавляет расширения, распознаваемые фильтром.
@param extension расширение файла (например, ".txt"!
*/
public void addExtension(String extension)
{
if (!extension.startsWith("."))
extension = "." + extension;
extensions .add(extension.toLowerCase (.)');
}
/**
Задает описание файлов, распознаваемых фильтром.
@param aDescription описание распознаваемых файлов
*/
public void setDescription(String aDescription)
{
description = aDescription;
}
/**
Возвращает описание файлов,
распознаваемых данным фильтром.
@return описание распознаваемых файлов
*/
public String getDescription(I
(
return description;
}
■1 -
public boolean accept(File f)
{
751751
751
751
}}ifreturn true;
}
}
String name =;
// Проверяем, имеет ли файл заданное расширение.
for (int i = 0; i < extensions.size(); i++)
if
return true;
return false;
private String description = "";
private ArrayList extensions = new ArrayListO;
}
Если во время считывания произошла ошибка, поток ввода ZIP-файлов возбуждает исключительную ситуацию zipException. Обычно эта ошибка возникает, если поврежден.
Для того чтобы записать ZIP-файл, нужно открыть потокпередав его в качестве параметра конструктору классаДля каждого
элемента, подлежащего сжатию-, нужно создать объект классапередав имя
файла в конструктор класса ZipEntry. Другие параметры, например, дата создания файла и метод распаковки, задаются автоматически. Чтобы начать запись архива, нужно вызвать методкласса ZipOutputStream. Затем файлы посы
лаются в поток ZIP. После этого вызывается метод closeEntry. Эту процедуру необходимо повторять для каждого элемента, подлежащего архивации. Ниже приведен скелет кода, реализующего описанный процесс.
FileOutputStream fout = new zout = new для всех файлов {
ZipEntry ze • newфайла);
;
посылаем данные в поток zout;
zout. closeEntry() ;
}
zout.close();
представляют собойс манифестом. Для чтения и записи
манифеста используются методыи
ярко иллюстрируют мощь, которой обладает понятие потока. И источник, и адресат ZIP-файлов могут быть чем угодно. Для того чтобы прочесть данные, хранящиеся в сжатой форме, нужно передать подходящий поток в конструктор ZIP- потока, причем соответствующий объект даже не узнает, что он считывает сжатые данные. Источником байтов в формате ZIP не всегда бывает файл — сжатые данные могут поступать из сети. Фактически JAR-файлы, которые обсуждались в главе 10, являются Когда загрузчик классов некоторого аплета JAR-файл, он читает и распаковывает данные, поступающие из сети.
A Pjj^Java.util.zip.Zipinputstream 1.1
■"■*• Zipinputstream (Inputstream in)
Этот конструктор создает объект класса ZipInputStream, позволяющий загружать данные из заданного потока InputStream.
Параметры: inПоток ввода.
ZipEntry getNextEntry(]
Возвращает объект класса ZipEntry для следующего элемента или ссылку null, если элементов в архиве больше нет.
void closeEntry()
Закрывает текущий открытый элемент в ZIP-файле. После этого можно прочесть следующий элемент с помощью метода getNextEntry ().
APIJava.util.zip.ZipOutputStream 1.1
^^^l • ZipOutputStream(OutputStream out)
Этот конструктор создает объект класса ZipOutStream, позволяющий загружать данные из заданного потока OutputStream.
Параметры: outПоток вывода.
• void putNextEntry(ZipEntry ze)
Записывает информацию из заданного объекта класса ZipEntry в поток и настраивает этот поток на получение данных.
Параметры: zeНовый элемент.
void closeEntry()
Закрывает текущий открытый элемент в ZIP-файле. Для перехода к следующему элементу использует метод putNextEntry.
void setLevel(int level)
Устанавливает уровень сжатия, принятый по умолчанию. Этот уровень обозначается константой Deflater.DEFAULT_COMPRESSION. Если уровень задан неправильно, возбуждается исключительная ситуация IllegalArgumentException.
Параметры: level Уровень сжатия в диапазоне от О (NO_COMPRESSION) до 9 (BEST_COMPRESSION).
void setMethod(int method)
Задает метод сжатия по умолчанию для класса ZipOutputStream и для любого количества элементов, у которых не заданы никакие методы сжатия.
Параметры: method Метод сжатия (DEFLATED или STORED).
|А P^Java. util. zip. ZipEntry 1.1
^^■•i« ZipEntry (String name)
Параметры: nameИмя элемента.
long getCrc()
Возвращает контрольную сумму CRC32 для заданного объекта класса ZipEntry.
String getName(J
Возвращает имя заданного элемента.
long getSize()
Возвращает размер распакованного элемента или -1, если этот размер неизвестен.
boolean isDirectory(]
Возвращает индикатор наличия или отсутствия элемента в каталоге.
void setMethod
Параметры: method Метод сжатия для заданного элемента (DEFLATED или STORED).
void setSize(long size)
Задает размер данного элемента. Нужен только для метода сжатия STORED.
Параметры: sizeРазмер распакованного элемента.
void setCrc(long crc]
Устанавливает контрольную сумму CRC32 для заданного элемента. Для вычисления этой суммы используется класс CRC32. Требуется только для метода STORED.
Параметры:crcКонтрольная сумма для заданного элемента.
API Java.util.zip.ZipFile 1.1
—*• ZipFile(String name)
Создает объект класса ZipFile для чтения заданной строки.
Параметры: name Строка, содержащая путь к файлу.
ZipFile(File file)
Создает объект класса ZipFile для чтения из заданного объекта класса File.
Параметры: fileФайл для чтения. Класс File описывается в конце
этой главы.
Enumeration entries(’
Возвращает объект класса Enumeration, в котором перечисляются объекты класса ZipEntry, описывающие элементы архива.
ZipEntry getEntry(String name)
Возвращает элемент, соответствующий заданному имени, или ссылку null, если такого элемента нет.
Параметры: nameИмя элемента.
Inputstream getlnputStreamf ZipEntry ze)
Возвращает объект класса InputStream для заданного элемента.
Параметры: zeОбъект класса ZipEntry в ZIP-файле.
String getName(;
Возвращает путь к данному ZIP-файлу.
Использование потоков
В следующих четырех подразделах мы покажем, как использовать достоинства некоторых из представителей многочисленного семейства потоков. Будем предполагать, что мы работаем с классом Employee и производными от него классами, например, классом Manager. (Более подробно эти классы описаны в главах 5 и 6.) Рассмотрим четыре разных сценария, которые могут осуществляться при записи массива, в котором хранится информация о сотрудниках, в файл, а затем при считывании его обратно в память.
Сохранение данных одного и того же типа (Employee) в текстовом формате.
Сохранение данных одного и того же типа в бинарном формате.
Сохранение и считывание полиморфных данных (комбинация объектов типа Employee и Manager).
Сохранение и считывание данных, содержащих встроенные ссылки (объекты класса Manager с указателями на другие объекты класса Employee).
Запись выходных данных с разделителями
В этом подразделе мы рассмотрим, как следует сохранять данные в массиве, состоящем из объектов типа Employee, в широко известном формате с разделителями (delimited format). Это значит, что каждая запись расположена в отдельной строке. Поля отделяются друг от друга разделителями. В качестве разделителя будет использоваться вертикальная черта (|). (Популярен также разделитель ":".) Естественно, мы узнаем, что может случиться, если разделитель " | " действительно окажется внутри одной из сохраняемых строк.
В операционной системе UNIX большинство файлов хранится именно в таком формате. Нам известны случаи, когда в этом формате хранились целые базы данных о сотрудниках, содержащие тысячи записей, причем для работы с ними не требовалось ничего, кроме утилит системы UNIX awk, sort и join. (В сфере применения персональных компьютеров, где программы для работы с базами данных достаточно дешевы, такой формат применяется намного реже.)
Вот пример такой записи.
Harry Hacker I 35500 11989 110 11
Carl Cracker17500011987112115
Tony Tester13800011990,3115
Сохранить эти записи достаточно легко. Поскольку мы сохраняем текстовый файл, следует использовать класс Printwriter. Мы просто записываем в файл все
поля, за которыми следует разделитель " | ", а если запись является последней, за ней указывается символ "\п". Затем, вспомнив, что нам нужен класс, реагирующий на сообщения, добавим в класс Employee метод writeData.
public void writeData(Printwriter out) throws IOException
{
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(hireDay);
out.printIn(name + "I"
+ salary + "|"
+ calendar.get(Calendar.YEAR) +"I"
+ (calendar.get(Calendar.MONTH) +1) + "I"
+ calendar.get(Calendar.DAY_OF_MONTH)) ;
}
При чтении записи вводятся строка за строкой, а затем из этих строк выделяются поля. Это является темой следующего подраздела, в котором используется вспомогательный класс, предусмотренный в языкеJava для облегчения этой работы.
Класс StringTokenizerи размеченный текст
При считывании мы получаем одну очень длинную строку. Затем ее нужно разбить на отдельные строки. Для этого необходимо найти разделитель " |" и выделить отдельные части, т.е. последовательности символов вплоть до следующего разделителя. (Эти части называются лексемами (tokens).) Класс StringTokenizer из пакета java. util предназначен именно для этой цели. Он позволяет легко разбивать на части длинные строки, в которых записан размеченный текст. Идея заключается в том, что объект этого класса присоединяется к строке. Создавая такой объект, нужно указать символ-разделитель.
StringTokenizer t = new StringTokenizer(line, "I");
В одной строке может быть несколько разделителей. Например, можно создать объект класса StringTokenizer, позволяющий найти любой из заданных разделителей
"\t\\n\r":
StringTokenizer t = new StringTokenizer(line, " \t\r\n"); (Это значит, что любой пробел также является разделителем.)
Если объект класса StringTokenizer создается с помощью оператора StringTokenizer t = new StringTokenizer (line), эти четыре разделителя используются по умолчанию.
Объекты класса StringTokenizer позволяют быстро извлекать лексемы из строки. Метод nextToken возвращает следующую непрочитанную лексему. Метод hasMoreToken возвращает значение true, если в строке еще остались непрочитанные лексемы.
В данном случае нам известно, сколько лексем хранится в каждой строке. В общем случае следует быть более осторожным: метод hasMoreToken следует вызывать до метода nextToken, поскольку, если лексем больше нет, метод nextToken может возбудить исключительную ситуацию.
API java.util.StringTokenizer 1.0
Vstr, String delim)
Параметры: strВходная строка, ИЗ которой считываются лексемы.
delim Строка, в которой записаны символы-разделители (каждый символ в этой строке является разделителем).
StringTokenizer(String str)
Создает объект класса StringTokenizer с разделителями, заданными по умолчанию (" \t\n\r").
boolean hasMoreTokens()
Если в строке еще есть лексемы, возвращает значение true.
String nextToken()
Возвращает следующую лексему. Если лексем больше нет, возбуждает исключительную ситуацию NoSuchElementException.
String nextToken(String delm)
После переключения на новое множество разделителей возвращает следующую лексему. Далее используется новое множество разделителей.
int countTokens()
Возвращает количество лексем, оставшихся в строке.
Чтение размеченного текста
Записи класса Employee считываются довольно просто: метод readLine из класса BufferedReader считывает их строка за строкой. Для того чтобы внести одну запись в отдельную строку, нужен следующий фрагмент кода.
BufferedReader in
= new Bufferedreader(new FileReader("employee.dat"));
String line = in.readLine();
Затем нужно выделить отдельные лексемы. После этого мы получаем строки, которые следует преобразовать в числа.
В класс Employee необходимо добавить метод readData, а затем вызвать его. e.readData(in);
Этот метод уничтожает предыдущее содержимое объекта е, заменяя его новой строкой. Учтите, что такой метод может возбуждать исключительную ситуацию IOException, если это позволено методу readLine. Если подобная исключительная ситуация все же возникнет, метод ничего не сделает, поэтому ее следует передать по цепочке вызовов для дальнейшей обработки.
Вот код этого метода.
public void readData(BufferedReader in) throws lOException (
String s = in.readLine();
StringTokenizer t = new StringTokenizer(s, "I");
name = t .nextTokenО;
salary = Double.parseDouble(t.nextToken());
int у = Integer.parselnt(t.nextToken()); int m=
int d = Integer.parselnt(t.nextToken());
GregorianCalendar calendar
= new GregorianCalendar(y, m - 1, d) ;
// В классе GregorianCalendar январь обозначается нулем. hireDay = calendar.getTime();
Итак, в программе, тестирующей этот метод, статический метод
void writeData(Employee(j е, PrinWriter out)
сначала записывает длину массива, а затем — каждую запись. Статический метод
Employee[I readData(BufferedReader in)
сначала считывает длину массива а затем читает каждую запись, как показано в лис- тинге12.2.
1.4.17.18.24.25.26.27.35.36.37.1.4.17.18.24.25.26.27.35.36.37.import java.io.*;
1.
4.
17.
18.
24.
25.
26.
27.
35.
36.
37.
1.
4.
17.
18.
24.
25.
26.
27.
35.
36.
37.
import java.util.*;
public class DataFileTest
public static void main(String[] args)
(
Employee!] staff = new Employee[3];
staff[O' = new Employee("Carl Cracker", 75000, 1987, 12, 15);
staff[l] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2‘ = new Employee("Tony Tester", 40000, 1990, 3, 15);
try
{
// Записать все данные о сотрудниках в файл
// employee.dat
PrintWriter out = new Printwriter(new
Filewriter("employee.dat"));
writeData(staff, out);
out.close();
// Поместить все записи в новый массив.
BufferedReader in = new BufferedReader(new FileReader("employee.dat"));
Employee!] newStaff = readData (in);
in.close();
// Распечатать вновь прочитанные записи.
for (int i = 0; i < newStaff.length; i++) System.out.printIn(newStaff[i]);
}
catch(lOException exception)
{
exception.printStackTrace();
38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.}
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
/**
Записывает все объекты класса Employee в массив
и передает объекту класса
e массив объектов класса Employee
Sparam out объект класса PrintWriter
*/
static voide, PrintWriter out)
throws IOException
{
// Количество сотрудников.
out.printin(e.length);
for (int i = 0; i < e.length; i++)
e[i].writeData(out);
}
/**
Считывает массив объектов класса Employee из объекта класса
@param in Объект класса
массив объектов класса Employee
*/
static Employee!]in)
throws IOException
{
// Определяем размер массива.
int n =
Employee!] e = new
for (int i = 0; i < n; i++) {
e[i] = new Employee();
(in);
}
return e;
}
}
class Employee
{
public{}
publicn, double s,
int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= newmonth - 1, day);
// В классе GregorianCalendar 0 означает январь. hireDay =
}
public String
{
return name;
97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.148.149.150.151.152.153.154.p{ublic double getSalary(] } return salary;
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
148.
149.
150.
151.
152.
153.
154.
public Date getHireDayO
} return hireDay;
public void raiseSalary(double byPercent)
double raise = salary * byPercent / 100; } salary += raise;
p{ublic String toStringOp{ublic String toStringO}}return getClass() .getName()
p{ublic String toStringO
p{ublic String toStringO
}
}
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay + "]";
/**
Записывает данные в объект класса PrintWriter.
@param out объект класса PrintWriter
*/
public void writeData(Printwriter out) throws IOException
{
GregorianCalendar calendar = new GregorianCalendar() ;
calendar.setTime(hireDay) ;
out.printin(name + "I"
+ salary + " |"
+ calendar.get(Calendar.YEAR) +
+ (calendar.get(Calendar.MONTH) + 1) + "|"
+ calendar.get(Calendar.DAY_OF_MONTH));
}
/**
Считывает данные из объекта класса BufferedReader.
@param in объект класса BufferedReader
*/
public void readData(BufferedReader in) throws IOException {
String s = in.readLine();
StringTokenizer t = new StringTokenizer(s, "I");
name = t.nextToken() ;
salary = Double.parseDouble(t.nextToken());
int у = Integer.parselnt(t.nextToken!)); int m=
int d = Integer.parselnt(t.nextToken());
GregorianCalendar calendar
= new GregorianCalendar(y, m - 1, d) ;
// В классе GregorianCalendar 0 означает январь.
hireDay = calendar.getTime();
}
private String name;
private double salary;
private Date hireDay;
}
Потоки с произвольным доступом
Если у вас много записей переменной длины, способ хранения данных, описанный в предыдущем подразделе, имеет один недостаток: запись, хранящуюся в середине файла, невозможно прочитать, не читая все предыдущие записи. Будем считать, что все записи имеют одинаковую длину. Это позволит нам реализовать метод произвольного доступа с помощью уже знакомого нам класса RandomAccessFile. Его можно использовать для получения любой записи, причем на это всегда будет уходить одно и то же время.
В полях нашего класса будут храниться числа, записанные в бинарном формате с помощью методов writelnt и writeDouble, относящихся к интерфейсу DataOutput. (Как уже отмечалось, этот интерфейс является общим для классов DataOutputStream и RandomAccessFile.)
Однако, поскольку размер каждой записи должен оставаться постоянным, размер строк нужно сделать одинаковым. Формат UTF, символы которого имеют разный размер, для этой цели не подходят, и в библиотеке языка Java таких методов тоже нет. Поэтому нам придется написать два вспомогательных метода, которые позволят выровнять строки по заданному размеру. Назовем эти методы writeFixedString и readFixedString. Они считывают и записывают строки в кодировке Unicode, всегда имеющие одинаковую длину.
Метод writeFixedString получает параметр size. Затем он записывает указанное количество символов, отсчитывая от начала строки. (Если символов недостаточно, в строку добавляются символы, код которых равен 0.)
Вот как выглядит метод writeFixedString.
static void writeFizxedString(String s, int size,
DataOutput out) throws IOException {
int i;
for (i=0; i < size; i++)
{
char ch = 0;
• if (i < s.kengthO) ch = s.charAt(i); out.writeChar(ch);
}
Метод readFixedString считывает символы из входного потока, пока их количество не станет равным параметру size или не встретится символ, имеющий код 0 в системе Unicode. Оставшиеся нулевые символы в поле ввода игнорируются.
Для большей эффективности этот метод для чтения строк использует вспомогательный класс StringBuffer, позволяющий выделять динамическую память заданного размера. Размер строки в байтах задается параметром size. Создается строковый буфер, в котором резервируется size байтов. Символы добавляются в буфер по мере считывания.
Использование класса более эффективно, чем считывание символов и добавление их к существующей строке. Каждый раз, когда символ добавляется к строке, объект класса должен найти свободную память, в которую можно поместить увеличившуюся строку: на это уходит много времени. Добавление всего лишь одного символа к строке означает все новое и новое выделение памяти. Класс StringBuffer позволяет этого избежать.
Когда в строковом буфере будет записана нужная строка, нам понадобится преобразовать ее в настоящий объект класса String. Для этого нужно вызвать конструктор String (StringBuffer b) или метод StringBuffer . toString (). Эти методы не копируют символы из строкового буфера в строку. Вместо этого они фиксируют (freeze) содержимое буфера. При дальнейших вызовах методов, модифицирующих объект класса StringBuffer, этот объект будет просто получать новую копию символов и модифицировать именно ее. Объект класса String хранит зафиксированное содержание буфера. public static voids, int size,
DataOutput out) throws IOException { int i; for (i =0; i < size; i++) { char0;
if (i < s.lengthO) ch =
} out.writeChar(ch); }
Для того чтобы сохранить запись фиксированной длины, нужно просто записать все поля в бинарном виде. public voidout) throws IOException
{
out); out.writeDouble(salary);
GregorianCalendar calendar = new calendar.setTime(hireDay); out.writelnt(calendar.get(Calendar.YEAR));
+1);
out.writelnt(calendar.get(Calendar.DAY_OF_MONTH)); }
Прочитать данные обратно так же просто.
public voidin) throws IOException
{ name =in);
salary = int у = in.readlnt();/
int=
int d = GregorianCalendar calendar = new- 1, d);
// GregorianCalendar uses 0 = January hireDay =
В нашем примере длина каждой записи равна 100 байт, поскольку мы указали, что имя name всегда записывается с помощью 40 символов. В таком случае мы имеем следующие параметры.
40 символов =80 байт для записи имени 1 double = 8 байт
3 int = 12 байт
Допустим, что нам нужно переместить указатель файла на третью запись. Используем следующий вариант метода seek.
long n = 3;
int RECORD_SIZE = 100;
in.seek((n - 1) & RECORD_SIZE);
Теперь можно прочитать запись.
Employee e = new Emplotee();
e.readData(in);
Если вы хотите изменить запись, а затем вставить ее на то же самое место, не забудьте переместить указатель на начало этой записи.
in.seekf(n - 1) * RECORD_SIZE;
e.wtiteData(in);
Чтобы определить полное количество байтов в файле, используйте метод length. Общее количество записей равно длине файла, поделенной на размер каждой записи. long int nbytes = in.lengthO; // Длина в байтах.
inbt nrecords = (int)(nbytes / RECORD_SIZE);
Тестовая программа, приведенная в листинге 12.3, сохраняет три записи в файл, а затем считывает их оттуда в обратном порядке. Для того чтобы сделать это эффективно, необходим произвольный доступ — сначала нам нужно считать третью запись.
•10.11.12.13.14.15.16.17.18.19.20.21.22.23.•10.11.12.13.14.15.16.17.18.19.20.21.22.23.import java.io.*;
•10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
•10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
import java.util.*;
public class RandomFileTest {
public static void main(String[] args) {
Employee!] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker", 75000, 1987,12,15);
staff[1] = new Employee("Harry Hacker", 50000, 1989,10,1);
try{// Сохраняем все записи в файле employee.dat.DataOutputStream out = new DataOutputStream(new FileOutputStreamCemployee.dat"));for (int i = 0; i < staff.length; i++) staff[i].writeData(out);try{// Сохраняем все записи в файле employee.dat.DataOutputStream out = new DataOutputStream(new FileOutputStreamCemployee.dat"));for (int i = 0; i < staff.length; i++) staff[i].writeData(out);staff[2] = new Employee("Tony Tester", 40000, 1990,3,15);
try
{
// Сохраняем все записи в файле employee.dat.
DataOutputStream out = new DataOutputStream(new FileOutputStreamCemployee.dat"));
for (int i = 0; i < staff.length; i++) staff[i].writeData(out);
try
{
// Сохраняем все записи в файле employee.dat.
DataOutputStream out = new DataOutputStream(new FileOutputStreamCemployee.dat"));
for (int i = 0; i < staff.length; i++) staff[i].writeData(out);
24.24.25.25.26.27.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44 .45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44 .45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.out.closed ;
24.
24.
25.
25.
26.
27.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44 .
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44 .
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
// Извлекаем все записи в новый массив.
RandomAccessFile in
= new RandomAccessFile("employee.dat", "r");
// Вычисляем размер массива.
int n = (int) (in.lengthd / Employee.RECORD_SIZE);
Employee!] newStaff = new Employee[n];
// Считываем записи в обратном порядке.
for (int i = n - 1; i >= 0; i—)
{
newStaff[ij = new Employee!);
in.seekd * Employee.RECORD_SIZE);
newStaff[i].readData(in);
}
in.close ();
// Распечатываем вновь прочитанные записи.
for (int i = 0; i < newStaff.length; i++) System.out.printin(newStaff[i]) ;
}
catch(lOException e)
{
e.printStackTrace();
}
}
}
class Employee
{
public Employee() {}
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar 0 обозначает январь. hireDay = calendar.getTime();
}
public String getNamed •
{
return name;
}
public double getSalary(]
{
return salary;
}
public Date getHireDay(I
{
return hireDay;
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toStringO
{
return getClass () .getName(;
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]" ;
}
/**
Записывает объекты класса Employee в поток вывода.
@param out поток вывода
*/
public void writeData(DataOutput out) throws lOException
{
DatalO.writeFixedString(name, NAME_SIZE, out);
out.writeDouble(salary) ;
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(hireDay);
out.writelnt(calendar.get(Calendar.YEAR));
out.writelnt(calendar.get(Calendar.MONTH) + 1) ;
out.writelnt(calendar.get(Calendar.DAY_OF_MONTH));
}
/**
Считывает объекты класса Employee из потока ввода.
@param in поток ввода
*/
public void readData(Datalnput in) throws lOException
{
name = DatalO.readFixedString(NAME_SIZE, in);
salary = in.readDouble ();
int у = in.readlnt;) ;
int m=
int d = in.readlnti ) ;
GregorianCalendar calendar
= new GregorianCalendar(y, m -d) ;
// В классе GregorianCalendar 0 обозначает январь.
hireDay = calendar.getTime();
}
public static final int NAME_SIZE = 40;
public static final int RECORD_SIZE
= 2 * NAME_SIZE + 8 + 4 + 4 + 4;
private String name;
private double salary;
private Date hireDay;
}
class DatalO {class DatalO {public static String readFixedString(int size, Datalnput in) throws IOException
class DatalO {
class DatalO {
{{StringBuffer b = new;
{
{
int i = 0; boolean more = true;
while (more &s i < size)
(
char ch =;
i++;
if (ch == 0) more = false;
else b.append(ch);
}
in.skipBytes(2 * (size - i));
return b.toString() ;
•160.public static voidwriteFixedString(String s161.DataOutput out)throws IOException162.{163.int i;164.for (i =0 ; i <size; i++)165.{166.char ch = 0;167.if (i < s.lengthO) ch = s.charAt(i);•160.public static voidwriteFixedString(String s161.DataOutput out)throws IOException162.{163.int i;164.for (i =0 ; i <size; i++)165.{166.char ch = 0;167.if (i < s.lengthO) ch = s.charAt(i);}}out.writeChar(ch);out.writeChar(ch);}}168.169.170.171.168.169.170.171., int size,, int size,}
•160.
public static void
writeFixedString(String s
161.
DataOutput out)
throws IOException
162.
{
163.
int i;
164.
for (i =0 ; i <
size; i++)
165.
{
166.
char ch = 0;
167.
if (i < s.lengthO) ch = s.charAt(i);
•160.
public static void
writeFixedString(String s
161.
DataOutput out)
throws IOException
162.
{
163.
int i;
164.
for (i =0 ; i <
size; i++)
165.
{
166.
char ch = 0;
167.
if (i < s.lengthO) ch = s.charAt(i);
}
}
out.writeChar(ch);
out.writeChar(ch);
}
}
168.
169.
170.
171.
168.
169.
170.
171.
, int size,
, int size,
}}java.lang.StringBuffer 1.0
}
}
StringBuffer()
Создаетпустойстроковыйбуфер.
StringBuffer(int length)
Создает пустой строковый буфер, имеющий начальную емкость, равную параметру length.
StringBuffer(String str)
Создает строковый буфер, содержащий строку str.
int length('
Возвращает количество символов в буфере.
inr capacity(I
Возвращает текущую емкость буфера, т.е. количество символов, которые можнозаписатьвбуфербезвыделениядополнительнойпамяти.
void ensureCapacity(int m)
Увеличивает буфер, если его емкости не достаточно для хранения m сим-
волов.
void setLength(int n)
Если значение параметра п меньше текущей длины буфера, символы в конце строки игнорируются. В противном случае буфер дополняется символами ' \O'.
char charAt(int i)
Возвращает i-й символ (число i может изменяться от 0 до lengh () - 1)).
Если индекс задан неправильно, возбуждает исключительную ситуацию
StringlndexOutOfBoundsException.
void getChars(int from, int to, char[] a, int offset) Возвращает символы из строкового буфера в массив.
Параметры: from Первый символ, подлежащий копированию.
toПервый символ, не подлежащий копированию.
аМассив, в который производится копирование.
offset Первая позиция в массиве а, в которую произво- дитсякопирование.
void setCharAt(int i, char ch)
Заменяет i-й символ символом ch.
StringBuffer append(String str)
Добавляет строку в конец буфера (в результате может произойти перераспределение памяти). Возвращает указатель this.
StringBuffer append(char с)
Добавляет символ в конец буфера (в результате может произойти перераспределение памяти). Возвращает указатель this.
StringBuffer insert(int offset, String str)
Вставляет строку в ячейку буфера, определенную параметром offset (в результате может произойти перераспределение памяти). Возвращает указатель this.
StringBuffer insert(int offset, char c)
Вставляет символ в ячейку буфера, определенную параметром offset (в результате может произойти перераспределение памяти). Возвращает указатель this.
String toStringt)
Возвращает строку, указывающую на те же самые данные, что и в буфере. (Копия не создается.)
java.lang.String 1.0
String(StringBuffer buffer)
Возвращает строку, указывающую на те же самые данные, что и в буфере. (Копия не создается.)
Объектныепотоки
Формат, в котором записи имеют фиксированную длину, хорош, если данные имеют одинаковый тип. Однако объекты, создающиеся в объектно-ориентированных программах, редко относятся к одному и тому же типу. Например, массивно
минально имеющий тип Employee, фактически содержит объекты дочерних классов, например, класса Manager.
При сохранении таких файлов сначала нужно записать тип каждого объекта, а затем — данные, определяющие его текущее состояние. При считывании этой информации из файла необходимо сделать следующее.
Прочитать тип объекта.
Создать пустой объект этого типа.
Заполнить его данными, хранящимися в файле.
В принципе, это можно сделать вручную (хотя и очень утомительно). Именно так мы и поступали в первых изданиях нашей книги. Однако компания Sun Microsystems разработала мощный механизм, позволяющий проделать это намного эффективнее. Этот механизм, известный под названиемобъектов (object serialization)
полностью автоматизирует описанную выше процедуру. (Позднее мы объясним, откуда происходит название "сериализация".)
Сохранение объектов переменного типа
Для того чтобы сохранить данные, сначала необходимо открыть объект класса ObjectOutputstream.
ObjectOutputStream out = new
FileOutputStream("employee.dat"));
Теперь сохранить объект можно с помощью методакласса
ObjectOutputStream..
Employee harry = newHacker", 50000,
1989, 10, 1)
Manager boss = newCracker", 80000,
1987, 12, 15)
out.writeObject(harry);
out.writeObject(boss);
Для того чтобы прочитать объект из файла в память, сначала нужно получить объ- ектклассаInputStream.
in = new
FileInputStream("employee.dat"));
Затем нужно извлечь объекты в том же порядке, в котором они были записаны, используя методect.
Employee el - (Employee)in.readobject();
Employee e2 =
При считывании объектов из файла следует внимательно следить за их количеством, порядком и типами. Каждый вызов метода readOb j ect приводит к считыванию следующего объекта классаСледовательно, их необходимо привести к правиль-
ному типу. Если это не нужно или вы забыли правильный тип, можно привести тип объекта к любому суперклассу или даже просто оставить тип Object. Например, переменная е2 представляет собой объект класса Employee, даже если она ссылается на объект класса Manager. Для динамического запроса о типе объекта можно использовать метод getClass, описанный в главе 5.
С помощью методов writeObject/readobjectможно записывать и читать только объекты, а не числа. Для того чтобы записывать и читать числа, применяются методы writelnt/readlnt или writeDouble/readDouble. (Классы объектных потоков реализуют интерфейсы Datalnput/DataOutput.) Разумеется, числа, хранящиеся внутри объектов (например, в полях salary объектов класса Employee), записываются и считываются автоматически. Напомним, что в языке Java строки и массивы представляют собой объекты и, следовательно, могут записываться и считываться с помощью методов writeObj ect/readObject.
Однако в каждый класс, который записывает и считывает объекты из объектного потока, нужно внести одно изменение. Такой класс должен реализовывать интерфейс Serializable.
class Employee implements Serializzable (...)
В интерфейсе Serializable нет никаких методов, поэтому сам класс изменять не требуется. Это напоминает интерфейс Cloneable, рассмотренный в главе 5. Однако для того, чтобы сделать класс клонируемым, приходилось замещать метод clone из класса Object.Для сериализации класса не нужно делать вообще ничего. Тогда почему все классы не сериализуются по умолчанию? На этот вопрос мы ответим в подразделе "Безопасность".
В листинге 12.4 приведена тестовая программа, записывающая на диск массив, содержащий два объекта класса Employee и один объект класса Manager, а затем считывающая их оттуда. Запись массива выполняется одним оператором.
Employee[ staff = new Employee[ 3 ];
out.writeObject(staff);
Чтение массива выполняется также одним оператором. Однако после считывания значение, возвращаемое методом readObj ect, необходимо привести к правильному типу. Employee!] newtaff =
После считывания информации оклад каждого сотрудника увеличивается на 100%, не потому, что мы такие щедрые, а потому что после этого мы сможем легко отличать объекты классов Employee и Manager по их методам raiseSalary. Это позволит нам проверять, правильный ли у объекта тип.
.import java.io.*;
. import java.util .* ;
class ObjectFileTest
{
public static void main(String[ args)
V.{
Manager boss = new Manager("Carl Cracker", 80000,
1987,12, 15) ;
22.23.24 .25.26.27.32.33.34.47.48.49.22.23.24 .25.26.27.32.33.34.47.48.49.boss.setBonus(5000);
22.
23.
24 .
25.
26.
27.
32.
33.
34.
47.
48.
49.
22.
23.
24 .
25.
26.
27.
32.
33.
34.
47.
48.
49.
Employee[ staff = new Employee[ 3 ];
staff(0= boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2: = new Employee("Tony Tester", 40000, 1990, 3, 15);
try
{
// Сохраняем все записи в файле employee.dat.
Objectoutputstream out = new ObjectOutputStream(new FileOutputStream("employee.dat"));
out.writeObject(staff);
out.close();
// Извлекаем все записи в новый массив.
ObjectInputStream in = new Objectinputstream(new FileInputStream("employee.dat"));
Employee(' newStaff = (Employee[])in.readobject(); in.close();
// Распечатываем вновь прочитанные записи.
for (int i = 0; i < newStaff.length; i++) System.out.println(newStaff[i]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class Employee implements Serializable
{
public Employee() {}
public Employee(String n, double s,int year, int month, int day)
{ name = n; salary = s; GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar число 0 обозначает январь. hireDay = calendar.getTime();
}
public String getName()
{ return name;
}
public double getSalaryO
{ return salary;
)
92.100101102103104105106107108109112113114115116.117118119120121.12212312412512612792.100101102103104105106107108109112113114115116.117118119120121.122123124125126127public Date getHireDay()
92.
100
101
102
103
104
105
106
107
108
109
112
113
114
115
116.
117
118
119
120
121.
122
123
124
125
126
127
92.
100
101
102
103
104
105
106
107
108
109
112
113
114
115
116.
117
118
119
120
121.
122
123
124
125
126
127
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100; salary += raise;
}
public String toString()
{
return getClass().getName(]
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]";
}
private String name;
private double salary;
private Date hireDay;
}
class Manager extends Employee
{
/**
@param n имя сотрудника
@param s оклад
@param year год найма на работу
Sparam month месяц найма на работу
Sparam day день найма на работу
*/
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary(;
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double b)
{ bonus = b;
}
public String toString(I
{
return super.toString()
+ "[bonus=" + bonus
+ "]";
}
private double bonus;
}
1.1
out)
Создает объект классапосле чего можно записы
вать объекты в указанный поток
void
Записывает указанный объект в потокЭтот метод
записывает класс объекта, сигнатуру класса, а также значение всех нестатических и нетранзитных полей класса и его суперклассов.
out)
Создает объект класса ObjectlnputStream, после чего можно считывать объекты в указанный поток InputStream.
• void
Считывает объект из потокаВ частности, этот метод
считывает класс объекта, сигнатуру класса, а также значение всех нестатических и нетранзитных полей класса и его суперклассов. Он выполняет десе- объекта, чтобы восстановить множественные ссылки на объект.
Формат файла, используемый при сериализации
объекта
При сериализации объекта данные записываются в особом формате. Разумеется, можно использовать методы writeObject/readObject и не интересоваться точным порядком следования байтов, представляющих объект в файле. Тем не менее знать этот формат чрезвычайно полезно, поскольку он позволяет глубже понять процессы, происходящие с объектными потоками. Рассмотрим шестнадцатеричные распечатки (hex dumps) файлов, в которые записаны объекты. Иногда детали будут слишком техническими, поэтому, если вас это не интересует, можете смело пропустить этот подраздел.
Каждый файл начинается с 2-байтового "магического числа".
ED
За ним записывается номер версии формата, использованного при сериализации объекта.
00 05
(Здесь и далее шестнадцатеричные числа обозначают байты.) Далее идет последовательность объектов в том порядке, в котором они были записаны в файл.
Строковые объекты записываются в следующем виде.
742-байтовая длинасимволы
Например, строка "Harry" сохраняется в следующем виде.
74 00 05 Harry
Символы в кодировке Unicode записываются в формате UTF.
После самого объекта должен быть записан его класс. Дескриптор класса состоит из следующих частей.
Имя класса.
Уникальный серийный номер версии ID (serial version unique ID), который указывает типы полей и сигнатуры методов.
Набор опций, описывающих метод сериализации.
Дескрипторы полей данных.
Серийный номер формируется следующим образом.
Упорядочиваются дескрипторы класса, суперкласса, интерфейсов, типов полей и сигнатур методов, записанных обычным способом.
Затем к этим данным применяется алгоритм SHA (Secure Hash Algorithm).
SHA — очень быстрый алгоритм, позволяющий получить "отпечаток" крупного блока информации. Этот отпечаток всегда представляет собой 20-байтовый пакет, независимо от размера исходного блока. Он образуется в результате изощренных операций, гарантирующих, что при изменении исходных данных "отпечаток" также изменится. Алгоритм SHA стал американским стандартом, рекомендованным Национальным институтом науки и технологии (National Institute for Science and Technology— NIST). Однако ЯЗЫК Java использует только 8 байт SHA-кода в качестве "отпечатка" класса. Вероятность того, что при изменении класса его "отпечаток" также изменится, очень велика.
"Отпечатки" классов можно проверять, защищая классы от следующего сценария: 1) объект записывается на диск; 2) разработчик класса вносит в него изменения, например, удаляя какое-то поле; 3) старый файл прочитывается снова; 4) теперь данные, записанные на диске, не соответствуют данным, записанным в памяти. Читение данных в старом формате может разрушить память. Язык Java делает это событие крайне маловероятным. Итак, для предотвращения такого развития событий при считывании объектов всегда проверяются "отпечатки" классов — "отпечатки", записанные на диске и в памяти, должны совпадать.
С технической точки зрения, если формат данных в классе не изменялся, можно безопасно считывать объекты с диска. Однако язык Java консервативен и проверяет, не изменялись ли также и методы. (Кроме всего прочего, методы определяют смысл данных.) Разумеется, на практике классы претерпевают изменения, а программе может понадобиться прочитать старые версии объектов. Эта проблема обсуждается в подразделе " Версификация".
Вот в какой последовательности записывается идентификатор класса: 72
2-байтовое длина имени класса
имя класса
8-байтовый отпечаток
1-байтовый флажок опции
2-байтовый счетчик дескрипторов полей данных
дескрипторы полей данных
7 8 (маркер конца)
тип суперкласса(70,если суперклассов нет)
Байт флажка опции состоит из 3-битовых масок, определенных в классе java.io.ObjectStreamConstants.
java.io.ObjectStreamConstants:
static final byte SC_WRITE_METHOC = 1;
// Класс содержит метод writeObject,
// записывающий дополнительные данные.
static final byte SC_SERIALIZABLE = 2;
// Класс реализует интерфейс Serializable.
static final byte SC_EXTERNALIZABLE = 3;
// Класс реализует интерфейс Externalizable.
Интерфейс Externalizable будет рассмотрен ниже. Классы, реализующие этот интерфейс, поддерживают настраиваемые методы чтения и записи полей. У классов, реализующих интерфейс Serializable, флажок равен 02. Однако класс java. util.Date реализует интерфейс Externalizable, а его флажок равен 03.
Каждый дескриптор поля данных имеет следующий формат:
1-байтовый код типа
2-байтовая длина имени поля
имя ПОЛЯ
имя класса (если поле является объектом)
Код типа может таким.
a
byte
С
char
D
double
F
float
I
int
J
long
L
объект
S
short
Z
boolean
t
массив
Если код типа равен L, за именем поля записывается его тип. Класс и строки, задающие имена полей, не начинаются с кода строки 74, в отличие от типов полей. Типы полей немного иначе шифруют свои имена — в этом формате используются системно-зависимые методы. (Эти методы рассматриваются во втором томе.)
Например, поле salary класса Employee кодируется так.
D 00 06 salary
А вот как кодируется полный дескриптор класса Employee.
72 00
08
Employee
E6
00
D2
03
86 7D AE АС 18 1В 02"Отпечаток" и флажки
Количество полей
D
00
06 salaryТип и имя поля
.L
00
07 hiredayТип и имя поля
74
00
10 Ljava/util/Date;Имя класса поля — String
L
00
04 nameТип и имя поля
74
00
12 Ljava/lang/String;Имя класса поля — String
78Маркер конца
70Нет суперкласса
Эти дескрипторы имеют довольно большую длину. Если в файле нужен еще один дескриптор того же класса, то используется его сокращенная форма.
714-байтовый серийный номер
Указанный серийный номер относится к предыдущему дескриптору данного класса. Эта схема нумерации обсуждается ниже.
Объект хранится в следующем виде.
7 3дескриптор классаданные объекта
Вот, например, как хранится объект класса Employee.
40 Е8 6А 00 00 00 00 00Значение поля salary - double
73Значение поля hireday — новый объект
71 00 7Е 00 08Существующий класс java.util.date
770800 0000 91 IB4EBl 8078 Внешняяпамять — см. далее
74 00ОСHarryHackerЗначениеполя name — String
Как видим, в файле находится достаточно информации для восстановления объекта.
Массивы хранятся в следующем формате.
75 Дескриптор класса 4-байтовое количество элементовЭлементы
В дескрипторе класса формат имени класса, которому принадлежит массив, определяется системно-зависимыми методами (native methods). В данном случае имя класса начинается буквой L, а заканчивается двоеточием.
Например, массив, состоящий из трех объектов класса Employee, начинается следующим образом.
75Массив
72 00 0B (LEmployee)Новый класс, длина строки,
имя класса Employee!]
FC BF 36 11 C5 91 11 С7 02 "Отпечаток" класса и флажки
00 00Количество полей
78Маркер конца
70Нет суперкласса
00 00 00 03Количество элементов массива
Обратите внимание на то, что "отпечаток" массива, состоящего из объектов класса Employee отличается от "отпечатка" самого класса Employee.
Разумеется, изучать эти коды так же интересно, как читать телефонную книгу. Однако они довольно поучительны и позволяют проследить, каким образом в объектных потоках хранятся детальные описания всех объектов, причем этой информации достаточно для восстановления как самих объектов, так и массивов, состоящих из них.
Проблемы, связанные с записью ссылок на объекты
Нам известно, как хранить объекты, состоящие из чисел, строк или других простых объектов. Однако мы еще не рассмотрели другую важную ситуацию. Что произойдет, если объект используется несколькими объектами и является частью их состояния?
Чтобы проиллюстрировать эту ситуацию, внесем в класс Manager небольшое изменение. Допустим, что каждый менеджер имеет секретаря, которому соответствует переменная secretary типа Employee. (Для этой цели имеет смысл создать класс Secretary, производный от класса Employee, но мы этого делать не будем.) class Manager extends Employee (
private Employee secretary;
}
Сделав это, мы должны учесть, что объект класса Manager теперь содержит ссылку на объект класса Employee, описывающий секретаря, а не копию этого объекта.
В частности, два менеджера могут пользоваться услугами одного и того же секретаря, как показано на рис. 12.5.
harry= newEmployee("HarryHacker", ...) ;
Manager carl = newManager("CarlCrakcer", ...) ;
;
Manager tony = newManager("TonyTester", ...) ;
;
Рис. 12.5. Два менеджера с одним секретарем
Допустим теперь, что мы записываем на диск данные о сотруднике. Нам совершенно не нужно, чтобы для объекта класса Manager операции выполнялись в таком порядке.
Сохраняем данные о сотруднике.
Сохраняем данные о секретаре.
Тогда данные об объекте harry будут сохраняться трижды. При повторной загрузке объекты могут образовывать такую конфигурацию (рис. 12.6).
Employee
staff =staff =name = | "Harry Hacker” |name = | "Harry Hacker” |name = | "Harry Hacker" |name = | "Harry Hacker" |Managername ="Carl Cracker"secretary =Managername ="Carl Cracker"secretary =EmployeeEmployeeManagerManagername = | "Tony Tester" |name = | "Tony Tester" |secretary = [secretary = [EmployeeEmployeename = ("Harry Hacker" |name = ("Harry Hacker" |
staff =
staff =
name = | "Harry Hacker” |
name = | "Harry Hacker” |
name = | "Harry Hacker" |
name = | "Harry Hacker" |
Manager
name =
"Carl Cracker"
secretary =
Manager
name =
"Carl Cracker"
secretary =
Employee
Employee
Manager
Manager
name = | "Tony Tester" |
name = | "Tony Tester" |
secretary = [
secretary = [
Employee
Employee
name = ("Harry Hacker" |
name = ("Harry Hacker" |
Рис. 12.6.Здесь запись о Harry сохранена три раза
Это совсем не то, что нам нужно. Допустим, что секретарю повысили оклад. Нам совсем не хочется отслеживать все остальные копии этого объекта и вносить в них изменения. Напротив, нам хотелось бы сохранять и восстанавливать только одну копию объекта. Чтобы это сделать, нужно скопировать и восстановить исходные ссылки на объекты. Иными словами, мы хотим, чтобы образ объектов на диске точно совпадал с образом объектов в памяти. Этот эффект называется живучестью (persistence) объекта в объектно-ориентированных циклах.
Разумеется, мы не можем сохранять и восстанавливать адреса памяти для объектов, описывающих секретарей. При повторной загрузке объект будет размещен совсем в другом месте.
Вместо этого в языке Java применяется сериплизация. Естественно назвать этот механизм сериализацией объектов (object serialization). Вот его алгоритм.
Все объекты, сохраненные на диске, получают серийный номер (1, 2, 3 и т.д., как показано на рис. 12.7).
При сохранении объекта следует проверить, не сохранен ли на диске такой же объект.
Если такой объект уже был сохранен, следует просто написать "как и предыдущий объект с серийным номером х". Если нет, сохраняем все данные, записанные в этом объекте.
файлфайлпамять
файл
файл
Рис. 12.7. Пример сериализации объектаРис. 12.7. Пример сериализации объекта
Рис. 12.7. Пример сериализации объекта
Рис. 12.7. Пример сериализации объекта
При считывании объекта процедуру следует выполнять в обратном порядке. Для каждого загружаемого объекта проверяется его номер и определяется адрес в памяти, где он записан. При обнаружении дескриптора "аналогичен предыдущему сохранен-
ному объекту с серийным номером хГ нужно найти объект с серийным номером х и установить на него ссылку.
Обратите внимание на то, что объекты необязательно сохранять в каком-то фиксированном порядке. На рис. 12.8 показано, что случится, если в массиве staffпер- вым будет обнаружен объект класса Manager.
файлфайлпамять
файл
файл
Рис. 12.8. Объекты, сохраненные в произвольном порядкеРис. 12.8. Объекты, сохраненные в произвольном порядкеВсе это сбивает с толку. К счастью, при использовании объектных потоков процесс выполняется полностью автоматически. Объектные потоки присваивают серийные номера и отслеживают дубликаты. Точная схема нумерации немного отличается от той, которая изображена на рисунках (см. следующий подраздел).Все это сбивает с толку. К счастью, при использовании объектных потоков процесс выполняется полностью автоматически. Объектные потоки присваивают серийные номера и отслеживают дубликаты. Точная схема нумерации немного отличается от той, которая изображена на рисунках (см. следующий подраздел).
Рис. 12.8. Объекты, сохраненные в произвольном порядке
Рис. 12.8. Объекты, сохраненные в произвольном порядке
Все это сбивает с толку. К счастью, при использовании объектных потоков процесс выполняется полностью автоматически. Объектные потоки присваивают серийные номера и отслеживают дубликаты. Точная схема нумерации немного отличается от той, которая изображена на рисунках (см. следующий подраздел).
Все это сбивает с толку. К счастью, при использовании объектных потоков процесс выполняется полностью автоматически. Объектные потоки присваивают серийные номера и отслеживают дубликаты. Точная схема нумерации немного отличается от той, которая изображена на рисунках (см. следующий подраздел).
В этой главе мы используем сериализацию для записи набора объектов в файл и для извлечения их оттуда в том же порядке. Другое важное применение этого механизма — передача последовательности объектов по сети на другой компьютер. Адреса памяти не имеют смысла по отношению к файлу и бесполезны для связи с другим процессором. При сериализации они заменяются серийными номерами. Это позволяет передавать наборы объектов с одного компьютера на другой. Такое применение сериализации будет рассмотрено при обсуждении удаленных вызовов методов во втором томе.
В листинге 12.5 приведена программа, передающая и получающая данные о сотрудниках и менеджерах по сети (некоторые из них пользуются услугами одного и того же секретаря). Обратите внимание на то, что объект, описывающий секретаря, после повторной загрузки уникален — когда элемент newSta f [ 1 ] получает повышение оклада, это отражается в поле secretary объекта класса Manager.
16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
import java.io.*;
import java.util.*;
class ObjectRefTest
{
public static void main(String[ args)
{
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setsecretary(harry);
Employee[] staff = new Employee[ 3 ];
staff[O' = boss;
staff[1; = harry;
staff[21 = new Employee("Tony Tester", 40000, 1990, 3, 15);
try {
// Сохраняем все записи в файле employee.dat.
Objectoutputstream out = new Objectoutputstream(new FileOutputStream("employee.dat"));
out.writeObject(staff); out.close();
// Извлекаем все записи в новый массив.
ObjectInputstream in = new ObjectInputstream(new FileInputStream("employee.dat"));
Employee!] newStaff = (Employee[])in.readobject(); in.close() ;
// Повышаем зарплату секретаря. newStaff [1] .raiseSalary(10);
// Выводим на печать вновь прочитанные записи.
for (int i = 0; i < newStaff.length; i++)System.out.printin(newStaff[i]);
}
,;,
catch (Exception e) {
e.printStackTrace();
.}
}..:,. .
}
49. class Employee implements Serializable
50.{
51. public Employee() {}
52.
53.
public Employee(String n, double s,
54.
int year, int month, int day)
55.
{
56.
name = n;
57.
salary = s;
58.
GregorianCalendar calendar
59.
= new GregorianCalendar(year, month - 1, day);
60.
// В классе GregorianCalendar число 0 обозначает январь.
61.
hireDay = calendar.getTime();
62.
}
63.
64.
public String getName()
65.
{
66.
return name;
67.
}
68.
69.
public double getSalary()
70.
{
71.
return salary;
72.
}
73.
74.
public Date getHireDay(]
75.
{
76.
return hireDay;
77.
}
78.
79.
public void raiseSalary(double byPercent)
80.
{
81.
double raisesalary * byPercent / 100;
82.
salary += raise;
83.
}
84.
85.
public String toString()
86.
{
87.
return getClass().getName()
88.
+ "[name=" + name
89.
+ ",salary=" + salary
90.
+ ",hireDay=" + hireDay
91.
+"]";
92.
}
93.
94.
private String name;
95.
private double salary;
96.
private Date hireDay;
97.
}
98.
99.
class Manager extends Employee
100.
{
101
./**
102.
Создает объект для менеджера без секретаря.
103.
@param n имя сотрудника
104.
Sparam s оклад
105.
Sparam year год найма на работу
106.
Sparam month месяц найма на работу
@param day день найма на работу
*/
public Manager(String n, double s,
int year, int month, int day)
{
super(n, s, year, month, day);
secretary = null;
}
/**
Назначаем секретаря данному менеджеру.
@param s секретарь
*/
public void setsecretary(Employee s)
{
secretary = s;
}
public String toString(I
{
return super.toString((.
+ "[secretary=" + secretary
+ "]" ;
}
private Employee secretary;
}
Формат вывода для объектных ссылок
Продолжим обсуждение формата вывода данных в объектные потоки. Если вы не читали предыдущий подраздел, эту часть главы также можно пропустить.
Все объекты (включая массивы и строки) и все дескрипторы классов при записи в файл получают серийные номера. Этот процесс называется сфиализауией (serialization), поскольку каждому сохраненному объекту присваивастся свой серийный номер. (Отсчет начинается с номера 00 7Е 00 00.)
Мы уже знаем, что полный дескриптор класса для заданного класса создается только однажды. Следующие дескрипторы только ссылаются на него. В рассмотренном выше примере вторая ссылка на класс Day в массиве объектов этого класса была закодирована следующим образом.
71 00 7Е 00 02
Тот же самый механизм используется и для объектов. Ссылка на ранее сохраненный объект записывается точно так же, т.е. после числа 71 ставится серийный номер объекта. По контексту всегда можно определить, к чему относится серийный номер — к дескриптору класса или объекту.
Нулевая ссылка записывается следующим образом. 70
Прокомментируем вывод программы ObjectRefTestиз предыдущего подраздела. Запустите программу и проанализируйте шестнадцатеричную распечатку файла employee. dat, а потом сравните с листингом...
AC ED 00 05
75
72 00 OB [LEmployee;
FC BF 36 11 С5 91 11 С7 02
00 00
78
70
00 00 00 03
73
72 00 07 Manager
36 06 AE 13 63 8F 59 В7 02 00 01
L 00 09 secretary
74 00 0А LEmployee
78
72 00 08 Employee
Е6 D2 86 7D АЕ AC 18 1В 02 00 03
D 00 06 salary
L 00 07 hireDay
74 00 10 Ljava/util/Date;
L 00 04 name
74 00 12 Ljava/lang/String;
78
70
40 F3 88 00 00 00 00 00
73
72 00 0E java.util.Date
68 6A 81 01 4B 59 74 19 03
00 00
78
70
77 08
00 00 00 83 E9 39 E0 00
Заголовок файлаМассив staff (серийный #1)Новый класс, длина строки, имя класса Employee [ ] (серийный #0) Отпечаток и флажки опций Количество полейМаркер концаНет суперклассаКоличество элементов в массиве staf [0] — новый объект (серийный #7)Новый класс, длина строки, имя класса (серийный #2)Отпечаток и флажки опций Количество полейТип и имя поляИмя класса, определяющего тип поля, - String (серийный #3) Маркер концаСуперкласс — новый класс, длина строки, имя класса (серийный #4) Отпечаток и флажки опций Количество полейТип и имя поляТип и имя поляИмя класса, определяющего тип поля, — String (серийный #5) Тип и имя поляИмя класса, определяющего тип поля, — String (серийный #6) Маркер концаНет суперклассаЗначение поля salary — double Значение поля hireDay — новый объект (серийный #9)Новый класс, длина строки, имя класса (серийный #8)Отпечаток и флажки опций Объектных переменных нет Маркер концаНет суперклассаВнешняя память, количество байтовДатаМаркер концаЗаголовок файлаМассив staff (серийный #1)Новый класс, длина строки, имя класса Employee [ ] (серийный #0) Отпечаток и флажки опций Количество полейМаркер концаНет суперклассаКоличество элементов в массиве staf [0] — новый объект (серийный #7)Новый класс, длина строки, имя класса (серийный #2)Отпечаток и флажки опций Количество полейТип и имя поляИмя класса, определяющего тип поля, - String (серийный #3) Маркер концаСуперкласс — новый класс, длина строки, имя класса (серийный #4) Отпечаток и флажки опций Количество полейТип и имя поляТип и имя поляИмя класса, определяющего тип поля, — String (серийный #5) Тип и имя поляИмя класса, определяющего тип поля, — String (серийный #6) Маркер концаНет суперклассаЗначение поля salary — double Значение поля hireDay — новый объект (серийный #9)Новый класс, длина строки, имя класса (серийный #8)Отпечаток и флажки опций Объектных переменных нет Маркер концаНет суперклассаВнешняя память, количество байтовДатаМаркер конца78
Заголовок файла
Массив staff (серийный #1)
Новый класс, длина строки, имя класса Employee [ ] (серийный #0) Отпечаток и флажки опций Количество полей
Маркер конца
Нет суперкласса
Количество элементов в массиве staf [0] — новый объект (серийный #7)
Новый класс, длина строки, имя класса (серийный #2)
Отпечаток и флажки опций Количество полей
Тип и имя поля
Имя класса, определяющего тип поля, - String (серийный #3) Маркер конца
Суперкласс — новый класс, длина строки, имя класса (серийный #4) Отпечаток и флажки опций Количество полей
Тип и имя поля
Тип и имя поля
Имя класса, определяющего тип поля, — String (серийный #5) Тип и имя поля
Имя класса, определяющего тип поля, — String (серийный #6) Маркер конца
Нет суперкласса
Значение поля salary — double Значение поля hireDay — новый объект (серийный #9)
Новый класс, длина строки, имя класса (серийный #8)
Отпечаток и флажки опций Объектных переменных нет Маркер конца
Нет суперкласса
Внешняя память, количество байтов
Дата
Маркер конца
Заголовок файла
Массив staff (серийный #1)
Новый класс, длина строки, имя класса Employee [ ] (серийный #0) Отпечаток и флажки опций Количество полей
Маркер конца
Нет суперкласса
Количество элементов в массиве staf [0] — новый объект (серийный #7)
Новый класс, длина строки, имя класса (серийный #2)
Отпечаток и флажки опций Количество полей
Тип и имя поля
Имя класса, определяющего тип поля, - String (серийный #3) Маркер конца
Суперкласс — новый класс, длина строки, имя класса (серийный #4) Отпечаток и флажки опций Количество полей
Тип и имя поля
Тип и имя поля
Имя класса, определяющего тип поля, — String (серийный #5) Тип и имя поля
Имя класса, определяющего тип поля, — String (серийный #6) Маркер конца
Нет суперкласса
Значение поля salary — double Значение поля hireDay — новый объект (серийный #9)
Новый класс, длина строки, имя класса (серийный #8)
Отпечаток и флажки опций Объектных переменных нет Маркер конца
Нет суперкласса
Внешняя память, количество байтов
Дата
Маркер конца
74
00
ОС
Carl
Cracker
Значение поля name — String
(серийный #10)
73
Значение поля secretary — но-
вый объект (серийный #11)
71
00
7Е 00
04
Существующий класс
(используется серийный #4)
40
Е8
6А 00
00 00 00 00
Значение поля salary — double
73
Значение поля hireDay — новый
объект (серийный #12)
71
00 7Е
00 08
Существующий класс
(используется серийный #8)
77
08
Внешняя память, количество байтов
00
00 00
91 IB 4E Bl 80
Дата
78
Маркер конца
74
00
ОС Hary Hacker
Значение поля name — String
(серийный #13)
71 00
7E
00
0B
staf [ 1 ] — существующий объект
(используется серийный #11)
73
staff [2] —новый объект
(серийный #14)
71
00
7E
00 04
Существующий класс
(используется серийный #4)
40
ЕЗ
88
00 00
00 00 00
Значение поля salary — double
73
Значение поля hireDay — новый
объект (серийный #15)
71
00
7Е 00
08
Существующий класс
(используется серийный #8)
77
08
Внешняя память, количество байтов
00
00
00 94
6D ЗЕ ЕС 00 00
Дата
78
Маркер конца
74
00
0В
Tony
Tester
Значение поля name — String
(серийный #16)
Знать точный формат файла необязательно (разве что вы собираетесь злонамеренно модифицировать его вручную — см. следующий раздел). Необходимо помнить следующее.
Объектный поток вывода содержит типы и поля всех объектов.
Каждому объекту присваивается свой серийный номер.
Повторяющиеся объекты сохраняются в виде ссылок на свой серийный номер.
Модификация механизма сериализации, предусмотренного по умолчанию
Некоторые поля классов никогда не должны подвергаться сериализации. Например, целые числа, хранящиеся в дескрипторах файлов или окон, предназначены только для системно-зависимых методов. Такая информация заведомо бесполезна
при повторной загрузке объекта или при пересылке на другой компьютер. Неверные данные, записанные в этих полях, при вызове системно-зависимых методов действительно могут привести к краху программы. В языке Java есть простой механизм, позволяющий защитить эти поля от сериализации. Эти поля следует отметить модификатором transient. Во время сериализации транзитные поля игнорируются.
С помощью механизма сериализации можно создавать индивидуальные классы, позволяющие осуществлять верификацию данных либо другие полезные действия. В классе, реализующем интерфейс Serializable, можно определить методы, имеющие следующие сигнатуры.
private void readobject(Objectinputstream in)
throws lOException, ClassNotFoundException;
private void writeObject(Objectoutputstream out)
throwslOException;
Теперь поля не будут автоматически подвергаться сериализации, а вместо этого будут вызываться указанные выше методы.
Рассмотрим типичный пример. Большое количество классов/ принадлежащих пакету j ava . awt.geom, не сериализуется. К таким классам относится, например, класс Point2D. Double. Допустим теперь, что нам нужно сериализовать класс Labeled- Point, в котором хранятся объекты класса String и Point2D. Double. Во-первых, необходимо пометить поле Point2D. Double ключевым словом transient, чтобы избежать возникновения исключительной ситуации NotSerializableException. public class LabeledPoint {
private String label;
private transient Point2D.Double point;
}
В методе writeObject мы сначала запишем дескриптор объекта и поле String, вызвав метод defaultWriteObject. Это специальный метод из класса ObjectoutputStream, который можно вызывать только из метода writeObject ce- риализуемого класса. Затем запишем координаты ТОЧКИ, используя вызов стандартных методов из класса DataOutput.
private void writeObject(Objectoutputstream out)
throw lOException {
out.defaultWriteObject();
out.writeDouble(point.getX());
out.writeDouble(point.getY());
}
В методе readObj ect чтение выполняется в обратном порядке.
private void readobject(Objectinputstream out)
throw lOException {
in.defaultReadObject();
double x = in.readDouble();
doubleу=in.readDouble();
point = new Point2D.Double(x,y);
Рассмотрим еще один пример. Класс j ava . util. Date предусматривает свои собственные методы readObject и writeObject, записывающие дату в виде количества миллисекунд, прошедших от начала нашей эры (1 января 1970 года, полночь по Гринвичу (Universal Coordinated Time— UTC)). Класс Date имеет сложное внутреннее устройство, которое хранит объект класса Calendar и счетчик миллисекунд, что позволяет оптимизировать поиск. Состояние объекта класса Calendar очень многословно, поэтому его не стоит сохранять.
Методы readobjectи writeObject нужны только для чтения и записи полей данных объекта. Они не нужны для вывода данных из суперкласса или другой информации.
Класс может не полагаться на механизм сериализации, а определять свой собственный механизм чтения и записи объектов. Для этого класс должен реализовывать интерфейс Externalizable. В свою очередь это вынуждает нас определить два новых метода.
public void readExternal(Objectinputstream in
throws IOException, ClassNotFoundException;
public void writeExternal(ObjectOutputStream in throwsIOException;
В отличие от методов readobjects и writeObjects, описанных выше, эти методы сохраняют и восстанавливают весь объект, включая данные суперкласса. Механизм сериализации просто записывает в поток класс объекта. В то же время при чтении объекта, реализующего интерфейс Externalizable, поток создает новый объект, имеющий конструктор по умолчанию, и вызывает метод readExternal. Вот как реализуются эти методы для класса Employee.
public void readExternal(java.io.ObjectInput s)
throwsClassNotFoundException,IOException
{
name = s.readUTF() ;
salary =
hireDay = new Date (s.reading());
}
public void writeExternal(java.io.Objectoutput s) throws IOException
{
s.writeUTF(name) ;
s.writeUTF(salary);
s.writeLong(hireDay.getTime()1 ;
Сериализация выполняется довольно медленно, поскольку виртуальная машина должна проанализировать структуру каждого объекта. Если вы хотите достичь большей производительности и вам приходится часто записывать и считывать большое количество объектов конкретного класса, изучите подробнее интерфейс Externalizable. На Web-странице http://developer.java.sun.com/ developer/TechTips/txtarchive/AprOO_Stu.txt показано, что для класса Employee использование внешних чтения и записи на 35-40% повышает скорость выполнения программы по сравнению со стандартной сериализацией.
В отличие от закрытых методов readObject и writeObject, которые можно вызывать только с помощью механизма сериализации, методы readExterna l и writeExternal являются открытыми. В частности, метод writeExternal позволяет модифицироватьсостояние существующихобъектов.
Еще более удивительные варианты сериализации можно найти на Web-странице http://www.absolutejava.com/serialization.
Сериализация перечислений, безопасных ПО типу
Следует уделить особое внимание сериализации и десериализации объектов, считающихся уникальными. Обычно это происходит при реализации перечислений, безопасных по типу (typesafe enumerations).
Перечислимый тип — это тип данных, имеющий конечное число значений. Язык Java не имеет встроенного механизма для реализации перечислимых типов. Они часто имитируются наборами чисел или строк, но это не гарантирует безопасности типов. Рассмотрим пример, связанный с классом JSlider. Объект этого класса можно создать, указав ориентацию, минимальное и максимальное значения, а также текущее значение ползункового регулятора.
JSlider slider = new JSlider(Swingconstants.HORIZONTAL, 0, 100, 50);
Интерфейс SwingConstants определяет констаннту HORIZONTAL как целое число, равное 1. Допустим теперь, что программист забыл порядок следования параметров и написал следующую строку.
JSlider slider = new JSlider(0, 100, 50,
Swingconstants.HORIZONTAL); // Неверный порядок параметров.
Этот вызов будет скомпилирован без ошибок, поскольку компилятор ожидает четыре значения, имеющих тип int, а порядок их следования ему безразличен.
Проблему можно было бы успешно решить, если бы первый параметр имел другой тип, например Orientation. Тогда компилятор обнаружил бы несовпадение типов, если бы вместо значения типа Orientation он получил параметр типа int.
В языке Java все типы должны реализовываться в виде классов. Класс, представляющий перечислимый тип, имеет особенность: мы хотим быть уверены, что количество его объектов конечно. Этого можно достичь следующим образом.
public class Orientation
public static final Orientation HORIZONTAL
= new Orientation(1);
public static final Orientation VERTICAL
= new Orientation(2);
private Orientation(int v) { value = v; }
private int value;
}
Обратите внимание на то, что конструктор имеет модификатор доступа private. Следовательно, кроме объектов Orientation. HORIZONTAL и Orientation. VERTICAL, другие объекты этого класса создать невозможно. В частности, для проверки равенства объектов можно применять оператор ==.
if (orientation == Orientation.HORIZONTAL] ...
Эта идиома программирования называется перечислением, безопасным по типу.
Следует помнить, что перечисление, безопасное по типу, реализует интерфейс Serializable. Механизм сериализации, предусмотренный по умолчанию, для такого перечисления не подходит. Допустим, что мы записали значение типа Orientation и считываем его вновь.
Orientation original = Orientation.HORIZONTAL;
ObjectOutputStream out = ...;
out.write(value);
out.closet);
ObjectInputStream in = ... ;
Orientation saved = (Orientation)in.read();
Теперь проверка
if (saved == Orientation.HORIZONTAL) ...
вернет значение false. Фактически значение saved является совершенно новым объектом типа Orientation и не равно ни одной из заранее определенных констант. Даже если конструктор имеет модификатор доступа private, механизм сериализации может создавать новые объекты!
Чтобы решить эту проблему, необходимо определить другой метод специальной сериализации под названием readResolve. Если метод readResolve определен, объект десериализуется. Этот метод должен возвращать объект, который затем становится значением, возвращаемым методом readObject. В нашем случае метод readResolve будет проверять поле value и возвращать подходящую перечислимую константу.
protected Object readResolve(; throws ObjectStreamException
(
if (value == 1) return Orientat ion.HORIZONTAL;
if (value == 1) return Orientation.VERTICAL;
return null; // Этого не должно случиться!
)
He забудьте добавить метод readResolve во все перечисления, безопасные по типу. Обратите также внимание на то, что класс перечисления должен хранить значение, по которому будет восстановлена константа.
Версификация
В предыдущем подразделе мы показали, как следует записывать относительно небольшие коллекции объектов в объектный поток. Однако все это сводилось к простой демонстрации работы программы. Допустим теперь, что вам нужно написать программу, позволяющую пользователю создавать некий документ. Этот документ состоит из абзацев, таблиц, графиков и т.п. Выполнив следующий оператор, можно вывести весь документ в виде объекта.
out.writeObject(doc) ;
Абзацы, таблицы и графики также можно автоматически выводить в виде объектов. Тогда один пользователь вашей программы сможет передать файл вывода другому пользователю, имеющему копию программы. Пользователь, получивший файл, может сразу загрузить весь документ, выполнив один-единственный оператор. doc =
Все это очень хорошо, но ваша программа когда-нибудь неизбежно будет модифицирована, и вы станете распространять ее версию 1.1. Сможет ли версия 1.1 читать старые файлы? Смогут ли пользователи версии 1.0 читать файлы, созданные с помощью новой версии программы? Очевидно, это было бы очень желательно.
На первый взгляд кажется, что это невозможно. Если определение класса вдруг изменилось, его "отпечаток", полученный с помощью алгоритма SHA, также изменится. Как вы знаете, в этом случае объектный поток откажется считывать объекты, не соответствующие своим "отпечаткам". Однако в классе можно указать, что он является совместимым со своей предыдущей версией. Для этого нужно сначала получить "отпечаток" предыдущей версии класса. Этосделать с помощью программы
serialver из пакета SDK. Например, при вызове
serialver Employee
на печать выводится следующая информация.
Employee:static final long=
-1814239825517340645L;
Если при запуске программы serialver задать опцию -show, то программа выведет на экран графический диалог (рис.
Senel Vertwin hwрак.*inURC
Full Class Name [EmployeeShow]
Serial version | state Mai tone senarveiuonUlD =-7810191 3640525538Z5L,
Графическая версия программы serialver
Во всех последующих версиях класса нужно определять константу serialVersionUID, значение которой должно совпадать с "отпечатком" первоначальной версии класса.
class Employee // Версия 1.1 {
public static final long =
}
Если в классе есть статический член с именем serialVersionUID, он не будет вычислять новый "отпечаток", а станет использовать старый. Теперь система сериализации сможет считывать разные версии объектов этого класса.
Если в классе изменились только методы, то проблем, связанных с чтением объектов новой версии класса, не будет. Однако, если изменению подверглись поля, эти проблемы могут возникнуть. Например, старый объект файла мог содержать больше или меньше полей данных, чем объект, используемый в программе. Кроме того, в разных версиях могут отличаться типы полей. В этом случае объектный поток попытается привести находящийся в нем объект в соответствие с текущей версией класса.
Объектный поток сравнивает поля данных текущей версии класса с полями данных версии, находящейся в потоке. Разумеется, объектный поток анализирует только нестатические и нетранзитные поля. Если два поля имеют одинаковые имена, но отличаются по типу, объектный поток не делает попыток преобразовать один тип в другой — объекты являются несравнимыми между собой. Если объект, находящийся в потоке, содержит поля, которых нет в текущей версии класса, эти поля игнорируют-
ся. Если текущая версия класса содержит поля, которых нет в объекте, находящемся в потоке, значения этих полей задаются по умолчанию (null— для объектов, О — для чисел и false— для булевских величин).
Рассмотрим пример. Допустим, нам нужно записать на диск большое количество записей о сотрудниках, используя исходную версию (1.0) класса Employee. В данный момент класс Employee модифицирован, и существует его версия 2.0, в которой добавлено поле department. На рис. 12.10 показано, что произойдет, если объект версии 1.0 будет читаться программой, использующей объекты версии 2.0. В поле department будет записана ссылка null. На рис. 12.11 показана противоположная ситуация: программа, использующая объекты версии 1.0, читает объекты версии 2.0. Дополнительное поле department при этом игнорируется.
Сериализованная версия 1.0Сериализованная версия 1.0ФайлФайлПамятьПамятьВерсия 2.0Версия 2.0
Сериализованная версия 1.0
Сериализованная версия 1.0
Файл
Файл
Память
Память
Версия 2.0
Версия 2.0
serial number = ... type = Employee name = "Harry Hacker" salary = 35000.0 hireDay =1989-01-15serial number = ... type = Employee name = "Harry Hacker" salary = 35000.0 hireDay =1989-01-15Employeename = | "HarryHacker"| salary =35000.0hireDay=1989-01-15 |department = I nuL IEmployeename = | "HarryHacker"| salary =35000.0hireDay=1989-01-15 |department = I nuL IРис. 12.10.Считывание объекта, содержащего меньшее количество полейРис. 12.10.Считывание объекта, содержащего меньшее количество полей
serial number = ... type = Employee name = "Harry Hacker" salary = 35000.0 hireDay =1989-01-15
serial number = ... type = Employee name = "Harry Hacker" salary = 35000.0 hireDay =1989-01-15
Employee
name = | "HarryHacker"| salary =35000.0
hireDay=1989-01-15 |
department = I nuL I
Employee
name = | "HarryHacker"| salary =35000.0
hireDay=1989-01-15 |
department = I nuL I
Рис. 12.10.Считывание объекта, содержащего меньшее количество полей
Рис. 12.10.Считывание объекта, содержащего меньшее количество полей
ФайлФайлПамятьПамятьСериапизованная версия 2.0Сериапизованная версия 2.0EmployeeEmployee
Файл
Файл
Память
Память
Сериапизованная версия 2.0
Сериапизованная версия 2.0
Employee
Employee
name = ("HarryHacker" salary =35000.0hireDay= | 1989-01-15name = ("HarryHacker" salary =35000.0hireDay= | 1989-01-15
name = ("HarryHacker" salary =35000.0
hireDay= | 1989-01-15
name = ("HarryHacker" salary =35000.0
hireDay= | 1989-01-15
serial number=... type=Employee name="HarryHacker" salary=35000.0 hireDay =1989-01-15 department="Finance"
Рис. 12.11.Считывание объекта, содержащего большее количество полей
Безопасен ли этот процесс? Не всегда. Игнорирование дополнительных полей данных выглядит безопасным — программа, получившая такой объект, продолжает успешно работать со всеми полями, которые ей уже известны. Однако записывать ссылку null в дополнительное поле небезопасно. Многие конструкторы специально
разрабатываются для того, чтобы инициализировать поля классов ненулевыми значениями, поэтому методы таких классов могут оказаться не готовыми к работе с нулевыми ссылками. Разработчик класса вынужден либо вносить в метод readobjectдо- полнительный код, решающий проблему несовместимости версий, либо создавать методы, устойчиво работающие с нулевыми ссылками.
Применение сериализациидля клонирования
Механизм сериализации весьма забавен (а иногда и очень полезен) — он позволяет легко клонировать объекты классов,, реализующие интерфейс Serializable. (Напомним, что в главе 6 мы уже рассматривали клонирование и отметили, что для его осуществления нужно выполнить довольно много работы.)
Для того чтобы клонировать сериализуемый объект, его нужно записать в поток вывода, а затем считать обратно. В результате возникнет новый объект, представляющий собой полную копию существующего объекта. Объект не обязательно записывать в файл — можно применить метод ByteArrayOutputStream и сохранить данные в виде последовательности байтов.
В листинге 12.6 показано, как легко можно применить метод clone — для этого достаточно создать класс Employee, производный от класса SerialCloneable.
Этот способ клонирования следует знать, хотя очевидно, что он работает медленнее, чем механизм непосредственного клонирования, в котором новый объект конструируется явным образом, а данные, хранящиеся в полях оригинала, просто копируются (см. главу 6).
Листинг 12.6. Программа SanajCIonaTest.,
import java.io.*;
import java.util.*;
public class SerialCloneTest
{
public static void main(String[ args)
{
Employee harry = new Employee("Harry Hacker", 35000,
1989, 10, 1);
// Клонируем объект harry.
Employee harry2 = (Employee)harry.clone();
// Изменяем объект harry.
harry.raiseSalary(10);
// Теперь объекты harry и его клон различаются
System.out.printin(harry);
;
)
}
/ * *
Класс, в котором метод clone использует сериализацию
*/
class SerialCloneable implements Cloneable, Serializable
{
27.28.29.34.42.43.44.47.48.49.50.51.52.53.54.57.62.69.72.73.74.77.78.79.82.83.84.87.27.28.29.34.42.43.44.47.48.49.50.51.52.53.54.57.62.69.72.73.74.77.78.79.82.83.84.87.public Object clone(; {
27.
28.
29.
34.
42.
43.
44.
47.
48.
49.
50.
51.
52.
53.
54.
57.
62.
69.
72.
73.
74.
77.
78.
79.
82.
83.
84.
87.
27.
28.
29.
34.
42.
43.
44.
47.
48.
49.
50.
51.
52.
53.
54.
57.
62.
69.
72.
73.
74.
77.
78.
79.
82.
83.
84.
87.
try {
// Сохраняем объект в виде массива байтов.
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectoutputStream out
= new Objectoutputstream(bout);
out.writeObject(this) ;
out.close();
// Считываем клон из массива байтов. ByteArraylnputStream bin = new
ByteArrayInputstream(bout.toByteArray());
ObjectInputStream in = new ObjectlnputStream(bin);
Object ret = in.readobject() ;
in.close ();
return ret;
} catch (Exception e) {
return null;
}
}
}
/**
Класс Employee, расширяющий класс SerialCloneable.
*/
class Employee extends SerialCloneable {
public Employee(String n, double s, int year, int month, int day)
(
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
// В классе GregorianCalendar число 0 означает январь. hireDay = calendar.getTime();
}
public String getName() {
return name;
}
public double getSalary()
{ return salary;
}
public Date getHireDay(]
{ return hireDay;
}
public void raiseSalary(double byPercent) (
double raise = salary * byPercent / 100;
salary += raise;
}
public String toStringO
{
return getClassO .getNamed
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]" ;
}
private String name;
private double salary;
private Date hireDay;
}
Работа с файлами
Вы уже знаете, как считывать данные из файла и записывать их в файл. Однако работа с файлами не сводится лишь к чтению и записи данных. Более широкие функциональные возможности, позволяющие работать с файловой системой на компьютере пользователя, инкапсулированы в классе File. Например, с помощью этого класса можно узнать, когда произошла последняя модификация того или иного файла, а также когда он был удален или переименован. Иными словами, потоковые классы сосредоточивают внимание на содержании файлов, а класс File — на особенностях хранения файлов на диске.
Как это часто бывает в языке Java, класс File представляет собой "наименьший общий знаменатель". Например, в системе Windows можно распознать (или установить) флажок опции "только для чтения". Хотя таким образом можно объявить файл скрытым, на самом деле скрыть его без использования системнозависимых методов невозможно (см. том 2).
Простейший конструктор объекта класса File получает полное имя файла. Если полный путь к файлу не указан, поиск производится в текущем каталоге. Например, вызов File f = new File("text.txt");
возвращает объект класса File, имеющий указанное имя. (Текущим считается каталог, из которого производится запуск программы.) Если файла с таким именем Heсуще- ствует, объект не создается. Фактически создание файла из объекта класса File осуществляется с помощью одного из конструкторов потокового класса или метода createFile класса File. Этот метод создает новый файл, только если файла с таким именем еще не было, возвращая значение булевского типа (true — если работа завершена успешно, и false— в противоположном случае).
С другой стороны, имея объект класса File, можно вызвать метод exists, который определяет, существует ли файл с таким именем. Например, в приведенной ниже программе практически на любом компьютере этот метод будет возвращать значение false и даже выводить на печать путь к несуществующему файлу.
importjava.io.*;
public class Test
public static void main(String args[])
Filef=newFile("afilethatprobablydoesntexist") ;
System.out.printl(f.getAbsolutePath());
System.out.printIn(f.exists());
}
}
В классе File есть еще два конструктора. Конструктор File (String path, String name) создает объект класса File с заданным именем в каталоге, указанном параметром path. (Если параметр path равен null, конструктор создает объект класса File в текущем каталоге.) Конструктор File (File dir, String name) позволяет использовать уже существующий файл, находящийся в каталоге, имя которого задано параметром dir. Если этот параметр равен null, конструктор создает объект класса File в текущем каталоге.
Как ни странно, объект класса File может представлять собой либо файл, либо каталог (возможно, потому, что в операционной системе, которую лучше всего знают разработчики языка Java, каталоги можно реализовывать в виде файлов). Определить, что именно представляет собой объект класса File — файл или каталог, можно с помощью методов isDirectory и isFile. Это удивительно, ведь в любой объектно-ориентированной системе логично было бы иметь отдельный класс Directory, например, расширяющий класс File.
Для того чтобы создать объект, представляющий собой каталог, достаточно задать имя каталога в конструкторе класса File.
File tempDir = new Fie(File.separator + "temp");
Если каталога с таким именем не было, его можно создать с помощью метода mkdir. tempDir.mkdir();
Если объект класса File представляет собой каталог, массив имен файлов, находящихся Внем, можно получить, вызвав метод list (). Программа, приведенная в листинге 12.7, использует все эти методы для вывода на печать структуры каталога, имя которого задается в командной строке. (На основе этой программы легко создать вспомогательный класс, позволяющий получить вектор подкаталогов для дальнейшей обработки.)
Манипулируя файлами или каталогами, всегда используйте объекты класса File. Например, метод equals класса File знает, что некоторые системы не различают прописные и строчные буквы и что завершать имя каталога символом / бессмысленно.
import java.io.*;
public class FindDirectories
{
public static void main(String[] args)
{
// Если аргументы не указаны, используется текущий каталог.
if (args.length== 0) args = new String!] { ".." } ;
try
{
File pathName = new File(args[0]);
String!] fileNames =
// Перечисляем все файлы В каталоге.
for (int i = 0; i < fileNames.length; i++)
{
File f = new File(pathName.getPath(),
fileNames[i]);
// Если объект снова представляет собой каталог,
// рекурсивно вызываем метод main.
if (f .isDirectory() )
{
System.out.printin(f.getCanonicalPathO);
main(new String [] { f.getPath() });
}
}
}
catch(lOException e)
{
e.printStackTrace();
}
}
}
Все файлы сразу можно не перечислять. Вместо этого, задав в качестве параметра метода list объект класса FileNameFilter, можно перемещаться по списку файлов с помощью навигационных клавиш. Эти объекты просто представляют собой экземпляры любого класса, реализующего интерфейс FilenameFilter.
Во всех классах, реализующих интерфейс FileNameFilter, должен быть определен метод accept. Рассмотрим простой пример класса FilenameFilter, распознающего только файлы с указанным расширением.
public class ExtensionFlter implements FilenameFilter
{
public ExtensionFiIter(String ext)
{
extension = "." + ext;
}
public boolean accept(File dir, String name)
{
returnname.endsWith(extension) ;
}
private String extension;
}
Для платформно-независимых программ подкаталоги представляют определенную сложность. Как уже указывалось, в разных операционных системах используются разные разделители файлов. Например, в системах UNIX и Windows применяется косая черта, а в других системах она может не распознаваться. Следовательно, применять ее не следует.
Если, работая на платформе Windows и создавая объекты класса File, вы используете косую черту, метод getAbsolutePath будет возвращать имя файла, содержащее косые черты, что для пользователей системы Windows выглядит довольно странно. Вместо этого метода следует вызывать метод getCanonicalPath, заменяющий косые черты обратными косыми чертами.
Лучше всего использовать текущий разделитель каталогов, который хранится в классе File в статическом поле separator. (В среде Windows— это обратная косая черта (\), а в системе UNIX — обычная косая черта (/).)
File foe = new File("Documents" + File.separator + "data.txt");
Разумеется, при использовании второго конструктора класса File система Java сама определит правильный разделитель файлов.
File foo = new File("Documents", "data.txt”);
Ниже указаны остальные методы класса File, которые мы считаем наиболее важными.
java.io.File 1.0 boolean canRead(J Показывает, можно ли прочитать файл.
boolean canWrite()
Показывает, можно ли записать файл, или он предназначен только для чтения.
static boolean createTempFile(String prefix, String suffix)1.2
static boolean createTempFile(String prefix, String suffix, File directory)1.2
Создает временный файл во временном каталоге, заданном системой по умолчанию, или в каталоге, указанном пользователем. Суффикс и префикс используются для создания временного имени файла.
Параметры:prefixПрефикс, состоящий по крайней мере из трех
символов.
sufficНеобязательный суффикс. Если задана ссылка
null, используется расширение tmp.
directory Каталог, в котором создается файл. Если задан параметр null, используется текущий каталог.
boolean delete()
Пытается удалить файл. Если файл был успешно удален, возвращает значение true, в противном случае — false.
void deleteOnExit(;
Запрашивает, удалять ли файл при завершении работы виртуальной машины.
boolean exists(;
Возвращает значение true, если файл или каталог существует, в противном случае возвращается значение false.
String getAbsolutePath()
Возвращает строку, содержащую абсолютное имя пути к файлу. Рекомендация: вместо этого метода следует использовать метод getCanonical- Filed.
File getCanonicalFile ()1.2
Возвращает строку, содержащую каноническое имя пути к файлу. В частности, удаляются необязательные каталоги " . ", используется правильный разделитель каталогов и прописные буквы.
String getCanonicalPath(J 1.1
Возвращает строку, содержащую каноническое имя пути. В частности, удаляются необязательные каталоги " . ", используется правильный разделитель каталогов и прописные буквы.
String getName(]
Возвращает строку, содержащую имя объекта класса File (информация о пути не учитывается).
String getParent(]
Возвращает строку, содержащую имя родительского объекта класса File. Если объект является файлом, то родительским объектом считается каталог, содержащий данный файл. Если объект является каталогом, то родительским классом является родительский каталог. Если родительского каталога нет, возвращается ссылка null.
File getParentFile()1.2
Возвращает объект класса File для родительского каталога. Определяет понятие "родительский" в описании класса getParent.
String getPath(I
Возвращает строку, содержащую имя пути к файлу.
boolean isDirectory(]
Возвращает значение true, если объект класса File представляет собой каталог, в противном случае возвращает значение false.
boolean isFileO
Возвращает значение true, если объект класса File представляет собой файл, в противном случае возвращает значение false.
boolean isHidden()1.2
Возвращает значение true, если объект класса File представляет собой скрытый файл или каталог, в противном случае возвращает значение false.
e long lastModified(J
Возвращает время, когда файл в последний раз изменялся. Время измеряется в миллисекундах, прошедших от начала новой эры (полночь 1 января 1970 по Гринвичу). Метод возвращает число 0, если файла не существует.
Для преобразования числа миллисекунд в дату используется метод Date(long).
long length()
Возвращает длину файла в байтах, или число 0, если файла не существует.
String!] list()
Возвращает массив строк, содержащих имена файлов и каталогов, указанных в объекте File, или ссылку null, если объект не является каталогом.
Параметры: filter Используемый объект класса FilenameFilter.
String!] list(FilenameFilter filter)
Возвращает массив строк, содержащих имена файлов и каталогов, указанных в объекте File и удовлетворяющих требованиям фильтра, или ссылку null, если объекта нет.
File[] listFiles()1.2
Возвращает массив объектов класса File, соответствующих файлам и каталогам, указанным в объекте File, или ссылку null, если объект не является каталогом.
File[] listFiles(FilenameFilter filter)1.2
Возвращает масс 11 в объектов класса File, соответствующих файлам и каталогам, указанным в объекте File и удовлетворяющим требованиям фильтра, или ссылку null, если объекта нет.
Параметры: filter Используемый объект класса FilenameFilter.
static File[] listRoots(]1.2
Возвращает массив объектов класса File, представляющих собой доступные логические устройства. (Например, в системе Windows этот метод возвращает объекты класса File, представляющие собой инсталлированные логические устройства (как локальные, так и сетевые). В системе UNIX просто возвращается символ "/"•)•
boolean createNewFile(]1.2
Автоматически создает новый файл, имя которого задается объектом класса File, если файла с таким именем еще не было. Таким образом, проверка имени файла и процесс его создания системой не прерываются. Если файл успешно создан, возвращается значение true.
boolean mkdir()
Создает подкаталог, имя которого задается объектом класса File. Если каталог успешно создан, возвращает значение true. Если создать каталог невозможно, возвращает значение false.
boolean mkdirs()
В отличие от метода mkdir создает при необходимости родительские каталоги. Если создать такие каталоги невозможно, возвращает значение false.
boolean renameTo(File dest)
Возвращает значение true, если имя было изменено, в противном случае возвращает значение false.
Параметры: dest Объект класса FilenameFilter, задающий новое ИМЯ.
e boolean setLastModifieddong time)1.2
Задает время последней модификации файла. Если работа завершена успешно, возвращает значение true, в противном случае возвращает значение false.
Параметры: time Длинное целое число, представляющее собой количество миллисекунд, прошедших от начала новой эры (полночь 1 января 1970 по Гринвичу). Для вычисления этого числа используется метод getTime класса Date.
boolean setReadOnly(;1.2
Устанавливает флажок опции "только для чтения". Если работа завершена успешно, возвращает значение true, в противном случае возвращает значение false.
URL toURLC1.2
Конвертирует объект класса File в объект класса URL.
java.io.FilenameFilter 1.0
boolean accept(Fie dir, String name)
Должен возвращать значение true, если файл соответствует требованиям фильтра.
Параметры: dirОбъект класса File, представляющий собой каталог,
в котором содержится файл.
name Имя файла.
Новые средства ввода-вывода
Пакет SDK 1.4 содержит большое количество усовершенствованных средств ввода- вывода, которые в совокупности называются "новые средства ввода-вывода" и хранятся в пакете java.nio. (Разумеется, эпитет "новые" не совсем удачен— через несколько лет пакет больше не будет новым.)
Пакет содержит следующие средства.
Файлы виртуальной памяти.
Блокировка файлов.
Шифраторы и дешифраторы наборов символов.
Неблокирующие средства ввода-вывода.
Мы обсудим только первые три свойства. Неблокирующие средства ввода-вывода, связанные с использованием потоков, будут рассмотрены во втором томе.
Файлы виртуальной памяти
Большинство операционных систем позволяют загружать файл или его часть в память, создавая его виртуальную реализацию. В таком случае файл можно обрабатывать как массив ячеек, что намного эффективнее традиционных способов работы.
В конце этого раздела мы приведем две программы, вычисляющие контрольную сумму CRC32 файла. Одна из них считывает данные, записанные в файле с помощью метода read, а другая использует виртуальную память. Второй подход обеспечивает намного более быстрое выполнение программы. На одной и той же машине при вычислении контрольной суммы CRC32 файла rt.jar объемом 22 Мбайт, хранящегося в каталоге j re/lib, мы получили следующие результаты.
Традиционный файл: 350 с.
Виртуальный файл: 8 с.
Разумеется, эти результаты на других машинах окажутся иными, но разница между ними весьма ощутима.
Пакет java.nio довольно просто загружает файлы в память. Для этого достаточно сделать следующее.
Во-первых, нужно получить канал (channel) для файла. Канал — это новая абстракция файлов, хранящихся на диске, позволяющая операционной системе загружать их в память, блокировать и выполнять быстрый обмен данными между файлами. Получить канал можно, вызвав метод getChannel, добавленный в классы Fileinputstream,FileOutputStream и RandomAccessFile.
Fileinputstream in - new Fileinputstream( ... );
FileChannel channel = in.getChannel();
Затем, вызвав метод map класса FileChannel, нужно получить объект класса MappedByteBuffer, соответствующий созданному каналу. Далее следует указать, какую область файла необходимо загрузить в память, а также режим загрузки (mapping mode). ЯзыкJava поддерживает три режима загрузки файлов.
FileChannel.MapMode .READ_ONLY. В результате создается буфер, предназначенный только для чтения. Любая попытка записать в него какие-либо данные вызовет исключительную ситуацию ReadOnlyBufferException.
FileChannel.MapMode.READ_WRITE. В результате создается буфер, предназначенный для записи, и в определенный момент времени все изменения записываются обратно в файл. Обратите внимание на то, что другие программы, загрузившие в память тот же самый файл, могут не сразу обнаружить эти изменения. Точное определение процесса одновременной загрузки файла несколькими программами зависит от операционной системы.
FileChannel.MapMode . PRIVATE. В результате создается буфер, предназначенный для записи, но любые изменения производятся только в буфере и пе возвращаются вфайл.
Создав буфер, можно считывать и записывать данные, используя методы класса ByteBuffer и суперкласса Buffer.
Буферы обеспечивают как относительный, так и абсолютный доступ к данным. Буфер имеет позицию (position), на которую могут ссылаться методы get и put. Например, с помощью следующего фрагмента кода можно просмотреть все байты в буфере. while
{
byte b =
}
Кроме того, можно использовать абсолютный адрес.
for (int i = 0; i < buffer, limit (); i++)
{
byte b =
}
Можно также прочитать и записать массив байтов с помощью методов bytes) int offset, int length)
Отметим методы getint, getLong, getShort, getChar, getFloat и getDouble, которые позволяют читать значения элементарных типов, хранящиеся в файлах в
Например, целое число 1234 может храниться в виде последовательности четырех байтов 00 00 04 D2
(ввиде), поскольку+ 2. Фактически существует два способа хранения числав двоичном виде. Они различаются порядком
следования байтов. Порядок, использованный выше, называется прямым ("big- endian"), поскольку старшие двоичные разряды указываются первыми. Другой порядок называется обратнымпоскольку при его использовании первыми
записываются младшие двоичные разряды.
D2 04 00 00
По умолчанию в языке Java используется прямой порядок записи, возможно, потому что имено этот порядок используется в процессорахОднако процессо
ры Intel используют обратный порядок записи. Следовательно, формат файлов на компьютерах семейства PC часто основан на обратном порядке записи двоичных чисел. Если нужно обработать такой файл, следует просто вызвать метод buffer,order(ByteOrder.LITTLE_ENDIAN);
Чтобы определить порядок, принятый в буфере в данный момент, необходимо вызвать метод. order ().
ByteOrder b =
Эта пара методов не использует соглашение об именах set/get
Для записи чисел в буфер используется один из следующих методов: putLong, putShort, putChar, putFloat и putDouble. В листингах 12.8 и 12.9 приведены две программы, вычисляющие 32-битовую контрольную сумму файла при помощи избыточного циклического кодаЭта величина часто используется для распо
знавания повреждений файлов. Если файл поврежден, весьма вероятно, что контроль-
ная сумма будет изменена. Пакет j ava.util.zip содержит класс CRC32, вычисляющий контрольную сумму последовательности байтов с помощью следующего цикла:
CRC32 crc = new CRC32 ();
while (байты)
crc.update(следующий байт)
long checksum = crc.getValue();
Более подробное описание алгоритма CRC дано на Web-странице
http://www.relisoft.com/Science/CrcMath.html.
Детали вычислений контрольной суммы не важны. Мы просто использовали ее в качестве примера для демонстрации полезных операций с файлами.
Первая программа применяет традиционный подход, а вторая использует виртуальную память. Как мы уже говорили, вторая программа работает намного быстрее. Попробуйте сами. Запустите обе программы для одного и того же файла, например,
j ava CRC ИмяФайла
**и
*
*
java NI0CRC ИмяФайла
import java.io.*;
import java.util.zip.
/**
Программа вычисляет контрольную сумму файла (CRC),
используя входной поток
Команда запуска: java CRC filename
*/
public class CRC
{
public static void main(String[ ’ args) throws IOException
{
InputStream in = new FileInputStream(args[0]);
CRC32 crc = new CRC32();
int c;
long start = System.currentTimeMillis();
while I (c = in.read()1 != -1)
crc.update(c);
long end = System.currentTimeMillis();
System.out.printIn(Long.toHexString(crc.getvalue())] ;
System.out.printIn((end - start) + " milliseconds");
)
}
[Митинг 12^ Программа NIOCRCIava^ ~__ МЕЗ
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.zip.*;
/**
Программа вычисляет контрольную сумму файла (CRC),
используя виртуальную память
Команда запуска: java CRC filename
*/
public class NIOCRC
{
public static void main(String[] args) throws Exception
(
FilelnputStream in = new FileInputStream(args[0]);
FileChannel channel = in.getChannel();
CRC32 crc = new CRC32();
long start = System.currentTimeMillis();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0,
'(int)channel.size());
while (buffer.hasRemaining())
crc.update(buffer.get());
long end = System.currentTimeMillis();
System.out.printIn(Long.toHexString(crc.getValue()));
System.out.printing(end - start) + " milliseconds");
)
}
java.io.Fileinputstream 1.0
FileChannel getChannelO1.4
Возвращает канал для доступа к потоку.
java.io.FileOutputStream 1.0
FileChannel getChannelO1.4
Возвращает канал для доступа к потоку.
java.io.RandomAccessFile 1.0
FileChannel getChannelO1.4
Возвращает канал для доступа к файлу.
java.nio.channels.FileChannel 1.4
MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
Загружает указанную область файла в память.
Параметры: modeПараметры: modeОдна из констант READ_ONLY, READ_WRITE или PRIVATE, определенных в классе FileChannel.MapMode.
Параметры: mode
Параметры: mode
sizesizeposition Начало загружаемой области.
size
size
Конец загружаемой области.
java.nio.Buffer 1. 4
boolean hasRemaining() int limit(1
java.nio.ByteBuffer 1.4
byte get()
Возвращает байт, извлеченный из указанной позиции, и перемещает указатель на следующий байт.
byte get(int index)
Возвращает байт, извлеченный из позиции с указанным индексом.
ByteBuffer put(byte b)
Записывает байт в текущую позицию и перемещает указатель на следующий байт. Возвращает ссылку на буфер.
ByteBuffer put(int index, byte b)
Записывает байт в позицию с указанным индексом и перемещает указатель на следующий байт. Возвращает ссылку на буфер.
ByteBuffer get(byte[] destination)
ByteBuffer get(byte[] destination, int offset, int length)
Заполняет массив или его часть байтами, считанными из буфера, и перемещает указатель вперед, пропуская количество считанных байтов. Если в буфере осталось меньше байтов, чем требуется, то они не считываются, и возбуждается исключительная ситуация BufferUnderflowException. Возвращает ссылку на буфер.
Параметры: destination
Заполняемый массив байтов.
offset
Смещение заполняемой области.
length
Длина заполняемой области.
ByteBuffer put(byte[]
ByteBuffer put(byte(]
source)
source, int offset, int length)
Записывает все байты из массива или его части в буфер и перемещает указатель вперед, пропуская количество считанных байтов. Если в буфере осталось меньше места, чем требуется, то байты не записываются, и возбуждается исключительная ситуация BufferOverflowException. Возвращает ссылку на буфер.
Параметры: source
Записываемый массив байтов.
offset
Смещение записываемой области.
length
Длина записываемой области.
Ххх getXxx()
Ххх getXxx(int index)
ByteBuffer putXxx (xxx value)
ByteBuffer order(ByteBuffer order)
Эти методы используются для считывания и записи двоичных чисел по относительному или абсолютному адресу. Символы Ххх означают имена классов Int, Long, Char, Float или Double.
ByteBuffer order()
Задает или определяет порядок записи байтов. Значение параметра order равно одной из констант BIG_ENDIAN или LITTLE_ENDIAN, определенных в классе ByteOrder.
Блокировка файлов
Рассмотрим ситуацию, в которой несколько программ, выполняемых одновременно, стремятся модифицировать один и тот же файл. Очевидно, что программы должны иметь средства для обмена сообщениями, иначе файл легко повредить.
Блокировку файла можно использовать для управления доступом к нему или к его части. Однако блокировка файлов сильно зависит от конкретной операционной системы. Этим объясняется тот факт, что средства блокировки файлов отсутствовали в предыдущих версиях пакета SDK.
Честно говоря, блокировка файлов нужна не во всех приложениях. Многие из них работают с базами данных, в которых есть свой механизм для решения проблемы конкурентного доступа. Если информация хранится в обычном файле и вас беспокоит проблема конкурентного доступа, возможно, вам лучше применить базу данных и не вникать в сложные детали схем блокировки файлов.
Однако на практике по-прежнему возникают ситуации, в которых без блокировки файлов не обойтись. Допустим, приложение сохраняет файл конфигурации программы. Если пользователь вызовет два экземпляра приложения, они могут попытаться записать один и тот же файл конфигурации одновременно. В этой ситуции первый экземпляр должен заблокировать файл. Тогда второй экземпляр будет вынужден подождать, пока файл не окажется разблокированным, или просто прекратить процесс записи.
Чтобы заблокировать файл, нужно вызвать метод lock или tryLock из класса FileChannel:
FileLock lock = channel.lock();
и
FileLock lock = channel.tryLock();
Если объект lock недоступен, первый вызов блокируется. Второй вызов выполняется немедленно и либо блокирует объект, либо возвращает ссылку null, если объект lock недоступен.
Можно также заблокировать только часть файла:
FileLock lock(long start, long size, boolean exclusive)
и
FileLock tryLock(long start, long size, boolean exclusive)
Если файл заблокирован для чтения и записи, параметр exclusive принимает значение true. Если используется совместная блокировка (shared lock), позволяющая нескольким процессам одновременно считывать файл, параметр exclusive равен значению false. Это не позволяет ни одному процессу получить исключительные
права доступа к файлу. Не все операционные системы поддерживают совместную блокировку. Вы можете получить эксклюзивную блокировку, даже если просили сделать ее совместной. Для того чтобы определить, допускается ли совместная блокировка, следует вызвать метод isShared из класса FileLock.
Если последняя часть файла заблокирована, а сам файл, увеличиваясь в размерах, выходит за пределы заблокированной части, дополнительная часть файла не блокируется. Чтобы заблокировать все байты, следует использовать константу Long.MAXVALUE.
Учтите, что блокировка файла зависит от операционной системы. Нужно уделить особое внимание следующим пунктам.
В некоторых системах блокировка файла является просто желательной. Если приложению не удается заблокировать файл, он остается доступным.
В некоторых системах файл нельзя одновременно блокировать и загружать в виртуальную память.
Блокировка файлов поддерживается всей виртуальной машиной языка Java. Если две программы запускаются одной и той же виртуальной машиной (например, в виде аплета или приложения), они не могут одновременно блокировать один и тот же файл. Если это произойдет, методы lock и tryLock возбудят исключительную ситуацию OverlappingFileLockException.
В некоторых системах при закрытии канала освобождаются все блокировки файла, захваченного виртуальной машиной языка Java. Следовательно, следует избегать использования нескольких каналов для одного и того же заблокированного файла.
Г" ‘-'W
|A P11 java.nio.channels.FileChannel 1.4
■—■■I • FileLock lock()
Запрашивает эксклюзивную блокировку целого файла. Если файл заблокировать невозможно, вызов не выполняется.
FileLock tryLock(;
Запрашивает эксклюзивную блокировку целого файла. Если файл заблокировать невозможно, возвращается ссылка null.
FileLock lock(long position, long size, boolean shared) FileLock tryLock(long position, long size, boolean shared) Эти методы запрашивают эксклюзивную блокировку части файла. Если файл заблокировать невозможно, первый метод не выполняется, а второй возвращает ссылку null.
Параметры: positionПараметры: positionНачало блокируемого участка.
Параметры: position
Параметры: position
sizeРазмер блокируемого участка.
sharedПринимает значение true, если применяется
совместная блокировка, и false— если эксклю-
зивная.
1.41.4java. nio.channels.FileLock
1.4
1.4
void release()
Снимает блокировку.
Наборы символов
В прошлом наборы международных символов в библиотеке языка Java обрабатывались несколько хаотично. Пакет j ava. nio унифицировал процесс преобразования наборов символов, предусмотрев для этого класс Charset. (Обратите внимание на то, что буква s — строчная.)
Наборы символов кодируются либо с помощью 16-битовых символов Unicode, либо в виде последовательностей символов в системе кодирования локальных символов. Одна из наиболее популярных систем кодирования символов система ISO-8859-L представляющая собой 8-битовую кодировку первых 256 символов системы Unicode. В настоящее время получает все большую популярность система ISO-8859-15, в которой некоторые редко используемые символы из системы ISO-8859-1 заменены символами, имеющими под- и надстрочные элементы, как это принято во французском или финском языках. Еще более важно то, что символ "международной валюты" заменен символом евро, который кодируется как 0хА4. Другие примеры кодировки представляют собой иероглифы, используемые в японском и китайском языках.
В настоящее время пакет SDK использует имена наборов символов, стандартизованных в регистре LANA Character Set Registry (http://www.iana.org/assignments/ character-sets). Эти имена незначительно отличаются от предыдущих версий. Например, "официально" кодировка ISO-8859-1 теперь называется "ISO-8859-1", а не "1508859 1", как в пакете SDK 1.3. Для обеспечения совместимости с другими соглашениями об именах, каждый набор символов имеет большое количество псевдонимов. Например, кодировка ISO-8859-1 имеет следующие псевдонимы:
IS08859-1
ISO_8859_1
ISO8859_1
ISO_8859_1:1987
8859_1 latinl 11
csISOLatinl iso-ir-lOC cp819 IBM819 IBM-819 819
В именах наборов символов строчные и прописные буквы различаются.
Объект класса Charset можно получить с помощью статического метода for Name, примененного к официальному имени или любому псевдониму.
Charset cset = Charset.forName("ISO-8859-1");
Метод aliases возвращает объект класса Set, в котором хранятся псевдонимы. Класс Set описывает коллекцию, которую мы будем обсуждать в томе 2. Рассмотрим фрагмент, позволяющий просмотреть коллекцию элементов.
Set aliases = cset.aliases ();
Iterator iter = aliases.iretator ();
while (iter.hasNext ())
{
String alias = (String)iter.next();
}
Все виртуальные машины должны поддерживать кодировки символов, указанные в табл. 12.3.
Стандартное имя
Описание
US-ASCII
7-битовые коды системы US ASCII
ISO-8859-1
8-битовые коды системы ISO Latin 1
UTF-8
8-битовые коды системы Unicode Transformation Format
UTF-16BE
16-битовые коды системы Unicode Transformation Format, прямой порядок
UTF-16LE
16-битовые коды системы Unicode Transformation Format, обратный порядок
UTF-1б
16-битовые коды системы Unicode Transformation Format. По умолчанию принят прямой порядок, но его можно задать и явно ('\uFFFE').
Превосходное описание системы "ISO 8859 alphabet soup" находится на Web- странице http:://-czyborra.com/charsets/iso8859.html. Определения систем кодирования UTF-8 и UTF-16 можно найти на WEeb-страницах http://ietf.org/rfc/rfc2279.txt и http://ietf.org/rfc/rfc2781.txt.
Международные версии языка Java поддерживают много различных систем кодирования символов. Существует даже механизм для включения в набор дополнительных символов (детали можно найти в документации). Чтобы определить, какие наборы символов доступны в конкретной реализации, необходимо вызвать статический метод availableCharsets. Он возвращает объект класса SortedMap, представляющего собой еще один вид коллекций. Чтобы определить имена всех доступных наборов символов, можно воспользоваться приведенным ниже кодом.
Set names = Charset.availableCharsets().keyset();
Iterator iter = names.iterator();
while (iter.hasNext())
{
String name = (String)iter.next();
}
Имея набор символов, его можно кодировать либо в системе Unicode, либо как последовательность байтов. Вот как можно закодировать строку символов в системе Unicode.
String str = ... ; .
ByteBuffer buffer = cset.encode(strl ;
byte[] bytes = buffer.array();
И, наоборот, чтобы перекодировать последовательность байтов, нужен буфер. Для превращения массива байтов в буфер следует использовать метод wrap из класса ByteBuf fer. В результате возникнет объект класса CharBuffer. Чтобы получить строку, нужно вызвать его метод toString.
byte[] bytes = .. . ;
ByteBuffer bbuf = ByteBuffer.wrap(bytes, offset, length);
CgharBuffer cbuf = cset.decode(bbuf);
String str = cbuf.toString() ;
java.nio.charset.Charset1.4
static SortedMap availableCharsets()
Возвращает все наборы символов, доступные на данной виртуальной машине. Возвращает ассоциированный массив, ключами которого являются имена наборов символов, а значениями — сами наборы.
static Charset forName(String name)
Возвращает набор символов, имеющий указанное имя.
Set aliases(]
Возвращает множество, состоящее из псевдонимов данного набора символов.
ByteBuffer encode(String str)
Кодирует заданную строку в виде последовательности байтов.
CharBuffer decode(ByteBuffer buffer)
Декодирует заданную последовательность символов. Нераспознанные символы конвертируются в символ замены, принятый в системе Unicode ('\uFFFD').
A P^java. nio. ByteBuffer 1.4
• byte (I array (!
Возвращает массив байтов, хранящихся в данном буфере.
static ByteBiffer wrap(byte [ ] bytes)
staticByteBuffer wrap(byte [ ] bytes, int offset,int
length)
Возвращает буфер байтов, состоящий из элементов массива байтов, лежащих в указанных пределах.
APIjava.nio.CharBuffer 1.4
L*
11e chart] array()
Возвращает массив символов, хранящихся в указанном буфере.
Регулярныевыражения
Регулярные выражения используются для задания шаблонных строк. Их можно использовать для того, чтобы определить, совпадает ли указанная строка с заданным шаблоном. Например, в одной из демонстрационных программ анализируются все гипертекстовые ссылки, находящиеся в HTML-файле, путем сравнения их с шабло- ном<а href=". . . ">.
Разумеется, в определении шаблона многоточие не вполне уместно. Нужно совершенно точно указать, какая последовательность символов считается допустимой. Для этого существуют определенные синтаксические правила.
Рассмотрим пример. Регулярное выражение
[Jj]ava.+
совпадает с любой строкой, имеющей следующую форму.
Первой буквой строки является буква J илиj.
Следующие три буквы — ava.
Остальная часть строки может быть произвольной.
Например, строка " Javanese" совпадает с заданным регулярным выражением, а строка"Core Java" — нет.
Чтобы понимать смысл регулярных выражений, нам необходимо знать синтаксические правила. К счастью, в большинстве случаев оказывается вполне достаточно нескольких синтаксических конструкций.
Класс символов (character class) представляет собой набор символьных альтернатив, заключенных в квадратные скобки, например [Jj], [0-9], [A-Za-z] или
[л0-9]. Здесь символ - означает диапазон (все символы системы Unicode, лежащие между двумя указанными символами), а символ A означает дополнение (любой символ, не равный указанному).
Существует много классов символов, определенных заранее, например, \d (цифры) или \р (Sc) (символ валюты в системе Unicode). Детали см. в табл. 12.2 и 12.3.
Большинство символов совпадают сами с собой, как символы ava в приведенном выше примере.
Символ . совпадает с любым символом (за исключением, возможно, символа конца строки).
В качестве управляющего используется символ \, например, символы . \ соответствуют точке, а символы X \ — обратной косой черте.
Если шаблоны X и Y являются регулярными выражениями, то шаблон XY означает "любая строка, совпадающая со строкой X, за которой следует строка Y". Выражение X | Y означает "любая строка, совпадающая со строками X или Y".
К выражению X можно применять квантификаторы (quantifiers) X+ (1 или больше), X* (0 или больше) и X? (0 или 1).
По умолчанию квантификатор совпадает с максимально возможным повторением символа, гарантирующим полное совпадение строки с шаблоном. Это соглашение можно изменить, указав суффиксы ? (слабое, или неполное совпадение — совпадение с наименьшим количеством символов) и + (собственное, или "жадное" совпаде-
ние — совпадение с максимальным количеством символов, даже если полного совпадения нет). Например, строка cab совпадает с шаблоном [a-z ] *ab, но не совпадает с шаблоном [a-z] *+ab. В первом случае выражению [a-z] *ab соответствует только символ с, так что символы ab соответствуют оставшейся части шаблона. Однако "жадная" версия [а-z ]* +ab совпадает со всей строкой cab, несмотря на то, что оставшаяся часть шаблона не совпадает со строкой.
Для определения подвыражений (subexpressions) можно использовать группы (groups). Группы заключаются в скобки (), например, ([+-]?) ([0-9]+). Затем метод, выполняющий сравнение шаблона, должен вернуть строку, совпадающую с каждой группой, или вернуть группу назад, присоединив к ней символы \п, где п — номер группы, начинающийся с \1.
Рассмотрим довольно сложное, но полезное регулярное выражение. Оно описывает десятичные или шестнадцатеричные целые числа.
[+-]?[0-9]+|0[Хх][0-9A-Fa-f]+
К сожалению, синтаксис выражения еще не полностью стандартизован и варьируется в разных программах и библиотеках. В настоящее время достигнуто общее соглашение об основных конструкциях, но осталось много неясностей в деталях. Классы регулярных выражений используют синтаксис, очень похожий на синтаксис языка Perl, хотя и не совпадающий с ним. Все эти синтаксические конструкции приведены в табл. 12.4. Более подробно регулярные выражения описаны в документации по интерфейсу API (класс Pattern).
Символ
Описание
с
\nnnnn, \хпп, \0п, \0пп, \0nnn
\t, \n, \r, \f, \a, \е
\сс
Классы символов
[С,С,...]
(*...]
[...&&...]
Встроенные классы символов
\d
Символ с
Символ, имеющий заданное десятичное или шестнадцатеричное значение
Управляющие символы табуляции, перехода на новую строку, возврата каретки, прогона бумаги, сигнала и смены регистра
Управляющий символ, соответствующий символу с
Любой символ, представленный в списке С)Са. . .. Знак с. означает символ, диапазон символов (Cj— с,) или класс символов
Дополнение класса символов
Пересечение двух классов символов
Любой символ, за исключением символа конца строки (или просто любой символ, если установлен флаг D0TALL)
Цифра [0-9]
Продолжение табл. 12.4
Символ
Описание
\D
Не цифра
\S
Пробельный символ [\t\n\r\f \хОВ]
\S
Непробельный символ
\w
Буква или цифра
\w
Не буква и не цифра
\р{имя}
Именованный класс символов (см. табл. 12.5)
\Р{ш<я)
Ограничители
Дополнение именованного класса символов
Начало и конец ввода (или начало и конец строки в многострочном режиме)
\b
Граница слова
\в
Граница не слова
\А
Начало ввода
\z
Конец ввода
\Z
Конец ввода, за исключением признака конца последней строки
\G
Квантификаторы
Конец предыдущего совпадения
X?
Необязательная строка X
X*
Строка X, 0 и более раз
х+
Строка X, 1 и более раз
X{n}, X{n,}, X{n, т)
Суффиксы квантификаторов
Строка X, п раз, не менее чем п раз, отраз
7
Превращает совпадение по умолчанию (жадное) в неполное совпадение
+
Операции над множествами
Превращает совпадение по умолчанию (жадное) в собственное совпадение
XY
Любая строка, начинающаяся строкой X, за которой следует строка Y
Х|Y
Группировка
Любая из строк X или Y
(X)
Помещает строку, совпадающую со строкой X, в группу
Окончание табл. 12.4
Символ
Описание
\п
Совпадение в П-Й группой
Управляющие символы
\с
№.. ЛЕ
Символ с (не должен принадлежать алфавиту)
Цитата... буквально
(?...)
Специальная конструкция — см. описание класса Pattern в документации по интерфейсу API
ТабЛ1
Lower
Строчные символы в кодировке ASCII [ ^”z ]
Upper
Прописные символы в кодировке ASCII [a-z]
Alpha
Алфавитные символы в кодировке ASCII [A-Za-z]
Digit
Цифры в кодировке ASCII [0-9]
Alnum
Алфавитные символы или цифры в кодировке ASCII [A-Za-zO-9]
Xdigit
Шестнадцатеричные цифры [0-9A-Fa-f]
Print или
Печатаемые символы в кодировке ASCII [\x21-\x7E]
Graph
Punct
He буквы и не цифры в кодировке ASCII [\p { Print} &&\P{Alnum]
ASCII
Все символы в кодировке ASCII [\x00-\x7F]
Cntrl
Управляющие символы в кодировке ASCII
[\p{ASCII}&&\P{Print}]
Blank
Пробел или символ табуляции [ \t ]
Space
In Блок
Пробельный символ [\t\n\r\f \хОВ]
Блок — это имя блока символов в кодировке Unicode, из которого удален пробел, например, BasicLatin или Mongolian. Список имен блоков приведен на Web-странице http://www.inicode.org
Категория ИЛИ Категория— это имя категории символов в кодировке Unicode, на-
1пКатегория
пример, L (буква) или Sc (символ валюты). Список имен категорий приведен на Web-странице http : / /www. inicode . org
Простейший способ применения регулярных выражений — проверить, совпадает ли с ним конкретная строка. Рассмотрим процесс тестирования этой программы. Сначала создадим объект класса Pattern, используя строку, задающую регулярное выражение. Затем получим по шаблону объект класса Master и вызовем метод matches.
Pattern patter = Pattern.compile(patternstring);
Matcher matcher = pattern.matcher(input);
if(matcher.matches() ] ...
На вход в метод сопоставления строк подается объект любого класса, реализующего интерфейс CharSequence, например, String, StringBu fer или CharBuffer из пакета java. io.
При компиляции шаблона можно установить один или несколько флагов. Например, Pattern pattern = Pattern.compile)patternstring,
Pattern.CASE_INSENSITIVE + Pattern.CASE_UNICODE_CASE);
Существуют шесть флагов.
CASE_INSENSITIVE: сопоставление символов без учета регистра (строчные и прописные буквы не различаются). По умолчанию этот флаг учитывает лишь символы из набора US ASCII.
UNICODE_CASE: в комбинации с флагом CASE_INSENSITIVE использует для сравнения буквы в кодировке Unicode.
MULTILINE: символы A и $ проверяются только в начале и конце строки, но не в середине.
UNIX LINE: при сопоставлении символовA и $ в многострочном режиме в качестве разделителя строк распознается только символ ' \n1.
DOTALL: если установлен этот флаг, символ . совпадает с любым символом, включая символ конца строки.
CANON EQ: учитывает каноническую эквивалентность символов в системе Unicode. Например, символ и, за которым следует трема ", совпадает с буквой и.
ЕСЛИ регулярное выражение содержит группы, то объект Matcher может определить их границы. Методы int start(int grouplndex] int end(inr grouplndex)
возвращают первый и следующий за последним индексы указанной группы.
Сравниваемую строку можно просто выделить с помощью метода
String group(int grouplndex)
Группа 0 означает всю строку ввода, индекс первой реальной группы равен 1. Для подсчета групп следует вызывать метод groupCount.
Вложенные группы упорядочиваются с помощью скобок. Например, пусть нам задан шаблон
(1(1?[0-9]: : ( [0-5] [0-9])) [ар]т
и мы вводим строку
11:59am
Объект класса Matcher сообщит следующее.
Индекс группы
Начало
Конец
Строка
0
0
7
11:59am
1
0
5
11:59
2
0
2
11
3
3
5
59
Приведенные ниже программы предлагают ввести шаблон, а затем сравниваемую строку. Она определяет, совпадает ли строка с заданным шаблоном. Если строка сов-
падает с шаблоном и при этом шаблон содержит группы, программы выводят на печать границы групп, заключая их в скобки, например,
{ (11) : (59))ат
Листинг 12.10. ПрограммЛистинг 12.10. Программ
Листинг 12.10. Программ
Листинг 12.10. Программ
import
import
/**
Эта программа проверяет совпадение регулярных выражений.
12.22.27.32.33.34.35.36.37.412.22.27.32.33.34.35.36.37.4Введите шаблон и строку,сравнению, либо
12.
22.
27.
32.
33.
34.
35.
36.
37.
4
12.
22.
27.
32.
33.
34.
35.
36.
37.
4
Щелкните на кнопке Cancel, чтобы выйти из программы. Если шаблон содержит группы, они выделяются скобками. */
public class RegExTest
{ public static voidargs)
{
String patternString =
"Enter pattern:");
Pattern pattern = null; try { pattern = } catch (PatternSyntaxException exception) {
syntax error");
System.exit(1);
}
while (true) {
String input =
•"Enter string to match:");
if (input == null)
Matcher matcher = if {
System.out.printin("Match"); int g = if (g > 0)
{
for (int i = 0; i <i++)
{
for (int j = 1; j <= g; j++) if (i ==
System.out.print('(');
System.out.print(input.charAt(i));
for (int j = 1; j <= g; j++) if (i + 1 ==
System.out.print(*)');
}
System.out.printin() ;
}
}
53.53.}57.}}57.}}}elseelseSystem.out.printIn("No match");System.out.printIn("No match");
53.
53.
}
57.}
}
57.}
}
}
else
else
System.out.printIn("No match");
System.out.printIn("No match");
Обычно с регулярным выражением сравнивается не вся входная строка, а лишь ее часть. Для определения следующего совпадения следует применять метод find из класса Matcher. Если он возвращает значение true, используйте методы startи end для определения длины совпадения. while (mathcer.findd )
{
int start = matcher.start();
int end = matcher.end();
String match = input.substring(start, end);
}
Приведенная ниже программа демонстрирует работу этого механизма. Она распознает все гипертекстовые ссылки, расположенные на Web-странице, и выводит их на экран. Чтобы запустить эту программу, укажите URL-адрес в командной строке, например, java HrefMatch http://www.horstmann.com
import java.io.*;
import java.net.*;
import java.util.regex.*;
/**
Программа выводит на экран все URL-адреса, содержащиеся
на Web-странице, сравнивая их с регулярным выражением,
описывающим дескриптор языка HTML <a href=...>.
Для запуска программы следует выполнить команду
. java HrefMatch URL
*/
public class HrefMatch
{
public static void main(String[ args)
{
try
{
// Получаем URL-адрес из командной строки
// или принимаем по умолчанию
String urlString;
if (args.length > 0) urlString = args[0];
else urlString = "http://java.sun.com";
// Открывает поток для чтения URL-адреса
InputStreamReader in = new InputStreamReader(
new URL(urlString).openStream());
// Считываем содержание буферной строки
StringBuffer input = new StringBuffer();
int
31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57. ]31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57. ]while ( (ch =!= -1) input.append((char)ch);
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57. ]
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57. ]
// Находим все элементы шаблона String patternString =
Pattern pattern = Pattern.compile(patternstring, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
while (matcher.find()]
{
int start = matcher.start ();
int end = matcher.end();
String match = input.substring(start, end); System.out.printin(match);
}
}
catch (lOException exception)
{
exception.printStackTrace();
}
catch (PatternSyntaxException exception)
{
' exception.printStackTrace();
}
}
Метод replaceAll класса Matcher заменяет все элементы регулярного выражения соответствующей строкой. Например, приведенный ниже фрагмент программы заменяет все последовательности цифр символом #.
Pattern pattern = Pattern.compile("[0-9]+});
Mathcer mathcer = pattern.matcher(input);
String output = mathcer.replaceAll("#");
Заменяющая строка может содержать ссылки на группы элементов шаблона: символ $п заменяется п-й группой. Для включения символа $ в заменяющий текст нужно использовать обозначение \$.
Существует также метод replaceFirst, заменяющий первое вхождение элемента шаблона.
И, наконец, класс Pattern содержит метод split, который разбивает входную строку на массив строк, используя в качестве границ элементы строки, совпадающие с элементами регулярного выражения. Например, следующие операторы разбивают строку на лексемы, причем в качестве разделителей используются знаки пунктуации, окруженные необязательными пробелами.
Pattern pattern = Pattern.compile("\\s*\\p(Punct)\\s*);
String!] tokens = pattern.split(input);
java.util.regex.Pattern 1.4
staticPattern compile(String expression)
staticPattern compile(String expression, int flag)
Компилирует регулярное выражение в шаблонный объект с целью быстрого выполнения сопоставлений.
Параметры: expression Регулярное выражение.
flagsОдна из констант CASE_INSENSITIVE,
UNICODE_CASE,MULTILINE, UNIX_LINES, DOTALL и CANON_EQ.
Matcher matcher(CharSequence input)
Возвращает объект класса Matcher, который можно использовать для обнаружения элементов шаблона во входной строке.
String!] split(CharSequence input)
String!] split(CharSequence input, int limit)
Разбивает входную строку на лексемы, используя разделители, заданные в шаблоне. Возвращает массив лексем. Разделители в лексемы не входят.
Параметры: input
Строка, разделяемая на лексемы.
limit
Максимальное количество строк, полученных в результате. Если число limit-1 означает количество распознанных разделителей, то последним элементом возвращаемого массива является часть строки, оставшаяся не разделенной на лексемы. Если limit<0, то на лексемы разбивается вся входная строка. Если limit=0, то оставшиеся пустые строки в массив не записываются.
API java.util .regex.Matcher 1.4
boolean matches(]
Если входная строка соответствует шаблону, возвращает значение true.
boolean lookingAt()
Если начало входной строки соответствует шаблону, возвращает значение true.
boolean find()
boolean find(int
start)
Пытается найти следующее совпадение и в случае удачи возвращает значение true.
Параметры: start Индекс, с которого начинается поиск.
int start ()
int end ()
Возвращает первый индекс и индекс, следующий за последним индексом текущего совпадения.
String group ()
Возвращает текущее совпадение.
int groupCount ()
Возвращает количество групп во введенном шаблоне.
int start (int grouplndex)
int end(int grouplndex)
Возвращает первый индекс и индекс, следующий за последним индексом заданной группы в текущем совпадении.
Параметры: grouplndex Индекс группы (начиная с 1) или 0 в случае полного совпадения.
String group(int grouplndex)
Возвращает строку, совпадающую с заданной группой.
Параметры: grouplndex Индекс группы (начиная с 1) или 0 в случае полного совпадения.
String replaceAll(String replacement)
String replaceFirst(String replacement)
Возвращает строку, полученную из входной строки путем замены строкой replacement либо всех совпадений, либо первого совпадения с шаблоном.
Параметры: replacement Заменяющая строка. Может содержать ссылки $л на группы шаблона, Для включения символа $ применяется обозначение \$.
Matcher reset(1
Matcher reset(CharSequence input)
Обновляет состояние объекта класса Matcher. Второй метод выполняет сопоставление шаблона с другой входной строкой. Оба метода возвращают указатель this.
Вот мы и добрались до конца первого тома. В нем изложены основы программирования на языке Java, а также описаны части стандартной библиотеки, необходимые в большинстве программ. Надеемся, что чтение этой книги доставило вам удовольствие, и вы нашли в ней много полезной информации. Во втором томе будут рассмотрены более сложные проблемы, связанные с работой в сети, многопоточностью, интернационализацией и безопасностью. Вот мы и добрались до конца первого тома. В нем изложены основы программирования на языке Java, а также описаны части стандартной библиотеки, необходимые в большинстве программ. Надеемся, что чтение этой книги доставило вам удовольствие, и вы нашли в ней много полезной информации. Во втором томе будут рассмотрены более сложные проблемы, связанные с работой в сети, многопоточностью, интернационализацией и безопасностью. Вот мы и добрались до конца первого тома. В нем изложены основы программирования на языке Java, а также описаны части стандартной библиотеки, необходимые в большинстве программ. Надеемся, что чтение этой книги доставило вам удовольствие, и вы нашли в ней много полезной информации. Во втором томе будут рассмотрены более сложные проблемы, связанные с работой в сети, многопоточностью, интернационализацией и безопасностью.
Приложение.
Ключевые слова
Ключевое слово
Значение слова
Глава
absract
Абстрактный класс или метод
5
assert
Используется для локализации ошибок в программе
11
boolean
Булевский тип
3
break
Выход из оператора switch или цикла
3
byte
8-битовое целое число
3
case
Метка оператора switch
3
catch
Раздел оператора try, предназначенный для перехвата исключительной ситуации
11
char
Символьный тип в кодировке Unicode
3
class
const
Определяет тип класса He используется
4
continue
Оператор продолжения в конце цикла
3
default
Раздел по умолчанию в операторе switch
3
do
Начало цикла do/while
3
double
Число с плавающей точкой, имеющее двойную точность
3
else
Раздел else в операторе if
3
extends
Определяет родительский класс данного класса
4
final
Константа, класс или метод, которые нельзя замещать
5
finally
Часто блока try, которая выполняется всегда
11
float
Число с плавающей точкой, имеющее одинарную точ-
HOCTb
3
for
goto
Разновидность цикла He используется
3
i
Условный оператор
3
implements
Определяет интерфейс, реализуемый классом
6
import
Импортирует пакет•
4
instanceof
Проверяет, является ли объект экземпляром класса
5
int
32-битовое целое число
3
interface
Абстрактный тип, содержащий методы, которые реализуются неким классом
6
820 Приложение. Ключевые слова
long native
64-битовое целое число3
Метод, реализуемый операционной системой (см. том 2)
new null package private protected
Размещает в памяти новый объект или массив3
Нулевая ссылка3
Пакет классов3
Свойство, доступное только методам данного класса4
Свойство, доступное только методам данного класса, его 5
public return short static
дочерним классам и другим классам в том же пакете
Свойство, доступное только методам всех классов4
Возвращает результат работы метода3
16-битовое целое число3
Свойство, уникальное для данного класса, но не для его3
объектов
strictfp
Использует строгие правила при вычислениях чисел с2
плавающей точкой
super switch synchronized this
Объект или конструктор суперкласса5
Оператор ветвления3
Метод, являющийся атомарным для потока (см. том 2)3
Неявный аргумент метода или конструктора данного4
throw throws
класса
Возбуждает исключительную ситуацию11
Исключительные ситуации. которые могут возбуждаться 11
transient
данным методом
Отмечает данные, которые не должны быть постоянны- 12 ми
try
Блок кода, перехватывающего исключительные ситуа-11
void volatile
ции
Обозначает метод, который ничего не возвращает3
Обеспечивает нескольким потокам согласованный дос-том 2
туп к ПОЛЮ
while
Цикл3
Предметный указатель
A
Action
методы
getValue, 380
putValue, 380
ActionListener
метод
actionPerformed, 381
D
ButtonModel
методы
getActionCommand, 407 getMnemonic, 407
isArmed, 407
isEnabled, 407
isPressed,407
isRollover, 407 isSelected, 407
J
java.applet.Applet
методы
destroy, 578
getAppletContext, 598
getAppletlnfo, 593
getAudioClip, 596; 598 getCodeBase,596
get Doc u men t Base, 596
getlmage, 596 getParameter, 593
get Parameterinfo, 593
init, 577; 578
play, 596
setSize, 577
showStatus, 598 start, 578 stop,578
java.appletAppletContext методы
getApplet, 599
getApplets, 598
showDocument, 599 java.awt.BorderLayout, 412 методы
BorderLayout, 412 java.awt.Color методы
brighter, 314
darker, 314 java.awt.Component
методы
createlmage, 333 enablcEvents, 400 getAlignmentX, 504 getAlignmentY, 504 getLocation, 298 getLocationOnScreen, 298 getSize, 298
isEnabled, 297 isFocusable, 378 isFocusOwner, 378 isShowing, 297 isVisible, 297 requestFocus, 378 requestFocusWindow, 378 setBackGround, 314; 316 setBonus, 298 setBounds, 521 setCursor, 375 setEnabled, 298
setFocusable, 378
set ForeGround, 314; 316
setLocation, 298
setSize, 298
setVisible, 297
validate, 423
java. awt .Container
методы
add, 344; 412
setLayout, 410
java.awt.event.ActionEvent
метод
getActionCommand, 348 java.awt.event.ComponentEvent методы
getComponent, 361
java.awt.event.FocusEvent
метод
getOppositeComponent, 379 java.awt.event.InputEvent
методы
getModifiers, 367
get Modi fiersEx, 368; 374 getModifiersExText, 375 isAltDown, 367
isControlDown, 367 isGraphDown, 367 isMetaDown, 367 isShiftDown, 367
java.awt .event .Key Event
методы
getKeyChar, 367
getKeyCoder, 367
get Key Mod i fiersKey, 367
getKeytext, 367
isActionKey, 367
java.awt.event.Mouse Event
методы
getClickCount, 368; 374
getPoint, 374
getX, 368; 374
getY, 368; 374
isPopupTrigger, 485
translatePoint, 374
java.awt.event. WindowEvent
методы
getNewState, 355
getOldState, 355
getOppositeWindow, 379
java.awt.event.WindowFocusListener методы
windowGainedFocus, 379
windowLostFocus, 379
java.awt.event.WindowListener
методы
windowActivated, 354
windowClosed, 354
windowClosing, 354
windowDeactivated, 354
windowDeiconified, 354
windowlconificd, 354
windowOpened, 354
java.awt.event.WindowStateListener
метод
windowStateChanged, 355
java. awl. Event Queue
методы
getNextEvent, 392
peekEvent, 392
postEvent, 392
java.awt.FlowLayout
метод
FlowLayout, 410
java.awt.Font
методы
deriveFont, 324
Font, 323
getFamily, 324
getFontName, 324
getLineMetrics, 324
getName, 324
getStringBounds, 324
java.awt.font .LineMetrics
методы
getAscent, 324
getDescent, 324
getHeight, 325
getLeading, 324
java.awt. Frame
методы
gctExtendedState, 299
setExtendedState, 299
seticonimage, 294; 298
setResizable, 294; 298
setTitle, 294; 298
sctUndecorated, 299
java.awt.geom.Ellipse2D, 305
java.awt. geom.RectangularShape
методы
getCenterX, 312
getCenterY, 312
getHeight, 312
getMaxX, 312
getMaxY, 312
getMinX, 312
getMinY, 312
getWidth, 312
getX, 312
getY,312
java.awt.Graphics
методы
dispose, 333
draw, 305
drawString, 325
Graphics2D, 305
setColor, 315
setFont, 325
java.awt.Graphics2D
методы
drawString, 325
getFontRenderContext, 325
setPaints, 315
java.awt. GraphicsEnvironment
методы
getAvailableFamilyNames, 318
getDefaultScreenDevice, 707
getLocalGraphicsEnvironment, 707
java.awt.GridConstraints
метод
GridBagConstraints, 511 поля
anchor, 511
fill, 511
gridheight, 511
gridwidth, 511
gridx, 511
gridy, 511
insets, 511
ipadx, 511
ipady, 511
weightx, 511
weighty, 511
java.awt. Image।
методы
copyArea, 333
drawlmage, 332
flush, 333
getGraphics, 333
java.awt.KeyboardFocusManager
методы
getActiveWindow, 378
getCurrentKeyboardFocusManager, 378
getFocusOwner, 378
getFocusWindow, 378
java.awt.LayoutManager
методы
addLayoutComponent, 525
LayoutContainer, 525
minimumLayoutSize, 525
preferredLayoutComponent, 525
removeLayoutComponent, 525
java.awt.MediaTracker
методы
addimage, 334
waitForAll, 334
waitForlD, 334
java.awt.Point
метод
toString, 204
java.awt.Robot
методы
createScreenCapture, 708
delay, 708
keyPress, 707
mouseMove, 708
mousePress, 708
writeChars, 734 writeDouble, 734 writeFloat, 734 writelnt, 734 writeLong, 734 writeShort, 734 writeUTF, 734java.io.FileметодыcanRead, 795canWrite, 795 createNewFile, 797createTempFile, 795 delete, 795deleteOnExit, 795 exists, 796getAbsolutePath, 796 getCanonicalFile, 796getCanonicalPath, 796 getName, 796 getParent, 796 getParentFile, 796 getPath, 796 isDirectory, 796 isFile, 796isHidden, 796lastModified, 796 length, 797 list, 797list Files, 797listRoots, 797mkdir, 797mkdirs, 797renameTo, 797set LastModified, 798 setRcadOnly, 798 toURL, 798java.io.FilelnputStream методыFilelnputStream, 730getChannel, 802java.io.FilcnameFilter методaccept, 798java.io.FileOutputStreamwriteChars, 734 writeDouble, 734 writeFloat, 734 writelnt, 734 writeLong, 734 writeShort, 734 writeUTF, 734java.io.FileметодыcanRead, 795canWrite, 795 createNewFile, 797createTempFile, 795 delete, 795deleteOnExit, 795 exists, 796getAbsolutePath, 796 getCanonicalFile, 796getCanonicalPath, 796 getName, 796 getParent, 796 getParentFile, 796 getPath, 796 isDirectory, 796 isFile, 796isHidden, 796lastModified, 796 length, 797 list, 797list Files, 797listRoots, 797mkdir, 797mkdirs, 797renameTo, 797set LastModified, 798 setRcadOnly, 798 toURL, 798java.io.FilelnputStream методыFilelnputStream, 730getChannel, 802java.io.FilcnameFilter методaccept, 798java.io.FileOutputStream
writeChars, 734 writeDouble, 734 writeFloat, 734 writelnt, 734 writeLong, 734 writeShort, 734 writeUTF, 734
java.io.File
методы
canRead, 795
canWrite, 795 createNewFile, 797
createTempFile, 795 delete, 795
deleteOnExit, 795 exists, 796
getAbsolutePath, 796 getCanonicalFile, 796
getCanonicalPath, 796 getName, 796 getParent, 796 getParentFile, 796 getPath, 796 isDirectory, 796 isFile, 796
isHidden, 796
lastModified, 796 length, 797 list, 797
list Files, 797
listRoots, 797
mkdir, 797
mkdirs, 797
renameTo, 797
set LastModified, 798 setRcadOnly, 798 toURL, 798
java.io.FilelnputStream методы
FilelnputStream, 730
getChannel, 802
java.io.FilcnameFilter метод
accept, 798
java.io.FileOutputStream
writeChars, 734 writeDouble, 734 writeFloat, 734 writelnt, 734 writeLong, 734 writeShort, 734 writeUTF, 734
java.io.File
методы
canRead, 795
canWrite, 795 createNewFile, 797
createTempFile, 795 delete, 795
deleteOnExit, 795 exists, 796
getAbsolutePath, 796 getCanonicalFile, 796
getCanonicalPath, 796 getName, 796 getParent, 796 getParentFile, 796 getPath, 796 isDirectory, 796 isFile, 796
isHidden, 796
lastModified, 796 length, 797 list, 797
list Files, 797
listRoots, 797
mkdir, 797
mkdirs, 797
renameTo, 797
set LastModified, 798 setRcadOnly, 798 toURL, 798
java.io.FilelnputStream методы
FilelnputStream, 730
getChannel, 802
java.io.FilcnameFilter метод
accept, 798
java.io.FileOutputStream
mouseRelease, 708
Robot, 707 java.awt.Toolkit методы beep,256 createCustomCursor, 370; 375 getDefaultToolkit, 255; 299 getlmage, 299 getScreenSize, 299
java.awt.Window методы
dispose, 293
isActive, 378
isFocused, 378
toBack, 298
toFront, 298 java.beans.EventHandler метод
create, 348
java.io.BufferedlnputStream методы
BufferedlnputStream, 730 java.io.BufferedoutputStream методы
BufferedOutputStream, 730 java.io.Datalnput методы
readBoolean, 733
readByte, 733
readChar, 733
readDouble, 733
readFloat, 733
readFully, 733
rcadlnt, 733
readLine, 733
readLong, 733
readShort, 733
readUTF, 733
skipBytes, 733 java.io.DataOutput методы
writeBoolean, 733
writeByte, 734
writeChar, 734
Предметный указатель 825
методы
FileOutputStre^m, 730
getChannel, 802
java.io.InputStream
методы
available, 72.4
close, 724
mark, 724
markSupported, 724
read, 723
reset, 724
skip, 724
java.io.ObjectlnputStream
методы
ObjectlnputStream, 771
readObject, 771
java.io.ObjectOutputStream методы
ObjectOutputStream, 771
writeObject, 771
java.io.OutputStream методы
close, 724
flush, 724
write, 724 java.io.PrintWriter методы
checkError, 745
print, 744
println, 744
PrintWriter, 743
java.io .PushbacklnputStream методы
PushbacklnputStream, 731
unread, 731
java.io. RandomAccessFile методы
getChannel, 802
getFilePointer, 735
length, 735
RandomAccessFile, 735
seek, 735
java.lang.Class
forName, 224
getConstructors, 229
getDeclaredConstructors, 229
getDeclaredFields, 229
getDeclaredMethods, 229
getFields, 229
getMethods, 229
getName, 211
getResource, 616
get ResourceAsStream, 616
getSuperclass, 211
newlnstance, 224
java.lang.ClassLoader
методы
clearAssertionStatus, 694
setClassAssertionStatus, 694
setDefaultAssertionStatus, 694
setPackageAssertionStatus, 694
java.lang.Comparable
метод
compareTo, 249
java.lang.Exception
методы
Exception, 664
java.lang.Integer
методы
intValue, 221
parselnt, 81; 221
toString, 221
valueOf, 221
java.lang.Math
методы
atan, 70
atan2, 70
cos, 70
exp,70
log, 70
pow, 70
random, 109
round,72
sin, 70
sqrt, 70
tan, 70
java.lang.Object
методы
методы
clone, 210
equals, 202; 203; 210
getClass, 202; 210; 221
toString, 204: 211
java.lang.reflect.AccessibleObject
методы
isAccessible, 234
setAccessible, 234
java.lang.reflect.Array
методы
get, 237
getLength,238
getXxx, 238
newlnstance, 238
set, 238
setXxx, 238
java.lang.reflect.Constructor
методы
getDeclaringClass, 229
getExceptionTypes, 229
getModifiers, 229
getName, 230
getParameterTypes, 230 newlnstance, 224; 225
java.lang.reflect.Field
методы
getDeclaringClass, 229
getModifiers, 229
getName, 230
j ava. lang.reflect .InvocationHandler метод
invoke, 284
java.lang.reflect.Method
методы
getDeclaringClass, 229
getExceptionTypes, 229
getModifiers, 229
getName, 230
getParameterTypes, 230
getReturnType, 230
invoke, 242
java.lang.reflect.Modifier
методы
isAbstract, 230
isFinal, 230 islnterface, 230 isNative, 230 isPrivate, 230
isProtected, 230 isPublic, 230 isStatic, 230
isStrict, 230 isSynchronized, 230 isVolatile, 230
toString, 230
java.lang.reflect.Proxy
методы
getProxy, 284 isProxyClass, 284
newProxylnstance, 284 java.lang.RuntimeException методы
RuntimeException, 664 java.lang.StackTraceElement методы
getClassName, 664 getFileName, 664 getLineNumber, 664
getMethodName, 664 isNativeMethod, 665
toString, 665
java.lang.String
методы
charAt, 77 compareTo, 76; 77
compareToIgnoreCase, 78
endsWith, 77 equals, 76; 77 equalsIgnoreCase, 76; 77 index, 77
indexOf, 77
isjavaldentifierStart, 64
JavaldentifierPart, 64 lastlndexOf, 77 length, 74; 77 replace, 77
start With, 78
String, 766 strncpy, 75
substring, 74; 78
toLowerCase, 78
toUpperCase, 78
trim, 78
java.lang.StringBuffer
методы
append, 766
capacity, 765
charAt, 766
ensureCapacity, 765
getChars, 766
insert, 766
length, 765
setCharAt, 766
setLength, 766
StringBuffer, 765
toString, 766
java.lang.System
методы
arraycopy, 106; 110
exit, 82; 83
getProperties, 639
getProperty, 639
runFinalizerOnExit, 165
j ava. lang.Throwable
методы
getCause, 664
getMessage, 655
getStackTrace, 664
initCause, 664
printStackTrace, 225
Throwable, 655; 664
java.math.BigDecimal
методы
add,104
compareTo, 104
divide, 104
multiply, 104
subtract, 104
valueOf, 104
java.math. Biginteger
методы
add,103
compareTo, 104
divide, 103
mod, 104
multiply, 103
subtract, 103
valueOf, 104
java.net.URL
метод
URL, 595
java.nio.Buffer
методы
hasRemaining, 803
limit, 803
java.nio.ByteBuffer
методы
array, 808
get, 803
order, 804
put, 803
wrap, 808
java.nio.channels.FileChannel методы
lock, 805
map, 802
trylock, 805
java.nio.channels.FileLock
метод
release, 806
java.nio.CharBuffer
метод
array, 808
java.nio.charset. Charset
методы
aliases, 808
availableCharsets, 808
decode, 808 encode, 808 forName, 808
java.swing.JColorChooser
методы
createDialog, 568
getColor, 568
JColorChooser, 568
setColor, 568
showDialog, 568
java.swing.JComponent
getSource, 348java.util.GregorianCalendar методыadd, 130; 135after, 134before, 134equals, 134get, 130; 134getTime, 131; 135GregorianCalendar, 133set, 130; 134setTime, 131; 135java.util.logging.ConsoleHandler методыConsoleHandler, 688java.util.logging.FileHandlerметодыFileHandler, 688 java.util.logging.Filter методыisLoggable, 689java.util.logging.Formatterметодыformat, 689getHead, 690getTail, 690java.util.logging.Handler методыclose, 688flush,688getFilter, 688getFormatter, 688getLevel, 688publish, 688setFilter, 688setFormatter, 688setLevel, 688java.util.logging.LoggerметодыaddHandler, 687config, 686entering, 686fine, 686finer, 686getSource, 348java.util.GregorianCalendar методыadd, 130; 135after, 134before, 134equals, 134get, 130; 134getTime, 131; 135GregorianCalendar, 133set, 130; 134setTime, 131; 135java.util.logging.ConsoleHandler методыConsoleHandler, 688java.util.logging.FileHandlerметодыFileHandler, 688 java.util.logging.Filter методыisLoggable, 689java.util.logging.Formatterметодыformat, 689getHead, 690getTail, 690java.util.logging.Handler методыclose, 688flush,688getFilter, 688getFormatter, 688getLevel, 688publish, 688setFilter, 688setFormatter, 688setLevel, 688java.util.logging.LoggerметодыaddHandler, 687config, 686entering, 686fine, 686finer, 686
getSource, 348
java.util.GregorianCalendar методы
add, 130; 135
after, 134
before, 134
equals, 134
get, 130; 134
getTime, 131; 135
GregorianCalendar, 133
set, 130; 134
setTime, 131; 135
java.util.logging.ConsoleHandler методы
ConsoleHandler, 688
java.util.logging.FileHandler
методы
FileHandler, 688 java.util.logging.Filter методы
isLoggable, 689
java.util.logging.Formatter
методы
format, 689
getHead, 690
getTail, 690
java.util.logging.Handler методы
close, 688
flush,688
getFilter, 688
getFormatter, 688
getLevel, 688
publish, 688
setFilter, 688
setFormatter, 688
setLevel, 688
java.util.logging.Logger
методы
addHandler, 687
config, 686
entering, 686
fine, 686
finer, 686
getSource, 348
java.util.GregorianCalendar методы
add, 130; 135
after, 134
before, 134
equals, 134
get, 130; 134
getTime, 131; 135
GregorianCalendar, 133
set, 130; 134
setTime, 131; 135
java.util.logging.ConsoleHandler методы
ConsoleHandler, 688
java.util.logging.FileHandler
методы
FileHandler, 688 java.util.logging.Filter методы
isLoggable, 689
java.util.logging.Formatter
методы
format, 689
getHead, 690
getTail, 690
java.util.logging.Handler методы
close, 688
flush,688
getFilter, 688
getFormatter, 688
getLevel, 688
publish, 688
setFilter, 688
setFormatter, 688
setLevel, 688
java.util.logging.Logger
методы
addHandler, 687
config, 686
entering, 686
fine, 686
finer, 686
методы
paintComponcnt, 301
repaint, 302
java.swing.JTextField
методы
JTextField, 424
set Columns, 424
java.text.NumberFormat
методы
format, 84; 85
getCurrencylnstance, 85 getMaximumFractionDigits, 85 getNumberinstance, 84; 85 getPercentlnstance, 85 setMaximumFraction, 84 setMaximumlntegerDigits, 85 setMinimumFractionDigits, 86 setMinimumlntegerDigits, 84; 86
java.util.Array
метод
sort, 246; 250
java.util.ArrayList
методы
add, 212:213;217
ArrayList, 213
ensureCapacity, 212; 213
get, 213; 215
remove, 217
set, 213; 215
size, 213
toArray, 214
trimToSize, 213
java.util.Arrays
методы
binarySearch, 110
equals, 111
fill, 111
sort, 108; 110
java.util.Date
методы
after, 129
before, 129
java.util.EventObject
метод
finest, 686
getFilter, 687
getHandlers, 687
getLevel, 687
getLogger, 686
getParent, 687
getUseParentHandlers, 687
info, 686
log, 686
logp,687
logrb, 687
removeHandler, 687
setFiker, 687
setParent, 687
setUseParentHandlers, 687
severe, 686
throwing, 686
warning, 686
java.util.logging.LogRecord
методы
getLevel, 689
get LoggerName, 689
getMessage, 689
getMillis, 689
getParameters, 689
getResourccBundle, 689
getResourceBundleName, 689 getSequenceNumber, 689 getSourceClassName, 689 getSourceMethodName, 689 getThreadID, 689
getThrown, 689
java.util.prefs.Preferences
методы
exportNode, 646
exportSubtree, 646
get, 645
getBoolean, 645
getByteArray, 645
getDouble, 645
getFloat, 645
getlnt. 645
importPreferences, 646
keys, 645
nodeRoot, 645
put, 645
put Boolean, 645
putByteArray, 645
putDouble, 645
putFloat, 645
putlnt, 645
putLong, 645
systemNodeForPackage, 645
systcmRoot, 645
userNodeForPackage, 645
userRoot, 645
java.util.Properties
методы
getProperty, 636
load, 637
Properties, 636
store, 637
java.util.Random
методы
nextlnt, 165
Random, 165
java.util.regex.Matcher
методы
end, 817; 818
find, 817
group, 817; 818
groupCount, 817
lookingAt, 817
matches, 817
replaceAll, 818
replaceFirst, 818
reset, 818
start, 817; 818
java.util.regex.Pattern
методы
compile, 816
matcher, 817
split, 817
java.util.StringTokeniz.er
методы
countTokens, 756
hasMoreTokens, 756
nextToken,756
StringTokenizer, 756
java.util.zip.ZipEntry
методы
getCrc, 753
getName, 753
getSize, 753
isDirectory, 753
setCrc, 753
setMethod, 753
setSize, 753
ZipEntry, 752 java.util.zip.ZipFile методы
entries, 753
getEntry, 753
getlnputStream, 754
getName, 754
ZipFile, 753
Java.util.zip.ZipInputStream методы
CloseEntry, 752
getNextEntry, 752
ZipInputStream, 752 java.util.zip.ZipOutputStream методы
closeEntry, 752
putNextEntry, 752
set Level, 752
setMethod, 752
ZipOutputStream, 752 javax.jnlp.BasicSemce методы
getCodeBase, 630
isWebBrowserSuppoited, 631 showDocument, 631
javax.jnlp.FileContents методы
canRead, 631
canWrite, 631
getlnputStream, 631
getName, 631
getOutputStream, 631 javax.jnlp.FileOpenService методы
openFileDialog, 631
openMultiDialog, 631 javax.jnlp.FileSaveService
метод
saveFileDialog, 631
javax.jnlp.PersistenceService методы
create, 632
delete, 632
get, 632
getNames, 632
javax.jnlp.ServiceManager
методы
getServiceNames, 630
lookup, 630
javax.swing,event.EventListenerList методы
add,399
getListenerList, 400
getListeners, 399
remove, 399
javax.swing.AbstractAction
метод
AbstractAction, 482
javax.swingAbstractButton методы
isSelected, 483
setAction, 481
setActionCommand, 456
setHorizontalTextPosition, 482
setMnemonic, 487
setSelected, 483
javax.swing.AbstractSpinnerModel методы
getNextValue, 478
getPreviousValuc, 478
getValue, 477
setValue, 478
javax.swing.Action
методы
getValue, 387
isEnabled, 386
putValue, 386
setEnabled, 386
javax.swing.border.LineBorder метод
LineBorder, 460
javax.swing.border.SoftBevelBorder
методы
SoftBevelBorder, 460
javax. swing. BorderFactory
методы
createBevelBorder, 459
createCompoundBorder, 460
createEmptyBorder, 459 createEtchedBorder, 459
createLineBorder, 458
createLoveredBevelBorder, 459 createMatteBorder, 458 createRaisedBevelBorder, 459 createTitleBorder, 459
javax.swing.box
методы
createCreateGlue, 504
createHorizontalBox, 504
createHorizontalGlue, 504
createHorizontalStrut, 504
createRigidArea, 504
createVerticalBox, 504
createVerticalGlue, 504
createVerticalStrut, 504
javax.swing.ButtonGroup
методы
add.456
getSelection, 456
javax.swing.ButtonModel
метод
getActionCommand, 456
javax.swing.event.DocumentEvent
методы
getDocument, 424
javax.swing.event.DocumentListener
методы
changedUpdate, 424
insertUpdate, 424
removedUpdate, 424
javax.swing.event.MenuListener
методы
menuCanceled, 488
menuDeselected, 488
menuSelected, 488
javax.swing.filechooser.FileFilter методы
accept, 562
getDescription, 562
javax.swing.fileChooser.FileView методы
getDescription, 562
gctlcon, 562
getName, 562
getTypeDescription, 562
isTraversable, 562
javax.swing.JButton
методы
isDefault Button, 550
JButton, 343
javax.swing.JCheckBox
методы
isSelected, 452
JCheckBox, 451
setSelected, 452
javax.swing.JCheckBoxMenuItem метод
BoxMenuItem, 483
javax.swing.JComboBox
методы
additem, 463
getSlectcdltem, 463 removeAllItems, 463 removeitem, 463
setEditable, 463
javax.swing.JComponent
методы
getActionMap, 388
getlnputMap, 388
getRootPanel, 550
setBorder, 460
setToolTipText, 497
javax.swing.JDialog
метод
JDialog, 543
javax.swing.JFileChooser
методы
getSelectedFile, 561
getSelectedFiles, 561
JFileChooser, 561
setAccessory, 562
setAceptAHFileFilterUsed, 561
setCurrentDirectory, 561
setFileFilter, 562
setFileSelectionMode, 561
setFileView, 562
setMultiSelectionEnabled, 561
setSelectedFile, 561
showDialog, 561
showOpenDialog, 561
showSaveDialog, 561
javax.swing.JFormattedTextField
методы
commitEdit, 438
getLocusLostBehaviour, 438
getValue, 438
isEditValue, 438
JFormattcdTextField, 438
setLocusLostBehaviour, 438
setValue, 438
javax.swing.JFormattedTextField.Abstrac tFormatter
методы
gctDocumentFilter, 439
stringToValue, 439
valueToString, 439
javax.swing.JFrame
методы
getContentPane, 304
paintComponent, 304
repaint, 304
setJMenuBar, 481
javax.swing.JLabel
методы
JLabel, 445
seticon, 446
setText, 446
javax.swing.JMenu
методы
add, 387; 480
addSeparator, 480
insert, 480
insertSeparator, 480
JMenu, 480
remove, 480
javax.swing.JMenuItem
методы
JMenuItem, 481; 482; 487
setAccelerator, 487
setEnabled, 488
javax.swing.JOptionPane
методы
showConfirmDialog, 537
showInputDialog, 81; 83; 539
showInternalConfirmDialog, 537 showInternallnputDialog, 539 showIntemalMessageDialog, 537 showInternalOptionConfirmDialog,
538
showMessageDialog, 255; 536 showOptionConfirmDialog, 538
javax.swing.JPasswordField
методы
getPassword, 425
JPasswordField, 425
setEchoChar, 425
javax.swing.JPopupMenu
методы
isPopupTrigger, 485
show, 485
javax.swing.JRadioButton
метод
JRadioButton, 455
javax.swing.JRadioButtonMenuItem
метод
JRadioButtonMenuItem, 483 javax.swing.JRootPane
метод
setDefaultButton, 550
javax.swing.JSlider
методы
JSlider, 469
setLabelTable, 470
setMajorTickSpacing, 469
setMinorTickSpacing, 469 setPaintLabels, 469
setPaintTics, 469
setSnapToTick, 470
javax.swing.JSpinner методы
getValue, 476
JSpinner, 476
setEditor, 476
set Value, 476
javax.swing.JSpinner .DateEditor метод
DateEditor, 477 javax.swing.JTextArea методы
append, 444
JTextArea, 443
setColumns, 444
setLineWrap, 444
setRows, 444
setTabSize, 444
setWrapStyleForward, 444 javax.swing.JToolBar методы
add,496
addSeparator, 497
JToolBar, 496 javax.swing.KeyStroke метод
getKeyStroke, 387 javax.swing.SpinnerListModel методы
SpinnerDateModel, 477
SpinncrListMode, 477 javax.swing.SpinnerNumberModel метод
SpinnerNumberModel, 476 javax.swing.Spring методы
constant, 520
getMaximimValue, 520
getMinimumValue, 520
getPreferred, 520
getValue, 520
max, 520
minus, 520
setValue, 520
sum, 520
javax.swing.SpringLayout
метод
putConstraints, 519
javax.swing.SpringLayout.Constraints методы
getConstraints, 519; 520
getHeight, 520
getWidth, 520
getX, 520
getY, 520
setConstraints, 520
javax.swing.SwingUtilities
метод
getAncestorOfClass, 550
javax.swing.text.DefaultFormatter методы
getOverwriteMode, 439
setOverwriteMode, 439
javax.swing.text.Document
методы
addDocumentListener, 424
getLength, 424
getText, 424
javax.swing.text. DocumentFilter
методы
insertString, 439
replace, 439
javax.swing.text.JTextComponent
методы
detSelectedTextl, 448
detSelectionStart, 448
getSelectionEnd, 448
getText, 418
insert, 448
replaceRange, 449
select, 448
selectAll, 448
setEditable, 418
setText, 418
javax.swing.text.MaskFormatter
методы
getlnvalidCharacter, 440
getPlaceholder, 440
getPlaceholderCharacter, 440
getValidCharacter, 440
атрибуты
getValueContainsLiteralCharacter,
absbottom, 584
440
absmiddle, 584
MaskFormatter, 440
align, 583
setlnvalidCharacter, 440
archive, 585
setPlaceholder, 440
baseline, 584
setPlaceholderCharacter, 440
bottom, 584
setValidCharacter, 440
classid, 588
setValueContainsLiteralCharacter,
code,585
440
codebase, 585
javax.swing. Timer
height, 583
методы
hspace, 584
start, 255
left, 584
stop, 255
middle, 584
Timer, 255
name, 586
javax.text.DateFormat
object, 586 right, 584
метод
texttop, 584
getDatelnstance, 438
top, 584
javax.text.SimpleDateFormat
vspace, 584
метод
weight, 583
toPattern, 477
контекст, 596
кэширование, 609
Кправила работы, 579
с закладками, 599 KeyListener
методы
keyPressed, 362
key Released, 362
keyTyped, 362
M
MouseListener
методы
mouseClicked, 368
mouseReleased, 368 MouseMotionListener методы
mouseDragged, 370
mouseMoved, 370
A
Аплет, 570
Б
Базовая линия, 321
Библиотека
AWT, 286
fdlibm, 70
IFC, 286
Java 2D, 305
JFC, 287
Swing, 286
Блок, 58; 86
вложенный, 86
инициализации, 161
статический,162
область видимости, 87
В
Верификатор, 429
Версификация, 787
Вызов метода
по значению, 151
по ссылке, 151
д
Действия, 380
Дескриптор, 174
©author, 176
©deprecated, 176
@param, 175
©return, 175
@see, 176
©since, 176
©throws, 175
©version, 176
applet, 582
embed, 52
Object, 575; 587
param, 588
title, 570
Диагностическое утверждение, 690
отключение, 691
подключение, 691
предусловие, 692
Директива
import, 82; 166
Доклет, 178
3
Загрузчик класса, 279; 607
Заместители,278
Заполнители, 501
И
Инкапсуляция, 120
Интерлиньяж, 321
Интерпретатор java, 39
Интерфейс, 246; 356
Action, 380
ActionListener, 339; 356; 380
AdjustmentListener, 356
AppletStub, 605
ButtonModel, 407
Clonable, 258
ComponentListener, 356
Container Listener, 356
FocusListener, 356
ItemListener, 356
KeyListener, 356
MouseListener, 356
MouseMotionListener, 356
MouseWheelListener, 356
Shape, 305
TextListener, 356
WindowFocusListener, 356
WindowListener, 356
WindowStateListener, 356 помеченный, 258
Интерфейс прикладных программ API
документация, 78
Исключительная ситуация, 81
ArraylndexOutBoundsException ,650
CloneNotSupportedException, 259
IOException, 651
MalformedURLExccption, 594
NullPointerException, 650
RuntimeException, 650; 652
SecurityException, 579
возбуждение, 653
иерархия наследования, 650
Error, 649
Exception, 649
контролируемая, 222; 223; 651
неконтролируемая, 223; 651
обработка, 648
обработчик, 222
перехват, 223; 655
блок try/catch, 656
перехват нескольких ситуаций, 658
повторное возбуждение, 658
применение, 669
раздел finally, 659
создание класса, 655 спецификация, 652 цепочка, 662
К
Канал, 799
Клавиша
назначенная, 485
ускоряющая, 485
Класс
java.appIet.Applet, 572; 577; 578; 593; 598
java.applet.AppletContext, 598 java.appletApplet, 596
java.awt.Color, 313; 315 java.awt.Component, 297; 316; 333; 375; 378; 400; 423; 504; 521 java.awt.Container, 344; 410; 412 java.awt.event.ActionEvent, 348 java.awt.event.ComponentEvent, 361 java.awt.event.FocusEvent, 379 java.awt.event.InputEvent, 367; 374 java.awt.event.KeyEvent, 367 java.awt.event.MouseEvent, 374; 485 java.awt.event. WindowEvent, 355; 379 java.awt.event.WindowFocusListener, 379
java.awt.event.WindowListener, 354 java.awt.event.WindowStateListener, 355
java.awt.EventQueue, 392 java.awt.FlowLayout, 410 java.awt.Font, 323
java.awt. font. LineMetrics, 324
java.awt.Frame, 298
java.awt.geom.Ellipse2D.Double, 312
java.awt.geom.Line2D, 305
java.awt.geom.Point2D.Double, 312
java.awt.geom.Rectangle2D.Double, 312 java.awt.geom.Rectangle2D.Float, 312 java.awt.geom.RectangularShape, 312
java.awt.Graphics, 315; 325; 333
java.awt.Graphics2D, 315; 325
java.awt.GraphicsEnvironment, 318; 707
java.awt .GridConstraints, 511
java.awt.GridLayout, 417
java.awt .Image, 332
java.awt.KeyboardFocusManager, 378
java.awt.LayoutManager, 525
java.awt.McdiaTracker, 334
java.awt.Rectangle2D, 305
java.awt.Robot, 707
java.awt.SystemColor, 314
java.awt.Toolkit, 299; 370; 375
java.awt.Window, 298; 378
java.beans.EvcntHandler, 348
java.io.BufferedlnputStream, 730
java.io.BufferedOutputStream, 730
java.io.Datalnput, 733
java.io.DataOutput, 733
java.io.File, 795
java.io.FilelnputStream, 730; 802
java.io.FilenameFilter, 798
java.io.FileOutputStream, 802
java.io.InputStream, 723
java.io.ObjectlnputStream, 771
java.io.ObjectOutputStream, 771
java.io.OutputStream, 724
java.io.PrintWriter, 743
java.io.PushbacklnputStream, 731
java.io.RandomAccessFile, 735; 802
java.lang.Class, 211; 229
java.lang.Class, 221; 224; 616
java.lang.ClassLoader, 694
java.lang. Comparable, 249
java.lang.Exception, 664
java.lang.Integer, 221
java.lang.Math, 70
java.lang.Object, 210
java.lang.Object, ,201
java.lang.reflect.AccessibleObject, 234
java.lang.reflect.Array, 237
java.lang.reflect.Array, 234
java.lang.reflect.Constructor, 224; 225; 226; 229
java.lang.reflect.Field, 226; 229
java.lang.reflect.InvocationHandler, 284 java.lang.reflect.Method, 242 java.lang.reflect.Method, 229
java.lang.reflect.Methof, 226
java.lang.reflect.Modifier, 230
java.lang.reflect.Proxy, 284
java.lang.RuntimcException, 664 java.lang.StackTraceElement, 664 java.lang.StrictMath, 70
java.lang.String, 73; 77; 766
java.lang.StringBuffer, 765 java.lang.System, 83; 110;639
java.lang.Throwable, 225; 655; 664 java.math.BigDecimal, 102
java.math. Biginteger, 102; 103
java.math BigDeci mal, 104
java.net.URL, 595
java.nio.Buffer, 803
java.nio.ByteBuffer, 803; 808
java.nio.channels.FileChannel, 802; 805 java.nio.channels.FileLock, 806
java.nio.CharBuffer, 808
java.nio.charset.Charset, 808
java.swing.JColorChooser, 568
java.swing.JComponent, 301
java.swing.JPanel, 301
java.text.Format, 84
java.text.NumberFormat, 83; 85; 221
java.utiLArray, 250
java.util.ArrayList, 213; 215; 217
java.utilArrayList, 211
java.util.Arrays, 108; 110
java.util.Date, 129
java.util.EventObject, 348
java.util.GregorianCalendar, 129; 133 java.util.logging.ConsoleHandler, 688 java.util.logging.FileHandler, 688 java.util.logging.Filter, 689
java.util.logging.Formatter, 689 java.util.logging.Handler, 688
java.util.logging.Logger, 686
java.util.logging.LogRecord, 689
java.util.prefs.Preferences, 645
java.util.Properties, 636
java.util.Random, 165
java.util.regex.Matcher, 817
java.util. regex.Pattcm, 816
java.util.StringTokenizer, 756
java.util.zip.ZipEntry, 752
java.util.zip.ZipFile, 753
java.util.zip.ZipInputStream, 752
java.util.zip.ZipOutputStream, 752
javax.awt.Component, 304
javax.jnlp.BasicService, 630 javax.jnlp.FileContents, 631 javax.jnlp.FileOpenService, 631 javax.jnlp.FileSaveService, 631 javax.jnlp.PersistenceService, 632 javax.jnlp.ServiceManager, 630 javax.swing,event.EventListenerList, 399 javax.swing.AbstractAction, 381; 482 javax.swing.AbstractButton, 456; 481; 482; 483; 487
javax.swing.AbstractSpinnerModel, 477 javax.swing.Action, 386
javax.swing.border.LineBorder, 460 javax.swing.border.SoftBevelBorder, 460
javax.swing.BorderFactory, 458
javax.swing.box, 504
javax.swing.ButtonGroup, 456
javax.swing.ButtonModel, 456
javax.swing.event.DocumentEvent, 424 javax.swing.event.DocumentListener, 424
javax.swing.event.MenuListener, 488 javax.swing.filechooser.FileFilter, 562 javax.swing.flleChooser.FileView, 562 javax.swing.Imageicon, 344 javax.swing.JButton, 343; 550
javax.swing.JCheckBox, 451
javax.swing.JCheckBoxMenuItem, 483 javax.swing.JComboBox, 463
абстрактный, 196адаптер,352ComponentAdapter, 357 ContainerAdapter, 357 FocusAdapter, 357 KeyAdapter, 357MouseAdapter, 357MouseMotionAdapter, 357 WindowAdapter, 357 базовый, 182 вложенный, 277 внутренний,261безымянный,272 локальный, 270 статический,275диаграмма, 122дочерний, 182 интерфейсный,218 комментарии, 175 подкласс, 182 производный, 182 родительский, 182 суперкласс, 182 терминальный, 192Клонирование,790 Ключевое словоabstract, 819assert, 690; 819boolean, 819break, 819byte, 819case, 819catch, 223; 819char, 819class, 819const, 819continue, 819default, 819do, 819double, 819else, 819extends, 182; 819final, 65; 186; 192; 272; 819finally, 659; 819абстрактный, 196адаптер,352ComponentAdapter, 357 ContainerAdapter, 357 FocusAdapter, 357 KeyAdapter, 357MouseAdapter, 357MouseMotionAdapter, 357 WindowAdapter, 357 базовый, 182 вложенный, 277 внутренний,261безымянный,272 локальный, 270 статический,275диаграмма, 122дочерний, 182 интерфейсный,218 комментарии, 175 подкласс, 182 производный, 182 родительский, 182 суперкласс, 182 терминальный, 192Клонирование,790 Ключевое словоabstract, 819assert, 690; 819boolean, 819break, 819byte, 819case, 819catch, 223; 819char, 819class, 819const, 819continue, 819default, 819do, 819double, 819else, 819extends, 182; 819final, 65; 186; 192; 272; 819finally, 659; 819
абстрактный, 196
адаптер,352
ComponentAdapter, 357 ContainerAdapter, 357 FocusAdapter, 357 KeyAdapter, 357
MouseAdapter, 357
MouseMotionAdapter, 357 WindowAdapter, 357 базовый, 182 вложенный, 277 внутренний,261
безымянный,272 локальный, 270 статический,275
диаграмма, 122
дочерний, 182 интерфейсный,218 комментарии, 175 подкласс, 182 производный, 182 родительский, 182 суперкласс, 182 терминальный, 192
Клонирование,790 Ключевое слово
abstract, 819
assert, 690; 819
boolean, 819
break, 819
byte, 819
case, 819
catch, 223; 819
char, 819
class, 819
const, 819
continue, 819
default, 819
do, 819
double, 819
else, 819
extends, 182; 819
final, 65; 186; 192; 272; 819
finally, 659; 819
абстрактный, 196
адаптер,352
ComponentAdapter, 357 ContainerAdapter, 357 FocusAdapter, 357 KeyAdapter, 357
MouseAdapter, 357
MouseMotionAdapter, 357 WindowAdapter, 357 базовый, 182 вложенный, 277 внутренний,261
безымянный,272 локальный, 270 статический,275
диаграмма, 122
дочерний, 182 интерфейсный,218 комментарии, 175 подкласс, 182 производный, 182 родительский, 182 суперкласс, 182 терминальный, 192
Клонирование,790 Ключевое слово
abstract, 819
assert, 690; 819
boolean, 819
break, 819
byte, 819
case, 819
catch, 223; 819
char, 819
class, 819
const, 819
continue, 819
default, 819
do, 819
double, 819
else, 819
extends, 182; 819
final, 65; 186; 192; 272; 819
finally, 659; 819
javax.swing.JComponent, 304; 388; 460; 497; 550
javax.swing.JDialog, 543
javax.swing.JFileChooser, 561 javax.swing.JFormattedTextField, 438 javax.swing.JFormattedTextField.Abstra ctFormatter, 439
javax.swing.JFrame, 304; 481
javax.swing.JLabel, 445
javax.swing.JMenu, 387; 480 javax. swingJMenuItem, 480; 482; 487;
488
javax.swing.JOptionPane, 81; 83; 255; 536
javax.swing.JPanel, 414
javax.swing.JPasswordField, 425
javax.swing.JPopupMenu, 485 javax.swing.JRadioButtonMenuItem, 483
javax.swing.JRootPane, 550
javax.swing.JScrollPane, 444
javax.swing.JSIider, 469 javax.swing.JSpinner, 476 javax.swing.JSpinner.DateEditor, 477 javax.swing.JTextArea, 443 javax.swing.JTextField, 424 javax.swing.JToolBar, 496 javax.swing.KeyStroke, 387 javax.swing.SpinnerListMode, 477 javax.swing.SpinnerNumberModel, 476 javax.swing.Spring, 520
javax.swing.SpringLayout, 519; 520
javax.swing.SwingUtilities, 550 javax.swing.text.DefaultFormatter, 439 javax.swing.text.Document, 424 javax.swing.text.DocumentFilter, 439 javax.swing.text.JTextComponent, 418;
448
javax.swing.text.MaskFormatter, 440 javax.swing.Timer, 255
javax.swingRadioButton, 455 javax.text.DateFormat, 438 javax.text.SimpleDatcFormat, 477 jawa.awt.Toolkit, 255
float, 819
for, 819
goto, 819
if, 819
implements, 246; 819
import, 166; 819
int, 819
interface, 819
long, 820
native, 820
new, 820
null, 820
package, 820
private, 820
protected, 200; 820
public, 820
return, 820
short, 820
static, 820
strictfp, 820
super, 184; 820
switch, 820
synchronized, 820
this, 161:185;820
throw, 820
throws, 651; 820
JTextArea, 418
JTextComponent, 418
JTextField, 405
Компоновка, 409
блочная, 500
потоковая, 409
рамок, 411
сеточная, 414
сеточного мультимножества, 504
эластичная, 511
Константа, 65
Math.E, 70
Math.PI, 70
класса, 66
математическая, 70
статическая, 147
Конструктор,126
по умолчанию, 158
Контейнер, 290: 361
Копирование
глубокое, 258
поверхностное, 257
Курсор, 369
м
transient, 784; 820 try, 223; 820 unstanceOf, 819 void, 820
volatile, 820
while, 820
Код клавиши, 362
Комментарии, 59
Компилятор]ауас, 39
Компонент, 361
IMenuItem, 478
[Frame, 290
JLabel, 445
JMenu, 480
JMenuBar, 478
JPanel, 413
JSpinner, 470
Максимальная высота литер, 321
Максимальная глубина посадки литер, 321
Манифест, 610
Маска мыши, 368
ALT_DOW_MASK, 368
ALT_GRAPH_DOWN_MASK, 368
BUTTON 1_DOWN_MASK. 368
BUTTON2_DOWN_MASK, 368
BUTTONS JTOWN_MASK, 368
CTRL_DOWN_MASK, 368
META_DOWN_MASK, 368
SH1FT_DOWN_MASK, 368
Массив, 104
"неровный", 114
безымянный, 105
динамический, 234
инициализация, 105
копирование, 106
многомерный, 111
объявление, 104
создание, 105
сортировка, 108
Менеджер
безопасности, 291
компоновки, 409
потоковой, 409
рамок, 410
сеточной, 414
Меню, 478
блокирование пунктов, 487
всплывающее, 484
линейка, 478
подменю, 478
пункт, 478
разблокирование пунктов, 487
с группами переключателей, 482
с комбинированными списками, 482
с пиктограммами, 481
Метка, 444
Метод
finalize, 165
main, 57; 149
System .out .print in, 58
абстрактный, 197
доступа, 130
комментарии, 175
модифицирующий, 130
перегруженный, 157
сигнатура, 190
статический, 70; 147
терминальный, 192
Множество свойств, 632
Модели безопасности, 580
Модификатор доступа
private, 172
protected, 200
public, 172
Мультимедиа, 593
н
Набивка, 507
Набор инструментальных средств Java
Software Development Kit, 34
библиотеки и документация, 35
загрузка, 34
инсталляция, 34
Наследование, 120
иерархия, 188
принципы применения, 242
цепочка, 188
О
Обратный вызов, 253
Объект
клонирование, 256
копирование, 256
методы, 120
перечислимый, 597
полиморфный, 189
поля, 120
состояние, 120
сущность, 120
Окно
активное, 376
всплывающее, 580
диалоговое, 527; 540
фокусированное, 376
Оператор, 66
- (вычитание), 66
! (логическое отрицание), 68
!= (проверка неравенства), 68
% (остаток от деления целых чисел), 66
& (побитовое "и"), 69
&& (логическое "и"), 68
* (умножение), 66
/ (деление), 66
?: (тернарный), 69
[] (обращение к элементу массива), 107
л (исключающее "или"), 69| (побитовое "или"), 69|| (логическое "или"), 68- (побитовое отрицание, или инверсия), 69+ (сложение), 66< (меньше), 68<= (меньше или равно), 68= (присваивания), 65==68=> (больше или равно), 68> (больше), 68» (, 69>» , 69breakбез метки, 100с меткой, 100continue, 101instanceof, 195switch, 98метка case, 98раздел default, 98арифметический, 66сокращенный, 66декрементации, 67постфиксный, 68префиксный, 68инкрементации, 67постфиксный, 68префиксный, 68логический,68отношения, 68побитовый, 69приоритет, 72составной, 87условный, 87Основные типы, 60boolean, 63byte, 60char, 62double, 61float, 61int, 60long, 60л (исключающее "или"), 69| (побитовое "или"), 69|| (логическое "или"), 68- (побитовое отрицание, или инверсия), 69+ (сложение), 66< (меньше), 68<= (меньше или равно), 68= (присваивания), 65==68=> (больше или равно), 68> (больше), 68» (, 69>» , 69breakбез метки, 100с меткой, 100continue, 101instanceof, 195switch, 98метка case, 98раздел default, 98арифметический, 66сокращенный, 66декрементации, 67постфиксный, 68префиксный, 68инкрементации, 67постфиксный, 68префиксный, 68логический,68отношения, 68побитовый, 69приоритет, 72составной, 87условный, 87Основные типы, 60boolean, 63byte, 60char, 62double, 61float, 61int, 60long, 60
л (исключающее "или"), 69
| (побитовое "или"), 69
|| (логическое "или"), 68
- (побитовое отрицание, или инверсия), 69
+ (сложение), 66
< (меньше), 68
<= (меньше или равно), 68
= (присваивания), 65
==68
=> (больше или равно), 68
> (больше), 68
» (, 69
>» , 69
break
без метки, 100
с меткой, 100
continue, 101
instanceof, 195
switch, 98
метка case, 98
раздел default, 98
арифметический, 66
сокращенный, 66
декрементации, 67
постфиксный, 68
префиксный, 68
инкрементации, 67
постфиксный, 68
префиксный, 68
логический,68
отношения, 68
побитовый, 69
приоритет, 72
составной, 87
условный, 87
Основные типы, 60
boolean, 63
byte, 60
char, 62
double, 61
float, 61
int, 60
long, 60
л (исключающее "или"), 69
| (побитовое "или"), 69
|| (логическое "или"), 68
- (побитовое отрицание, или инверсия), 69
+ (сложение), 66
< (меньше), 68
<= (меньше или равно), 68
= (присваивания), 65
==68
=> (больше или равно), 68
> (больше), 68
» (, 69
>» , 69
break
без метки, 100
с меткой, 100
continue, 101
instanceof, 195
switch, 98
метка case, 98
раздел default, 98
арифметический, 66
сокращенный, 66
декрементации, 67
постфиксный, 68
префиксный, 68
инкрементации, 67
постфиксный, 68
префиксный, 68
логический,68
отношения, 68
побитовый, 69
приоритет, 72
составной, 87
условный, 87
Основные типы, 60
boolean, 63
byte, 60
char, 62
double, 61
float, 61
int, 60
long, 60
short, 60
интерфейсные классы, 218
преобразование, 71
приведение, 71
Отладка программ, 694
анализ компонентов, 697
вывод промежуточных результатов, 695
запись сообщений в файл, 696
класс Robot, 704
отдельные класы main, 695
отладчик графики, 698
отслеживание событий AWT, 700
применение консольного окна, 698
проверка состояние объекта, 695
профилирование, 708
тестирование охвата, 712
трасировка стека, 695
трассировка загрузки класса, 697
Отладчик
JDB, 714
Sun ONE Studio, 719
Отношение
агрегирование ("содержит"), 121
ассоциированность, 122
зависимость ("использует"), 121
наследование ("является"), 121
Отражение, 225
п
Пакет, 165
java.applet, 572
java.awt.event, 356; 357
java.lang, 82
java.lang.reflect, 224; 225
java.math, 102
java.nio, 799
java.text, 83
javax.swing, 82; 291
вспомогательный, 616
запечатывание, 617
комментарии, 174
область видимости, 172
по умолчанию, 167
Панель, 412
инструментальная, 492
Переключатель, 452
Переменная
адрес, 151
инициализация,65
объявление, 64
присваивание, 65
Подсказки, 494
Поле
весовое, 506
инициализация, 158
комментарии, 176
статическое, 146
Ползунковый регулятор, 464
Порядок записи байтов
big-endian, 23; 732
little-endian, 23; 732
Поток, 721
ZIP-файл, 746
блокирование, 723
ввода, 745
вывода, 742
запись, 722
объектный, 767
с произвольным доступом, 760
семейство, 725
текстовый, 735
управления, 86
файл прямого доступа, 734
фильтр, 727
чтение,722
Потоки
использование, 754
Предусловие, 693
Приведение типов, 193
Принцип подстановки, 189
Программа
ActionTest, 384
AnonimousInnerClassTest, 274
AppletFrame, 605
ArrayGrowTest, 236
ArrayList, 216
BiglntegerTest, 103
Bookmark, 601
BorderTest, 457
BoxLayoutText, 502
BuggyButtonFrame, 714
BuggyButtonPanel, 715
BuggyButtonTest, 714
ButtonTest, 341
Calculator, 414
CalculatorApplet, 577
CalculatorAppletApplication, 606
CalendarTest, 132
CenteredFrameTest, 296
Chart, 590
CheckBoxTest, 450
CircleLayoutText, 522
CloneTest, 260
ColorChooserText, 565
ComboBoxTest, 462
Compoundinterest, 112
ConsoleWindow, 699
ConstructorTest, 163
CRC, 801
Custom EventTest, 396
CustomWorld, 635
DataExchangeText, 546
DataFileTest, 757; 762
DialogTest, 541
DrawTest, 310
EmployeeSortTest, 248
EmployeeTest, 137
EqualsTest, 206
EventTracer, 702
EventTracerTest, 704
ExceptionalTest, 671
ExceptTest, 666
FileChooserText, 557
FillTest, 316
FindDirectories, 793; 801
FirstSample, 59
StaticTest, 150
FontDialog, 508; 516
Systeminfo, 639
FontTest, 322
TextAreaTest, 442
FormatTest, 433
TextEditTest, 447
HrefMatch, 815
TextTest, 421
ImageTest, 330
TimerTest, 254
ImageViewer, 49
ToolBarTest, 495
InnerClassTest, 265
WebCalculatorJava, 624
InputTest, 82
Welcome, 39
LogginglmageWindow, 683
WordCount, 712
LotteryDrawing, 109
ZipTest, 747
LotteryOdds, 97
динамически расширяемая, 192
ManagerTest, 186
Программирование
MenuTest, 489
обобщенное, 209
MethodPoiterTest, 240
объектно-ориентированное, 118
MouseTest, 371
процедурное, 123
MulticastTest, 389
событийно-ориентированное, 336
NotHelloWorld, 303
структурное, 119
NotHelloWorldApplet, 573
ObjectAnalyzciTcst, 233
P
ObjectFileTest, 768
Object RefTcst, 779
Разрешение перегрузки, 190
OptionDialogText, 531
Рамка, 456
PackageTest, 168; 169
стиль
ParamTcst, 156
гравировка, 456
PersonTest, 199
линия, 456
PlafTest, 350
матовая, 456
PopupCalculatorApplet, 582
приподнятая фаска, 456
PreferencesTest, 642
пустая, 456
ProxyTest, 281
утопленная фаска, 456
RadioButtonTest, 454
Распорка, 513
ReflectionTest, 227
Регистрация ошибок, 672
RegexText, 814
локализация, 676
ResourceTest, 614
менеджер, 675
Retirement, 93
обработчики, 677
Retirement2, 94
простая, 673
RobotTest, 706
рецепты,681
SerialCloneTest, 790
сложная, 673
SimplestFrameTest, 290
средства форматирования, 681
Sketch, 364
уровни, 673
SliderTest, 466; 472
CONFIG, 673
StackTraceTest, 663
FINE, 673
StaticInnerClassTest, 277
FINER, 673
FINEST, 673
INFO, 673
SEVERE, 673
WARNING, 673
фильтры, 680
Регулярное выражение, 809
синтаксис, 810
флаги, 813
Рендеринг, 302
Ресурсы, 612
С
Связывание
динамическое, 190
статическое, 190
Серверная страница, 572; 618
Сервлет, 20; 572; 618
Сериализация, 771
перечислений, безопасных по типу,
786
по умолчанию, 783
Символ
заполнитель, 431
система кодирирования, 807
система кодирования, 736
Событие
источник, 337
механизм делегирования, 360
многоадресная передача, 388
низкоуровневое, 357
ComponentEvent, 358
ContainerEvent, 358
KeyEvent, 358
MouseEvent, 358
MouseFocusEvent, 358
MouseWheelEvent, 358
MouseWindowEvent, 358
обработка, 337; 358
объект, 337
очередь, 391
пользовательского типа, 392
процедура события, 336
регистрация, 338
семантическое, 357
ActionEvent, 357
AdjustmentEvent, 357
ItemEvent, 357
TextEvent, 357
слушатель, 337
Сообщение
ERROR-MESSAGE, 529
INFORMATION-MESSAGE, 529
PLAIN_MESSAGE, 529
QUESTION-MESSAGE, 529
WARNING_MESSAGE, 529
Список
комбинированный, 460
массивов, 211
Среда разработки программ
Sun ONE Studio Community Edition,
39; 41
текстовый редактор
Emacs, 38; 45
TextPad, 38; 46
Строгая типизация, 60
Строка, 73
конкатенация,73
Схема проектирвоание
"модель-контроллер-представление", 402
Схема проектирования, 404
"наблюдатель", 405
T
Таблица методов, 191
Текстовая область, 440
Технология
ActiveX, 22;31; 32; 253;580
Java Web Start, 618
JavaBeans, 32; 279
Трассировка стека, 662
У
Указатель на метод, 238
Универсальный адрес ресурса, 593
абсолютный, 594
относительный, 594
Управление
доступом, 262
именами, 262
Утилита
appletviewer, 51; 574
JAR
опции,608
Java Plug-In, 571
javadoc, 177; 178
Ф
Файл
JAR, 607
кэширование, 609
самораспаковывающийся, 611
JNLP, 618
блокировка, 804
виртуальной памяти, 799
выбор,550
miaccjava.io.File, 792
режим загрузки, 799
указатель, 734
Фильтр, 427
Флажок, 449
Фокус, 375
владелец, 376
выход из фокуса, 375
пребывание в фокусе, 375
Формат
XML,641;678
с разделителями, 754
Фрейм, 290
ц
Цвета
выбор, 562
системные, 314
стандартные, 313
Цикл
неопределенный, 90
do, 92
while, 90
определенный, 94
for, 94
ш
Шрифт
высота, 321
логическое название, 319
начертание, 318
размер, 319
стиль, 319
Я
Язык HTML, 582
дескрипторы, 582
модификаторы, 174
формат файла, 445
Язык Java
ошибки, 57
спецификация, 57
Язык UML, 122; 125
диаграмма класса, 122
обозначение отношения, 122
Язык XML, 29
формат файла, 641
Научно-популярное издание
Кей С. Хорстманн, Гари Корнелл
Библиотека профессионала. Java 2
Том 1. Основы
Литературный редактор О.Ю. Белазовская
Верстка О.В. Мишутина
Художественный редактор ВТ. Павлютин
Корректоры Л. А. Гордиенко, Т. А. Корзун,Л. В. Коровкина
Издательский дом “Вильямс”.
101509, Москва, ул. Лесная, д. 43, стр. 848.
Изд. лиц. ЛР № 090230 от 23.06.99
Госкомитета РФ по печати.
Подписано в печать 23.12.2002. Формат70x100/16.
Гарнитура Times. Печать офсетная.
Усл. печ. л. 71,57. Уч.-изд. л. 42,35.
Тираж 3000 экз. Заказ № 2065.
Отпечатано с диапозитивов В ФГУП "Печатныйдвор"Министерства РФ по делам печати,телерадиовещания и средств массовых коммуникаций.197110,Санкт-Петербург, Чкаловский пр., 15.
БИБЛИОТЕКА ПРОФЕССИОНАЛА.
JAVA 2. том 2. тонкости
ПРОГРАММИРОВАНИЯ
Кей С. Хорстманн,
Гари Корнелл
от Sun Microsystems Press
♦Sun
серия
Кей С Хорст манн • Гари Корнелл
www.williamspublishing.com
в продаже
Книга предназначена для опытных программистов, которые хотели бы овладеть профессиональными приемами программирования J ava-при ложений и аплетов: многопоточное программирование, коллекции, работа с сетями и базами данных, распределенное программирование, библиотеки Swing и AWT, технология JavaBeans, вопросы обеспечения безопасности и локализации приложений, использование платформно-зависимого кода и языка XML.
Каждая глава посвящена описанию отдельной темы, они практически не связаны друг с другом, поэтому их можно читать в произвольном порядке. Описание всех важных концепций иллюстрируется надежными и практичными, а не примитивными или чрезмерно академичными примерами.
Все они сопровождаются многочисленными полезными и уместными комментариями. Как сказал Гради Буч (Grady Booch), авторы "уделили большое внимание деталям этого мощного и выразительного языка без ущерба для концептуальной модели объектно-ориентированного программирования".